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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +3 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +26 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +420 -1
- package/config/gate-templates.json +372 -0
- package/config/mcp-allowlists.json +25 -0
- package/config/model-candidates.json +59 -2
- package/config/model-tiers.json +4 -1
- package/package.json +79 -22
- package/public/compare.html +6 -0
- package/public/index.html +144 -11
- package/public/numbers.html +8 -8
- package/public/pro.html +22 -24
- package/scripts/agent-design-governance.js +211 -0
- package/scripts/agent-reasoning-traces.js +683 -0
- package/scripts/agent-reward-model.js +438 -0
- package/scripts/agent-stack-survival-audit.js +231 -0
- package/scripts/ai-engineering-stack-guardrails.js +256 -0
- package/scripts/billing.js +16 -4
- package/scripts/chatgpt-ads-readiness-pack.js +195 -0
- package/scripts/cli-schema.js +277 -0
- package/scripts/code-graph-guardrails.js +176 -0
- package/scripts/deepseek-v4-runtime-guardrails.js +253 -0
- package/scripts/gemini-embedding-policy.js +198 -0
- package/scripts/inference-cache-policy.js +39 -0
- package/scripts/judge-reward-function.js +396 -0
- package/scripts/llm-behavior-monitor.js +251 -0
- package/scripts/long-running-agent-context-guardrails.js +176 -0
- package/scripts/multimodal-retrieval-plan.js +31 -11
- package/scripts/oss-pr-opportunity-scout.js +240 -0
- package/scripts/proactive-agent-eval-guardrails.js +230 -0
- package/scripts/profile-router.js +5 -4
- package/scripts/prompting-operating-system.js +273 -0
- package/scripts/proxy-pointer-rag-guardrails.js +189 -0
- package/scripts/rag-precision-guardrails.js +202 -0
- package/scripts/rate-limiter.js +1 -1
- package/scripts/reasoning-efficiency-guardrails.js +176 -0
- package/scripts/reward-hacking-guardrails.js +251 -0
- package/scripts/seo-gsd.js +1201 -11
- package/scripts/single-use-credential-gate.js +182 -0
- package/scripts/structured-prompt-driven.js +226 -0
- package/scripts/telemetry-analytics.js +31 -6
- package/scripts/tool-registry.js +92 -0
- package/scripts/upstream-contribution-engine.js +379 -0
- package/scripts/vector-store.js +119 -4
- package/src/api/server.js +333 -100
- package/scripts/agents-sdk-sandbox-plan.js +0 -57
- package/scripts/ai-org-governance.js +0 -98
- package/scripts/artifact-agent-plan.js +0 -81
- package/scripts/enterprise-agent-rollout.js +0 -34
- package/scripts/experience-replay-governance.js +0 -69
- package/scripts/inference-economics.js +0 -53
- package/scripts/knowledge-layer-plan.js +0 -108
- package/scripts/memory-store-governance.js +0 -60
- package/scripts/post-training-governance.js +0 -34
- package/scripts/production-agent-readiness.js +0 -40
- package/scripts/scaling-law-claims.js +0 -60
- 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}'
|
|
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
|
-
|
|
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">
|
|
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
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
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
|
|
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,
|
|
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
|
-
};
|