thumbgate 1.27.6 → 1.27.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.claude/commands/thumbgate-blocked.md +27 -0
  2. package/.claude/commands/thumbgate-doctor.md +30 -0
  3. package/.claude/commands/thumbgate-guard.md +36 -0
  4. package/.claude/commands/thumbgate-protect.md +30 -0
  5. package/.claude/commands/thumbgate-rules.md +30 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +6 -2
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/README.md +49 -5
  10. package/adapters/claude/.mcp.json +2 -2
  11. package/adapters/letta/README.md +41 -0
  12. package/adapters/letta/thumbgate-letta-adapter.js +133 -0
  13. package/adapters/mcp/server-stdio.js +16 -1
  14. package/adapters/opencode/opencode.json +1 -1
  15. package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
  16. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
  17. package/bench/observability-eval-suite.json +26 -0
  18. package/bin/cli.js +180 -2
  19. package/bin/postinstall.js +1 -1
  20. package/config/gate-templates.json +84 -0
  21. package/config/gates/claim-verification.json +6 -0
  22. package/config/gates/default.json +20 -0
  23. package/config/github-about.json +1 -1
  24. package/config/model-candidates.json +50 -0
  25. package/package.json +65 -25
  26. package/public/agent-manager.html +41 -1
  27. package/public/agents-cost-savings.html +1 -1
  28. package/public/ai-malpractice-prevention.html +2 -1
  29. package/public/assets/brand/github-social-preview.png +0 -0
  30. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  31. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  32. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  33. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  34. package/public/assets/brand/thumbgate-mark-inline.svg +15 -0
  35. package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
  36. package/public/assets/brand/thumbgate-mark-team.svg +26 -0
  37. package/public/assets/brand/thumbgate-mark.svg +15 -0
  38. package/public/assets/brand/thumbgate-wordmark.svg +20 -0
  39. package/public/assets/claude-thumbgate-statusbar.svg +8 -0
  40. package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
  41. package/public/assets/legal-intake-control-flow.svg +66 -0
  42. package/public/blog.html +1 -1
  43. package/public/brand/thumbgate-mark.svg +15 -0
  44. package/public/brand/thumbgate-og.svg +16 -0
  45. package/public/codex-enterprise.html +1 -1
  46. package/public/codex-plugin.html +1 -1
  47. package/public/compare.html +23 -3
  48. package/public/dashboard.html +312 -30
  49. package/public/federal.html +1 -1
  50. package/public/guide.html +5 -4
  51. package/public/index.html +167 -49
  52. package/public/js/buyer-intent.js +672 -0
  53. package/public/learn.html +74 -7
  54. package/public/lessons.html +2 -1
  55. package/public/numbers.html +3 -3
  56. package/public/pricing.html +63 -15
  57. package/public/pro.html +7 -7
  58. package/scripts/activation-quickstart.js +187 -0
  59. package/scripts/agent-memory-lifecycle.js +211 -0
  60. package/scripts/async-eval-observability.js +236 -0
  61. package/scripts/auto-promote-gates.js +75 -4
  62. package/scripts/build-metadata.js +24 -3
  63. package/scripts/cli-schema.js +22 -0
  64. package/scripts/dashboard-chat.js +2 -1
  65. package/scripts/dashboard.js +8 -0
  66. package/scripts/export-databricks-bundle.js +5 -1
  67. package/scripts/export-dpo-pairs.js +7 -2
  68. package/scripts/feedback-aggregate.js +281 -0
  69. package/scripts/feedback-loop.js +34 -0
  70. package/scripts/filesystem-search.js +35 -10
  71. package/scripts/gates-engine.js +198 -6
  72. package/scripts/gemini-embedding-policy.js +2 -1
  73. package/scripts/hook-stop-anti-claim.js +227 -0
  74. package/scripts/hook-thumbgate-cache-updater.js +18 -2
  75. package/scripts/lesson-inference.js +8 -3
  76. package/scripts/lesson-search.js +17 -1
  77. package/scripts/operational-integrity.js +39 -5
  78. package/scripts/plausible-domain-config.js +4 -2
  79. package/scripts/rate-limiter.js +12 -6
  80. package/scripts/secret-redaction.js +166 -0
  81. package/scripts/security-scanner.js +100 -0
  82. package/scripts/self-distill-agent.js +3 -1
  83. package/scripts/self-harness-optimizer.js +141 -0
  84. package/scripts/seo-gsd.js +635 -0
  85. package/scripts/statusline-cache-path.js +17 -2
  86. package/scripts/statusline-cache-read.js +57 -0
  87. package/scripts/statusline-local-stats.js +9 -1
  88. package/scripts/statusline-meta.js +5 -2
  89. package/scripts/statusline.sh +13 -1
  90. package/scripts/sync-telemetry-from-prod.js +374 -0
  91. package/scripts/telemetry-analytics.js +9 -0
  92. package/scripts/thumbgate-search.js +85 -19
  93. package/scripts/tool-contract-validator.js +76 -0
  94. package/scripts/vector-store.js +44 -0
  95. package/scripts/workspace-evolver.js +62 -2
  96. package/src/api/server.js +715 -86
@@ -11,7 +11,7 @@
11
11
  <meta name="robots" content="noindex">
12
12
  <meta name="referrer" content="same-origin">
13
13
  <!-- Privacy-friendly analytics by Plausible -->
14
- <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
14
+ <script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.js"></script>
15
15
  <script type="application/ld+json">
16
16
  {
17
17
  "@context": "https://schema.org",
@@ -103,7 +103,7 @@
103
103
  .result-tags { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
104
104
  .tag { font-size: 11px; background: var(--cyan-dim); color: var(--cyan); padding: 2px 8px; border-radius: 4px; cursor: pointer; transition: background 0.15s, transform 0.1s; border: none; font-family: inherit; }
105
105
  .tag:hover { background: rgba(34,211,238,0.25); transform: translateY(-1px); }
106
- mark { background: rgba(34,211,238,0.25); color: var(--cyan); border-radius: 2px; padding: 0 2px; }
106
+ mark { background: #fbbf24; color: #000000; font-weight: bold; border-radius: 2px; padding: 0 3px; }
107
107
 
108
108
  /* GATES */
109
109
  .gates-section { margin-bottom: 32px; }
@@ -179,6 +179,74 @@
179
179
  .demo-banner a { color: var(--cyan); text-decoration: none; font-weight: 600; }
180
180
  .demo-banner a:hover { text-decoration: underline; }
181
181
 
182
+ /* Tooltips styling */
183
+ .tooltip-trigger {
184
+ display: inline-flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ width: 14px;
188
+ height: 14px;
189
+ border-radius: 50%;
190
+ background: var(--border);
191
+ color: var(--text-muted);
192
+ font-size: 9px;
193
+ font-weight: bold;
194
+ cursor: help;
195
+ margin-left: 6px;
196
+ position: relative;
197
+ vertical-align: middle;
198
+ user-select: none;
199
+ }
200
+ .tooltip-trigger:hover {
201
+ background: var(--cyan);
202
+ color: var(--bg);
203
+ }
204
+ .tooltip-trigger .tooltip-content {
205
+ visibility: hidden;
206
+ opacity: 0;
207
+ width: 220px;
208
+ background-color: #161618;
209
+ color: #e8e8ec;
210
+ text-align: left;
211
+ border: 1px solid var(--border);
212
+ border-radius: 6px;
213
+ padding: 8px 12px;
214
+ position: absolute;
215
+ z-index: 1000;
216
+ bottom: 130%;
217
+ left: 50%;
218
+ transform: translateX(-50%);
219
+ transition: opacity 0.15s, visibility 0.15s;
220
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6);
221
+ font-weight: normal;
222
+ font-size: 11px;
223
+ line-height: 1.4;
224
+ white-space: normal;
225
+ pointer-events: none;
226
+ font-family: var(--font);
227
+ text-transform: none;
228
+ letter-spacing: normal;
229
+ }
230
+ .tooltip-trigger .tooltip-content::after {
231
+ content: "";
232
+ position: absolute;
233
+ top: 100%;
234
+ left: 50%;
235
+ transform: translateX(-50%);
236
+ border-width: 5px;
237
+ border-style: solid;
238
+ border-color: #161618 transparent transparent transparent;
239
+ }
240
+ .tooltip-trigger:hover .tooltip-content {
241
+ visibility: visible;
242
+ opacity: 1;
243
+ }
244
+ #disconnectBtn:hover {
245
+ background: rgba(248,113,113,0.1) !important;
246
+ border-color: #f87171 !important;
247
+ color: #f87171 !important;
248
+ }
249
+
182
250
  .empty { text-align: center; padding: 48px; color: var(--text-muted); font-size: 15px; }
183
251
  .loading { text-align: center; padding: 24px; color: var(--text-muted); }
184
252
  h2 { font-size: 20px; font-weight: 700; margin-bottom: 16px; letter-spacing: -0.02em; }
@@ -198,6 +266,19 @@
198
266
  .enterprise-source-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
199
267
  .enterprise-source { font-size: 11px; border: 1px solid var(--border); border-radius: 999px; padding: 4px 9px; color: var(--text-muted); background: var(--bg-card); }
200
268
 
269
+ .quick-nav-link {
270
+ cursor: pointer;
271
+ transition: opacity 0.15s ease, color 0.15s ease;
272
+ }
273
+ .quick-nav-link:hover {
274
+ color: var(--cyan);
275
+ opacity: 0.95;
276
+ }
277
+ .quick-nav-link:hover strong {
278
+ color: var(--cyan) !important;
279
+ text-decoration: underline;
280
+ }
281
+
201
282
  @media (max-width: 700px) {
202
283
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
203
284
  .search-filters { flex-wrap: wrap; }
@@ -222,13 +303,14 @@
222
303
  <div class="auth-bar">
223
304
  <div class="container">
224
305
  <div style="margin-bottom:10px;">
225
- <span style="font-size:14px;font-weight:600;">Connect your API key</span>
306
+ <span id="authTitle" style="font-size:14px;font-weight:600;">Connect your API key</span>
226
307
  <span id="authHelp" style="font-size:13px;color:var(--text-muted);margin-left:8px;">Pro auto-connects locally when you launch with <code style="font-family:var(--mono);font-size:12px;background:var(--cyan-dim);color:var(--cyan);padding:2px 6px;border-radius:4px;">npx thumbgate pro</code>. Otherwise paste your <code style="font-family:var(--mono);font-size:12px;background:var(--cyan-dim);color:var(--cyan);padding:2px 6px;border-radius:4px;">THUMBGATE_API_KEY</code> manually.</span>
227
308
  </div>
228
309
  <div style="display:flex;gap:12px;align-items:center;">
229
310
  <input type="password" class="auth-input" id="apiKey" placeholder="Paste your API key here (THUMBGATE_API_KEY or THUMBGATE_PRO_KEY)..." />
230
311
  <button class="btn" id="connectBtn" onclick="connect()">Connect</button>
231
312
  <button class="btn-outline" id="demoBtn" onclick="loadDemo()">Try Demo</button>
313
+ <button class="btn-outline" id="disconnectBtn" onclick="disconnectKey()" style="display:none; border-color:#f87171; color:#f87171;">Disconnect Key</button>
232
314
  <span class="auth-status" id="authStatus"></span>
233
315
  </div>
234
316
  </div>
@@ -241,10 +323,10 @@
241
323
  <p style="font-size:12px;color:var(--text-muted);margin-bottom:8px;">Updated: <time datetime="2026-04-20">2026-04-20</time> · by <a href="https://github.com/IgorGanapolsky" style="color:inherit;">Igor Ganapolsky</a></p>
242
324
  <p style="font-size:14px;color:var(--text-muted);line-height:1.6;max-width:700px;">What's happening right now? Search memories, inspect active checks, manage your team, and export training data. <span style="color:var(--cyan);font-weight:600;">This is your control plane for AI agent behavior.</span></p>
243
325
  <div style="display:flex;gap:16px;margin-top:12px;font-size:12px;color:var(--text-muted);">
244
- <span>🔍 <strong style="color:var(--text);">Search</strong> — find any memory</span>
245
- <span>🛡️ <strong style="color:var(--text);">Gates</strong> — what's blocking</span>
246
- <span>👥 <strong style="color:var(--text);">Team</strong> — org metrics</span>
247
- <span>📤 <strong style="color:var(--text);">Export</strong> — DPO training data</span>
326
+ <span class="quick-nav-link" onclick="switchTab('search')">🔍 <strong style="color:var(--text);">Search</strong> — find any memory</span>
327
+ <span class="quick-nav-link" onclick="switchTab('gates')">🛡️ <strong style="color:var(--text);">Gates</strong> — what's blocking</span>
328
+ <span class="quick-nav-link" onclick="switchTab('team')">👥 <strong style="color:var(--text);">Team</strong> — org metrics</span>
329
+ <span class="quick-nav-link" onclick="switchTab('export')">📤 <strong style="color:var(--text);">Export</strong> — DPO training data</span>
248
330
  </div>
249
331
  </div>
250
332
 
@@ -255,10 +337,10 @@
255
337
 
256
338
  <!-- STATS -->
257
339
  <div class="stats-grid" id="statsGrid">
258
- <a class="stat-card" data-card-action="all" onclick="selectCard(this,'all')" href="/lessons?signal=all" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view all feedback → Lessons page"><div class="stat-label">Total Feedback</div><div class="stat-value cyan" id="statTotal">—</div></a>
259
- <a class="stat-card" data-card-action="up" onclick="selectCard(this,'up')" href="/lessons?signal=up" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view positive feedback → Lessons page"><div class="stat-label">👍 Positive</div><div class="stat-value green" id="statPositive">—</div></a>
260
- <a class="stat-card" data-card-action="down" onclick="selectCard(this,'down')" href="/lessons?signal=down" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view negative feedback → Lessons page"><div class="stat-label">👎 Negative</div><div class="stat-value red" id="statNegative">—</div></a>
261
- <a class="stat-card" data-card-action="gates" onclick="selectCard(this,'gates');return false;" href="#" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view active checks"><div class="stat-label">Active Gates</div><div class="stat-value cyan" id="statGates">—</div></a>
340
+ <a class="stat-card" data-card-action="all" onclick="selectCard(this,'all')" href="/lessons?signal=all" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view all feedback → Lessons page"><div class="stat-label">Total Feedback <span class="tooltip-trigger" onclick="event.stopPropagation(); event.preventDefault();">?<span class="tooltip-content">Total thumbs-up and thumbs-down signals captured from your AI agent interactions.</span></span></div><div class="stat-value cyan" id="statTotal">—</div></a>
341
+ <a class="stat-card" data-card-action="up" onclick="selectCard(this,'up')" href="/lessons?signal=up" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view positive feedback → Lessons page"><div class="stat-label">👍 Positive <span class="tooltip-trigger" onclick="event.stopPropagation(); event.preventDefault();">?<span class="tooltip-content">Successful outcomes where the agent's work met expectations (thumbs-up).</span></span></div><div class="stat-value green" id="statPositive">—</div></a>
342
+ <a class="stat-card" data-card-action="down" onclick="selectCard(this,'down')" href="/lessons?signal=down" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view negative feedback → Lessons page"><div class="stat-label">👎 Negative <span class="tooltip-trigger" onclick="event.stopPropagation(); event.preventDefault();">?<span class="tooltip-content">Unsuccessful runs or errors that we need to learn from to prevent repeat failures (thumbs-down).</span></span></div><div class="stat-value red" id="statNegative">—</div></a>
343
+ <a class="stat-card" data-card-action="gates" onclick="selectCard(this,'gates');return false;" href="#" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view active checks"><div class="stat-label">Active Gates <span class="tooltip-trigger" onclick="event.stopPropagation(); event.preventDefault();">?<span class="tooltip-content">Pre-action checks actively monitoring and blocking known-bad tool calls before they run.</span></span></div><div class="stat-value cyan" id="statGates">—</div></a>
262
344
  </div>
263
345
 
264
346
  <div class="panel" id="chatPanel" style="margin-bottom:20px;">
@@ -281,7 +363,7 @@
281
363
  <div class="panel" id="reviewDeltaPanel" style="margin-bottom:20px;">
282
364
  <div style="display:flex;justify-content:space-between;gap:16px;align-items:flex-start;flex-wrap:wrap;">
283
365
  <div>
284
- <h3 style="margin:0 0 8px 0;font-size:14px;letter-spacing:0.04em;text-transform:uppercase;color:var(--text-muted);">🆕 Since Last Review</h3>
366
+ <h3 style="margin:0 0 8px 0;font-size:14px;letter-spacing:0.04em;text-transform:uppercase;color:var(--text-muted);display:inline-flex;align-items:center;">🆕 Since Last Review <span class="tooltip-trigger" onclick="event.stopPropagation(); event.preventDefault();">?<span class="tooltip-content">Shows new feedback, negative signals, lessons, and blocks registered since your last baseline checkpoint.</span></span></h3>
285
367
  <p class="template-summary" id="reviewDeltaHeadline" style="margin-bottom:0;">No review checkpoint yet. Mark the current dashboard as reviewed to start seeing only new changes.</p>
286
368
  </div>
287
369
  <button class="btn-outline" id="reviewCheckpointBtn" onclick="markReviewed()">Mark Current Dashboard Reviewed</button>
@@ -320,6 +402,12 @@
320
402
 
321
403
  <!-- SEARCH TAB -->
322
404
  <div class="tab-content active" id="tab-search">
405
+ <div class="panel" style="margin-bottom:24px;">
406
+ <h3 style="margin-top:0;">📈 Feedback Trend (Last 30 Days)</h3>
407
+ <div style="position:relative;height:180px;margin-top:12px;">
408
+ <canvas id="feedbackTrendChart"></canvas>
409
+ </div>
410
+ </div>
323
411
  <div class="search-section">
324
412
  <div class="search-bar">
325
413
  <input type="text" class="search-input" id="searchQuery" placeholder="Search memories... (try: git, test, deploy, secrets, database)" onkeydown="if(event.key==='Enter')search()" />
@@ -341,6 +429,74 @@
341
429
  <div class="tab-content" id="tab-gates">
342
430
  <div class="gates-section">
343
431
  <h2>Active Pre-Action Checks</h2>
432
+
433
+ <!-- Interactive Gating Workflow Diagram -->
434
+ <details class="flow-details" style="margin-bottom: 24px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-card); padding: 16px;" open>
435
+ <summary style="font-weight: 600; cursor: pointer; color: var(--cyan); display: flex; align-items: center; gap: 8px; user-select: none;">
436
+ <span>🛡️ How Pre-Action Gating Works (Interactive Flow Diagram)</span>
437
+ </summary>
438
+ <div style="margin-top:20px; text-align:center;">
439
+ <svg viewBox="0 0 800 220" width="100%" height="auto" style="max-width: 800px; font-family: var(--font); font-size: 11px;">
440
+ <style>
441
+ .flow-node { fill: #1c1c1f; stroke: var(--border); stroke-width: 1.5; rx: 8px; transition: stroke 0.2s, fill 0.2s; }
442
+ .flow-node:hover { fill: rgba(34,211,238,0.06); stroke: var(--cyan); cursor: help; }
443
+ .flow-text { fill: var(--text); font-weight: 600; pointer-events: none; }
444
+ .flow-subtext { fill: var(--text-muted); font-size: 10px; pointer-events: none; }
445
+ .flow-arrow { fill: none; stroke: var(--border); stroke-width: 1.5; marker-end: url(#arrow-head); }
446
+ </style>
447
+
448
+ <defs>
449
+ <marker id="arrow-head" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
450
+ <path d="M 0 1 L 10 5 L 0 9 z" fill="#8b8b96" />
451
+ </marker>
452
+ </defs>
453
+
454
+ <!-- Step 1: Tool Call -->
455
+ <g>
456
+ <rect x="10" y="80" width="130" height="60" class="flow-node" />
457
+ <text x="75" y="105" text-anchor="middle" class="flow-text">1. Tool Call</text>
458
+ <text x="75" y="125" text-anchor="middle" class="flow-subtext">Agent requests action</text>
459
+ </g>
460
+
461
+ <path d="M 140 110 L 190 110" class="flow-arrow" />
462
+
463
+ <!-- Step 2: Hook Intercept -->
464
+ <g>
465
+ <rect x="200" y="80" width="140" height="60" class="flow-node" />
466
+ <text x="270" y="105" text-anchor="middle" class="flow-text">2. PreToolUse Hook</text>
467
+ <text x="270" y="125" text-anchor="middle" class="flow-subtext">Intercepts tool execution</text>
468
+ </g>
469
+
470
+ <path d="M 340 110 L 390 110" class="flow-arrow" />
471
+
472
+ <!-- Step 3: Policy / DB Lookup -->
473
+ <g>
474
+ <rect x="400" y="80" width="150" height="60" class="flow-node" />
475
+ <text x="475" y="105" text-anchor="middle" class="flow-text">3. Gate Evaluation</text>
476
+ <text x="475" y="125" text-anchor="middle" class="flow-subtext">Validates matching rules</text>
477
+ </g>
478
+
479
+ <!-- Branch Arrows -->
480
+ <path d="M 550 100 L 640 55" class="flow-arrow" />
481
+ <path d="M 550 120 L 640 165" class="flow-arrow" />
482
+
483
+ <!-- Step 4a: Block -->
484
+ <g>
485
+ <rect x="650" y="20" width="130" height="60" class="flow-node" style="stroke:#ef4444;" />
486
+ <text x="715" y="45" text-anchor="middle" class="flow-text" style="fill:#ef4444;">❌ BLOCK</text>
487
+ <text x="715" y="65" text-anchor="middle" class="flow-subtext">Matches rule -> aborted</text>
488
+ </g>
489
+
490
+ <!-- Step 4b: Allow -->
491
+ <g>
492
+ <rect x="650" y="140" width="130" height="60" class="flow-node" style="stroke:#22c55e;" />
493
+ <text x="715" y="165" text-anchor="middle" class="flow-text" style="fill:#22c55e;">✅ ALLOW</text>
494
+ <text x="715" y="185" text-anchor="middle" class="flow-subtext">Runs safely & logs</text>
495
+ </g>
496
+ </svg>
497
+ </div>
498
+ </details>
499
+
344
500
  <div id="gatesList"><div class="loading">Loading gates...</div></div>
345
501
  </div>
346
502
  </div>
@@ -537,13 +693,7 @@
537
693
  </div>
538
694
 
539
695
  <!-- Charts -->
540
- <div class="team-columns" style="margin-bottom:24px;">
541
- <div class="panel">
542
- <h3>Feedback Trend (30 days)</h3>
543
- <div style="position:relative;height:260px;margin-top:12px;">
544
- <canvas id="feedbackTrendChart"></canvas>
545
- </div>
546
- </div>
696
+ <div style="margin-bottom:24px;">
547
697
  <div class="panel">
548
698
  <h3>Lessons Generated (30 days)</h3>
549
699
  <div style="position:relative;height:260px;margin-top:12px;">
@@ -849,6 +999,37 @@ function hasBootstrapKey() {
849
999
  return LOCAL_PRO_BOOTSTRAP && Boolean(BOOTSTRAP_API_KEY);
850
1000
  }
851
1001
 
1002
+ async function checkLocalLlmReachable(url) {
1003
+ try {
1004
+ var controller = new AbortController();
1005
+ var timeout = setTimeout(function() { controller.abort(); }, 400);
1006
+ // Use no-cors mode to bypass CORS block (only care if network request succeeds/fails)
1007
+ await fetch(url, { method: 'GET', signal: controller.signal, mode: 'no-cors' });
1008
+ clearTimeout(timeout);
1009
+ return true;
1010
+ } catch (e) {
1011
+ return false;
1012
+ }
1013
+ }
1014
+
1015
+ function disconnectKey() {
1016
+ API_KEY = '';
1017
+ isDemo = false;
1018
+ var input = document.getElementById('apiKey');
1019
+ input.value = '';
1020
+ input.disabled = false;
1021
+ input.placeholder = "Paste your API key here (THUMBGATE_API_KEY or THUMBGATE_PRO_KEY)...";
1022
+ var btn = document.getElementById('connectBtn');
1023
+ btn.disabled = false;
1024
+ document.getElementById('demoBtn').style.display = 'inline-block';
1025
+ document.getElementById('disconnectBtn').style.display = 'none';
1026
+ document.getElementById('authStatus').className = 'auth-status';
1027
+ document.getElementById('authStatus').textContent = 'Disconnected';
1028
+ document.getElementById('authTitle').textContent = 'Connect your API key';
1029
+ document.getElementById('authHelp').innerHTML = 'Pro auto-connects locally when you launch with <code style="font-family:var(--mono);font-size:12px;background:var(--cyan-dim);color:var(--cyan);padding:2px 6px;border-radius:4px;">npx thumbgate pro</code>. Otherwise paste your <code style="font-family:var(--mono);font-size:12px;background:var(--cyan-dim);color:var(--cyan);padding:2px 6px;border-radius:4px;">THUMBGATE_API_KEY</code> manually.';
1030
+ document.getElementById('dashboardContent').style.display = 'none';
1031
+ }
1032
+
852
1033
  async function connect(options) {
853
1034
  var opts = options || {};
854
1035
  var input = document.getElementById('apiKey');
@@ -875,7 +1056,15 @@ async function connect(options) {
875
1056
 
876
1057
  if (data.localLlmConfigured) {
877
1058
  var modelLabel = data.localLlmModel ? ' (' + data.localLlmModel + ')' : '';
878
- document.getElementById('chatHint').innerHTML = '<span style="color:var(--green)">✓ Local LLM' + modelLabel + ' ready. Chat answers are generated locally — no cloud calls.</span>';
1059
+ var llmUrl = data.localLlmEndpoint || 'http://localhost:11434/v1';
1060
+ document.getElementById('chatHint').innerHTML = '<span style="color:var(--text-muted)">Checking local LLM reachability...</span>';
1061
+ checkLocalLlmReachable(llmUrl.replace(/\/v1$/, '')).then(function(reachable) {
1062
+ if (reachable) {
1063
+ document.getElementById('chatHint').innerHTML = '<span style="color:var(--green)">✓ Local LLM' + modelLabel + ' ready. Chat answers are generated locally — no cloud calls.</span>';
1064
+ } else {
1065
+ document.getElementById('chatHint').innerHTML = '<span style="color:#f59e0b">⚠ Ollama is offline. Launch it to chat locally: <code style="font-family:var(--mono);font-size:12px;background:rgba(245,158,11,0.1);color:#f59e0b;padding:2px 6px;border-radius:4px;">ollama run qwen2.5-coder:14b</code></span>';
1066
+ }
1067
+ });
879
1068
  } else if (data.geminiKeyStatus === 'validated' || data.geminiValidatedAt) {
880
1069
  var when = data.geminiValidatedAt ? ' (validated ' + new Date(data.geminiValidatedAt).toLocaleTimeString() + ')' : '';
881
1070
  document.getElementById('chatHint').innerHTML = '<span style="color:var(--green)">✓ Gemini API key validated' + when + '. You can now chat with your data.</span>';
@@ -887,10 +1076,15 @@ async function connect(options) {
887
1076
 
888
1077
  status.className = 'auth-status ok';
889
1078
  status.textContent = opts.localPro ? `✓ Local ${tierName} connected` : '✓ Connected';
1079
+ document.getElementById('disconnectBtn').style.display = 'inline-block';
1080
+ const titleEl = document.getElementById('authTitle');
1081
+ if (titleEl) {
1082
+ titleEl.textContent = opts.localPro ? `${tierName} License Active` : 'API Key Connected';
1083
+ }
890
1084
  document.getElementById('dashboardContent').style.display = 'block';
891
1085
  if (opts.localPro) {
892
1086
  const localProActiveMessage = 'Local Pro is active on this machine. Your dashboard is using the saved license key automatically.';
893
- const localEnterpriseActiveMessage = 'Local Enterprise is active on this machine. Your dashboard is using the saved license key automatically.';
1087
+ const localEnterpriseActiveMessage = 'Local Enterprise is active on this machine. Your dashboard is using the saved license key automatically. Click Disconnect to switch keys.';
894
1088
  input.value = 'local-license';
895
1089
  input.disabled = true;
896
1090
  input.placeholder = `Local ${tierName} auto-connected`;
@@ -908,6 +1102,10 @@ async function connect(options) {
908
1102
  } catch (e) {
909
1103
  status.className = 'auth-status err';
910
1104
  status.textContent = '✗ ' + e.message;
1105
+ const titleEl = document.getElementById('authTitle');
1106
+ if (titleEl) {
1107
+ titleEl.textContent = 'Connect your API key';
1108
+ }
911
1109
  if (opts.localPro) {
912
1110
  document.getElementById('authHelp').textContent = `Local ${tierName} bootstrap failed. Paste your THUMBGATE_API_KEY manually or retry the local launcher.`;
913
1111
  }
@@ -973,24 +1171,88 @@ async function search() {
973
1171
  }
974
1172
 
975
1173
  function renderResult(r) {
976
- const signal = r.signal || r.type || '';
1174
+ let contextText = r.context || r.text || r.content || '';
1175
+ let signal = r.signal || r.type || '';
1176
+
1177
+ if (contextText && contextText.trim().startsWith('{')) {
1178
+ const originalText = contextText;
1179
+ try {
1180
+ let cleanText = contextText.trim();
1181
+ if (cleanText.endsWith('…')) {
1182
+ cleanText = cleanText.slice(0, -1);
1183
+ }
1184
+ if (cleanText.endsWith(',...')) {
1185
+ cleanText = cleanText.slice(0, -4);
1186
+ }
1187
+ let parsed = null;
1188
+ try {
1189
+ parsed = JSON.parse(cleanText);
1190
+ } catch (_) {
1191
+ try { parsed = JSON.parse(cleanText + '"}'); } catch (_) {}
1192
+ try { parsed = JSON.parse(cleanText + '}'); } catch (_) {}
1193
+ try { parsed = JSON.parse(cleanText + '"} }'); } catch (_) {}
1194
+ }
1195
+ if (parsed && (parsed.context || parsed.text || parsed.content || parsed.message)) {
1196
+ contextText = parsed.context || parsed.text || parsed.content || parsed.message;
1197
+ } else {
1198
+ let contextMatch = originalText.match(/"context"\s*:\s*"((?:[^"\\]|\\.)*?)"/);
1199
+ if (!contextMatch) {
1200
+ contextMatch = originalText.match(/"context"\s*:\s*"((?:[^"\\]|\\.)*)/);
1201
+ }
1202
+ if (!contextMatch || !contextMatch[1]) {
1203
+ let otherMatch = originalText.match(/"(?:text|content|message)"\s*:\s*"((?:[^"\\]|\\.)*?)"/);
1204
+ if (!otherMatch) {
1205
+ otherMatch = originalText.match(/"(?:text|content|message)"\s*:\s*"((?:[^"\\]|\\.)*)/);
1206
+ }
1207
+ if (otherMatch && otherMatch[1]) {
1208
+ contextMatch = otherMatch;
1209
+ }
1210
+ }
1211
+ if (contextMatch && contextMatch[1]) {
1212
+ let val = contextMatch[1];
1213
+ val = val.replace(/(?:"\s*,\s*"[^"]*"\s*:?|"\s*,\s*|"\s*\}?\s*|\s*,\s*|\s*\}?\s*)$/, '');
1214
+ val = val.replace(/(?:\.\.\.|…|",\.\.\.|",…|"\s*,\s*\.\.\.|"\s*,\s*…)$/, '');
1215
+ contextText = val.replace(/\\"/g, '"').replace(/\\\\/g, '\\');
1216
+ }
1217
+ }
1218
+ if (parsed && parsed.signal) {
1219
+ signal = parsed.signal;
1220
+ } else {
1221
+ const signalMatch = originalText.match(/"signal"\s*:\s*"([^"]+)"/);
1222
+ if (signalMatch && !signal) {
1223
+ signal = signalMatch[1];
1224
+ }
1225
+ }
1226
+ } catch (_) {}
1227
+ }
1228
+
977
1229
  const isUp = signal === 'up' || signal === 'positive' || signal === 'thumbs_up';
978
1230
  const isDown = signal === 'down' || signal === 'negative' || signal === 'thumbs_down';
979
1231
  const signalClass = isUp ? 'up' : isDown ? 'down' : '';
980
1232
  const signalLabel = isUp ? '👍 Positive' : isDown ? '👎 Negative' : signal;
981
- const title = r.title || r.context || r.text || '';
982
- const context = r.context || r.text || r.content || '';
1233
+
1234
+ let displayTitle = r.title || contextText || '';
1235
+ if (displayTitle && displayTitle.startsWith('feedback_')) {
1236
+ const parts = displayTitle.split('_');
1237
+ if (parts.length >= 3) {
1238
+ const type = parts[1];
1239
+ const id = parts.slice(2).join('_');
1240
+ displayTitle = (type === 'positive' ? '👍' : '👎') + ' Feedback (' + id + ')';
1241
+ }
1242
+ }
1243
+
983
1244
  const tags = r.tags || [];
984
1245
  const date = r.timestamp ? new Date(r.timestamp).toLocaleDateString() : '';
1246
+
985
1247
  return '<div class="result-card">' +
986
1248
  '<div class="result-header">' +
987
1249
  (signalLabel ? '<span class="result-signal ' + signalClass + '">' + signalLabel + '</span>' : '<span></span>') +
988
1250
  '<span class="result-date">' + date + '</span>' +
989
1251
  '</div>' +
990
- (title ? '<div class="result-title">' + highlightText(title) + '</div>' : '') +
991
- (context && context !== title ? '<div class="result-context">' + highlightText(context.slice(0, 300)) + '</div>' : '') +
1252
+ (displayTitle ? '<div class="result-title">' + highlightText(displayTitle) + '</div>' : '') +
1253
+ (contextText && contextText !== displayTitle ? '<div class="result-context">' + highlightText(contextText.slice(0, 300)) + '</div>' : '') +
992
1254
  (tags.length ? '<div class="result-tags">' + tags.map(function(t) {
993
- return '<button type="button" class="tag" data-tag="' + encodeURIComponent(String(t)) + '" title="' + escAttr('Search for ' + t) + '">' + escHtml(t) + '</button>';
1255
+ return '<button type="button" class="tag" data-tag="' + encodeURIComponent(String(t)) + '" title="' + escAttr('Search for ' + t) + '">' + highlightText(t) + '</button>';
994
1256
  }).join('') + '</div>' : '') +
995
1257
  '</div>';
996
1258
  }
@@ -1828,9 +2090,28 @@ function renderEnterpriseStatus(status) {
1828
2090
  { name: 'Dialogflow guard adapter', value: dfcx.liveAgentConfigured ? 'Configured' : 'Optional', note: dfcx.verification || 'Only needed for customer-owned Dialogflow CX agent deployments.' },
1829
2091
  { name: 'Legacy gcloud CX command', value: 'Unsupported', note: 'Do not use the old alpha gcloud CX command group; use REST API or console for DFCX proof.' }
1830
2092
  ];
1831
- target.innerHTML = rows.map(function(row) {
1832
- return '<div class="inventory-row"><div><div class="inventory-name">' + escHtml(row.name) + '</div><div class="inventory-subtitle">' + escHtml(row.note) + '</div></div><span class="remediation-action">' + escHtml(row.value) + '</span></div>';
1833
- }).join('');
2093
+
2094
+ function draw() {
2095
+ target.innerHTML = rows.map(function(row) {
2096
+ return '<div class="inventory-row"><div><div class="inventory-name">' + escHtml(row.name) + '</div><div class="inventory-subtitle">' + escHtml(row.note) + '</div></div><span class="remediation-action">' + escHtml(row.value) + '</span></div>';
2097
+ }).join('');
2098
+ }
2099
+
2100
+ draw();
2101
+
2102
+ if (localLlm) {
2103
+ var llmUrl = 'http://localhost:11434/v1';
2104
+ checkLocalLlmReachable(llmUrl).then(function(reachable) {
2105
+ if (reachable) {
2106
+ rows[0].value = '✓ Local LLM Online';
2107
+ draw();
2108
+ } else {
2109
+ rows[0].value = '⚠ Local LLM Offline';
2110
+ rows[0].note = 'Ollama is offline. Launch it to chat locally: ollama run qwen2.5-coder:14b';
2111
+ draw();
2112
+ }
2113
+ });
2114
+ }
1834
2115
  }
1835
2116
 
1836
2117
  async function loadEnterpriseDataChatStatus() {
@@ -2482,5 +2763,6 @@ function renderGateAuditChartFromData(gateAudit) {
2482
2763
  }
2483
2764
 
2484
2765
  </script>
2766
+ <script src="/js/buyer-intent.js"></script>
2485
2767
  </body>
2486
2768
  </html>
@@ -16,7 +16,7 @@ __GOOGLE_SITE_VERIFICATION_META__
16
16
  <meta property="og:image" content="/og.png">
17
17
  <meta name="keywords" content="ThumbGate federal, AI agent governance federal, NIST 800-53 AI agent, OMB M-24-10, EO 14110, FedRAMP AI coding agent, federal AI use case inventory, agent audit log, agency AI policy enforcement, Bedrock GovCloud, Azure Government AI, SBIR AI governance">
18
18
 
19
- <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
19
+ <script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.js"></script>
20
20
  __GA_BOOTSTRAP__
21
21
 
22
22
  <script>
package/public/guide.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>How to Stop AI Coding Agents From Repeating Mistakes — ThumbGate Guide</title>
7
7
  <!-- Privacy-friendly analytics by Plausible -->
8
- <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
+ <script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.js"></script>
9
9
  <meta name="description" content="The complete guide to preventing AI coding agent mistakes with pre-action checks, history-aware lesson distillation, and automatic prevention rules.">
10
10
  <meta name="keywords" content="AI agent mistakes, Claude Code force push, AI coding agent memory, MCP server guardrails, pre-action checks, vibe coding safety, PreToolUse hooks, ThumbGate, SpecLock alternative, Mem0 alternative">
11
11
  <meta property="og:title" content="How to Stop AI Coding Agents From Repeating Mistakes">
@@ -376,11 +376,12 @@ npx thumbgate init --agent gemini</code></pre>
376
376
  <pre><code>npx thumbgate init</code></pre>
377
377
  <p>One command. Works with Claude Code, Cursor, Codex, Gemini, Amp, and OpenCode. Claude Code can also call Codex for review, adversarial review, and second-pass handoffs through the repo-local bridge plugin.</p>
378
378
  <a href="https://thumbgate.ai/checkout/pro?utm_source=guide&utm_medium=cta_button&utm_campaign=pro_pack" class="cta">Get Pro — $19/mo or $149/yr</a>
379
- <a rel="nofollow noopener noreferrer" target="_blank" href="https://buy.stripe.com/00w14neyUcXA5pL5e33sI0e" class="cta cta-secondary">Pay $499 diagnostic</a>
380
- <a rel="nofollow noopener noreferrer" target="_blank" href="https://buy.stripe.com/fZu9AT76saPsg4pbCr3sI0f" class="cta cta-secondary">Pay $1500 sprint</a>
379
+ <a href="__SPRINT_DIAGNOSTIC_CHECKOUT_URL__" class="cta cta-secondary">Pay $499 diagnostic</a>
380
+ <a href="__WORKFLOW_SPRINT_CHECKOUT_URL__" class="cta cta-secondary">Pay $1500 sprint</a>
381
381
  <a href="https://thumbgate.ai/#workflow-sprint-intake" class="cta cta-secondary">Send workflow first</a>
382
- <p style="color:var(--muted); font-size:0.85rem;">Free: 5 captures/day, 25 total captures, 3 active prevention rules, hook blocking. Pro: hosted sync, dashboard, recall, lesson search, unlimited captures/rules, DPO export. Enterprise: intake first, then custom pricing scoped to your rollout.</p>
382
+ <p style="color:var(--muted); font-size:0.85rem;">Free: 2 captures/day, 10 total captures, 3 active prevention rules, hook blocking. Pro: hosted sync, dashboard, recall, lesson search, unlimited captures/rules, DPO export. Enterprise: intake first, then custom pricing scoped to your rollout.</p>
383
383
 
384
384
  </div>
385
+ <script src="/js/buyer-intent.js"></script>
385
386
  </body>
386
387
  </html>