thumbgate 1.27.8 → 1.27.10
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/plugin.json +1 -1
- package/.well-known/llms.txt +1 -2
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +4 -2
- 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 +259 -78
- package/config/gate-templates.json +228 -0
- package/config/gates/claim-verification.json +18 -0
- package/package.json +14 -21
- package/public/blog.html +30 -0
- package/public/compare/adopt-ai.html +219 -0
- package/public/compare/agentix-labs.html +197 -0
- package/public/compare/ai-experience-orchestration.html +216 -0
- package/public/compare/anthropic-claude-for-legal.html +260 -0
- package/public/compare/anthropic-containment.html +280 -0
- package/public/compare/arcade.html +175 -0
- package/public/compare/arcjet.html +239 -0
- package/public/compare/bumblebee.html +307 -0
- package/public/compare/claude-code-hooks.html +294 -0
- package/public/compare/databricks-unity-ai-gateway.html +215 -0
- package/public/compare/fallow.html +351 -0
- package/public/compare/heidi.html +233 -0
- package/public/compare/mem0.html +342 -0
- package/public/compare/oak-and-sparrow-gatekeeper.html +289 -0
- package/public/compare/rein.html +236 -0
- package/public/compare/sigmashake.html +256 -0
- package/public/compare/speclock.html +342 -0
- package/public/compare.html +2 -0
- package/public/guides/agent-harness-optimization.html +342 -0
- package/public/guides/agentic-web-governance.html +406 -0
- package/public/guides/ai-agent-governance-sprint.html +415 -0
- package/public/guides/ai-agent-pre-action-approval-gates.html +401 -0
- package/public/guides/ai-agent-workflow-migration-checklist.html +392 -0
- package/public/guides/ai-deployment-readiness.html +415 -0
- package/public/guides/ai-mode-ads-agent-governance.html +401 -0
- package/public/guides/ai-search-topical-presence.html +342 -0
- package/public/guides/autoresearch-agent-safety.html +342 -0
- package/public/guides/background-agent-governance.html +358 -0
- package/public/guides/best-tools-stop-ai-agents-breaking-production.html +363 -0
- package/public/guides/browser-automation-safety.html +342 -0
- package/public/guides/chatgpt-ads-trust.html +353 -0
- package/public/guides/claude-code-feedback.html +339 -0
- package/public/guides/claude-code-prevent-repeated-mistakes.html +161 -0
- package/public/guides/claude-code-skills-guardrails.html +343 -0
- package/public/guides/claude-desktop.html +356 -0
- package/public/guides/code-knowledge-graph-guardrails.html +365 -0
- package/public/guides/codex-cli-guardrails.html +339 -0
- package/public/guides/cursor-agent-guardrails.html +339 -0
- package/public/guides/cursor-prevent-repeated-mistakes.html +161 -0
- package/public/guides/database-agent-safety.html +406 -0
- package/public/guides/deepseek-v4-runtime-guardrails.html +346 -0
- package/public/guides/developer-machine-supply-chain-guardrails.html +358 -0
- package/public/guides/gcp-mcp-guardrails.html +147 -0
- package/public/guides/gemini-cli-feedback-memory.html +339 -0
- package/public/guides/gpt-5-5-model-evaluation.html +358 -0
- package/public/guides/internal-ai-engineering-stack-guardrails.html +348 -0
- package/public/guides/long-running-agent-context-management.html +346 -0
- package/public/guides/mcp-tool-governance.html +401 -0
- package/public/guides/multica-thumbgate-setup.html +134 -0
- package/public/guides/native-messaging-host-security.html +342 -0
- package/public/guides/policy-engine-pre-action-gates.html +346 -0
- package/public/guides/pre-action-checks.html +342 -0
- package/public/guides/pretooluse-hooks-vs-advisory-prompt-rules.html +342 -0
- package/public/guides/prompt-tricks-to-workflow-rules.html +365 -0
- package/public/guides/proxy-pointer-rag-guardrails.html +352 -0
- package/public/guides/rag-precision-tuning-guardrails.html +352 -0
- package/public/guides/reasoning-compression-guardrails.html +346 -0
- package/public/guides/relational-knowledge-ai-recommendations.html +342 -0
- package/public/guides/roo-code-alternative-cline.html +339 -0
- package/public/guides/semantic-programmatic-seo-guardrails.html +352 -0
- package/public/guides/seo-agent-skills-guardrails.html +344 -0
- package/public/guides/stop-repeated-ai-agent-mistakes.html +342 -0
- package/public/index.html +192 -50
- package/public/learn/ac-dc-runtime-enforcement.html +277 -0
- package/public/learn/agent-harness-pattern.html +181 -0
- package/public/learn/agent-identity-connector-governance.html +146 -0
- package/public/learn/agent-swarms-shared-gates.html +173 -0
- package/public/learn/agentic-enterprise-context-brain.html +117 -0
- package/public/learn/agentic-os-team-governance.html +146 -0
- package/public/learn/ai-agent-governance.html +158 -0
- package/public/learn/ai-agent-persistent-memory.html +211 -0
- package/public/learn/anthropomorphic-claim-gates.html +180 -0
- package/public/learn/background-agent-control-layer.html +184 -0
- package/public/learn/claude-code-goal-with-rubrics.html +205 -0
- package/public/learn/codex-role-plugins-need-governance.html +125 -0
- package/public/learn/cost-aware-agent-gate-routing.html +173 -0
- package/public/learn/databricks-unity-ai-gateway-runtime-governance.html +157 -0
- package/public/learn/deterministic-agent-workflows.html +185 -0
- package/public/learn/feedback-loop-vs-decision-layer.html +283 -0
- package/public/learn/from-prototype-to-production.html +223 -0
- package/public/learn/learn.css +51 -0
- package/public/learn/mcp-pre-action-checks-explained.html +172 -0
- package/public/learn/pretix-stripe-connect-marketplaces.html +161 -0
- package/public/learn/regulated-agent-execution-boundary.html +196 -0
- package/public/learn/spec-driven-development.html +168 -0
- package/public/learn/stop-ai-agent-force-push.html +134 -0
- package/public/learn/vibe-coding-safety-net.html +142 -0
- package/public/learn.html +34 -50
- package/public/numbers.html +2 -2
- package/public/pro.html +6 -6
- package/scripts/cli-schema.js +10 -22
- package/scripts/dashboard-chat.js +1 -2
- package/scripts/document-intake.js +49 -1
- package/scripts/gemini-embedding-policy.js +1 -2
- package/scripts/hook-stop-anti-claim.js +103 -42
- package/scripts/hosted-config.js +12 -0
- package/scripts/plausible-domain-config.js +1 -3
- package/scripts/reddit-browser-notification-watch.js +230 -0
- package/scripts/seo-gsd.js +0 -239
- package/scripts/tool-registry.js +2 -2
- package/scripts/vector-store.js +0 -44
- package/scripts/workspace-evolver.js +2 -62
- package/src/api/server.js +126 -335
- package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
package/public/learn.html
CHANGED
|
@@ -115,6 +115,18 @@
|
|
|
115
115
|
{
|
|
116
116
|
"@type": "ListItem",
|
|
117
117
|
"position": 13,
|
|
118
|
+
"url": "https://thumbgate.ai/learn/anthropomorphic-claim-gates",
|
|
119
|
+
"name": "Anthropomorphic Claim Gates for AI Agents"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"@type": "ListItem",
|
|
123
|
+
"position": 14,
|
|
124
|
+
"url": "https://thumbgate.ai/learn/agent-identity-connector-governance",
|
|
125
|
+
"name": "Agent Identity and Connector Governance"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"@type": "ListItem",
|
|
129
|
+
"position": 15,
|
|
118
130
|
"url": "https://thumbgate.ai/learn/pretix-stripe-connect-marketplaces",
|
|
119
131
|
"name": "Building a Pretix + Stripe Connect Plugin for Live-Music Venues"
|
|
120
132
|
},
|
|
@@ -145,54 +157,42 @@
|
|
|
145
157
|
{
|
|
146
158
|
"@type": "ListItem",
|
|
147
159
|
"position": 10,
|
|
148
|
-
"url": "https://thumbgate.ai/guides/hermes-agent-guardrails",
|
|
149
|
-
"name": "Hermes Agent Guardrails for Self-Improving Agents"
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
"@type": "ListItem",
|
|
153
|
-
"position": 11,
|
|
154
|
-
"url": "https://thumbgate.ai/guides/agent-context-governance",
|
|
155
|
-
"name": "Agent Context Governance for Long-Running Agents"
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
"@type": "ListItem",
|
|
159
|
-
"position": 12,
|
|
160
160
|
"url": "https://thumbgate.ai/guides/roo-code-alternative-cline",
|
|
161
161
|
"name": "Roo Code Alternative: Migrating to Cline with Portable Lesson Memory"
|
|
162
162
|
},
|
|
163
163
|
{
|
|
164
164
|
"@type": "ListItem",
|
|
165
|
-
"position":
|
|
165
|
+
"position": 11,
|
|
166
166
|
"url": "https://thumbgate.ai/guides/browser-automation-safety",
|
|
167
167
|
"name": "Browser Automation Safety for AI Agents"
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
"@type": "ListItem",
|
|
171
|
-
"position":
|
|
171
|
+
"position": 12,
|
|
172
172
|
"url": "https://thumbgate.ai/guides/native-messaging-host-security",
|
|
173
173
|
"name": "Native Messaging Host Security"
|
|
174
174
|
},
|
|
175
175
|
{
|
|
176
176
|
"@type": "ListItem",
|
|
177
|
-
"position":
|
|
177
|
+
"position": 13,
|
|
178
178
|
"url": "https://thumbgate.ai/guides/ai-search-topical-presence",
|
|
179
179
|
"name": "AI Search Topical Presence"
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
"@type": "ListItem",
|
|
183
|
-
"position":
|
|
183
|
+
"position": 14,
|
|
184
184
|
"url": "https://thumbgate.ai/guides/relational-knowledge-ai-recommendations",
|
|
185
185
|
"name": "Relational Knowledge in AI Recommendations"
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
"@type": "ListItem",
|
|
189
|
-
"position":
|
|
189
|
+
"position": 15,
|
|
190
190
|
"url": "https://thumbgate.ai/guides/ai-deployment-readiness",
|
|
191
191
|
"name": "AI Deployment Readiness Before Production Rollout"
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
"@type": "ListItem",
|
|
195
|
-
"position":
|
|
195
|
+
"position": 16,
|
|
196
196
|
"url": "https://thumbgate.ai/guides/database-agent-safety",
|
|
197
197
|
"name": "Database Safety for AI Agents"
|
|
198
198
|
}
|
|
@@ -421,6 +421,22 @@
|
|
|
421
421
|
<span class="article-tag">Cost Control</span>
|
|
422
422
|
</a>
|
|
423
423
|
|
|
424
|
+
<a href="/learn/anthropomorphic-claim-gates" class="article-card">
|
|
425
|
+
<h3>Anthropomorphic Claim Gates for AI Agents</h3>
|
|
426
|
+
<p>Do not let an agent say a model understands, knows, decides, or behaves human-like unless it can show the measurement criteria and evidence behind that claim.</p>
|
|
427
|
+
<span class="article-tag">Claim Verification</span>
|
|
428
|
+
<span class="article-tag">Research</span>
|
|
429
|
+
<span class="article-tag">Proof Gates</span>
|
|
430
|
+
</a>
|
|
431
|
+
|
|
432
|
+
<a href="/learn/agent-identity-connector-governance" class="article-card">
|
|
433
|
+
<h3>Agent Identity and Connector Governance</h3>
|
|
434
|
+
<p>Agents connected to Merge, Glean, MCP gateways, and enterprise apps are identities. Gate owner, credential, connector scope, DLP, audit, and purpose before they act.</p>
|
|
435
|
+
<span class="article-tag">Agent Identity</span>
|
|
436
|
+
<span class="article-tag">MCP Connectors</span>
|
|
437
|
+
<span class="article-tag">Least Privilege</span>
|
|
438
|
+
</a>
|
|
439
|
+
|
|
424
440
|
<a href="/learn/from-prototype-to-production" class="article-card">
|
|
425
441
|
<h3>From git init to v1.17.0 in 70 days: an honest ThumbGate build log</h3>
|
|
426
442
|
<p>70 days, 112 commits, 17 minor releases, 6k npm downloads, $0 cold-traffic revenue. The unedited story of taking ThumbGate from a one-line repo init to live production — including the part that's still broken.</p>
|
|
@@ -473,14 +489,6 @@
|
|
|
473
489
|
<span class="article-tag">Governance</span>
|
|
474
490
|
</a>
|
|
475
491
|
|
|
476
|
-
<a href="/guides/vllm-serving-guardrails" class="article-card">
|
|
477
|
-
<h3>vLLM Serving Guardrails</h3>
|
|
478
|
-
<p>Gate self-hosted inference changes before agents route production work through PagedAttention, batching, prefix-cache, or model-swap optimizations.</p>
|
|
479
|
-
<span class="article-tag">vLLM</span>
|
|
480
|
-
<span class="article-tag">LLM Serving</span>
|
|
481
|
-
<span class="article-tag">Runtime Safety</span>
|
|
482
|
-
</a>
|
|
483
|
-
|
|
484
492
|
<a href="/guides/relational-knowledge-ai-recommendations" class="article-card">
|
|
485
493
|
<h3>Relational Knowledge in AI Recommendations</h3>
|
|
486
494
|
<p>How stored brand-to-problem associations shape AI answers, and why ThumbGate should own the pre-action-checks category in those retrieval paths.</p>
|
|
@@ -521,30 +529,6 @@
|
|
|
521
529
|
<span class="article-tag">Enforcement</span>
|
|
522
530
|
</a>
|
|
523
531
|
|
|
524
|
-
<a href="/guides/hermes-agent-guardrails" class="article-card">
|
|
525
|
-
<h3>Hermes Agent Guardrails for Self-Improving Agents</h3>
|
|
526
|
-
<p>Hermes-style agents bring persistent memory, generated skills, gateways, automations, and sandboxes. ThumbGate adds the pre-action firewall before those agents touch real systems.</p>
|
|
527
|
-
<span class="article-tag">Hermes Agent</span>
|
|
528
|
-
<span class="article-tag">Persistent Agents</span>
|
|
529
|
-
<span class="article-tag">Execution Firewall</span>
|
|
530
|
-
</a>
|
|
531
|
-
|
|
532
|
-
<a href="/guides/safe-self-evolution" class="article-card">
|
|
533
|
-
<h3>Safe Self-Evolution: Prompt Optimization without Regression</h3>
|
|
534
|
-
<p>Hermes-style autonomous agents learn by observing failures and rewriting skills. ThumbGate introduces Safe Self-Evolution: weakness mining, automated prompt optimization, verification suites, and atomic git rollbacks.</p>
|
|
535
|
-
<span class="article-tag">Self-Evolution</span>
|
|
536
|
-
<span class="article-tag">Harness Optimizer</span>
|
|
537
|
-
<span class="article-tag">Verification Gate</span>
|
|
538
|
-
</a>
|
|
539
|
-
|
|
540
|
-
<a href="/guides/agent-context-governance" class="article-card">
|
|
541
|
-
<h3>Agent Context Governance for Long-Running Agents</h3>
|
|
542
|
-
<p>AdaCoM, tokenmaxxing backlash, Managed Agents, lockdown modes, and model-provenance scares all point to one need: clean context and tool gates before agents act.</p>
|
|
543
|
-
<span class="article-tag">Context Governance</span>
|
|
544
|
-
<span class="article-tag">Tool Lockdown</span>
|
|
545
|
-
<span class="article-tag">Model Provenance</span>
|
|
546
|
-
</a>
|
|
547
|
-
|
|
548
532
|
<a href="/guides/roo-code-alternative-cline" class="article-card">
|
|
549
533
|
<h3>Roo Code Alternative: Migrate to Cline Without Losing Agent Memory</h3>
|
|
550
534
|
<p>Use the Roo shutdown window to pitch portable lesson memory and local-first enforcement instead of making operators re-teach the same failures after they switch.</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.27.
|
|
28
|
+
"softwareVersion": "1.27.7",
|
|
29
29
|
"url": "https://thumbgate.ai/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.27.
|
|
205
|
+
<div class="freshness">Updated: 2026-05-07 · Version 1.27.7</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
|
@@ -37,7 +37,7 @@ __GA_BOOTSTRAP__
|
|
|
37
37
|
<script type="application/ld+json">
|
|
38
38
|
{
|
|
39
39
|
"@context": "https://schema.org",
|
|
40
|
-
"@type": "FAQPage", "mainEntity": [{ "@type": "Question", "name": "How is Pro different from the free install?", "acceptedAnswer": { "@type": "Answer", "text": "Free keeps local recall, checks, and MCP. Pro adds the personal dashboard, DPO export, auto-connect, and founder support." } }, { "@type": "Question", "name": "Does Pro require a cloud account?", "acceptedAnswer": { "@type": "Answer", "text": "No. Pro stays local-first;
|
|
40
|
+
"@type": "FAQPage", "mainEntity": [{ "@type": "Question", "name": "How is Pro different from the free install?", "acceptedAnswer": { "@type": "Answer", "text": "Free keeps local recall, checks, and MCP. Pro adds the personal dashboard, DPO export, auto-connect, and founder support." } }, { "@type": "Question", "name": "Does Pro require a cloud account?", "acceptedAnswer": { "@type": "Answer", "text": "No. Pro stays local-first; Team is the hosted rollout lane for shared lessons, org visibility, and reviews." } }, { "@type": "Question", "name": "What happens after checkout?", "acceptedAnswer": { "@type": "Answer", "text": "You activate Pro, connect the local dashboard, and inspect blocked actions, lessons, and exports." } }, { "@type": "Question", "name": "When should I choose Team instead of Pro?", "acceptedAnswer": { "@type": "Answer", "text": "Choose Team when one correction needs to protect multiple developers or agents across shared repositories." } }]
|
|
41
41
|
}
|
|
42
42
|
</script>
|
|
43
43
|
|
|
@@ -731,7 +731,7 @@ __GA_BOOTSTRAP__
|
|
|
731
731
|
<h1>Buy the operator loop that proves your AI agent stopped repeating the mistake.</h1>
|
|
732
732
|
<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>
|
|
733
733
|
<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>
|
|
734
|
-
<p>Start Pro when you want the local dashboard, DPO export, and a single proof lane for the repeated mistake you need to stop.
|
|
734
|
+
<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>
|
|
735
735
|
<div class="hero-proof">
|
|
736
736
|
<div class="proof-pill">Personal local dashboard</div>
|
|
737
737
|
<div class="proof-pill">DPO export from real corrections</div>
|
|
@@ -951,15 +951,15 @@ __GA_BOOTSTRAP__
|
|
|
951
951
|
</div>
|
|
952
952
|
<div class="faq-item">
|
|
953
953
|
<button class="faq-q" type="button" onclick="toggleFaq(this)" onkeydown="handleFaqKeydown(event)" aria-expanded="false">Does Pro require a cloud account?</button>
|
|
954
|
-
<div class="faq-a">No. Pro is still local-first for the individual operator lane.
|
|
954
|
+
<div class="faq-a">No. Pro is still local-first for the individual operator lane. Team is the hosted rollout lane for shared lessons, org visibility, and hosted review views.</div>
|
|
955
955
|
</div>
|
|
956
956
|
<div class="faq-item">
|
|
957
957
|
<button class="faq-q" type="button" onclick="toggleFaq(this)" onkeydown="handleFaqKeydown(event)" aria-expanded="false">What happens after checkout?</button>
|
|
958
958
|
<div class="faq-a">You activate Pro, connect the personal local dashboard, and your running agents can appear automatically so you can inspect blocked actions, active lessons, and exportable DPO pairs without adding a separate cloud dashboard dependency.</div>
|
|
959
959
|
</div>
|
|
960
960
|
<div class="faq-item">
|
|
961
|
-
<button class="faq-q" type="button" onclick="toggleFaq(this)" onkeydown="handleFaqKeydown(event)" aria-expanded="false">When should I choose
|
|
962
|
-
<div class="faq-a">Choose
|
|
961
|
+
<button class="faq-q" type="button" onclick="toggleFaq(this)" onkeydown="handleFaqKeydown(event)" aria-expanded="false">When should I choose Team instead of Pro?</button>
|
|
962
|
+
<div class="faq-a">Choose Team when one thumbs-down should protect multiple people or agents across shared repositories, or when you need shared hosted lessons, org dashboard visibility, and a workflow hardening pilot with rollout review views.</div>
|
|
963
963
|
</div>
|
|
964
964
|
</div>
|
|
965
965
|
</div>
|
|
@@ -987,7 +987,7 @@ __GA_BOOTSTRAP__
|
|
|
987
987
|
<a href="__VERIFICATION_URL__" target="_blank" rel="noopener">Verification Evidence</a>
|
|
988
988
|
<a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub</a>
|
|
989
989
|
</div>
|
|
990
|
-
<div class="footer-copy">ThumbGate Pro for individual operators.
|
|
990
|
+
<div class="footer-copy">ThumbGate Pro for individual operators. Team stays intake-first.</div>
|
|
991
991
|
</div>
|
|
992
992
|
</footer>
|
|
993
993
|
|
package/scripts/cli-schema.js
CHANGED
|
@@ -123,6 +123,16 @@ const CLI_COMMANDS = [
|
|
|
123
123
|
{ name: 'remote', type: 'boolean', description: 'Fetch from hosted Railway instance' },
|
|
124
124
|
],
|
|
125
125
|
},
|
|
126
|
+
{
|
|
127
|
+
name: 'community',
|
|
128
|
+
aliases: ['registry'],
|
|
129
|
+
description: 'Query or share verified prevention rules with the community knowledge registry',
|
|
130
|
+
group: 'discovery',
|
|
131
|
+
flags: [
|
|
132
|
+
{ name: 'json', type: 'boolean', description: 'Output as JSON' },
|
|
133
|
+
{ name: 'remote', type: 'boolean', description: 'Fetch from community remote API' },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
126
136
|
{
|
|
127
137
|
name: 'gate-stats',
|
|
128
138
|
description: 'Check engine statistics — active checks, blocks, warns, time saved',
|
|
@@ -505,12 +515,6 @@ const CLI_COMMANDS = [
|
|
|
505
515
|
group: 'gates',
|
|
506
516
|
flags: [],
|
|
507
517
|
},
|
|
508
|
-
{
|
|
509
|
-
name: 'hermes-gate',
|
|
510
|
-
description: 'Hermes Agent pre_tool_call hook: gate runtime tool calls (incl. skill_manage) before they run',
|
|
511
|
-
group: 'gates',
|
|
512
|
-
flags: [],
|
|
513
|
-
},
|
|
514
518
|
{
|
|
515
519
|
name: 'force-gate',
|
|
516
520
|
description: 'Immediately create a blocking gate from a pattern string',
|
|
@@ -656,22 +660,6 @@ const CLI_COMMANDS = [
|
|
|
656
660
|
{ name: 'json', type: 'boolean', description: 'Output results as JSON' },
|
|
657
661
|
],
|
|
658
662
|
},
|
|
659
|
-
{
|
|
660
|
-
name: 'check-update',
|
|
661
|
-
aliases: ['upgrade-check'],
|
|
662
|
-
description: 'Check for newer versions of ThumbGate from npm or GitHub',
|
|
663
|
-
group: 'ops',
|
|
664
|
-
flags: [
|
|
665
|
-
{ name: 'json', type: 'boolean', description: 'Output results as JSON' },
|
|
666
|
-
],
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
name: 'self-update',
|
|
670
|
-
aliases: ['upgrade-cli'],
|
|
671
|
-
description: 'Automatically install the latest version of ThumbGate globally',
|
|
672
|
-
group: 'ops',
|
|
673
|
-
flags: [],
|
|
674
|
-
},
|
|
675
663
|
];
|
|
676
664
|
|
|
677
665
|
/**
|
|
@@ -317,8 +317,7 @@ async function answerDataQuestion(question, opts = {}) {
|
|
|
317
317
|
if (isPerplexity) return await callPerplexityEndpoint({ apiKey, prompt, fetchImpl, sources });
|
|
318
318
|
return await callGeminiEndpoint({ apiKey, model, prompt, fetchImpl, sources });
|
|
319
319
|
} catch (err) {
|
|
320
|
-
|
|
321
|
-
return { ok: false, error: 'network', message: safeMessage, sources };
|
|
320
|
+
return { ok: false, error: 'network', message: err?.message || String(err), sources };
|
|
322
321
|
}
|
|
323
322
|
}
|
|
324
323
|
|
|
@@ -708,6 +708,7 @@ function buildDocumentSummary(document) {
|
|
|
708
708
|
sourcePath: document.sourcePath || null,
|
|
709
709
|
sourceName: document.sourceName || null,
|
|
710
710
|
sourceFormat: document.sourceFormat,
|
|
711
|
+
sourceUrl: document.sourceUrl || null,
|
|
711
712
|
importedAt: document.importedAt,
|
|
712
713
|
tags: normalizeTags(document.tags),
|
|
713
714
|
excerpt: document.excerpt,
|
|
@@ -768,7 +769,11 @@ function persistDocument(document, options = {}) {
|
|
|
768
769
|
const summaries = listImportedDocuments({
|
|
769
770
|
...options,
|
|
770
771
|
limit: MAX_SEARCH_SCAN,
|
|
771
|
-
}).documents.filter((entry) =>
|
|
772
|
+
}).documents.filter((entry) => {
|
|
773
|
+
if (entry.documentId === document.documentId) return false;
|
|
774
|
+
if (document.sourceUrl && entry.sourceUrl === document.sourceUrl) return false;
|
|
775
|
+
return true;
|
|
776
|
+
});
|
|
772
777
|
const nextSummaries = [
|
|
773
778
|
buildDocumentSummary(document),
|
|
774
779
|
...summaries,
|
|
@@ -882,6 +887,48 @@ function importDocument(options = {}) {
|
|
|
882
887
|
sourceFormat,
|
|
883
888
|
});
|
|
884
889
|
const fingerprint = sha256(`${title}\n${normalizedContent}`);
|
|
890
|
+
|
|
891
|
+
// -- deduplication and RAG drift tracking ----------------------------------
|
|
892
|
+
const paths = getDocumentStorePaths(options);
|
|
893
|
+
let duplicate = null;
|
|
894
|
+
if (fs.existsSync(paths.catalogPath)) {
|
|
895
|
+
try {
|
|
896
|
+
const catalog = readJsonl(paths.catalogPath);
|
|
897
|
+
const urlMatch = options.sourceUrl ? String(options.sourceUrl).trim() : null;
|
|
898
|
+
const matchedSummary = catalog.find((summary) =>
|
|
899
|
+
(urlMatch && summary.sourceUrl === urlMatch) ||
|
|
900
|
+
(summary.fingerprint === fingerprint)
|
|
901
|
+
);
|
|
902
|
+
if (matchedSummary) {
|
|
903
|
+
const fullDoc = readImportedDocument(matchedSummary.documentId, options);
|
|
904
|
+
if (fullDoc) {
|
|
905
|
+
if (fullDoc.fingerprint === fingerprint) {
|
|
906
|
+
// Case A: Content is identical
|
|
907
|
+
const dedupReason = urlMatch && fullDoc.sourceUrl === urlMatch
|
|
908
|
+
? 'url-and-content-unchanged'
|
|
909
|
+
: 'content-identical';
|
|
910
|
+
return {
|
|
911
|
+
...fullDoc,
|
|
912
|
+
duplicate: true,
|
|
913
|
+
updated: false,
|
|
914
|
+
dedupReason,
|
|
915
|
+
};
|
|
916
|
+
} else {
|
|
917
|
+
// Case B: URL matches but content changed (RAG Drift!)
|
|
918
|
+
duplicate = {
|
|
919
|
+
previousDocumentId: fullDoc.documentId,
|
|
920
|
+
previousFingerprint: fullDoc.fingerprint,
|
|
921
|
+
updated: true,
|
|
922
|
+
dedupReason: 'url-content-updated',
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
} catch (err) {
|
|
928
|
+
// best-effort
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
885
932
|
const importedAt = nowIso();
|
|
886
933
|
const sourceName = sourcePath ? path.basename(sourcePath) : null;
|
|
887
934
|
const documentId = `doc_${slugify(title || sourceName || 'document').slice(0, 24) || 'document'}_${fingerprint.slice(0, 12)}`;
|
|
@@ -901,6 +948,7 @@ function importDocument(options = {}) {
|
|
|
901
948
|
contentBytes: Buffer.byteLength(normalizedContent, 'utf8'),
|
|
902
949
|
lineCount: normalizedContent.split('\n').filter(Boolean).length,
|
|
903
950
|
headings: extractHeadings(normalizedContent),
|
|
951
|
+
...(duplicate || {}),
|
|
904
952
|
};
|
|
905
953
|
document.proposals = options.proposeGates === false
|
|
906
954
|
? []
|
|
@@ -122,7 +122,7 @@ function resolveGeminiEmbeddingConfig(env = process.env) {
|
|
|
122
122
|
|
|
123
123
|
return {
|
|
124
124
|
enabled,
|
|
125
|
-
provider:
|
|
125
|
+
provider: enabled ? 'gemini' : 'local',
|
|
126
126
|
model: String(env.THUMBGATE_GEMINI_EMBED_MODEL || GEMINI_EMBEDDING_2_MODEL).trim() || GEMINI_EMBEDDING_2_MODEL,
|
|
127
127
|
apiKey,
|
|
128
128
|
apiBaseUrl: trimTrailingSlashes(env.THUMBGATE_GEMINI_API_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta'),
|
|
@@ -171,7 +171,6 @@ function buildGeminiEmbeddingRolloutPlan(args = {}) {
|
|
|
171
171
|
},
|
|
172
172
|
rolloutSteps: [
|
|
173
173
|
'Keep local embeddings as the default offline path.',
|
|
174
|
-
'For Apple Silicon developers, route local queries through Core AI (AOT compiled models) to bypass CPU overhead.',
|
|
175
174
|
'Enable Gemini Embedding 2 only when a Gemini API key is present.',
|
|
176
175
|
'Use task-specific query/document prefixes at index and retrieval time.',
|
|
177
176
|
'Start at 768 dimensions, then benchmark 1536 only if recall misses show up.',
|
|
@@ -45,21 +45,6 @@ const CLAIM_PATTERNS = [
|
|
|
45
45
|
/\b(?:about|metadata|description|topics?)\b.*\b(?:updated|verified|fixed|match(?:es|ed)?)\b.*\b(?:github|repo|repository)\b/i,
|
|
46
46
|
/\b(?:money|payment|charge|checkout|revenue|price|pricing|invoice|billing|tax|sales tax|inventory|stock|permission|access|customer[-\s]facing)\b.*\b(?:correct|accurate|verified|valid|matches|working|fixed|resolved|calculated|configured)\b/i,
|
|
47
47
|
/\b(?:correct|accurate|verified|valid|matches|working|fixed|resolved|calculated|configured)\b.*\b(?:money|payment|charge|checkout|revenue|price|pricing|invoice|billing|tax|sales tax|inventory|stock|permission|access|customer[-\s]facing)\b/i,
|
|
48
|
-
// Added 2026-06-11 after a cross-project failure analysis: these completion
|
|
49
|
-
// claims ("all green / stable / verified / race over / tests pass") were
|
|
50
|
-
// asserted without proof and slipped past the original set. The proof-gate
|
|
51
|
-
// below suppresses them whenever the SAME turn ran a verification tool, so a
|
|
52
|
-
// "verified" claim backed by a test/curl/Read stays silent.
|
|
53
|
-
/\ball\s+(?:the\s+)?(?:tests?\s+|checks?\s+)?(?:are\s+)?green\b/i,
|
|
54
|
-
/\b(?:all\s+)?(?:tests?|checks?|ci)\s+(?:are\s+)?(?:now\s+)?passing\b/i,
|
|
55
|
-
/\ball\s+(?:tests?|checks?)\s+pass(?:ed)?\b/i,
|
|
56
|
-
/\bverified\b/i,
|
|
57
|
-
/\bconfirmed\b/i,
|
|
58
|
-
/\b(?:is|are|it'?s|now)\s+stable\b/i,
|
|
59
|
-
/\ball\s+clear\b/i,
|
|
60
|
-
/\bgood\s+to\s+go\b/i,
|
|
61
|
-
/\brace\s+(?:is\s+)?over\b/i,
|
|
62
|
-
/\bno\s+longer\s+racing\b/i,
|
|
63
48
|
];
|
|
64
49
|
|
|
65
50
|
// Proof-of-verification patterns. If the SAME turn included one of these
|
|
@@ -83,29 +68,63 @@ const PROOF_PATTERNS = [
|
|
|
83
68
|
/\bshopify\b/,
|
|
84
69
|
/\bsquare\b/,
|
|
85
70
|
/\bquickbooks\b/,
|
|
71
|
+
/\bgh\s+api\b/,
|
|
72
|
+
/\bls\b/,
|
|
73
|
+
/\bcat\b/,
|
|
86
74
|
/Read\s*\(/, // Claude Code Read tool call
|
|
87
75
|
/Bash\s*\(/, // Claude Code Bash tool call
|
|
88
76
|
];
|
|
89
77
|
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
const POSITIVE_FEEDBACK_PATTERNS = [
|
|
79
|
+
/\bthumbs?\s*up\b/i,
|
|
80
|
+
/👍/,
|
|
81
|
+
/\bthank(s| you)\b/i,
|
|
82
|
+
/\bgood\b/i,
|
|
83
|
+
/\bgreat\b/i,
|
|
84
|
+
/\bperfect\b/i,
|
|
85
|
+
/\bok(?:ay)?\b/i,
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const LOW_VALUE_CLOSEOUT_PATTERNS = [
|
|
89
|
+
/^(?:good|great|perfect|ok(?:ay)?|thanks?|thank you)[.!,\s-]*(?:$|\b)/i,
|
|
90
|
+
/\buse\s+\w+\s*\/\s*\w+\b/i,
|
|
91
|
+
/\bsounds good\b/i,
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const SUBSTANTIVE_CLOSEOUT_PATTERNS = [
|
|
95
|
+
/\b(?:evidence|verified|tested|proof|result|residual risk|next state|next action|timestamp|source|url|file|line|passed|failed|blocked|unknown)\b/i,
|
|
96
|
+
/\b(?:node --test|npm run|curl|gh |git |pytest|playwright|screenshot|diff --check)\b/i,
|
|
97
|
+
/https?:\/\//i,
|
|
98
|
+
/\/[\w.-]+\/[\w./-]+/,
|
|
99
|
+
/`[^`]+`/,
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
function readTranscriptEntries(transcriptPath) {
|
|
103
|
+
if (!transcriptPath || !fs.existsSync(transcriptPath)) return [];
|
|
92
104
|
let content;
|
|
93
105
|
try {
|
|
94
106
|
content = fs.readFileSync(transcriptPath, 'utf8');
|
|
95
107
|
} catch {
|
|
96
|
-
return
|
|
108
|
+
return [];
|
|
97
109
|
}
|
|
98
|
-
|
|
110
|
+
return content
|
|
111
|
+
.trim()
|
|
112
|
+
.split('\n')
|
|
113
|
+
.map((raw) => {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(raw);
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
.filter(Boolean);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readLastAssistantTurn(transcriptPath) {
|
|
124
|
+
const entries = readTranscriptEntries(transcriptPath);
|
|
99
125
|
// Walk backwards to find the last assistant message
|
|
100
|
-
for (let i =
|
|
101
|
-
const
|
|
102
|
-
if (!raw) continue;
|
|
103
|
-
let entry;
|
|
104
|
-
try {
|
|
105
|
-
entry = JSON.parse(raw);
|
|
106
|
-
} catch {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
126
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
127
|
+
const entry = entries[i];
|
|
109
128
|
if (entry.type === 'assistant' && entry.message) {
|
|
110
129
|
return entry.message;
|
|
111
130
|
}
|
|
@@ -113,6 +132,22 @@ function readLastAssistantTurn(transcriptPath) {
|
|
|
113
132
|
return null;
|
|
114
133
|
}
|
|
115
134
|
|
|
135
|
+
function readPreviousUserText(transcriptPath) {
|
|
136
|
+
const entries = readTranscriptEntries(transcriptPath);
|
|
137
|
+
let seenAssistant = false;
|
|
138
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
139
|
+
const entry = entries[i];
|
|
140
|
+
if (!seenAssistant && entry.type === 'assistant') {
|
|
141
|
+
seenAssistant = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (seenAssistant && entry.type === 'user' && entry.message) {
|
|
145
|
+
return extractText(entry.message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
|
|
116
151
|
function extractText(message) {
|
|
117
152
|
if (!message || !Array.isArray(message.content)) return '';
|
|
118
153
|
return message.content
|
|
@@ -139,6 +174,23 @@ function extractToolUseSummary(message) {
|
|
|
139
174
|
.join('\n');
|
|
140
175
|
}
|
|
141
176
|
|
|
177
|
+
function wordCount(text) {
|
|
178
|
+
return String(text || '').trim().split(/\s+/).filter(Boolean).length;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function hasPositiveFeedback(text) {
|
|
182
|
+
return POSITIVE_FEEDBACK_PATTERNS.some((p) => p.test(text || ''));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isLowValueCloseout(text, toolUseSummary = '') {
|
|
186
|
+
const normalized = String(text || '').trim();
|
|
187
|
+
if (!normalized) return false;
|
|
188
|
+
if (toolUseSummary.trim()) return false;
|
|
189
|
+
if (wordCount(normalized) > 45) return false;
|
|
190
|
+
if (SUBSTANTIVE_CLOSEOUT_PATTERNS.some((p) => p.test(normalized))) return false;
|
|
191
|
+
return LOW_VALUE_CLOSEOUT_PATTERNS.some((p) => p.test(normalized));
|
|
192
|
+
}
|
|
193
|
+
|
|
142
194
|
function findClaim(text) {
|
|
143
195
|
for (const p of CLAIM_PATTERNS) {
|
|
144
196
|
const m = text.match(p);
|
|
@@ -174,29 +226,33 @@ function main() {
|
|
|
174
226
|
|
|
175
227
|
const text = extractText(message);
|
|
176
228
|
const toolUseSummary = extractToolUseSummary(message);
|
|
229
|
+
const previousUserText = readPreviousUserText(transcriptPath);
|
|
230
|
+
|
|
231
|
+
if (hasPositiveFeedback(previousUserText) && isLowValueCloseout(text, toolUseSummary)) {
|
|
232
|
+
const reminder = [
|
|
233
|
+
'⚠️ ThumbGate response-quality gate: previous turn answered positive feedback',
|
|
234
|
+
' with a low-value social closeout instead of silence-level brevity or an evidence checkpoint.',
|
|
235
|
+
' Positive feedback after operational work should trigger either no extra noise,',
|
|
236
|
+
' a compact evidence checkpoint, or a concrete next-state update.',
|
|
237
|
+
' Do not reply with generic "Good / Great / Use X/Y" filler.',
|
|
238
|
+
].join('\n');
|
|
239
|
+
process.stdout.write(reminder + '\n');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
177
243
|
const claim = findClaim(text);
|
|
178
244
|
if (!claim) return; // no completion claim made; silent
|
|
179
245
|
|
|
180
246
|
const proofText = `${text}\n${toolUseSummary}`;
|
|
181
247
|
if (hasProof(proofText)) return; // claim backed by proof in same turn
|
|
182
248
|
|
|
183
|
-
//
|
|
184
|
-
// Stop-hook block decision so Claude Code does NOT end the turn — the agent
|
|
185
|
-
// must run the verification (or retract) before it can stop. Default mode
|
|
186
|
-
// stays soft (a reminder for the next turn) so we don't break existing wiring.
|
|
187
|
-
if (process.env.THUMBGATE_STRICT_ENFORCEMENT === '1') {
|
|
188
|
-
const reason = `ThumbGate anti-claim gate (strict): you claimed completion ("${claim}") without a proof tool call in the same message. Run the verification (curl / grep / test / Read) and restate with the proof, or retract — do not end the turn on an unverified claim.`;
|
|
189
|
-
process.stdout.write(JSON.stringify({ decision: 'block', reason }) + '\n');
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Default (soft): surface a system reminder for the NEXT turn. Do not hard-block.
|
|
249
|
+
// Surface a system reminder for the NEXT turn. Do not hard-block.
|
|
194
250
|
const reminder = [
|
|
195
251
|
'⚠️ ThumbGate anti-claim gate: previous turn claimed completion',
|
|
196
252
|
` ("${claim}") without a proof tool call in the same message.`,
|
|
197
|
-
' Per CLAUDE.md anti-lying: never claim "done / live / deployed / fixed
|
|
198
|
-
'
|
|
199
|
-
'
|
|
253
|
+
' Per CLAUDE.md anti-lying: never claim "done / live / deployed / fixed"',
|
|
254
|
+
' or commercial truth (money / tax / inventory / permissions / customer-facing state)',
|
|
255
|
+
' without curl / grep / test / source-of-truth output in the SAME turn.',
|
|
200
256
|
' If the work really is verified, re-state the claim with the proof.',
|
|
201
257
|
' If not, retract and run the verification before re-asserting.',
|
|
202
258
|
].join('\n');
|
|
@@ -220,8 +276,13 @@ if (path.resolve(process.argv[1] || '') === path.resolve(__filename)) {
|
|
|
220
276
|
module.exports = {
|
|
221
277
|
CLAIM_PATTERNS,
|
|
222
278
|
PROOF_PATTERNS,
|
|
279
|
+
POSITIVE_FEEDBACK_PATTERNS,
|
|
280
|
+
LOW_VALUE_CLOSEOUT_PATTERNS,
|
|
223
281
|
findClaim,
|
|
224
282
|
hasProof,
|
|
283
|
+
hasPositiveFeedback,
|
|
284
|
+
isLowValueCloseout,
|
|
225
285
|
extractText,
|
|
226
286
|
extractToolUseSummary,
|
|
287
|
+
readPreviousUserText,
|
|
227
288
|
};
|
package/scripts/hosted-config.js
CHANGED
|
@@ -13,6 +13,7 @@ const DEFAULT_PRO_PRICE_DOLLARS = PRO_MONTHLY_PRICE_DOLLARS;
|
|
|
13
13
|
const DEFAULT_PRO_PRICE_LABEL = PRO_PRICE_LABEL;
|
|
14
14
|
const DEFAULT_SPRINT_DIAGNOSTIC_PRICE_DOLLARS = 499;
|
|
15
15
|
const DEFAULT_WORKFLOW_SPRINT_PRICE_DOLLARS = 1500;
|
|
16
|
+
const DEFAULT_SNAPSHOT_PRICE_DOLLARS = 97;
|
|
16
17
|
const GA_MEASUREMENT_ID_PATTERN = /^G-[A-Z0-9]+$/i;
|
|
17
18
|
|
|
18
19
|
function normalizeOrigin(value) {
|
|
@@ -129,6 +130,10 @@ function resolveHostedBillingConfig({ requestOrigin } = {}, env = process.env) {
|
|
|
129
130
|
const googleSiteVerification = normalizeTrackingId(env.THUMBGATE_GOOGLE_SITE_VERIFICATION);
|
|
130
131
|
const sprintDiagnosticCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_SPRINT_DIAGNOSTIC_CHECKOUT_URL);
|
|
131
132
|
const workflowSprintCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_WORKFLOW_SPRINT_CHECKOUT_URL);
|
|
133
|
+
const paypalDiagnosticCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_PAYPAL_DIAGNOSTIC_CHECKOUT_URL);
|
|
134
|
+
const paypalWorkflowSprintCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_PAYPAL_WORKFLOW_SPRINT_CHECKOUT_URL);
|
|
135
|
+
const morSnapshotCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_MOR_SNAPSHOT_CHECKOUT_URL);
|
|
136
|
+
const morProvider = String(env.THUMBGATE_MOR_PROVIDER || '').trim();
|
|
132
137
|
|
|
133
138
|
return {
|
|
134
139
|
appOrigin,
|
|
@@ -142,10 +147,16 @@ function resolveHostedBillingConfig({ requestOrigin } = {}, env = process.env) {
|
|
|
142
147
|
proPriceLabel,
|
|
143
148
|
sprintDiagnosticCheckoutUrl,
|
|
144
149
|
workflowSprintCheckoutUrl,
|
|
150
|
+
paypalDiagnosticCheckoutUrl,
|
|
151
|
+
paypalWorkflowSprintCheckoutUrl,
|
|
152
|
+
morSnapshotCheckoutUrl,
|
|
153
|
+
morProvider,
|
|
145
154
|
sprintDiagnosticPriceDollars: normalizePriceDollars(env.THUMBGATE_SPRINT_DIAGNOSTIC_PRICE_DOLLARS)
|
|
146
155
|
|| DEFAULT_SPRINT_DIAGNOSTIC_PRICE_DOLLARS,
|
|
147
156
|
workflowSprintPriceDollars: normalizePriceDollars(env.THUMBGATE_WORKFLOW_SPRINT_PRICE_DOLLARS)
|
|
148
157
|
|| DEFAULT_WORKFLOW_SPRINT_PRICE_DOLLARS,
|
|
158
|
+
snapshotPriceDollars: normalizePriceDollars(env.THUMBGATE_SNAPSHOT_PRICE_DOLLARS)
|
|
159
|
+
|| DEFAULT_SNAPSHOT_PRICE_DOLLARS,
|
|
149
160
|
gaMeasurementId,
|
|
150
161
|
googleSiteVerification,
|
|
151
162
|
posthogApiKey,
|
|
@@ -159,6 +170,7 @@ module.exports = {
|
|
|
159
170
|
DEFAULT_PRO_PRICE_LABEL,
|
|
160
171
|
DEFAULT_SPRINT_DIAGNOSTIC_PRICE_DOLLARS,
|
|
161
172
|
DEFAULT_WORKFLOW_SPRINT_PRICE_DOLLARS,
|
|
173
|
+
DEFAULT_SNAPSHOT_PRICE_DOLLARS,
|
|
162
174
|
GA_MEASUREMENT_ID_PATTERN,
|
|
163
175
|
normalizeAbsoluteUrl,
|
|
164
176
|
normalizeOrigin,
|
|
@@ -16,9 +16,7 @@ function normalizeDomain(value) {
|
|
|
16
16
|
try {
|
|
17
17
|
return new URL(input.includes('://') ? input : `https://${input}`).hostname.toLowerCase();
|
|
18
18
|
} catch {
|
|
19
|
-
|
|
20
|
-
const hostnameAndPort = withoutProtocol.split('/')[0];
|
|
21
|
-
return hostnameAndPort.toLowerCase().split(':')[0];
|
|
19
|
+
return input.replace(/^https?:\/\//i, '').replace(/\/.*$/, '').toLowerCase().split(':')[0];
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
|