thumbgate 1.19.0 โ 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +6 -6
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +39 -2
- package/package.json +10 -4
- package/public/compare.html +6 -0
- package/public/index.html +29 -2
- package/public/learn.html +15 -0
- package/public/numbers.html +2 -2
- package/public/pro.html +3 -22
- package/scripts/auto-promote-gates.js +153 -7
- package/scripts/auto-wire-hooks.js +50 -29
- package/scripts/cli-feedback.js +9 -1
- package/scripts/rate-limiter.js +11 -0
- package/src/api/server.js +1 -98
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate-marketplace",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"owner": {
|
|
5
5
|
"name": "Igor Ganapolsky",
|
|
6
6
|
"email": "ig5973700@gmail.com"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"source": "npm",
|
|
14
14
|
"package": "thumbgate"
|
|
15
15
|
},
|
|
16
|
-
"version": "1.
|
|
16
|
+
"version": "1.20.0",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Igor Ganapolsky"
|
|
19
19
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
3
|
"description": "Type ๐ or ๐ on any agent action. ThumbGate captures it, distills a lesson, and blocks the pattern from repeating. One thumbs-down = the agent physically cannot make that mistake again. 33 pre-action checks, budget enforcement, self-protection, and NIST/SOC2 compliance tags.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.20.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Igor Ganapolsky"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"description": "ThumbGate โ ๐๐ feedback that teaches your AI agent. Thumbs down a mistake, it never happens again.",
|
|
5
5
|
"homepage": "https://thumbgate-production.up.railway.app",
|
|
6
6
|
"transport": "stdio",
|
package/README.md
CHANGED
|
@@ -91,8 +91,8 @@ ThumbGate doesn't make your agent smarter. It makes your agent *cheaper to be wr
|
|
|
91
91
|
## Quick Start
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
npx thumbgate init
|
|
95
|
-
npx thumbgate capture "Never run DROP on production tables"
|
|
94
|
+
npx thumbgate init # auto-detects your agent, wires everything
|
|
95
|
+
npx thumbgate capture --feedback=down --context="Never run DROP on production tables"
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
That single command creates a prevention rule. Next time any AI agent tries to run `DROP` on production:
|
|
@@ -253,10 +253,10 @@ ThumbGate sells three concrete outcomes:
|
|
|
253
253
|
## CLI Reference
|
|
254
254
|
|
|
255
255
|
```bash
|
|
256
|
-
npx thumbgate init
|
|
257
|
-
npx thumbgate doctor
|
|
258
|
-
npx thumbgate capture #
|
|
259
|
-
npx thumbgate lessons
|
|
256
|
+
npx thumbgate init # detect agent, wire hooks
|
|
257
|
+
npx thumbgate doctor # health check
|
|
258
|
+
npx thumbgate capture --feedback=up|down --context="<text>" # capture a signal as a stored lesson
|
|
259
|
+
npx thumbgate lessons # see what's been learned
|
|
260
260
|
npx thumbgate explore # terminal explorer for lessons, checks, stats
|
|
261
261
|
npx thumbgate background-governance # review background-agent run risk
|
|
262
262
|
npx thumbgate model-candidates --workload=dashboard-analysis --provider=openai --json # evaluate GPT-5.5 routing
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"thumbgate": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["--yes", "--package", "thumbgate@1.
|
|
5
|
+
"args": ["--yes", "--package", "thumbgate@1.20.0", "thumbgate", "serve"]
|
|
6
6
|
}
|
|
7
7
|
},
|
|
8
8
|
"hooks": {
|
|
9
9
|
"preToolUse": {
|
|
10
10
|
"command": "npx",
|
|
11
|
-
"args": ["--yes", "--package", "thumbgate@1.
|
|
11
|
+
"args": ["--yes", "--package", "thumbgate@1.20.0", "thumbgate", "gate-check"]
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -216,7 +216,7 @@ const {
|
|
|
216
216
|
finalizeSession: finalizeFeedbackSession,
|
|
217
217
|
} = require('../../scripts/feedback-session');
|
|
218
218
|
|
|
219
|
-
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.
|
|
219
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.20.0' };
|
|
220
220
|
const COMMERCE_CATEGORIES = [
|
|
221
221
|
'product_recommendation',
|
|
222
222
|
'brand_compliance',
|
package/bin/cli.js
CHANGED
|
@@ -141,6 +141,10 @@ function telemetryPing(installId) {
|
|
|
141
141
|
|
|
142
142
|
function proNudge(context) {
|
|
143
143
|
if (process.env.THUMBGATE_NO_NUDGE === '1') return;
|
|
144
|
+
try {
|
|
145
|
+
const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
|
|
146
|
+
if (isProTier()) return;
|
|
147
|
+
} catch (_) { /* if rate-limiter is unavailable, fall through and nudge */ }
|
|
144
148
|
const messages = [
|
|
145
149
|
`\n ๐ก Unlock Pro (${PRO_PRICE_LABEL}): searchable dashboard, DPO export, multi-repo sync\n ${PRO_CHECKOUT_URL}\n`,
|
|
146
150
|
`\n ๐ก Pro tip: export your feedback as DPO training pairs to improve your models.\n Get Pro: ${PRO_CHECKOUT_URL}\n`,
|
|
@@ -1157,7 +1161,10 @@ function pro() {
|
|
|
1157
1161
|
}
|
|
1158
1162
|
|
|
1159
1163
|
const resolvedKey = resolveProKey();
|
|
1160
|
-
|
|
1164
|
+
// creator-dev legitimately returns {key: '', source: 'creator-dev', plan: 'enterprise'}
|
|
1165
|
+
// when THUMBGATE_DEV_KEY is unset โ startLocalProDashboard accepts the empty
|
|
1166
|
+
// key in that case (see pro-local-dashboard.js:141), so launch on source too.
|
|
1167
|
+
if (resolvedKey && (resolvedKey.key || resolvedKey.source === 'creator-dev')) {
|
|
1161
1168
|
return launchDashboard(resolvedKey.key, 'pro_dashboard_launch');
|
|
1162
1169
|
}
|
|
1163
1170
|
|
|
@@ -2299,6 +2306,36 @@ function startApi() {
|
|
|
2299
2306
|
|
|
2300
2307
|
function help() {
|
|
2301
2308
|
const v = pkgVersion();
|
|
2309
|
+
const helpArgs = process.argv.slice(3);
|
|
2310
|
+
const showAll = helpArgs.includes('all')
|
|
2311
|
+
|| helpArgs.includes('--all')
|
|
2312
|
+
|| helpArgs.includes('--full');
|
|
2313
|
+
|
|
2314
|
+
// Default `thumbgate help` shows a curated short list. The full ~70-command
|
|
2315
|
+
// surface lives behind `thumbgate help all` so first-time users aren't hit
|
|
2316
|
+
// with a wall of text. (Pre-2026-05-18 default: dump everything.)
|
|
2317
|
+
if (!showAll) {
|
|
2318
|
+
console.log(`thumbgate v${v} โ pre-action checks for AI coding agents`);
|
|
2319
|
+
console.log('');
|
|
2320
|
+
console.log('Common commands:');
|
|
2321
|
+
console.log(' init Detect agent and wire ThumbGate hooks');
|
|
2322
|
+
console.log(' capture --feedback=up|down --context="<text>" Capture a thumbs signal as a stored lesson');
|
|
2323
|
+
console.log(' stats Approval rate, recent trend, blocked-pattern count');
|
|
2324
|
+
console.log(' lessons [query] Search promoted lessons');
|
|
2325
|
+
console.log(' explore Interactive TUI for lessons, gates, stats');
|
|
2326
|
+
console.log(' dashboard Open the local ThumbGate dashboard');
|
|
2327
|
+
console.log(' doctor Audit runtime isolation + bootstrap context');
|
|
2328
|
+
console.log(' pro ThumbGate Pro (dashboard, exports, sync)');
|
|
2329
|
+
console.log('');
|
|
2330
|
+
console.log('More:');
|
|
2331
|
+
console.log(' thumbgate help all Full subcommand surface (~70 commands)');
|
|
2332
|
+
console.log(' thumbgate <cmd> --help Per-command flags (where supported)');
|
|
2333
|
+
console.log('');
|
|
2334
|
+
console.log('Docs: https://github.com/IgorGanapolsky/ThumbGate');
|
|
2335
|
+
proNudge();
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2302
2339
|
const { groupedCommands, commandHelpLine } = require(path.join(PKG_ROOT, 'scripts', 'cli-schema'));
|
|
2303
2340
|
const groups = groupedCommands();
|
|
2304
2341
|
const GROUP_LABELS = {
|
|
@@ -2310,7 +2347,7 @@ function help() {
|
|
|
2310
2347
|
advanced: 'Advanced',
|
|
2311
2348
|
};
|
|
2312
2349
|
|
|
2313
|
-
console.log(`thumbgate v${v} โ pre-action checks for AI coding agents`);
|
|
2350
|
+
console.log(`thumbgate v${v} โ pre-action checks for AI coding agents (full command surface)`);
|
|
2314
2351
|
console.log('');
|
|
2315
2352
|
|
|
2316
2353
|
for (const [groupKey, label] of Object.entries(GROUP_LABELS)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"description": "ThumbGate self-improving agent governance: thumbs-up/down turns every mistake into a prevention rule and blocks repeat patterns. 33 pre-action checks, budget enforcement, and self-protection for Claude Code, Cursor, Codex, Gemini CLI, and Amp.",
|
|
5
5
|
"homepage": "https://thumbgate-production.up.railway.app",
|
|
6
6
|
"repository": {
|
|
@@ -249,6 +249,7 @@
|
|
|
249
249
|
"verify:full": "node scripts/verify-run.js full",
|
|
250
250
|
"budget:status": "node scripts/budget-guard.js --status",
|
|
251
251
|
"revenue:status": "node scripts/revenue-status.js",
|
|
252
|
+
"revenue:doctor": "node scripts/revenue-observability-doctor.js",
|
|
252
253
|
"revenue:plan": "node scripts/may-2026-revenue-machine.js",
|
|
253
254
|
"revenue:status:local": "node bin/cli.js cfo",
|
|
254
255
|
"revenue:repair:github-marketplace": "node bin/cli.js repair-github-marketplace --write",
|
|
@@ -328,7 +329,7 @@
|
|
|
328
329
|
"social:prospect:bluesky:dry": "node scripts/social-bluesky-prospecting.js --dry-run",
|
|
329
330
|
"social:reply-publish:bluesky:dry": "node scripts/social-reply-monitor-bluesky.js --publish-approved --dry-run",
|
|
330
331
|
"test:python": "python3 -m pytest tests/*.py",
|
|
331
|
-
"test": "npm run test:python && npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:operational-integrity && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher && npm run test:autoresearch && npm run test:ops && npm run test:session-analyzer && npm run test:tessl && npm run test:gates && npm run test:evoskill && npm run test:gates-hardening && npm run test:workers && npm run test:social-analytics && npm run test:memalign && npm run test:xmemory-lite && npm run test:filesystem-search && npm run test:zernio && npm run test:platform-limits && npm run test:post-video && npm run test:post-everywhere-instagram && npm run test:post-everywhere-channels && npm run test:post-everywhere-zernio-default && npm run test:zernio-canonical-pollers && npm run test:zernio-status && npm run test:obsidian-export && npm run test:lesson-db && npm run test:lesson-rotation && npm run test:memory-dedup && npm run test:feedback-quality && npm run test:sync-version && npm run test:check-congruence && npm run test:tool-registry && npm run test:feedback-to-rules && npm run test:memory-firewall && npm run test:memory-scope-readiness && npm run test:belief-update && npm run test:hosted-config && npm run test:operational-summary && npm run test:operational-dashboard && npm run test:operator-artifacts && npm run test:operator-key-auth && npm run test:cloudflare-sandbox && npm run test:mcp-config && npm run test:plan-gate && npm run test:pulse && npm run test:semantic-layer && npm run test:data-pipeline && npm run test:optimize-context && npm run test:principle-extractor && npm run test:analytics-window && npm run test:funnel-analytics && npm run test:experiment-tracker && npm run test:build-metadata && npm run test:context-engine && npm run test:hf-papers && npm run test:marketing-experiment && npm run test:seo-gsd && npm run test:verify-run && npm run test:export-dpo-pairs && npm run test:export-hf-dataset && npm run test:license && npm run test:bot-detector && npm run test:audit-pr-bot-contamination && npm run test:stripe-bootstrap-saas-catalog && npm run test:postinstall && npm run test:funnel-invariants && npm run test:cli-telemetry && npm run test:pro-parity && npm run test:model-tier-router && npm run test:computer-use-firewall && npm run test:skill-exporter && npm run test:statusline && npm run test:evolution && npm run test:org-dashboard && npm run test:multi-hop-recall && npm run test:synthetic-dpo && npm run test:thumbgate-skill && npm run test:learn-hub && npm run test:feedback-fallback && npm run test:metaclaw && npm run test:server-lock && npm run test:control-tower && npm run test:pii-scanner && npm run test:data-governance && npm run test:lesson-inference && npm run test:semantic-dedup && npm run test:fs-utils && npm run test:cli-schema && npm run test:explore && npm run test:lesson-reranker && npm run test:lesson-retrieval && npm run test:cross-encoder && npm run test:reflector-agent && npm run test:feedback-session && npm run test:feedback-history-distiller && npm run test:hallucination-detector && npm run test:history-distiller && npm run test:predictive-insights && npm run test:prove-predictive-insights && npm run test:statusbar-cli && npm run test:generate-instagram-card && npm run test:instagram-thumbgate-post && npm run test:publish-instagram-thumbgate && npm run test:lesson-synthesis && npm run test:lesson-canonical && npm run test:background-governance && npm run test:memory-migration && npm run test:prompt-dlp && npm run test:ephemeral-store && npm run test:agent-security && npm run test:skill-progressive && npm run test:per-step-scoring && npm run test:weekly-auto-post && npm run test:social-post-hourly && npm run test:social-quality-gate && npm run test:a2ui-engine && npm run test:gate-satisfy && npm run test:money-watcher && npm run test:budget && npm run test:quick-start && npm run test:utm && npm run test:product-feedback && npm run test:feedback-root-consolidator && npm run test:engagement-audit && npm run test:install-growth-automation && npm run test:publish-thumbgate-launch && npm run test:community-course-platform-launch-kit && npm run test:reconcile-thumbgate-campaign && npm run test:reddit-publisher && npm run test:schedule-thumbgate-campaign && npm run test:social-reply-monitor && npm run test:social-dedupe-cleanup && npm run test:sync-launch-assets && npm run test:ai-search-visibility && npm run test:perplexity && npm run test:security-scanner && npm run test:llm-client && npm run test:managed-lesson-agent && npm run test:self-distill && npm run test:meta-agent && npm run test:harness-selector && npm run test:thumbgate-bench && npm run test:seo-guides && npm run test:enforcement-loop && npm run test:cli-agent-experience && npm run test:bot-detection && npm run test:checkout-bot-guard && npm run test:checkout-pro-confirmation-gate && npm run test:session-health && npm run test:session-episodes && npm run test:spec-gate && npm run test:decision-trace && npm run test:dashboard-insights && npm run test:telemetry-tracked-link-slug && npm run test:prompt-eval && npm run test:demo-voiceover && npm run test:gate-coherence && npm run test:gate-eval && npm run test:high-roi && npm run test:public-static-assets && npm run test:token-savings && npm run test:numbers-page && npm run test:workflow-gate-checkpoint && npm run test:lesson-export-import && npm run test:landing-page-claims && npm run test:competitive-positioning-marketing && npm run test:medium-weekly && npm run test:dashboard-deeplink-e2e && npm run test:public-package-parity && npm run test:token-savings-dashboard && npm run test:cursor-wiring && npm run test:pretooluse-injection && npm run test:recent-corrective-context && npm run test:durability-step && npm run test:mailer && npm run test:brand-assets && npm run test:enforcement-teeth && npm run test:bayes-optimal-gate && npm run test:swarm-coordinator && npm run test:session-report && npm run test:agent-reasoning-traces && npm run test:judge-reward && npm run test:llm-behavior-monitor && npm run test:prompting-os && npm run test:single-use-credential-gate && npm run test:structured-prompt-driven && npm run test:require-evidence-gate && npm run test:rule-validator && npm run test:bluesky-atproto && npm run test:social-reply-monitor-bluesky && npm run test:bluesky-delete-replies && npm run test:architect-kit-memory-bridge && npm run test:sonar-review-hotspots && npm run test:actionable-remediations && npm run test:gemini-embedding-policy && npm run test:agent-design-governance && npm run test:public-core-boundary && npm run test:hook-stop-verify-deploy && npm run test:hook-stop-anti-claim && npm run test:plausible-server-events && npm run test:activation-tracker && npm run test:unified-revenue-rollup && npm run test:conversion-rate-stats && npm run test:external-customer-audit && npm run test:telemetry-export",
|
|
332
|
+
"test": "npm run test:python && npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:operational-integrity && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher && npm run test:autoresearch && npm run test:ops && npm run test:session-analyzer && npm run test:tessl && npm run test:gates && npm run test:evoskill && npm run test:gates-hardening && npm run test:workers && npm run test:social-analytics && npm run test:memalign && npm run test:xmemory-lite && npm run test:filesystem-search && npm run test:zernio && npm run test:platform-limits && npm run test:post-video && npm run test:post-everywhere-instagram && npm run test:post-everywhere-channels && npm run test:post-everywhere-zernio-default && npm run test:zernio-canonical-pollers && npm run test:zernio-status && npm run test:obsidian-export && npm run test:lesson-db && npm run test:lesson-rotation && npm run test:memory-dedup && npm run test:feedback-quality && npm run test:sync-version && npm run test:check-congruence && npm run test:tool-registry && npm run test:feedback-to-rules && npm run test:memory-firewall && npm run test:memory-scope-readiness && npm run test:belief-update && npm run test:hosted-config && npm run test:operational-summary && npm run test:operational-dashboard && npm run test:operator-artifacts && npm run test:operator-key-auth && npm run test:cloudflare-sandbox && npm run test:mcp-config && npm run test:plan-gate && npm run test:pulse && npm run test:semantic-layer && npm run test:data-pipeline && npm run test:optimize-context && npm run test:principle-extractor && npm run test:analytics-window && npm run test:funnel-analytics && npm run test:experiment-tracker && npm run test:build-metadata && npm run test:context-engine && npm run test:hf-papers && npm run test:marketing-experiment && npm run test:seo-gsd && npm run test:verify-run && npm run test:export-dpo-pairs && npm run test:export-hf-dataset && npm run test:license && npm run test:bot-detector && npm run test:audit-pr-bot-contamination && npm run test:stripe-bootstrap-saas-catalog && npm run test:postinstall && npm run test:funnel-invariants && npm run test:cli-telemetry && npm run test:pro-parity && npm run test:model-tier-router && npm run test:computer-use-firewall && npm run test:skill-exporter && npm run test:statusline && npm run test:evolution && npm run test:org-dashboard && npm run test:multi-hop-recall && npm run test:synthetic-dpo && npm run test:thumbgate-skill && npm run test:learn-hub && npm run test:feedback-fallback && npm run test:metaclaw && npm run test:server-lock && npm run test:control-tower && npm run test:pii-scanner && npm run test:data-governance && npm run test:lesson-inference && npm run test:semantic-dedup && npm run test:fs-utils && npm run test:cli-schema && npm run test:explore && npm run test:lesson-reranker && npm run test:lesson-retrieval && npm run test:cross-encoder && npm run test:reflector-agent && npm run test:feedback-session && npm run test:feedback-history-distiller && npm run test:hallucination-detector && npm run test:history-distiller && npm run test:predictive-insights && npm run test:prove-predictive-insights && npm run test:statusbar-cli && npm run test:generate-instagram-card && npm run test:instagram-thumbgate-post && npm run test:publish-instagram-thumbgate && npm run test:lesson-synthesis && npm run test:lesson-canonical && npm run test:background-governance && npm run test:memory-migration && npm run test:prompt-dlp && npm run test:ephemeral-store && npm run test:agent-security && npm run test:skill-progressive && npm run test:per-step-scoring && npm run test:weekly-auto-post && npm run test:social-post-hourly && npm run test:social-quality-gate && npm run test:a2ui-engine && npm run test:gate-satisfy && npm run test:money-watcher && npm run test:budget && npm run test:quick-start && npm run test:utm && npm run test:product-feedback && npm run test:feedback-root-consolidator && npm run test:engagement-audit && npm run test:install-growth-automation && npm run test:publish-thumbgate-launch && npm run test:community-course-platform-launch-kit && npm run test:reconcile-thumbgate-campaign && npm run test:reddit-publisher && npm run test:schedule-thumbgate-campaign && npm run test:social-reply-monitor && npm run test:social-dedupe-cleanup && npm run test:sync-launch-assets && npm run test:ai-search-visibility && npm run test:perplexity && npm run test:security-scanner && npm run test:llm-client && npm run test:managed-lesson-agent && npm run test:self-distill && npm run test:meta-agent && npm run test:harness-selector && npm run test:thumbgate-bench && npm run test:seo-guides && npm run test:enforcement-loop && npm run test:cli-agent-experience && npm run test:bot-detection && npm run test:checkout-bot-guard && npm run test:checkout-pro-confirmation-gate && npm run test:session-health && npm run test:session-episodes && npm run test:spec-gate && npm run test:decision-trace && npm run test:dashboard-insights && npm run test:telemetry-tracked-link-slug && npm run test:prompt-eval && npm run test:demo-voiceover && npm run test:gate-coherence && npm run test:gate-eval && npm run test:high-roi && npm run test:public-static-assets && npm run test:token-savings && npm run test:numbers-page && npm run test:workflow-gate-checkpoint && npm run test:lesson-export-import && npm run test:landing-page-claims && npm run test:competitive-positioning-marketing && npm run test:medium-weekly && npm run test:dashboard-deeplink-e2e && npm run test:public-package-parity && npm run test:token-savings-dashboard && npm run test:cursor-wiring && npm run test:pretooluse-injection && npm run test:recent-corrective-context && npm run test:durability-step && npm run test:mailer && npm run test:brand-assets && npm run test:enforcement-teeth && npm run test:bayes-optimal-gate && npm run test:swarm-coordinator && npm run test:session-report && npm run test:agent-reasoning-traces && npm run test:judge-reward && npm run test:llm-behavior-monitor && npm run test:prompting-os && npm run test:single-use-credential-gate && npm run test:structured-prompt-driven && npm run test:require-evidence-gate && npm run test:rule-validator && npm run test:bluesky-atproto && npm run test:social-reply-monitor-bluesky && npm run test:bluesky-delete-replies && npm run test:architect-kit-memory-bridge && npm run test:sonar-review-hotspots && npm run test:actionable-remediations && npm run test:gemini-embedding-policy && npm run test:agent-design-governance && npm run test:public-core-boundary && npm run test:hook-stop-verify-deploy && npm run test:hook-stop-anti-claim && npm run test:plausible-server-events && npm run test:activation-tracker && npm run test:unified-revenue-rollup && npm run test:conversion-rate-stats && npm run test:external-customer-audit && npm run test:telemetry-export && npm run test:stripe-checkout-diagnostic && npm run test:stripe-business-identity-probe && npm run test:revenue-observability-doctor && npm run test:public-bundle-ratchet && npm run test:ci-cd-hygiene-audit",
|
|
332
333
|
"test:hook-stop-verify-deploy": "node --test tests/hook-stop-verify-deploy.test.js",
|
|
333
334
|
"test:hook-stop-anti-claim": "node --test tests/hook-stop-anti-claim.test.js",
|
|
334
335
|
"test:plausible-server-events": "node --test tests/plausible-server-events.test.js",
|
|
@@ -336,6 +337,9 @@
|
|
|
336
337
|
"test:unified-revenue-rollup": "node --test tests/unified-revenue-rollup.test.js",
|
|
337
338
|
"test:conversion-rate-stats": "node --test tests/conversion-rate-stats.test.js",
|
|
338
339
|
"test:external-customer-audit": "node --test tests/external-customer-audit.test.js",
|
|
340
|
+
"test:stripe-checkout-diagnostic": "node --test tests/stripe-checkout-diagnostic.test.js",
|
|
341
|
+
"test:stripe-business-identity-probe": "node --test tests/stripe-business-identity-probe.test.js",
|
|
342
|
+
"test:ci-cd-hygiene-audit": "node --test tests/ci-cd-hygiene-audit.test.js",
|
|
339
343
|
"test:telemetry-export": "node --test tests/telemetry-export.test.js",
|
|
340
344
|
"test:swarm-coordinator": "node --test tests/swarm-coordinator.test.js",
|
|
341
345
|
"test:session-report": "node --test tests/session-report.test.js",
|
|
@@ -431,7 +435,7 @@
|
|
|
431
435
|
"test:workflow": "node --test tests/workflow-contract.test.js tests/social-marketing-assets.test.js tests/social-pipeline.test.js tests/positioning-contract.test.js tests/docs-claim-hygiene.test.js tests/thumbgate-scope.test.js tests/workflow-runs.test.js tests/workflow-sprint-intake.test.js tests/gtm-revenue-loop.test.js tests/may-2026-revenue-machine.test.js tests/customer-discovery-sprint.test.js tests/revenue-pack-utils.test.js tests/aiventyx-marketplace-plan.test.js tests/cursor-marketplace-revenue-pack.test.js tests/codex-marketplace-revenue-pack.test.js tests/codex-plugin-revenue-pack.test.js tests/gemini-cli-demand-pack.test.js tests/roo-sunset-demand-pack.test.js tests/linkedin-workflow-hardening-pack.test.js tests/chatgpt-gpt-revenue-pack.test.js tests/mcp-directory-revenue-pack.test.js tests/money-marketplace-distribution-pack.test.js tests/autonomous-sales-agent.test.js tests/reddit-dm-workflow-hardening-pack.test.js tests/sales-pipeline.test.js tests/reddit-dm-outreach.test.js tests/github-outreach.test.js tests/enterprise-story.test.js tests/ralph-loop.test.js tests/ralph-mode-ci.test.js tests/guide-conversion-path.test.js tests/roo-sunset-marketing.test.js",
|
|
432
436
|
"test:sales-pipeline": "node --test tests/sales-pipeline.test.js",
|
|
433
437
|
"test:billing": "node --test tests/billing.test.js tests/stripe-sync-product-images.test.js",
|
|
434
|
-
"test:cli": "node --test tests/analytics-report.test.js tests/agent-design-governance.test.js tests/codex-self-heal.test.js tests/creator-campaigns.test.js tests/cli.test.js tests/codex-bridge-script.test.js tests/dependabot-changeset.test.js tests/dispatch-brief.test.js tests/feedback-normalize.test.js tests/install-mcp.test.js tests/pr-manager.test.js tests/pro-local-dashboard.test.js tests/published-cli.test.js tests/revenue-status.test.js tests/stripe-live-status.test.js",
|
|
438
|
+
"test:cli": "node --test tests/analytics-report.test.js tests/agent-design-governance.test.js tests/codex-self-heal.test.js tests/creator-campaigns.test.js tests/cli.test.js tests/codex-bridge-script.test.js tests/dependabot-changeset.test.js tests/dispatch-brief.test.js tests/feedback-normalize.test.js tests/install-mcp.test.js tests/pr-manager.test.js tests/pro-local-dashboard.test.js tests/published-cli.test.js tests/revenue-status.test.js tests/stripe-live-status.test.js tests/creator-dev-and-prune.test.js",
|
|
435
439
|
"test:evolution": "node --test tests/workspace-evolver.test.js",
|
|
436
440
|
"test:watcher": "node --test tests/jsonl-watcher.test.js",
|
|
437
441
|
"test:autoresearch": "node --test tests/autoresearch.test.js",
|
|
@@ -508,6 +512,7 @@
|
|
|
508
512
|
"test:bot-detection": "node --test tests/bot-detection.test.js",
|
|
509
513
|
"test:checkout-bot-guard": "node --test tests/checkout-bot-guard.test.js",
|
|
510
514
|
"test:checkout-pro-confirmation-gate": "node --test tests/checkout-pro-confirmation-gate.test.js",
|
|
515
|
+
"test:revenue-observability-doctor": "node --test tests/revenue-observability-doctor.test.js",
|
|
511
516
|
"test:postinstall": "node --test tests/postinstall.test.js",
|
|
512
517
|
"test:funnel-invariants": "node --test tests/funnel-invariants.test.js",
|
|
513
518
|
"test:cli-telemetry": "node --test tests/cli-telemetry.test.js",
|
|
@@ -640,7 +645,8 @@
|
|
|
640
645
|
"test:brand-assets": "node --test tests/brand-assets.test.js",
|
|
641
646
|
"test:enforcement-teeth": "node --test tests/enforcement-teeth.test.js",
|
|
642
647
|
"test:bayes-optimal-gate": "node --test tests/bayes-optimal-gate.test.js",
|
|
643
|
-
"test:actionable-remediations": "node --test tests/actionable-remediations.test.js"
|
|
648
|
+
"test:actionable-remediations": "node --test tests/actionable-remediations.test.js",
|
|
649
|
+
"test:public-bundle-ratchet": "node --test tests/public-bundle-ratchet.test.js"
|
|
644
650
|
},
|
|
645
651
|
"keywords": [
|
|
646
652
|
"mcp",
|
package/public/compare.html
CHANGED
|
@@ -261,6 +261,12 @@
|
|
|
261
261
|
<p><a href="/compare/heidi" class="cta">Read ThumbGate vs HEIDI</a></p>
|
|
262
262
|
</div>
|
|
263
263
|
|
|
264
|
+
<div class="card">
|
|
265
|
+
<h3>Evaluating Rein for AI agent governance?</h3>
|
|
266
|
+
<p>Rein is a Python decorator that wraps tool functions with policy checks, audit trails, and circuit breakers โ targeted at production apps in trading, healthcare, and legal. ThumbGate is the coding-agent specialist with a learning feedback loop and MIT licensing. Same category (pre-action gates), different layer and stack.</p>
|
|
267
|
+
<p><a href="/compare/rein" class="cta">Read ThumbGate vs Rein</a></p>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
264
270
|
<h2>How It Works</h2>
|
|
265
271
|
<div class="step-grid">
|
|
266
272
|
<div class="step-card">
|
package/public/index.html
CHANGED
|
@@ -19,7 +19,7 @@ __GOOGLE_SITE_VERIFICATION_META__
|
|
|
19
19
|
<meta property="og:image" content="https://thumbgate-production.up.railway.app/og.png">
|
|
20
20
|
<meta name="twitter:card" content="summary_large_image">
|
|
21
21
|
<meta name="twitter:image" content="https://thumbgate-production.up.railway.app/og.png">
|
|
22
|
-
<meta name="thumbgate-version" content="1.
|
|
22
|
+
<meta name="thumbgate-version" content="1.20.0">
|
|
23
23
|
<meta name="keywords" content="ThumbGate, thumbgate, AI agent orchestration, AI experience orchestration, agent enforcement layer, save LLM tokens, reduce Claude API cost, reduce OpenAI cost, AI agent token savings, prevent LLM retries, prevent hallucination retries, stop AI token waste, pre-action checks, agent governance, Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode, workflow hardening, context engineering, AI authenticity, brand authenticity AI">
|
|
24
24
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
25
25
|
|
|
@@ -1318,6 +1318,11 @@ __GA_BOOTSTRAP__
|
|
|
1318
1318
|
</div>
|
|
1319
1319
|
<span class="team-intake-badge">30-minute intake</span>
|
|
1320
1320
|
</div>
|
|
1321
|
+
<details style="border:1px solid var(--border);border-radius:14px;padding:18px 22px;background:var(--bg-raised);margin:18px 0;">
|
|
1322
|
+
<summary style="cursor:pointer;font-size:16px;font-weight:600;color:var(--cyan);list-style:none;display:flex;align-items:center;gap:8px;">
|
|
1323
|
+
<span style="display:inline-block;transition:transform 0.15s;">โธ</span>
|
|
1324
|
+
Need consulting, a one-off diagnostic, or a hardening sprint? View paid services & kits
|
|
1325
|
+
</summary>
|
|
1321
1326
|
<div class="team-paid-path" data-sprint-paid-path aria-label="Paid workflow hardening options">
|
|
1322
1327
|
<div class="team-paid-card">
|
|
1323
1328
|
<div>
|
|
@@ -1360,6 +1365,7 @@ __GA_BOOTSTRAP__
|
|
|
1360
1365
|
</div>
|
|
1361
1366
|
</div>
|
|
1362
1367
|
</div>
|
|
1368
|
+
</details>
|
|
1363
1369
|
<div class="team-intake-recovery" aria-label="Checkout recovery path for workflow sprint buyers">
|
|
1364
1370
|
<div>
|
|
1365
1371
|
<strong>Not ready to pay from a checkout page?</strong>
|
|
@@ -1494,7 +1500,7 @@ __GA_BOOTSTRAP__
|
|
|
1494
1500
|
<a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
|
|
1495
1501
|
<a href="/blog">Blog</a>
|
|
1496
1502
|
</div>
|
|
1497
|
-
<span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.
|
|
1503
|
+
<span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.20.0</span>
|
|
1498
1504
|
</div>
|
|
1499
1505
|
</footer>
|
|
1500
1506
|
|
|
@@ -1840,6 +1846,27 @@ async function handleProCheckout() {
|
|
|
1840
1846
|
}
|
|
1841
1847
|
globalThis.location.assign(checkoutUrl);
|
|
1842
1848
|
}
|
|
1849
|
+
|
|
1850
|
+
// Conversion-path safety: /services redirects to /#workflow-sprint-intake.
|
|
1851
|
+
// Modern browsers auto-open a <details> containing the hash target, but
|
|
1852
|
+
// older Safari/Firefox/Chrome (<2020) do not. Force-open any ancestor
|
|
1853
|
+
// <details> of the hash target so high-intent buyers always see the
|
|
1854
|
+
// paid intake content on arrival.
|
|
1855
|
+
function openDetailsForHash() {
|
|
1856
|
+
var hash = globalThis.location.hash;
|
|
1857
|
+
if (!hash || hash.length < 2) return;
|
|
1858
|
+
var target;
|
|
1859
|
+
try { target = document.querySelector(hash); } catch (_) { return; }
|
|
1860
|
+
if (!target) return;
|
|
1861
|
+
var el = target;
|
|
1862
|
+
while (el && el !== document.body) {
|
|
1863
|
+
if (el.tagName === 'DETAILS') { el.open = true; }
|
|
1864
|
+
el = el.parentElement;
|
|
1865
|
+
}
|
|
1866
|
+
target.scrollIntoView({ behavior: 'auto', block: 'start' });
|
|
1867
|
+
}
|
|
1868
|
+
globalThis.addEventListener('DOMContentLoaded', openDetailsForHash);
|
|
1869
|
+
globalThis.addEventListener('hashchange', openDetailsForHash);
|
|
1843
1870
|
</script>
|
|
1844
1871
|
</body>
|
|
1845
1872
|
</html>
|
package/public/learn.html
CHANGED
|
@@ -266,6 +266,13 @@
|
|
|
266
266
|
<span class="article-tag">PreToolUse</span>
|
|
267
267
|
<span class="article-tag">Technical</span>
|
|
268
268
|
</a>
|
|
269
|
+
<a href="/learn/ai-agent-governance" class="article-card">
|
|
270
|
+
<h3>AI Agent Governance โ The Four Layers Pattern</h3>
|
|
271
|
+
<p>AI agent governance has four layers: prompt rules, decorator wrappers, pre-action hooks, sandbox isolation. Each catches a different failure mode. Pick the layer that matches your stack โ and understand why prompt rules alone fail.</p>
|
|
272
|
+
<span class="article-tag">Governance</span>
|
|
273
|
+
<span class="article-tag">Architecture</span>
|
|
274
|
+
<span class="article-tag">Pattern</span>
|
|
275
|
+
</a>
|
|
269
276
|
<a href="/learn/agent-harness-pattern" class="article-card">
|
|
270
277
|
<h3>The Agent Harness Pattern: Why Your AI Needs a Seatbelt</h3>
|
|
271
278
|
<p>Tsinghua researchers formalized agent harnesses as first-class objects with contracts, verification checks, and durable state. ThumbGate implements this pattern in production today.</p>
|
|
@@ -274,6 +281,14 @@
|
|
|
274
281
|
<span class="article-tag">NLAH</span>
|
|
275
282
|
</a>
|
|
276
283
|
|
|
284
|
+
<a href="/learn/agent-swarms-shared-gates" class="article-card">
|
|
285
|
+
<h3>Agent Swarms: One Gate Layer, Every Model</h3>
|
|
286
|
+
<p>A 5-agent swarm without shared memory pays 5× the tokens on every repeated mistake. Here is how a single MCP gate layer makes Opus, GPT, and Gemini fail the same way only once.</p>
|
|
287
|
+
<span class="article-tag">Agent Swarms</span>
|
|
288
|
+
<span class="article-tag">Multi-Agent</span>
|
|
289
|
+
<span class="article-tag">Shared Memory</span>
|
|
290
|
+
</a>
|
|
291
|
+
|
|
277
292
|
<a href="/learn/ai-agent-persistent-memory" class="article-card">
|
|
278
293
|
<h3>How to Give Your AI Coding Agent Persistent Memory Across Sessions</h3>
|
|
279
294
|
<p>Your agent forgets everything when the session ends. Here is how to give Claude Code, Cursor, Codex, and Gemini memory that survives restarts โ without retraining.</p>
|
package/public/numbers.html
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"alternateName": "thumbgate",
|
|
26
26
|
"applicationCategory": "DeveloperApplication",
|
|
27
27
|
"operatingSystem": "Cross-platform, Node.js >=18.18.0",
|
|
28
|
-
"softwareVersion": "1.
|
|
28
|
+
"softwareVersion": "1.20.0",
|
|
29
29
|
"url": "https://thumbgate-production.up.railway.app/numbers",
|
|
30
30
|
"dateModified": "2026-05-07",
|
|
31
31
|
"creator": {
|
|
@@ -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.
|
|
205
|
+
<div class="freshness">Updated: 2026-05-07 ยท Version 1.20.0</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>
|
package/public/pro.html
CHANGED
|
@@ -718,7 +718,7 @@ __GA_BOOTSTRAP__
|
|
|
718
718
|
<a href="#pricing">Pricing</a>
|
|
719
719
|
<a href="#faq">FAQ</a>
|
|
720
720
|
<a href="/dashboard">Demo</a>
|
|
721
|
-
<a
|
|
721
|
+
<a class="nav-cta btn-pro-checkout" href="/checkout/pro?utm_source=website&utm_medium=pro_page_nav&utm_campaign=pro_pack&cta_id=pro_page_nav&cta_placement=nav&plan_id=pro&landing_path=%2Fpro">Start Pro</a>
|
|
722
722
|
</div>
|
|
723
723
|
</div>
|
|
724
724
|
</nav>
|
|
@@ -730,7 +730,7 @@ __GA_BOOTSTRAP__
|
|
|
730
730
|
<h1>Buy the operator loop that proves your AI agent stopped repeating the mistake.</h1>
|
|
731
731
|
<p style="font-size:13px;opacity:0.8;margin-bottom:0.5rem;">Updated: <time datetime="2026-04-20">2026-04-20</time> ยท by <a href="https://github.com/IgorGanapolsky" style="color:inherit;">Igor Ganapolsky</a></p>
|
|
732
732
|
<p>ThumbGate Pro is for one operator who already hit a repeated AI-agent failure and now needs proof: what was blocked, why it was blocked, and what changed before the next risky run.</p>
|
|
733
|
-
<p>
|
|
733
|
+
<p>Start Pro when you want the local dashboard, DPO export, and a single proof lane for the repeated mistake you need to stop. Team diagnostics and custom services are handled through intake, not this buyer path.</p>
|
|
734
734
|
<div class="hero-proof">
|
|
735
735
|
<div class="proof-pill">Personal local dashboard</div>
|
|
736
736
|
<div class="proof-pill">DPO export from real corrections</div>
|
|
@@ -738,8 +738,7 @@ __GA_BOOTSTRAP__
|
|
|
738
738
|
<div class="proof-pill">Founder support on risky flows</div>
|
|
739
739
|
</div>
|
|
740
740
|
<div class="hero-actions">
|
|
741
|
-
<a
|
|
742
|
-
<a class="btn-secondary btn-pro-checkout" href="/checkout/pro?utm_source=website&utm_medium=pro_page_hero&utm_campaign=pro_pack&cta_id=pro_page_primary&cta_placement=hero&plan_id=pro&landing_path=%2Fpro">Start Pro dashboard</a>
|
|
741
|
+
<a class="btn-primary btn-pro-checkout" href="/checkout/pro?utm_source=website&utm_medium=pro_page_hero&utm_campaign=pro_pack&cta_id=pro_page_primary&cta_placement=hero&plan_id=pro&landing_path=%2Fpro">Start Pro dashboard</a>
|
|
743
742
|
<a class="btn-secondary btn-demo" href="/dashboard?utm_source=website&utm_medium=pro_page&utm_campaign=pro_pack">Open dashboard demo</a>
|
|
744
743
|
<a class="btn-ghost btn-free-path" href="/guide?utm_source=website&utm_medium=pro_page&utm_campaign=free_install">Stay on Free and install locally</a>
|
|
745
744
|
</div>
|
|
@@ -775,18 +774,6 @@ __GA_BOOTSTRAP__
|
|
|
775
774
|
<p>Visual check debugger, DPO export, auto-connect after activation, Model Hardening Advisor, and founder support for the risky flow you need to harden first.</p>
|
|
776
775
|
</div>
|
|
777
776
|
|
|
778
|
-
<div class="aside-card" data-pro-paid-recovery>
|
|
779
|
-
<div class="aside-kicker">Team workflow blocked?</div>
|
|
780
|
-
<h3>Buy the paid diagnostic</h3>
|
|
781
|
-
<p>Skip self-serve Pro and pay for the smallest useful review now.</p>
|
|
782
|
-
<div class="price-stack">
|
|
783
|
-
<a rel="nofollow noopener noreferrer" target="_blank" class="btn-secondary" data-first-rule-link href="https://buy.stripe.com/fZu28rfCY6zcbO99uj3sI2G" onclick="sendFirstPartyTelemetry('first_failure_rule_checkout_started',{ctaId:'pro_page_first_failure_rule_checkout',price:1});sendGa4Event('begin_checkout',{currency:'USD',value:1,items:[{item_id:'first_failure_rule',item_name:'First AI Agent Failure Rule'}]});">Pay $1 first rule</a>
|
|
784
|
-
<a rel="nofollow noopener noreferrer" target="_blank" class="btn-secondary" data-quick-read-link href="https://buy.stripe.com/5kQ7sL76s1eSaK55e33sI2H" onclick="sendFirstPartyTelemetry('quick_read_checkout_started',{ctaId:'pro_page_quick_read_checkout',price:19});">Pay $19 quick read</a>
|
|
785
|
-
<a class="btn-primary" data-sprint-diagnostic-link href="__SPRINT_DIAGNOSTIC_CHECKOUT_URL__" onclick="sendFirstPartyTelemetry('workflow_sprint_diagnostic_checkout_started',{ctaId:'pro_page_sprint_diagnostic_checkout'});sendGa4Event('begin_checkout',{currency:'USD',value:__SPRINT_DIAGNOSTIC_PRICE_DOLLARS__});">Pay $__SPRINT_DIAGNOSTIC_PRICE_DOLLARS__ diagnostic</a>
|
|
786
|
-
<a class="btn-secondary" data-workflow-sprint-link href="__WORKFLOW_SPRINT_CHECKOUT_URL__" onclick="sendFirstPartyTelemetry('workflow_sprint_checkout_started',{ctaId:'pro_page_workflow_sprint_checkout'});sendGa4Event('begin_checkout',{currency:'USD',value:__WORKFLOW_SPRINT_PRICE_DOLLARS__});">Pay $__WORKFLOW_SPRINT_PRICE_DOLLARS__ sprint</a>
|
|
787
|
-
</div>
|
|
788
|
-
</div>
|
|
789
|
-
|
|
790
777
|
<div class="aside-card">
|
|
791
778
|
<div class="aside-kicker">Keep the buyer path warm</div>
|
|
792
779
|
<h3>Save your work email before you decide</h3>
|
|
@@ -1024,7 +1011,6 @@ function sendFirstPartyTelemetry(eventType, props) {
|
|
|
1024
1011
|
}
|
|
1025
1012
|
|
|
1026
1013
|
function sendGa4Event(e,p){if(typeof gtag==='function')gtag('event',e,p||{})}
|
|
1027
|
-
function initializeProPaidRecovery(){var c=document.querySelector('[data-pro-paid-recovery]');if(!c)return;var n=0;c.querySelectorAll('a[href]').forEach(function(a){var h=a.getAttribute('href')||'';if(/^https?:\/\//.test(h))n+=1;else a.hidden=true});c.hidden=n===0}
|
|
1028
1014
|
|
|
1029
1015
|
function initializeBuyerIntent() {
|
|
1030
1016
|
globalThis.ThumbGateBuyerIntent.initializeBuyerIntent({
|
|
@@ -1067,7 +1053,6 @@ trackClick('.btn-pro-checkout', 'pro_checkout_start', { tier: 'pro', page: 'pro'
|
|
|
1067
1053
|
trackClick('.btn-demo', 'pro_demo_click', { page: 'pro' });
|
|
1068
1054
|
trackClick('.btn-free-path', 'pro_free_path_click', { page: 'pro' });
|
|
1069
1055
|
trackClick('.proof-links a', 'pro_proof_click', { page: 'pro' });
|
|
1070
|
-
initializeProPaidRecovery();
|
|
1071
1056
|
initializeBuyerIntent();
|
|
1072
1057
|
globalThis.buyerJourney = globalThis.ThumbGateBuyerIntent.initializeBehaviorAnalytics({
|
|
1073
1058
|
pageType: 'marketing',
|
|
@@ -1084,10 +1069,6 @@ globalThis.buyerJourney = globalThis.ThumbGateBuyerIntent.initializeBehaviorAnal
|
|
|
1084
1069
|
],
|
|
1085
1070
|
ctaImpressions: [
|
|
1086
1071
|
{ selector: '.btn-pro-checkout', ctaId: 'pro_checkout', ctaPlacement: 'pro_page', planId: 'pro' },
|
|
1087
|
-
{ selector: '[data-first-rule-link]', ctaId: 'pro_page_first_failure_rule_checkout', ctaPlacement: 'pro_paid_recovery', planId: 'first_failure_rule' },
|
|
1088
|
-
{ selector: '[data-quick-read-link]', ctaId: 'pro_page_quick_read_checkout', ctaPlacement: 'pro_paid_recovery', planId: 'quick_read' },
|
|
1089
|
-
{ selector: '[data-sprint-diagnostic-link]', ctaId: 'pro_page_sprint_diagnostic_checkout', ctaPlacement: 'pro_paid_recovery', planId: 'sprint_diagnostic' },
|
|
1090
|
-
{ selector: '[data-workflow-sprint-link]', ctaId: 'pro_page_workflow_sprint_checkout', ctaPlacement: 'pro_paid_recovery', planId: 'workflow_sprint' },
|
|
1091
1072
|
{ selector: '.btn-demo', ctaId: 'pro_demo', ctaPlacement: 'pro_page', planId: 'proof' },
|
|
1092
1073
|
{ selector: '.btn-free-path', ctaId: 'pro_free_path', ctaPlacement: 'pro_page', planId: 'free' }
|
|
1093
1074
|
]
|
|
@@ -13,6 +13,22 @@ const WARN_THRESHOLD = 1;
|
|
|
13
13
|
const BLOCK_THRESHOLD = 3; // 3+ repeated failures hard-block the action
|
|
14
14
|
const WINDOW_DAYS = 30;
|
|
15
15
|
|
|
16
|
+
// Default TTL on auto-promoted gates. Reddit reviewer @MomSausageandPeppers
|
|
17
|
+
// (2026-05-13) flagged that without expiry, "accidental dislikes become policy
|
|
18
|
+
// forever." Gates expire 90 days after promotion UNLESS they keep firing โ
|
|
19
|
+
// every fire refreshes lastFiredAt, and expireGates() keeps any gate fired
|
|
20
|
+
// within the last TTL window regardless of original promotion date. Manual
|
|
21
|
+
// force-promote bypasses TTL (operator says "permanent"). Override via
|
|
22
|
+
// THUMBGATE_RULE_TTL_DAYS env var.
|
|
23
|
+
const DEFAULT_RULE_TTL_DAYS = 90;
|
|
24
|
+
function getRuleTtlDays() {
|
|
25
|
+
const raw = Number(process.env.THUMBGATE_RULE_TTL_DAYS);
|
|
26
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_RULE_TTL_DAYS;
|
|
27
|
+
}
|
|
28
|
+
function getRuleTtlMs() {
|
|
29
|
+
return getRuleTtlDays() * 24 * 60 * 60 * 1000;
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
const NEG_SIGNALS = new Set(['negative', 'negative_strong', 'down', 'thumbs_down']);
|
|
17
33
|
|
|
18
34
|
function getFeedbackLogPath() {
|
|
@@ -66,15 +82,54 @@ function isNegative(entry) {
|
|
|
66
82
|
return NEG_SIGNALS.has(sig);
|
|
67
83
|
}
|
|
68
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Normalize a captured command/context string so trivial variants collapse
|
|
87
|
+
* to the same gate signature.
|
|
88
|
+
*
|
|
89
|
+
* Reddit critique (MomSausageandPeppers, 2026-05-17): "commands are matched
|
|
90
|
+
* by string equality, so `rm -rf node_modules` and `rm -rf ./node_modules`
|
|
91
|
+
* create separate gates."
|
|
92
|
+
*
|
|
93
|
+
* Conservative โ only collapse variants that are *unambiguously* the same
|
|
94
|
+
* intent. Does NOT reorder flags, strip `&&` chains, or canonicalize
|
|
95
|
+
* subcommands (each can change semantics).
|
|
96
|
+
*
|
|
97
|
+
* 1. Lowercase
|
|
98
|
+
* 2. Strip `/Users/<name>` and `/home/<name>` home-dir prefixes (โ `~`)
|
|
99
|
+
* 3. Drop `:LINE` and `:LINE:COL` refs
|
|
100
|
+
* 4. Per-token: strip one layer of matching outer quotes/backticks
|
|
101
|
+
* 5. Per-token: drop leading `./`
|
|
102
|
+
* 6. Collapse whitespace + trim
|
|
103
|
+
*/
|
|
104
|
+
function normalizeCommandSignature(input) {
|
|
105
|
+
let text = String(input || '');
|
|
106
|
+
if (!text) return '';
|
|
107
|
+
text = text.toLowerCase();
|
|
108
|
+
text = text.replace(/\/users\/[^\s/]+/g, '~').replace(/\/home\/[^\s/]+/g, '~');
|
|
109
|
+
text = text.replace(/:\d+(?::\d+)?\b/g, '');
|
|
110
|
+
const tokens = text.split(/\s+/).filter(Boolean).map((tok) => {
|
|
111
|
+
let t = tok;
|
|
112
|
+
if (t.length >= 2) {
|
|
113
|
+
const first = t[0];
|
|
114
|
+
const last = t[t.length - 1];
|
|
115
|
+
if ((first === '"' || first === "'" || first === '`') && first === last) {
|
|
116
|
+
t = t.slice(1, -1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (t.startsWith('./')) t = t.slice(2);
|
|
120
|
+
return t;
|
|
121
|
+
}).filter(Boolean);
|
|
122
|
+
return tokens.join(' ').trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
69
125
|
function extractPatternKey(entry) {
|
|
70
126
|
// Use tags as primary grouping key; fall back to context normalization
|
|
71
127
|
const tags = (entry.tags || []).filter((t) => !['feedback', 'negative', 'positive'].includes(t));
|
|
72
128
|
if (tags.length > 0) return tags.sort().join('+');
|
|
73
129
|
|
|
74
|
-
const ctx = (entry.context || entry.whatWentWrong || '').
|
|
130
|
+
const ctx = (entry.context || entry.whatWentWrong || '').trim();
|
|
75
131
|
if (ctx.length < 10) return null;
|
|
76
|
-
|
|
77
|
-
return ctx.replace(/\/Users\/[^\s/]+/g, '~').replace(/:[0-9]+/g, '').replace(/\s+/g, ' ').slice(0, 100);
|
|
132
|
+
return normalizeCommandSignature(ctx).slice(0, 100);
|
|
78
133
|
}
|
|
79
134
|
|
|
80
135
|
function extractDiagnosticKeys(entry) {
|
|
@@ -147,10 +202,17 @@ function buildGateRule(group) {
|
|
|
147
202
|
: group.key.startsWith('constraint:')
|
|
148
203
|
? 'repeated constraint violation'
|
|
149
204
|
: 'repeated pattern';
|
|
150
|
-
|
|
205
|
+
|
|
151
206
|
const occurrencesText = group.count === 'MANUAL' ? 'manual' : `${group.count} occurrences`;
|
|
152
207
|
const suggestedMessage = `Auto-promoted ${kind}: "${context}" (${occurrencesText} in ${WINDOW_DAYS} days)`;
|
|
153
208
|
|
|
209
|
+
// TTL: auto-promoted rules expire after the configured window unless
|
|
210
|
+
// refreshed by a fresh fire. Manual force-promote bypasses TTL โ operator
|
|
211
|
+
// says "permanent" by going through the force path.
|
|
212
|
+
const nowMs = Date.now();
|
|
213
|
+
const isManual = group.count === 'MANUAL';
|
|
214
|
+
const expiresAt = isManual ? null : new Date(nowMs + getRuleTtlMs()).toISOString();
|
|
215
|
+
|
|
154
216
|
return {
|
|
155
217
|
id: patternToGateId(group.key),
|
|
156
218
|
trigger: `auto:${group.key}`,
|
|
@@ -160,10 +222,82 @@ function buildGateRule(group) {
|
|
|
160
222
|
severity,
|
|
161
223
|
occurrences: group.count,
|
|
162
224
|
promotedAt: new Date().toISOString(),
|
|
225
|
+
expiresAt,
|
|
226
|
+
lastFiredAt: null,
|
|
163
227
|
source: group.source || 'auto-promote',
|
|
164
228
|
};
|
|
165
229
|
}
|
|
166
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Drop expired gates from the data and return the gates removed.
|
|
233
|
+
*
|
|
234
|
+
* A gate is expired when its `expiresAt` is in the past AND its
|
|
235
|
+
* `lastFiredAt` (if set) is also outside the TTL window โ high-signal
|
|
236
|
+
* gates that keep firing get continuously renewed and never expire.
|
|
237
|
+
*
|
|
238
|
+
* `expiresAt: null` is treated as "permanent" (used by force-promote /
|
|
239
|
+
* legacy gates without TTL data).
|
|
240
|
+
*/
|
|
241
|
+
function expireGates(data, now = Date.now()) {
|
|
242
|
+
const safeData = data && typeof data === 'object'
|
|
243
|
+
? { version: data.version || 1, gates: Array.isArray(data.gates) ? data.gates : [], promotionLog: Array.isArray(data.promotionLog) ? data.promotionLog : [] }
|
|
244
|
+
: { version: 1, gates: [], promotionLog: [] };
|
|
245
|
+
const ttlMs = getRuleTtlMs();
|
|
246
|
+
const kept = [];
|
|
247
|
+
const expired = [];
|
|
248
|
+
for (const gate of safeData.gates) {
|
|
249
|
+
if (!gate || typeof gate !== 'object') continue;
|
|
250
|
+
// No expiresAt โ treat as permanent (manual force-promote, legacy gates).
|
|
251
|
+
if (gate.expiresAt == null) {
|
|
252
|
+
kept.push(gate);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const expiresMs = Date.parse(gate.expiresAt);
|
|
256
|
+
if (!Number.isFinite(expiresMs)) {
|
|
257
|
+
kept.push(gate);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
// If last fire is within TTL window, refresh the gate (extend expiresAt).
|
|
261
|
+
const lastFiredMs = gate.lastFiredAt ? Date.parse(gate.lastFiredAt) : NaN;
|
|
262
|
+
if (Number.isFinite(lastFiredMs) && now - lastFiredMs < ttlMs) {
|
|
263
|
+
kept.push({ ...gate, expiresAt: new Date(lastFiredMs + ttlMs).toISOString() });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (now < expiresMs) {
|
|
267
|
+
kept.push(gate);
|
|
268
|
+
} else {
|
|
269
|
+
expired.push({ id: gate.id, expiresAt: gate.expiresAt, lastFiredAt: gate.lastFiredAt });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
safeData.gates = kept;
|
|
273
|
+
if (expired.length > 0) {
|
|
274
|
+
safeData.promotionLog.push(
|
|
275
|
+
...expired.map((e) => ({ type: 'expired', gateId: e.id, expiredAt: e.expiresAt, timestamp: new Date(now).toISOString() }))
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return { data: safeData, expired };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Mark a gate as fired now. Refreshes lastFiredAt AND extends expiresAt by
|
|
283
|
+
* the full TTL โ a gate that keeps catching repeats sharpens, doesn't
|
|
284
|
+
* decay. Caller passes the gate ID; returns the updated gate (or null).
|
|
285
|
+
*/
|
|
286
|
+
function recordGateFire(data, gateId, now = Date.now()) {
|
|
287
|
+
if (!data || !Array.isArray(data.gates)) return null;
|
|
288
|
+
const idx = data.gates.findIndex((g) => g && g.id === gateId);
|
|
289
|
+
if (idx === -1) return null;
|
|
290
|
+
const gate = data.gates[idx];
|
|
291
|
+
const lastFiredAtIso = new Date(now).toISOString();
|
|
292
|
+
const updated = {
|
|
293
|
+
...gate,
|
|
294
|
+
lastFiredAt: lastFiredAtIso,
|
|
295
|
+
expiresAt: gate.expiresAt == null ? null : new Date(now + getRuleTtlMs()).toISOString(),
|
|
296
|
+
};
|
|
297
|
+
data.gates[idx] = updated;
|
|
298
|
+
return updated;
|
|
299
|
+
}
|
|
300
|
+
|
|
167
301
|
function forcePromote(context, action = 'block') {
|
|
168
302
|
if (!context) throw new Error('context is required for force-promote');
|
|
169
303
|
const data = loadAutoGates();
|
|
@@ -202,9 +336,15 @@ function promote(feedbackLogPath) {
|
|
|
202
336
|
const logPath = feedbackLogPath || getFeedbackLogPath();
|
|
203
337
|
const entries = readJSONL(logPath);
|
|
204
338
|
const groups = groupNegativeFeedback(entries, WINDOW_DAYS);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
339
|
+
// Expire stale gates BEFORE running the promotion loop so an expiring
|
|
340
|
+
// gate that's about to be re-promoted gets a fresh TTL via the normal
|
|
341
|
+
// path rather than carrying a near-stale expiresAt.
|
|
342
|
+
const { data: expiredData, expired } = expireGates(loadAutoGates());
|
|
343
|
+
const data = expiredData;
|
|
344
|
+
if (expired.length > 0) {
|
|
345
|
+
saveAutoGates(data);
|
|
346
|
+
}
|
|
347
|
+
const promotions = expired.map((e) => ({ type: 'expired', gateId: e.id, expiredAt: e.expiresAt }));
|
|
208
348
|
|
|
209
349
|
for (const group of Object.values(groups)) {
|
|
210
350
|
if (group.count < WARN_THRESHOLD) continue;
|
|
@@ -298,9 +438,15 @@ module.exports = {
|
|
|
298
438
|
patternToGateId,
|
|
299
439
|
buildGateRule,
|
|
300
440
|
extractPatternKey,
|
|
441
|
+
normalizeCommandSignature,
|
|
301
442
|
isNegative,
|
|
443
|
+
expireGates,
|
|
444
|
+
recordGateFire,
|
|
445
|
+
getRuleTtlDays,
|
|
446
|
+
getRuleTtlMs,
|
|
302
447
|
MAX_AUTO_GATES,
|
|
303
448
|
WARN_THRESHOLD,
|
|
304
449
|
BLOCK_THRESHOLD,
|
|
305
450
|
WINDOW_DAYS,
|
|
451
|
+
DEFAULT_RULE_TTL_DAYS,
|
|
306
452
|
};
|
|
@@ -186,48 +186,69 @@ function hookAlreadyPresent(hookArray, command) {
|
|
|
186
186
|
* (defaults to process.cwd()).
|
|
187
187
|
* @returns {{ hooks: Array, removedPaths: string[] }}
|
|
188
188
|
*/
|
|
189
|
+
// Shell-style variable expansion limited to the env vars Claude Code
|
|
190
|
+
// documents for hook commands (CLAUDE_PROJECT_DIR), plus other process env
|
|
191
|
+
// vars. Surrounding ASCII quotes are stripped first so tokens like
|
|
192
|
+
// `"$CLAUDE_PROJECT_DIR"/.claude/hooks/x.sh` resolve correctly.
|
|
193
|
+
function expandShellToken(token, resolveBase) {
|
|
194
|
+
let s = token;
|
|
195
|
+
if (s.startsWith('"') && s.includes('"', 1)) {
|
|
196
|
+
s = s.slice(1, s.indexOf('"', 1)) + s.slice(s.indexOf('"', 1) + 1);
|
|
197
|
+
} else if (s.startsWith("'") && s.includes("'", 1)) {
|
|
198
|
+
s = s.slice(1, s.indexOf("'", 1)) + s.slice(s.indexOf("'", 1) + 1);
|
|
199
|
+
}
|
|
200
|
+
const lookup = (name) => (name === 'CLAUDE_PROJECT_DIR'
|
|
201
|
+
? process.env.CLAUDE_PROJECT_DIR || resolveBase
|
|
202
|
+
: process.env[name]);
|
|
203
|
+
s = s.replace(/\$\{([A-Za-z_]\w*)\}/g, (_, n) => {
|
|
204
|
+
const v = lookup(n);
|
|
205
|
+
return v == null ? `\${${n}}` : v;
|
|
206
|
+
});
|
|
207
|
+
s = s.replace(/\$([A-Za-z_]\w*)/g, (_, n) => {
|
|
208
|
+
const v = lookup(n);
|
|
209
|
+
return v == null ? `$${n}` : v;
|
|
210
|
+
});
|
|
211
|
+
return s;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Returns the raw (unexpanded) script-path token if the command points at a
|
|
215
|
+
// missing script file, else null. Anything that doesn't look like a file
|
|
216
|
+
// reference, or contains unresolved $VAR after expansion, returns null โ
|
|
217
|
+
// caller treats null as "keep the hook" (err on the side of NOT pruning).
|
|
218
|
+
function staleHookPath(command, resolveBase) {
|
|
219
|
+
if (!command) return null;
|
|
220
|
+
const rawFirstToken = command.split(/\s+/)[0];
|
|
221
|
+
const firstToken = expandShellToken(rawFirstToken, resolveBase);
|
|
222
|
+
const looksLikePath =
|
|
223
|
+
firstToken.includes('/') ||
|
|
224
|
+
firstToken.includes('\\') ||
|
|
225
|
+
firstToken.endsWith('.sh');
|
|
226
|
+
if (!looksLikePath) return null;
|
|
227
|
+
if (firstToken.includes('$')) return null;
|
|
228
|
+
const resolved = path.isAbsolute(firstToken)
|
|
229
|
+
? firstToken
|
|
230
|
+
: path.resolve(resolveBase, firstToken);
|
|
231
|
+
return fs.existsSync(resolved) ? null : rawFirstToken;
|
|
232
|
+
}
|
|
233
|
+
|
|
189
234
|
function pruneStaleFileHooks(hookArray, baseDir) {
|
|
190
235
|
if (!Array.isArray(hookArray)) {
|
|
191
236
|
return { hooks: [], removedPaths: [] };
|
|
192
237
|
}
|
|
193
|
-
|
|
194
238
|
const resolveBase = baseDir || process.cwd();
|
|
195
239
|
const removedPaths = [];
|
|
196
|
-
|
|
197
240
|
const hooks = hookArray.filter((entry) => {
|
|
198
241
|
const entryHooks = Array.isArray(entry && entry.hooks) ? entry.hooks : [];
|
|
199
|
-
let shouldRemove = false;
|
|
200
|
-
|
|
201
242
|
for (const hook of entryHooks) {
|
|
202
243
|
const command = hook && typeof hook.command === 'string' ? hook.command : '';
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// Only treat it as a file reference if it looks like a path.
|
|
209
|
-
const looksLikePath =
|
|
210
|
-
firstToken.includes('/') ||
|
|
211
|
-
firstToken.includes('\\') ||
|
|
212
|
-
firstToken.endsWith('.sh');
|
|
213
|
-
|
|
214
|
-
if (!looksLikePath) continue;
|
|
215
|
-
|
|
216
|
-
// Resolve the path (absolute or relative to baseDir).
|
|
217
|
-
const resolved = path.isAbsolute(firstToken)
|
|
218
|
-
? firstToken
|
|
219
|
-
: path.resolve(resolveBase, firstToken);
|
|
220
|
-
|
|
221
|
-
if (!fs.existsSync(resolved)) {
|
|
222
|
-
removedPaths.push(firstToken);
|
|
223
|
-
shouldRemove = true;
|
|
224
|
-
break;
|
|
244
|
+
const stale = staleHookPath(command, resolveBase);
|
|
245
|
+
if (stale !== null) {
|
|
246
|
+
removedPaths.push(stale);
|
|
247
|
+
return false;
|
|
225
248
|
}
|
|
226
249
|
}
|
|
227
|
-
|
|
228
|
-
return !shouldRemove;
|
|
250
|
+
return true;
|
|
229
251
|
});
|
|
230
|
-
|
|
231
252
|
return { hooks, removedPaths };
|
|
232
253
|
}
|
|
233
254
|
|
package/scripts/cli-feedback.js
CHANGED
|
@@ -14,7 +14,15 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const { captureFeedback } = require('./feedback-loop');
|
|
17
|
-
const {
|
|
17
|
+
const { loadOptionalModule } = require('./private-core-boundary');
|
|
18
|
+
// `history-distiller` is a PRIVATE_CORE_MODULE โ present in this checkout and
|
|
19
|
+
// in ThumbGate-Core, but intentionally excluded from the public npm tarball.
|
|
20
|
+
// The hard `require('./history-distiller')` form crashed `hook-auto-capture`
|
|
21
|
+
// in published 1.19.0 with MODULE_NOT_FOUND. Public-shell fallback returns
|
|
22
|
+
// null distillation; caller already handles a null distillResult.
|
|
23
|
+
const { distillFromHistory } = loadOptionalModule('./history-distiller', () => ({
|
|
24
|
+
distillFromHistory: () => null,
|
|
25
|
+
}));
|
|
18
26
|
const { getRecentLesson, getLessonStats } = require('./lesson-inference');
|
|
19
27
|
|
|
20
28
|
const G = '\x1b[32m';
|
package/scripts/rate-limiter.js
CHANGED
|
@@ -42,6 +42,17 @@ const PAYWALL_MESSAGES = {
|
|
|
42
42
|
function isProTier(authContext) {
|
|
43
43
|
if (authContext && authContext.tier === 'pro') return true;
|
|
44
44
|
if (process.env.THUMBGATE_API_KEY || process.env.THUMBGATE_PRO_MODE === '1' || process.env.THUMBGATE_NO_RATE_LIMIT === '1') return true;
|
|
45
|
+
// Creator/dogfooding bypass: when the owner has the dev secret + bypass
|
|
46
|
+
// configured (env or ~/.config/thumbgate/dev.json), treat the install as Pro
|
|
47
|
+
// so marketing nudges and rate limits stop firing on the maintainer's own
|
|
48
|
+
// machine.
|
|
49
|
+
try {
|
|
50
|
+
const { isCreatorDev } = require('./pro-local-dashboard');
|
|
51
|
+
if (isCreatorDev()) return true;
|
|
52
|
+
} catch (_) {
|
|
53
|
+
// pro-local-dashboard unavailable in this runtime โ fall through to
|
|
54
|
+
// license/free-tier handling rather than failing the check.
|
|
55
|
+
}
|
|
45
56
|
try {
|
|
46
57
|
const { isProLicensed } = require('./license');
|
|
47
58
|
if (isProLicensed()) return true;
|
package/src/api/server.js
CHANGED
|
@@ -1499,50 +1499,11 @@ function buildCheckoutIntentHref(baseUrl, metadata = {}, overrides = {}) {
|
|
|
1499
1499
|
|
|
1500
1500
|
function renderCheckoutIntentPage({
|
|
1501
1501
|
confirmHref,
|
|
1502
|
-
firstRuleCheckoutHref,
|
|
1503
|
-
quickReadCheckoutHref,
|
|
1504
|
-
workflowTeardownCheckoutHref,
|
|
1505
1502
|
workflowIntakeHref,
|
|
1506
|
-
teamOptionsHref,
|
|
1507
|
-
diagnosticCheckoutHref,
|
|
1508
|
-
sprintCheckoutHref,
|
|
1509
|
-
sprintDiagnosticPriceDollars = 499,
|
|
1510
|
-
workflowSprintPriceDollars = 1500,
|
|
1511
1503
|
}) {
|
|
1512
1504
|
const safeConfirmHref = escapeHtmlAttribute(confirmHref);
|
|
1513
|
-
const safeFirstRuleCheckoutHref = firstRuleCheckoutHref
|
|
1514
|
-
? escapeHtmlAttribute(firstRuleCheckoutHref)
|
|
1515
|
-
: '';
|
|
1516
|
-
const safeQuickReadCheckoutHref = quickReadCheckoutHref
|
|
1517
|
-
? escapeHtmlAttribute(quickReadCheckoutHref)
|
|
1518
|
-
: '';
|
|
1519
|
-
const safeWorkflowTeardownCheckoutHref = workflowTeardownCheckoutHref
|
|
1520
|
-
? escapeHtmlAttribute(workflowTeardownCheckoutHref)
|
|
1521
|
-
: '';
|
|
1522
1505
|
const safeWorkflowIntakeHref = escapeHtmlAttribute(workflowIntakeHref);
|
|
1523
|
-
|
|
1524
|
-
const safeDiagnosticCheckoutHref = diagnosticCheckoutHref
|
|
1525
|
-
? escapeHtmlAttribute(diagnosticCheckoutHref)
|
|
1526
|
-
: '';
|
|
1527
|
-
const safeSprintCheckoutHref = sprintCheckoutHref
|
|
1528
|
-
? escapeHtmlAttribute(sprintCheckoutHref)
|
|
1529
|
-
: '';
|
|
1530
|
-
const diagnosticAction = safeDiagnosticCheckoutHref
|
|
1531
|
-
? `<a data-i="sprint_diagnostic_checkout" href="${safeDiagnosticCheckoutHref}">Book $${sprintDiagnosticPriceDollars} diagnostic</a>`
|
|
1532
|
-
: '';
|
|
1533
|
-
const sprintAction = safeSprintCheckoutHref
|
|
1534
|
-
? `<a data-i="workflow_sprint_checkout" href="${safeSprintCheckoutHref}">Start $${workflowSprintPriceDollars} sprint</a>`
|
|
1535
|
-
: '';
|
|
1536
|
-
const firstRuleAction = safeFirstRuleCheckoutHref
|
|
1537
|
-
? `<a data-i="first_failure_rule_checkout" href="${safeFirstRuleCheckoutHref}">Pay $1 first rule</a>`
|
|
1538
|
-
: '';
|
|
1539
|
-
const quickReadAction = safeQuickReadCheckoutHref
|
|
1540
|
-
? `<a data-i="quick_read_checkout" href="${safeQuickReadCheckoutHref}">Pay $19 quick read</a>`
|
|
1541
|
-
: '';
|
|
1542
|
-
const teardownAction = safeWorkflowTeardownCheckoutHref
|
|
1543
|
-
? `<a data-i="workflow_teardown_checkout" href="${safeWorkflowTeardownCheckoutHref}">Pay $99 teardown</a>`
|
|
1544
|
-
: '';
|
|
1545
|
-
return `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm โ ThumbGate Pro</title><style>body{background:#0a0a0a;color:#eee;font-family:system-ui,-apple-system,sans-serif;line-height:1.5}main{max-width:520px;margin:8vh auto;padding:0 20px}.brand{display:flex;align-items:center;gap:10px;margin-bottom:24px;font-size:14px;color:#94a3b8}.brand-mark{width:24px;height:24px;background:#22d3ee;border-radius:6px;display:inline-block}h1{font-size:24px;margin:0 0 8px;color:#fff}.price{font-size:32px;font-weight:700;color:#22d3ee;margin:8px 0 4px}.price small{font-size:14px;color:#94a3b8;font-weight:400}p{color:#cbd5e1;margin:8px 0}a{display:block;text-decoration:none}a.primary{background:#22d3ee;color:#000;padding:16px;text-align:center;border-radius:8px;font-weight:700;font-size:16px;margin:20px 0 10px}a.secondary{border:1px solid #374151;color:#cbd5e1;padding:12px;text-align:center;border-radius:8px;margin:8px 0;font-size:14px}.trust{margin:24px 0;padding:16px;border:1px solid #1f2937;border-radius:8px;background:#0f172a}.trust-item{font-size:13px;color:#cbd5e1;padding:4px 0;display:flex;gap:8px}.trust-item::before{content:"โ";color:#22d3ee;font-weight:700}details{margin-top:32px;font-size:13px;color:#94a3b8}details summary{cursor:pointer;padding:8px 0}details a{border:1px solid #374151;color:#94a3b8;padding:10px;text-align:center;border-radius:6px;margin:6px 0;font-size:13px}.back{text-align:center;color:#64748b;font-size:12px;margin-top:24px}.back a{color:#64748b;display:inline}</style><main><div class="brand"><span class="brand-mark"></span><span>ThumbGate</span></div><h1>Start ThumbGate Pro</h1><div class="price">$19<small>/mo</small></div><p>Block every repeat AI-agent mistake. Local-first. MIT-licensed CLI included. Cancel anytime.</p><a class="primary" data-i="pro_checkout_confirmed" href="${safeConfirmHref}">Pay $19/mo with Stripe โ</a><div class="trust"><div class="trust-item">6 paying customers, 18,000+ installs verified on npm</div><div class="trust-item">Cancel anytime โ instant refund within 7 days</div><div class="trust-item">MIT open source ยท no vendor lock-in</div><div class="trust-item">Works with Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode</div></div><details><summary>Other paid paths (diagnostic, sprint, teardown, single-rule)</summary>${diagnosticAction.replace('<a ', '<a class="secondary" ')}${sprintAction.replace('<a ', '<a class="secondary" ')}${teardownAction.replace('<a ', '<a class="secondary" ')}${quickReadAction.replace('<a ', '<a class="secondary" ')}${firstRuleAction.replace('<a ', '<a class="secondary" ')}<a class="secondary" data-i="workflow_sprint_intake" href="${safeWorkflowIntakeHref}">Send workflow first (intake)</a><a class="secondary" data-i="team_paid_path" href="${safeTeamOptionsHref}">See all options</a></details><p class="back"><a href="/">โ Back to thumbgate.ai</a></p></main><script>addEventListener('click',e=>{let a=e.target.closest('[data-i]');if(a&&navigator.sendBeacon)navigator.sendBeacon('/v1/telemetry/ping',new Blob([JSON.stringify({eventType:'checkout_interstitial_cta_clicked',clientType:'web',page:'/checkout/pro',ctaId:a.dataset.i,ctaPlacement:'checkout_interstitial'})],{type:'application/json'}))})</script></html>`;
|
|
1506
|
+
return `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm โ ThumbGate Pro</title><style>body{background:#0a0a0a;color:#eee;font-family:system-ui,-apple-system,sans-serif;line-height:1.5}main{max-width:520px;margin:8vh auto;padding:0 20px}.brand{display:flex;align-items:center;gap:10px;margin-bottom:24px;font-size:14px;color:#94a3b8}.brand-mark{width:24px;height:24px;background:#22d3ee;border-radius:6px;display:inline-block}h1{font-size:24px;margin:0 0 8px;color:#fff}.price{font-size:32px;font-weight:700;color:#22d3ee;margin:8px 0 4px}.price small{font-size:14px;color:#94a3b8;font-weight:400}p{color:#cbd5e1;margin:8px 0}a{display:block;text-decoration:none}a.primary{background:#22d3ee;color:#000;padding:16px;text-align:center;border-radius:8px;font-weight:700;font-size:16px;margin:20px 0 10px}a.secondary{border:1px solid #374151;color:#cbd5e1;padding:12px;text-align:center;border-radius:8px;margin:8px 0 0;font-size:14px}.trust{margin:24px 0;padding:16px;border:1px solid #1f2937;border-radius:8px;background:#0f172a}.trust-item{font-size:13px;color:#cbd5e1;padding:4px 0;display:flex;gap:8px}.trust-item::before{content:"โ";color:#22d3ee;font-weight:700}.choice-note{font-size:13px;color:#94a3b8;margin-top:14px}.back{text-align:center;color:#64748b;font-size:12px;margin-top:24px}.back a{color:#64748b;display:inline}</style><main><div class="brand"><span class="brand-mark"></span><span>ThumbGate</span></div><h1>Start ThumbGate Pro</h1><div class="price">$19<small>/mo</small></div><p>Block every repeat AI-agent mistake. Local-first. MIT-licensed CLI included. Cancel anytime.</p><a class="primary" data-i="pro_checkout_confirmed" href="${safeConfirmHref}">Pay $19/mo with Stripe โ</a><a class="secondary" data-i="workflow_sprint_intake" href="${safeWorkflowIntakeHref}">Not sure yet? Send the workflow first</a><p class="choice-note">One checkout path here. Diagnostics, sprints, kits, and custom services live outside the Pro checkout so the buyer is not asked to choose between unrelated offers.</p><div class="trust"><div class="trust-item">6 paying customers, 18,000+ installs verified on npm</div><div class="trust-item">Cancel anytime โ instant refund within 7 days</div><div class="trust-item">MIT open source ยท no vendor lock-in</div><div class="trust-item">Works with Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode</div></div><p class="back"><a href="/">โ Back to thumbgate.ai</a></p></main><script>addEventListener('click',e=>{let a=e.target.closest('[data-i]');if(a&&navigator.sendBeacon)navigator.sendBeacon('/v1/telemetry/ping',new Blob([JSON.stringify({eventType:'checkout_interstitial_cta_clicked',clientType:'web',page:'/checkout/pro',ctaId:a.dataset.i,ctaPlacement:'checkout_interstitial'})],{type:'application/json'}))})</script></html>`;
|
|
1546
1507
|
}
|
|
1547
1508
|
|
|
1548
1509
|
function buildCheckoutBootstrapBody(parsed, req, journeyState = resolveJourneyState(req, parsed)) {
|
|
@@ -4617,67 +4578,9 @@ async function addContext(){
|
|
|
4617
4578
|
ctaPlacement: 'checkout_interstitial',
|
|
4618
4579
|
planId: 'team',
|
|
4619
4580
|
});
|
|
4620
|
-
const teamOptionsHref = buildCheckoutIntentHref(`${hostedConfig.appOrigin}/guides/ai-agent-governance-sprint`, analyticsMetadata, {
|
|
4621
|
-
utmMedium: 'checkout_interstitial_paid_path',
|
|
4622
|
-
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_team_paid_path',
|
|
4623
|
-
ctaId: 'checkout_interstitial_team_paid_path',
|
|
4624
|
-
ctaPlacement: 'checkout_interstitial',
|
|
4625
|
-
planId: 'team',
|
|
4626
|
-
});
|
|
4627
|
-
const firstRuleCheckoutHref = buildCheckoutIntentHref(FIRST_FAILURE_RULE_CHECKOUT_URL, analyticsMetadata, {
|
|
4628
|
-
utmMedium: 'checkout_interstitial_paid_path',
|
|
4629
|
-
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_first_failure_rule',
|
|
4630
|
-
ctaId: 'checkout_interstitial_first_failure_rule_checkout',
|
|
4631
|
-
ctaPlacement: 'checkout_interstitial',
|
|
4632
|
-
planId: 'first_failure_rule',
|
|
4633
|
-
});
|
|
4634
|
-
const quickReadCheckoutHref = buildCheckoutIntentHref(QUICK_READ_CHECKOUT_URL, analyticsMetadata, {
|
|
4635
|
-
utmMedium: 'checkout_interstitial_paid_path',
|
|
4636
|
-
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_quick_read',
|
|
4637
|
-
ctaId: 'checkout_interstitial_quick_read_checkout',
|
|
4638
|
-
ctaPlacement: 'checkout_interstitial',
|
|
4639
|
-
planId: 'quick_read',
|
|
4640
|
-
});
|
|
4641
|
-
const workflowTeardownCheckoutHref = buildCheckoutIntentHref(WORKFLOW_TEARDOWN_CHECKOUT_URL, analyticsMetadata, {
|
|
4642
|
-
utmMedium: 'checkout_interstitial_paid_path',
|
|
4643
|
-
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_workflow_teardown',
|
|
4644
|
-
ctaId: 'checkout_interstitial_workflow_teardown_checkout',
|
|
4645
|
-
ctaPlacement: 'checkout_interstitial',
|
|
4646
|
-
planId: 'workflow_teardown',
|
|
4647
|
-
});
|
|
4648
|
-
const diagnosticCheckoutHref = buildCheckoutIntentHref(
|
|
4649
|
-
hostedConfig.sprintDiagnosticCheckoutUrl || SPRINT_DIAGNOSTIC_CHECKOUT_URL,
|
|
4650
|
-
analyticsMetadata,
|
|
4651
|
-
{
|
|
4652
|
-
utmMedium: 'checkout_interstitial_paid_path',
|
|
4653
|
-
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_diagnostic',
|
|
4654
|
-
ctaId: 'checkout_interstitial_sprint_diagnostic_checkout',
|
|
4655
|
-
ctaPlacement: 'checkout_interstitial',
|
|
4656
|
-
planId: 'sprint_diagnostic',
|
|
4657
|
-
}
|
|
4658
|
-
);
|
|
4659
|
-
const sprintCheckoutHref = buildCheckoutIntentHref(
|
|
4660
|
-
hostedConfig.workflowSprintCheckoutUrl || WORKFLOW_SPRINT_CHECKOUT_URL,
|
|
4661
|
-
analyticsMetadata,
|
|
4662
|
-
{
|
|
4663
|
-
utmMedium: 'checkout_interstitial_paid_path',
|
|
4664
|
-
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_workflow_sprint',
|
|
4665
|
-
ctaId: 'checkout_interstitial_workflow_sprint_checkout',
|
|
4666
|
-
ctaPlacement: 'checkout_interstitial',
|
|
4667
|
-
planId: 'workflow_sprint',
|
|
4668
|
-
}
|
|
4669
|
-
);
|
|
4670
4581
|
const html = renderCheckoutIntentPage({
|
|
4671
4582
|
confirmHref: buildCheckoutConfirmHref(parsed),
|
|
4672
|
-
firstRuleCheckoutHref,
|
|
4673
|
-
quickReadCheckoutHref,
|
|
4674
|
-
workflowTeardownCheckoutHref,
|
|
4675
4583
|
workflowIntakeHref,
|
|
4676
|
-
teamOptionsHref,
|
|
4677
|
-
diagnosticCheckoutHref,
|
|
4678
|
-
sprintCheckoutHref,
|
|
4679
|
-
sprintDiagnosticPriceDollars: hostedConfig.sprintDiagnosticPriceDollars || 499,
|
|
4680
|
-
workflowSprintPriceDollars: hostedConfig.workflowSprintPriceDollars || 1500,
|
|
4681
4584
|
botClassification,
|
|
4682
4585
|
});
|
|
4683
4586
|
sendHtml(res, 200, html, responseHeaders);
|