thumbgate 1.16.13 → 1.16.19

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 (62) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +3 -1
  5. package/adapters/claude/.mcp.json +2 -2
  6. package/adapters/mcp/server-stdio.js +26 -1
  7. package/adapters/opencode/opencode.json +1 -1
  8. package/bin/cli.js +420 -1
  9. package/config/gate-templates.json +372 -0
  10. package/config/mcp-allowlists.json +25 -0
  11. package/config/model-candidates.json +59 -2
  12. package/config/model-tiers.json +4 -1
  13. package/package.json +79 -22
  14. package/public/compare.html +6 -0
  15. package/public/index.html +144 -11
  16. package/public/numbers.html +8 -8
  17. package/public/pro.html +22 -24
  18. package/scripts/agent-design-governance.js +211 -0
  19. package/scripts/agent-reasoning-traces.js +683 -0
  20. package/scripts/agent-reward-model.js +438 -0
  21. package/scripts/agent-stack-survival-audit.js +231 -0
  22. package/scripts/ai-engineering-stack-guardrails.js +256 -0
  23. package/scripts/billing.js +16 -4
  24. package/scripts/chatgpt-ads-readiness-pack.js +195 -0
  25. package/scripts/cli-schema.js +277 -0
  26. package/scripts/code-graph-guardrails.js +176 -0
  27. package/scripts/deepseek-v4-runtime-guardrails.js +253 -0
  28. package/scripts/gemini-embedding-policy.js +198 -0
  29. package/scripts/inference-cache-policy.js +39 -0
  30. package/scripts/judge-reward-function.js +396 -0
  31. package/scripts/llm-behavior-monitor.js +251 -0
  32. package/scripts/long-running-agent-context-guardrails.js +176 -0
  33. package/scripts/multimodal-retrieval-plan.js +31 -11
  34. package/scripts/oss-pr-opportunity-scout.js +240 -0
  35. package/scripts/proactive-agent-eval-guardrails.js +230 -0
  36. package/scripts/profile-router.js +5 -4
  37. package/scripts/prompting-operating-system.js +273 -0
  38. package/scripts/proxy-pointer-rag-guardrails.js +189 -0
  39. package/scripts/rag-precision-guardrails.js +202 -0
  40. package/scripts/rate-limiter.js +1 -1
  41. package/scripts/reasoning-efficiency-guardrails.js +176 -0
  42. package/scripts/reward-hacking-guardrails.js +251 -0
  43. package/scripts/seo-gsd.js +1201 -11
  44. package/scripts/single-use-credential-gate.js +182 -0
  45. package/scripts/structured-prompt-driven.js +226 -0
  46. package/scripts/telemetry-analytics.js +31 -6
  47. package/scripts/tool-registry.js +92 -0
  48. package/scripts/upstream-contribution-engine.js +379 -0
  49. package/scripts/vector-store.js +119 -4
  50. package/src/api/server.js +333 -100
  51. package/scripts/agents-sdk-sandbox-plan.js +0 -57
  52. package/scripts/ai-org-governance.js +0 -98
  53. package/scripts/artifact-agent-plan.js +0 -81
  54. package/scripts/enterprise-agent-rollout.js +0 -34
  55. package/scripts/experience-replay-governance.js +0 -69
  56. package/scripts/inference-economics.js +0 -53
  57. package/scripts/knowledge-layer-plan.js +0 -108
  58. package/scripts/memory-store-governance.js +0 -60
  59. package/scripts/post-training-governance.js +0 -34
  60. package/scripts/production-agent-readiness.js +0 -40
  61. package/scripts/scaling-law-claims.js +0 -60
  62. package/scripts/student-consistent-training.js +0 -73
package/src/api/server.js CHANGED
@@ -112,6 +112,7 @@ const {
112
112
  getBillingSummaryLive,
113
113
  } = require('../../scripts/billing');
114
114
  const {
115
+ DEFAULT_PUBLIC_APP_ORIGIN,
115
116
  resolveHostedBillingConfig,
116
117
  createTraceId,
117
118
  buildHostedSuccessUrl,
@@ -462,6 +463,12 @@ const TRACKED_LINK_TARGETS = Object.freeze({
462
463
  // Stripe event tracking helpers
463
464
  // ---------------------------------------------------------------------------
464
465
  const STRIPE_EVENTS_PATH = path.resolve(__dirname, '../../.thumbgate/stripe-events.jsonl');
466
+ const LEGACY_STRIPE_EVENT_TYPES = new Set([
467
+ 'checkout.session.completed',
468
+ 'customer.subscription.created',
469
+ 'customer.subscription.updated',
470
+ 'customer.subscription.deleted',
471
+ ]);
465
472
 
466
473
  function ensureStripeEventsDir() {
467
474
  const dir = path.dirname(STRIPE_EVENTS_PATH);
@@ -473,6 +480,81 @@ function appendStripeEvent(record) {
473
480
  fs.appendFileSync(STRIPE_EVENTS_PATH, JSON.stringify(record) + '\n', 'utf8');
474
481
  }
475
482
 
483
+ function buildLegacyStripeEventRecord(event) {
484
+ const obj = event.data && event.data.object ? event.data.object : {};
485
+ return {
486
+ timestamp: new Date().toISOString(),
487
+ event_type: event.type,
488
+ event_id: event.id || null,
489
+ customer_email:
490
+ obj.customer_email ||
491
+ obj.email ||
492
+ (obj.customer_details && obj.customer_details.email) ||
493
+ null,
494
+ plan:
495
+ obj.plan
496
+ ? (obj.plan.nickname || obj.plan.id || null)
497
+ : (
498
+ obj.items &&
499
+ obj.items.data &&
500
+ obj.items.data[0] &&
501
+ obj.items.data[0].plan
502
+ ? (obj.items.data[0].plan.nickname || obj.items.data[0].plan.id)
503
+ : null
504
+ ),
505
+ amount_cents: obj.amount_total || (obj.plan && obj.plan.amount) || null,
506
+ currency: obj.currency || null,
507
+ subscription_id: obj.subscription || obj.id || null,
508
+ };
509
+ }
510
+
511
+ async function handleLegacyStripeWebhook(req, res) {
512
+ try {
513
+ const rawBody = await new Promise((resolve, reject) => {
514
+ const chunks = [];
515
+ req.on('data', (c) => chunks.push(c));
516
+ req.on('end', () => resolve(Buffer.concat(chunks)));
517
+ req.on('error', reject);
518
+ });
519
+
520
+ const sig = req.headers['stripe-signature'] || '';
521
+ if (!verifyWebhookSignature(rawBody, sig)) {
522
+ sendProblem(res, {
523
+ type: PROBLEM_TYPES.WEBHOOK_INVALID,
524
+ title: 'Invalid webhook signature',
525
+ status: 400,
526
+ detail: 'The webhook signature could not be verified.',
527
+ });
528
+ return;
529
+ }
530
+
531
+ let event;
532
+ try {
533
+ event = JSON.parse(rawBody.toString('utf-8'));
534
+ } catch {
535
+ sendProblem(res, {
536
+ type: PROBLEM_TYPES.INVALID_JSON,
537
+ title: 'Invalid JSON',
538
+ status: 400,
539
+ detail: 'Invalid JSON in webhook body.',
540
+ });
541
+ return;
542
+ }
543
+
544
+ if (LEGACY_STRIPE_EVENT_TYPES.has(event.type)) {
545
+ appendStripeEvent(buildLegacyStripeEventRecord(event));
546
+ }
547
+ sendJson(res, 200, { received: true, event_type: event.type });
548
+ } catch (err) {
549
+ sendProblem(res, {
550
+ type: PROBLEM_TYPES.INTERNAL,
551
+ title: 'Internal Server Error',
552
+ status: 500,
553
+ detail: err.message,
554
+ });
555
+ }
556
+ }
557
+
476
558
  function readStripeEvents() {
477
559
  ensureStripeEventsDir();
478
560
  if (!fs.existsSync(STRIPE_EVENTS_PATH)) return [];
@@ -688,6 +770,76 @@ function getMcpSkillManifests(hostedConfig) {
688
770
  footprintUrl: buildPublicUrl(hostedConfig, '/.well-known/mcp/footprint.json'),
689
771
  proofUrl: VERIFICATION_EVIDENCE_URL,
690
772
  },
773
+ {
774
+ name: 'agent-design-governance',
775
+ title: 'Agent Design Governance',
776
+ description: 'Decide when to stay single-agent, when to split into manager or handoff patterns, and which eval/tool safeguards are required first.',
777
+ triggers: ['agent architecture', 'multi-agent', 'tool overload', 'agent evals', 'agent instructions'],
778
+ recommendedFlow: [
779
+ 'Start with a single agent plus clear tools and instructions.',
780
+ 'Split only when instruction complexity or tool overload is measured.',
781
+ 'Require baseline evals before adding autonomy or subagents.',
782
+ 'Classify tool risk before allowing writes, money movement, production changes, or outbound actions.',
783
+ ],
784
+ contextUrl: buildPublicUrl(hostedConfig, '/public/llm-context.md'),
785
+ proofUrl: VERIFICATION_EVIDENCE_URL,
786
+ },
787
+ {
788
+ name: 'proactive-agent-eval-guardrails',
789
+ title: 'Proactive Agent Eval Guardrails',
790
+ description: 'Require state-machine modeling, active user simulation, goal inference, intervention timing, and multi-app orchestration proof before proactive agents write or interrupt users.',
791
+ triggers: ['proactive agents', 'PARE', 'active user simulation', 'intervention timing', 'multi-app orchestration'],
792
+ recommendedFlow: [
793
+ 'Model each app as states, actions, and valid transitions.',
794
+ 'Simulate active users before enabling anticipatory interventions.',
795
+ 'Grade goal inference separately from intervention timing.',
796
+ 'Block multi-app proactive writes until rollback and orchestration evidence exists.',
797
+ ],
798
+ contextUrl: buildPublicUrl(hostedConfig, '/public/llm-context.md'),
799
+ proofUrl: VERIFICATION_EVIDENCE_URL,
800
+ },
801
+ {
802
+ name: 'reward-hacking-guardrails',
803
+ title: 'Reward Hacking Guardrails',
804
+ description: 'Catch proxy-optimization failures such as unsupported completion claims, sycophancy, verbosity-as-proof, benchmark overfitting, and evaluator manipulation.',
805
+ triggers: ['reward hacking', 'benchmark overfitting', 'unsupported claims', 'sycophancy', 'verifier theater'],
806
+ recommendedFlow: [
807
+ 'Inspect candidate claims for completion, safety, test, or deployment language.',
808
+ 'Require proof artifacts before accepting done, fixed, safe, or ready-to-merge claims.',
809
+ 'Map every proxy metric to the real user objective.',
810
+ 'Require holdout or regression proof before treating benchmark gains as product gains.',
811
+ ],
812
+ contextUrl: buildPublicUrl(hostedConfig, '/public/llm-context.md'),
813
+ proofUrl: VERIFICATION_EVIDENCE_URL,
814
+ },
815
+ {
816
+ name: 'oss-pr-opportunity-scout',
817
+ title: 'OSS PR Opportunity Scout',
818
+ description: 'Find upstream GitHub repositories ThumbGate actually depends on, then rank issue, bounty, and proof-backed PR opportunities without spam.',
819
+ triggers: ['GitHub issues', 'bug bounty', 'upstream PR', 'open source promotion', 'maintainer outreach'],
820
+ recommendedFlow: [
821
+ 'Map package dependencies to upstream repositories.',
822
+ 'Search only maintainer-visible issues, help-wanted labels, regressions, and bounty surfaces.',
823
+ 'Reproduce locally before claiming a fix.',
824
+ 'Open one focused PR with tests, proof, and transparent ThumbGate context only when relevant.',
825
+ ],
826
+ contextUrl: buildPublicUrl(hostedConfig, '/public/llm-context.md'),
827
+ proofUrl: VERIFICATION_EVIDENCE_URL,
828
+ },
829
+ {
830
+ name: 'chatgpt-ads-readiness-pack',
831
+ title: 'ChatGPT Ads Readiness Pack',
832
+ description: 'Prepare ThumbGate intent clusters, proof-backed copy, landing routes, and measurement before ChatGPT Ads Manager becomes broadly self-serve.',
833
+ triggers: ['ChatGPT ads', 'AI ads', 'paid AI search', 'Ads Manager', 'agent governance advertising'],
834
+ recommendedFlow: [
835
+ 'Submit advertiser interest when eligible.',
836
+ 'Cluster high-intent conversational queries around agent governance and repeated workflow failures.',
837
+ 'Route self-serve intent to the guide and team pain to Workflow Hardening Sprint intake.',
838
+ 'Block unsupported ad and landing-page claims before spend scales.',
839
+ ],
840
+ contextUrl: buildPublicUrl(hostedConfig, '/public/llm-context.md'),
841
+ proofUrl: VERIFICATION_EVIDENCE_URL,
842
+ },
691
843
  ];
692
844
  }
693
845
 
@@ -788,6 +940,31 @@ function getMcpDiscoveryManifest(hostedConfig) {
788
940
  description: 'Measure MCP schema and feedback-context footprint before loading large manifests into model context.',
789
941
  tools: ['plan_context_footprint', 'construct_context_pack', 'context_provenance'],
790
942
  },
943
+ {
944
+ name: 'agent-design-governance',
945
+ description: 'Evaluate agent architecture, instruction quality, tool risk, and baseline eval readiness before adding subagents or autonomy.',
946
+ tools: ['plan_agent_design_governance', 'search_lessons', 'diagnose_failure', 'require_evidence_for_claim'],
947
+ },
948
+ {
949
+ name: 'proactive-agent-eval-guardrails',
950
+ description: 'Evaluate proactive-agent state modeling, active-user simulation, goal inference, timing, and multi-app write readiness.',
951
+ tools: ['plan_proactive_agent_eval_guardrails', 'require_evidence_for_claim', 'workflow_sentinel'],
952
+ },
953
+ {
954
+ name: 'reward-hacking-guardrails',
955
+ description: 'Detect proxy-optimization failures before accepting completion claims, benchmark wins, verifier approvals, or multimodal assertions.',
956
+ tools: ['plan_reward_hacking_guardrails', 'require_evidence_for_claim', 'verify_claim'],
957
+ },
958
+ {
959
+ name: 'oss-pr-opportunity-scout',
960
+ description: 'Rank upstream repositories for proof-backed issue fixes and PR opportunities using ThumbGate dependency evidence.',
961
+ tools: ['plan_oss_pr_opportunity_scout', 'require_evidence_for_claim', 'track_action'],
962
+ },
963
+ {
964
+ name: 'chatgpt-ads-readiness-pack',
965
+ description: 'Prepare AI-ads intent clusters, copy, proof links, and measurement gates for ThumbGate campaigns.',
966
+ tools: ['plan_chatgpt_ads_readiness', 'require_evidence_for_claim', 'get_business_metrics'],
967
+ },
791
968
  ],
792
969
  skills: getMcpSkillManifests(hostedConfig),
793
970
  applications: getMcpApplications(hostedConfig),
@@ -1318,6 +1495,16 @@ function buildCheckoutBootstrapBody(parsed, req, journeyState = resolveJourneySt
1318
1495
  };
1319
1496
  }
1320
1497
 
1498
+ function buildCheckoutConfirmHref(parsed) {
1499
+ const confirmUrl = new URL('/checkout/pro', 'https://thumbgate.invalid');
1500
+ confirmUrl.searchParams.set('confirm', '1');
1501
+ for (const [key, value] of parsed.searchParams.entries()) {
1502
+ if (key === 'confirm') continue;
1503
+ confirmUrl.searchParams.append(key, value);
1504
+ }
1505
+ return `${confirmUrl.pathname}${confirmUrl.search}`;
1506
+ }
1507
+
1321
1508
  function normalizeTrackedLinkSlug(value) {
1322
1509
  return String(value || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '');
1323
1510
  }
@@ -1625,6 +1812,34 @@ function escapeHtmlAttribute(value) {
1625
1812
  .replaceAll('>', '>');
1626
1813
  }
1627
1814
 
1815
+ function stripTrailingSlashes(value) {
1816
+ const input = String(value || '');
1817
+ let end = input.length;
1818
+ while (end > 0 && input[end - 1] === '/') end -= 1;
1819
+ return input.slice(0, end);
1820
+ }
1821
+
1822
+ function normalizePublicMarketingHtml(html, runtimeConfig) {
1823
+ const appOrigin = runtimeConfig?.appOrigin
1824
+ ? stripTrailingSlashes(runtimeConfig.appOrigin)
1825
+ : '';
1826
+ if (!appOrigin) return html;
1827
+
1828
+ let output = String(html);
1829
+ output = output.replaceAll(DEFAULT_PUBLIC_APP_ORIGIN, appOrigin);
1830
+ try {
1831
+ const host = new URL(appOrigin).host;
1832
+ output = output.replaceAll(
1833
+ 'data-domain="thumbgate-production.up.railway.app"',
1834
+ `data-domain="${escapeHtmlAttribute(host)}"`
1835
+ );
1836
+ } catch {
1837
+ // appOrigin is normalized by hosted-config; leave static analytics domains
1838
+ // untouched if a future caller deliberately supplies a non-URL value.
1839
+ }
1840
+ return output;
1841
+ }
1842
+
1628
1843
  function loadPublicMarketingTemplateHtml(templatePath, runtimeConfig, pageContext = {}) {
1629
1844
  const template = fs.readFileSync(templatePath, 'utf-8');
1630
1845
  const googleSiteVerificationMeta = runtimeConfig.googleSiteVerification
@@ -1637,11 +1852,11 @@ function loadPublicMarketingTemplateHtml(templatePath, runtimeConfig, pageContex
1637
1852
  ' window.dataLayer = window.dataLayer || [];',
1638
1853
  ' function gtag(){dataLayer.push(arguments);}',
1639
1854
  " gtag('js', new Date());",
1640
- ` gtag('config', '${runtimeConfig.gaMeasurementId}', { send_page_view: false });`,
1855
+ ` gtag('config', '${runtimeConfig.gaMeasurementId}');`,
1641
1856
  ' </script>',
1642
1857
  ].join('\n')
1643
1858
  : '';
1644
- return fillTemplate(template, {
1859
+ return normalizePublicMarketingHtml(fillTemplate(template, {
1645
1860
  '__PACKAGE_VERSION__': pkg.version,
1646
1861
  '__APP_ORIGIN__': runtimeConfig.appOrigin,
1647
1862
  '__CHECKOUT_ENDPOINT__': runtimeConfig.checkoutEndpoint,
@@ -1665,7 +1880,7 @@ function loadPublicMarketingTemplateHtml(templatePath, runtimeConfig, pageContex
1665
1880
  '__GTM_PLAN_URL__': 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/GO_TO_MARKET_REVENUE_WEDGE_2026-03.md',
1666
1881
  '__GITHUB_URL__': 'https://github.com/IgorGanapolsky/ThumbGate',
1667
1882
  '__POSTHOG_API_KEY__': runtimeConfig.posthogApiKey || '',
1668
- });
1883
+ }), runtimeConfig);
1669
1884
  }
1670
1885
 
1671
1886
  function loadLandingPageHtml(runtimeConfig, pageContext = {}) {
@@ -2208,9 +2423,7 @@ function renderRobotsTxt(runtimeConfig) {
2208
2423
  function renderSitemapXml(runtimeConfig) {
2209
2424
  const entries = [
2210
2425
  { path: '/', changefreq: 'weekly', priority: '1.0' },
2211
- // /pro consolidated into /#pro-pitch (2026-04-16) — removed from sitemap
2212
- // so search engines don't chase the 301 instead of indexing the canonical
2213
- // homepage directly.
2426
+ { path: '/pro', changefreq: 'weekly', priority: '0.9' },
2214
2427
  { path: '/llm-context.md', changefreq: 'weekly', priority: '0.8' },
2215
2428
  { path: '/codex-plugin', changefreq: 'weekly', priority: '0.75' },
2216
2429
  ...THUMBGATE_SEO_SITEMAP_ENTRIES,
@@ -2234,6 +2447,21 @@ function renderSitemapXml(runtimeConfig) {
2234
2447
  ].join('\n');
2235
2448
  }
2236
2449
 
2450
+ function buildHostedRuntimePresence(hostedConfig, { expectedApiKey, expectedOperatorKey } = {}) {
2451
+ return {
2452
+ THUMBGATE_FEEDBACK_DIR: Boolean(process.env.THUMBGATE_FEEDBACK_DIR),
2453
+ THUMBGATE_OPERATOR_KEY: Boolean(expectedOperatorKey),
2454
+ THUMBGATE_API_KEY: Boolean(expectedApiKey),
2455
+ THUMBGATE_PUBLIC_APP_ORIGIN: Boolean(hostedConfig.appOrigin),
2456
+ THUMBGATE_BILLING_API_BASE_URL: Boolean(hostedConfig.billingApiBaseUrl),
2457
+ THUMBGATE_GA_MEASUREMENT_ID: Boolean(hostedConfig.gaMeasurementId),
2458
+ THUMBGATE_CHECKOUT_FALLBACK_URL: Boolean(hostedConfig.checkoutFallbackUrl),
2459
+ THUMBGATE_SPRINT_DIAGNOSTIC_CHECKOUT_URL: Boolean(hostedConfig.sprintDiagnosticCheckoutUrl),
2460
+ THUMBGATE_WORKFLOW_SPRINT_CHECKOUT_URL: Boolean(hostedConfig.workflowSprintCheckoutUrl),
2461
+ STRIPE_SECRET_KEY: Boolean(process.env.STRIPE_SECRET_KEY),
2462
+ };
2463
+ }
2464
+
2237
2465
  function isSeoAttributionSource(source) {
2238
2466
  return source === 'organic_search' || source === 'ai_search';
2239
2467
  }
@@ -2699,6 +2927,33 @@ function renderCheckoutSuccessPage(runtimeConfig) {
2699
2927
  }
2700
2928
 
2701
2929
  function renderCheckoutCancelledPage(runtimeConfig) {
2930
+ const diagnosticCheckoutUrl = runtimeConfig.sprintDiagnosticCheckoutUrl
2931
+ ? escapeHtmlAttribute(runtimeConfig.sprintDiagnosticCheckoutUrl)
2932
+ : '';
2933
+ const workflowSprintCheckoutUrl = runtimeConfig.workflowSprintCheckoutUrl
2934
+ ? escapeHtmlAttribute(runtimeConfig.workflowSprintCheckoutUrl)
2935
+ : '';
2936
+ const sprintDiagnosticPriceDollars = runtimeConfig.sprintDiagnosticPriceDollars || 499;
2937
+ const workflowSprintPriceDollars = runtimeConfig.workflowSprintPriceDollars || 1500;
2938
+ const workflowSprintIntakeUrl = `${escapeHtmlAttribute(runtimeConfig.appOrigin)}/#workflow-sprint-intake`;
2939
+ const recoveryOfferLinks = [
2940
+ `<a id="send-workflow-first" href="${workflowSprintIntakeUrl}" data-recovery-offer="workflow_sprint_intake" data-offer-price="0">Send workflow first</a>`,
2941
+ diagnosticCheckoutUrl
2942
+ ? `<a href="${diagnosticCheckoutUrl}" data-recovery-offer="sprint_diagnostic" data-offer-price="${sprintDiagnosticPriceDollars}">Book $${sprintDiagnosticPriceDollars} diagnostic</a>`
2943
+ : '',
2944
+ workflowSprintCheckoutUrl
2945
+ ? `<a href="${workflowSprintCheckoutUrl}" data-recovery-offer="workflow_sprint" data-offer-price="${workflowSprintPriceDollars}">Start $${workflowSprintPriceDollars} sprint</a>`
2946
+ : '',
2947
+ ].filter(Boolean).join('\n ');
2948
+ const recoveryOfferCard = recoveryOfferLinks
2949
+ ? `<div class="card recovery-card">
2950
+ <h2>Need help deciding?</h2>
2951
+ <p>If Pro is not the right next step, send the workflow first. We can qualify the blocker, confirm the proof plan, and route you to the diagnostic or sprint only when the scope is real.</p>
2952
+ <div class="actions">
2953
+ ${recoveryOfferLinks}
2954
+ </div>
2955
+ </div>`
2956
+ : '';
2702
2957
  return `<!DOCTYPE html>
2703
2958
  <html lang="en">
2704
2959
  <head>
@@ -2784,6 +3039,9 @@ function renderCheckoutCancelledPage(runtimeConfig) {
2784
3039
  gap: 12px;
2785
3040
  margin-top: 18px;
2786
3041
  }
3042
+ .recovery-card {
3043
+ border-color: rgba(184, 92, 45, 0.38);
3044
+ }
2787
3045
  .note {
2788
3046
  font-size: 14px;
2789
3047
  margin-top: 12px;
@@ -2812,17 +3070,19 @@ function renderCheckoutCancelledPage(runtimeConfig) {
2812
3070
  </div>
2813
3071
  <div class="actions">
2814
3072
  <button type="button" id="submit-reason">Send feedback</button>
2815
- <a id="retry-checkout" href="/checkout/pro" class="secondary">Try checkout again</a>
3073
+ <a id="retry-checkout" href="/checkout/pro" class="secondary" data-recovery-offer="pro_trial_retry" data-offer-price="19">Restart $19 Pro trial</a>
2816
3074
  <a href="${runtimeConfig.appOrigin}" class="secondary">Return to Context Gateway</a>
2817
3075
  </div>
2818
3076
  <p class="note" id="status">No feedback sent yet.</p>
2819
3077
  </div>
3078
+ ${recoveryOfferCard}
2820
3079
  <script>
2821
3080
  (function () {
2822
3081
  const params = new URLSearchParams(window.location.search);
2823
3082
  const statusEl = document.getElementById('status');
2824
3083
  const noteEl = document.getElementById('buyer-note');
2825
3084
  const retryLink = document.getElementById('retry-checkout');
3085
+ const workflowIntakeLink = document.getElementById('send-workflow-first');
2826
3086
  let selectedReason = null;
2827
3087
 
2828
3088
  function sendTelemetry(eventType, extra) {
@@ -2875,6 +3135,19 @@ function renderCheckoutCancelledPage(runtimeConfig) {
2875
3135
  });
2876
3136
  retryLink.href = retryUrl.toString();
2877
3137
 
3138
+ if (workflowIntakeLink) {
3139
+ const intakeUrl = new URL(workflowIntakeLink.href, window.location.origin);
3140
+ ['trace_id', 'acquisition_id', 'visitor_id', 'session_id', 'visitor_session_id', 'install_id', 'utm_source', 'utm_campaign', 'utm_content', 'utm_term', 'creator', 'community', 'post_id', 'comment_id', 'campaign_variant', 'offer_code', 'landing_path', 'referrer_host'].forEach(function (key) {
3141
+ const value = params.get(key);
3142
+ if (value) intakeUrl.searchParams.set(key, value);
3143
+ });
3144
+ intakeUrl.searchParams.set('utm_medium', 'checkout_cancel_recovery');
3145
+ intakeUrl.searchParams.set('cta_id', 'checkout_cancel_workflow_sprint_intake');
3146
+ intakeUrl.searchParams.set('cta_placement', 'checkout_cancel_recovery');
3147
+ intakeUrl.searchParams.set('plan_id', 'team');
3148
+ workflowIntakeLink.href = intakeUrl.toString();
3149
+ }
3150
+
2878
3151
  sendTelemetry('checkout_cancelled');
2879
3152
 
2880
3153
  document.querySelectorAll('[data-reason]').forEach(function (button) {
@@ -2893,6 +3166,27 @@ function renderCheckoutCancelledPage(runtimeConfig) {
2893
3166
  ? 'Feedback saved: ' + selectedReason.replaceAll('_', ' ') + '.'
2894
3167
  : 'Feedback saved.';
2895
3168
  });
3169
+
3170
+ document.querySelectorAll('[data-recovery-offer]').forEach(function (link) {
3171
+ link.addEventListener('click', function () {
3172
+ if (link.getAttribute('data-recovery-offer') === 'workflow_sprint_intake') {
3173
+ sendTelemetry('checkout_cancel_workflow_intake_clicked', {
3174
+ ctaId: 'checkout_cancel_workflow_sprint_intake',
3175
+ ctaPlacement: 'checkout_cancel_recovery',
3176
+ offerCode: 'workflow_sprint_intake',
3177
+ planId: 'team',
3178
+ reasonCode: selectedReason || null
3179
+ });
3180
+ return;
3181
+ }
3182
+ sendTelemetry('checkout_recovery_offer_clicked', {
3183
+ ctaId: link.getAttribute('data-recovery-offer'),
3184
+ ctaPlacement: 'checkout_cancel_recovery',
3185
+ offerCode: link.getAttribute('data-recovery-offer'),
3186
+ offerPriceDollars: link.getAttribute('data-offer-price')
3187
+ });
3188
+ });
3189
+ });
2896
3190
  }());
2897
3191
  </script>
2898
3192
  </main>
@@ -3880,17 +4174,22 @@ async function addContext(){
3880
4174
  }
3881
4175
 
3882
4176
  if (isGetLikeRequest && pathname === '/pro') {
3883
- // Consolidated: /pro content now lives inline on `/` as the #pro-pitch
3884
- // strip (hero-adjacent pricing card). 301 so external links (README,
3885
- // plugin manifests, guides, compare pages) pass link equity onto the
3886
- // single canonical landing page. Query string is preserved so UTM
3887
- // tracking from inbound campaigns still reaches GA/PostHog on `/`.
3888
- const redirectTarget = `/#pro-pitch${parsed.search || ''}`;
3889
- res.writeHead(301, {
3890
- Location: redirectTarget,
3891
- 'Cache-Control': 'public, max-age=3600',
3892
- });
3893
- res.end();
4177
+ try {
4178
+ servePublicMarketingPage({
4179
+ req,
4180
+ res,
4181
+ parsed,
4182
+ hostedConfig,
4183
+ isHeadRequest,
4184
+ renderHtml: loadProPageHtml,
4185
+ extraTelemetry: {
4186
+ pageType: 'pro',
4187
+ planId: 'pro',
4188
+ },
4189
+ });
4190
+ } catch (err) {
4191
+ sendText(res, 500, err.message || 'Pro page unavailable');
4192
+ }
3894
4193
  return;
3895
4194
  }
3896
4195
 
@@ -4112,7 +4411,8 @@ async function addContext(){
4112
4411
  planId: analyticsMetadata.planId,
4113
4412
  reason: botClassification.reason,
4114
4413
  }, req.headers, 'checkout_bot_deflected');
4115
- const html = '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow"><title>ThumbGate Pro \u2014 Confirm checkout</title><style>*{box-sizing:border-box}body{background:#0a0a0a;color:#e5e5e5;font-family:system-ui,-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:20px}.card{background:#141414;border:1px solid #222;border-radius:16px;padding:48px 40px;max-width:460px;width:100%;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.5)}h1{margin:0 0 12px;font-size:22px;color:#22d3ee}p{color:#9ca3af;font-size:14px;line-height:1.55;margin:0 0 24px}.btn{display:inline-block;background:#22d3ee;color:#000;text-decoration:none;font-weight:700;padding:14px 32px;border-radius:999px;font-size:16px;cursor:pointer;border:none}.btn:hover{opacity:.9}.sub{margin-top:16px;font-size:12px;color:#6b7280}a.back{color:#6b7280;font-size:13px;text-decoration:underline}</style></head><body><div class="card"><h1>Continue to secure checkout</h1><p>You\'re one click from ThumbGate Pro at $19/mo. We create the payment session only after you confirm \u2014 keeps your path clean and our funnel honest.</p><a class="btn" href="/checkout/pro?confirm=1" rel="noopener">Continue to Stripe \u2192</a><div class="sub">Payments handled by Stripe. 7-day free trial. Cancel anytime.</div><div class="sub"><a class="back" href="/">\u2190 Back to homepage</a></div></div></body></html>';
4414
+ const confirmHref = escapeHtmlAttribute(buildCheckoutConfirmHref(parsed));
4415
+ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow"><title>ThumbGate Pro \u2014 Confirm checkout</title><style>*{box-sizing:border-box}body{background:#0a0a0a;color:#e5e5e5;font-family:system-ui,-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:20px}.card{background:#141414;border:1px solid #222;border-radius:16px;padding:48px 40px;max-width:460px;width:100%;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.5)}h1{margin:0 0 12px;font-size:22px;color:#22d3ee}p{color:#9ca3af;font-size:14px;line-height:1.55;margin:0 0 24px}.btn{display:inline-block;background:#22d3ee;color:#000;text-decoration:none;font-weight:700;padding:14px 32px;border-radius:999px;font-size:16px;cursor:pointer;border:none}.btn:hover{opacity:.9}.sub{margin-top:16px;font-size:12px;color:#6b7280}a.back{color:#6b7280;font-size:13px;text-decoration:underline}</style></head><body><div class="card"><h1>Continue to secure checkout</h1><p>You're one click from ThumbGate Pro at $19/mo. We create the payment session only after you confirm \u2014 keeps your path clean and our funnel honest.</p><a class="btn" href="${confirmHref}" rel="noopener">Continue to Stripe \u2192</a><div class="sub">Payments handled by Stripe. 7-day trial. Card required; no charge today. Cancel anytime.</div><div class="sub"><a class="back" href="/">\u2190 Back to homepage</a></div></div></body></html>`;
4116
4416
  sendHtml(res, 200, html, responseHeaders);
4117
4417
  return;
4118
4418
  }
@@ -4809,6 +5109,13 @@ async function addContext(){
4809
5109
  return;
4810
5110
  }
4811
5111
 
5112
+ // POST /webhook/stripe — legacy Stripe event log bridge kept for backward compatibility.
5113
+ // This must remain unauthenticated like /v1/billing/webhook; Stripe auth is the HMAC signature.
5114
+ if (req.method === 'POST' && pathname === '/webhook/stripe') {
5115
+ await handleLegacyStripeWebhook(req, res);
5116
+ return;
5117
+ }
5118
+
4812
5119
  // GitHub Marketplace webhook
4813
5120
  if (req.method === 'POST' && pathname === '/v1/billing/github-webhook') {
4814
5121
  try {
@@ -6010,7 +6317,13 @@ async function addContext(){
6010
6317
  }
6011
6318
 
6012
6319
  const summary = await getBillingSummaryLive(summaryOptions);
6013
- sendJson(res, 200, summary);
6320
+ sendJson(res, 200, {
6321
+ ...summary,
6322
+ runtimePresence: buildHostedRuntimePresence(hostedConfig, {
6323
+ expectedApiKey,
6324
+ expectedOperatorKey,
6325
+ }),
6326
+ });
6014
6327
  return;
6015
6328
  }
6016
6329
 
@@ -6375,86 +6688,6 @@ async function addContext(){
6375
6688
  return;
6376
6689
  }
6377
6690
 
6378
- // POST /webhook/stripe — legacy Stripe event log bridge kept for backward compatibility.
6379
- // When STRIPE_WEBHOOK_SECRET is configured, verify the same Stripe signature used by
6380
- // the /v1/billing/webhook route before touching any payload.
6381
- if (req.method === 'POST' && pathname === '/webhook/stripe') {
6382
- try {
6383
- const rawBody = await new Promise((resolve, reject) => {
6384
- const chunks = [];
6385
- req.on('data', (c) => chunks.push(c));
6386
- req.on('end', () => resolve(Buffer.concat(chunks)));
6387
- req.on('error', reject);
6388
- });
6389
-
6390
- const sig = req.headers['stripe-signature'] || '';
6391
- if (!verifyWebhookSignature(rawBody, sig)) {
6392
- sendProblem(res, {
6393
- type: PROBLEM_TYPES.WEBHOOK_INVALID,
6394
- title: 'Invalid webhook signature',
6395
- status: 400,
6396
- detail: 'The webhook signature could not be verified.',
6397
- });
6398
- return;
6399
- }
6400
-
6401
- let event;
6402
- try {
6403
- event = JSON.parse(rawBody.toString('utf-8'));
6404
- } catch {
6405
- sendProblem(res, {
6406
- type: PROBLEM_TYPES.INVALID_JSON,
6407
- title: 'Invalid JSON',
6408
- status: 400,
6409
- detail: 'Invalid JSON in webhook body.',
6410
- });
6411
- return;
6412
- }
6413
- const TRACKED_STRIPE_EVENTS = new Set([
6414
- 'checkout.session.completed',
6415
- 'customer.subscription.created',
6416
- 'customer.subscription.deleted',
6417
- ]);
6418
- if (TRACKED_STRIPE_EVENTS.has(event.type)) {
6419
- const obj = event.data && event.data.object ? event.data.object : {};
6420
- const record = {
6421
- timestamp: new Date().toISOString(),
6422
- event_type: event.type,
6423
- event_id: event.id || null,
6424
- customer_email:
6425
- obj.customer_email ||
6426
- obj.email ||
6427
- (obj.customer_details && obj.customer_details.email) ||
6428
- null,
6429
- plan:
6430
- obj.plan
6431
- ? (obj.plan.nickname || obj.plan.id || null)
6432
- : (
6433
- obj.items &&
6434
- obj.items.data &&
6435
- obj.items.data[0] &&
6436
- obj.items.data[0].plan
6437
- ? (obj.items.data[0].plan.nickname || obj.items.data[0].plan.id)
6438
- : null
6439
- ),
6440
- amount_cents: obj.amount_total || (obj.plan && obj.plan.amount) || null,
6441
- currency: obj.currency || null,
6442
- subscription_id: obj.subscription || obj.id || null,
6443
- };
6444
- appendStripeEvent(record);
6445
- }
6446
- sendJson(res, 200, { received: true, event_type: event.type });
6447
- } catch (err) {
6448
- sendProblem(res, {
6449
- type: PROBLEM_TYPES.INTERNAL,
6450
- title: 'Internal Server Error',
6451
- status: 500,
6452
- detail: err.message,
6453
- });
6454
- }
6455
- return;
6456
- }
6457
-
6458
6691
  // GET /api/conversions — Conversion stats derived from the Stripe event log
6459
6692
  if (req.method === 'GET' && pathname === '/api/conversions') {
6460
6693
  try {
@@ -1,57 +0,0 @@
1
- 'use strict';
2
-
3
- function buildAgentsSdkSandboxPlan(options = {}) {
4
- const provider = options.provider || 'unix_local';
5
- const mounts = options.mounts || [{ name: 'data', mode: 'read_only' }];
6
- const outputDir = options.outputDir || 'outputs';
7
-
8
- return {
9
- harness: 'openai_agents_sdk_sandbox',
10
- provider,
11
- manifest: {
12
- mounts,
13
- outputDir,
14
- network: options.network || 'disabled_by_default',
15
- allowedCommands: options.allowedCommands || ['npm test', 'npm run test:coverage'],
16
- },
17
- separation: {
18
- credentialsInSandbox: false,
19
- toolBrokerOwnsSecrets: true,
20
- sandboxGetsScopedFilesOnly: true,
21
- },
22
- durability: {
23
- externalState: true,
24
- checkpoints: ['manifest_loaded', 'tools_completed', 'patch_written', 'tests_run'],
25
- rehydrateOnSandboxLoss: true,
26
- },
27
- gates: [
28
- 'read manifest before file access',
29
- 'write only under declared output or scoped repo path',
30
- 'cite source filenames for data-room answers',
31
- 'run configured verification before completion claim',
32
- 'persist decision journal outside sandbox',
33
- ],
34
- };
35
- }
36
-
37
- function evaluateSandboxPlan(plan = {}) {
38
- const issues = [];
39
- if (!plan.manifest?.mounts?.length) issues.push('missing_manifest_mounts');
40
- if (!plan.manifest?.outputDir) issues.push('missing_output_dir');
41
- if (plan.separation?.credentialsInSandbox !== false) issues.push('credentials_must_stay_outside_sandbox');
42
- if (!plan.durability?.externalState) issues.push('external_state_required');
43
- if (!plan.durability?.rehydrateOnSandboxLoss) issues.push('rehydration_required');
44
- if (!Array.isArray(plan.gates) || !plan.gates.some((gate) => /verification/i.test(gate))) {
45
- issues.push('verification_gate_required');
46
- }
47
-
48
- return {
49
- decision: issues.length ? 'warn' : 'allow',
50
- issues,
51
- };
52
- }
53
-
54
- module.exports = {
55
- buildAgentsSdkSandboxPlan,
56
- evaluateSandboxPlan,
57
- };