thumbgate 1.5.8 → 1.7.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/src/api/server.js CHANGED
@@ -152,6 +152,9 @@ const {
152
152
  } = require('../../scripts/decision-journal');
153
153
  const {
154
154
  generateDashboard,
155
+ buildReviewSnapshot,
156
+ readDashboardReviewState,
157
+ writeDashboardReviewState,
155
158
  } = require('../../scripts/dashboard');
156
159
  const {
157
160
  buildDashboardRenderSpec,
@@ -216,6 +219,7 @@ const PRO_PAGE_PATH = path.resolve(__dirname, '../../public/pro.html');
216
219
  const DASHBOARD_PAGE_PATH = path.resolve(__dirname, '../../public/dashboard.html');
217
220
  const LESSONS_PAGE_PATH = path.resolve(__dirname, '../../public/lessons.html');
218
221
  const GUIDE_PAGE_PATH = path.resolve(__dirname, '../../public/guide.html');
222
+ const CODEX_PLUGIN_PAGE_PATH = path.resolve(__dirname, '../../public/codex-plugin.html');
219
223
  const COMPARE_PAGE_PATH = path.resolve(__dirname, '../../public/compare.html');
220
224
  const LEARN_PAGE_PATH = path.resolve(__dirname, '../../public/learn.html');
221
225
  const LEARN_DIR = path.resolve(__dirname, '../../public/learn');
@@ -1703,7 +1707,8 @@ body { font-family: var(--font); background: var(--bg); color: var(--text); line
1703
1707
  .container { max-width: 860px; margin: 0 auto; padding: 0 24px; }
1704
1708
  nav { position: sticky; top: 0; z-index: 50; background: rgba(10,10,11,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid var(--border); padding: 14px 0; }
1705
1709
  nav .container { display: flex; justify-content: space-between; align-items: center; }
1706
- .nav-logo { font-weight: 700; font-size: 15px; color: var(--text); text-decoration: none; }
1710
+ .nav-logo { font-weight: 700; font-size: 15px; color: var(--text); text-decoration: none; display: inline-flex; align-items: center; gap: 8px; }
1711
+ .nav-logo .logo-mark { width: 28px; height: 28px; display: block; }
1707
1712
  .nav-links { display: flex; gap: 16px; align-items: center; }
1708
1713
  .nav-links a { color: var(--text-muted); text-decoration: none; font-size: 13px; }
1709
1714
  .nav-links a:hover { color: var(--text); }
@@ -1748,7 +1753,7 @@ nav .container { display: flex; justify-content: space-between; align-items: cen
1748
1753
  </head>
1749
1754
  <body>
1750
1755
  <nav><div class="container">
1751
- <a href="/dashboard" class="nav-logo">👍👎 ThumbGate</a>
1756
+ <a href="/dashboard" class="nav-logo"><img src="/assets/brand/thumbgate-mark-inline.svg" alt="ThumbGate" class="logo-mark" width="28" height="28"><span class="logo-text">ThumbGate</span></a>
1752
1757
  <div class="nav-links">
1753
1758
  <a href="/dashboard">Dashboard</a>
1754
1759
  <a href="/lessons">Lessons</a>
@@ -1900,6 +1905,7 @@ function renderSitemapXml(runtimeConfig) {
1900
1905
  // so search engines don't chase the 301 instead of indexing the canonical
1901
1906
  // homepage directly.
1902
1907
  { path: '/llm-context.md', changefreq: 'weekly', priority: '0.8' },
1908
+ { path: '/codex-plugin', changefreq: 'weekly', priority: '0.75' },
1903
1909
  ...THUMBGATE_SEO_SITEMAP_ENTRIES,
1904
1910
  ];
1905
1911
  return [
@@ -2022,6 +2028,7 @@ function renderCheckoutSuccessPage(runtimeConfig) {
2022
2028
  --accent-dark: #8f451f;
2023
2029
  --card: #fffdf9;
2024
2030
  --success: #2f7d4b;
2031
+ --warning: #8f451f;
2025
2032
  --radius: 14px;
2026
2033
  }
2027
2034
  * { box-sizing: border-box; }
@@ -2073,6 +2080,15 @@ function renderCheckoutSuccessPage(runtimeConfig) {
2073
2080
  font-weight: 700;
2074
2081
  margin-bottom: 8px;
2075
2082
  }
2083
+ .email-status {
2084
+ color: var(--muted);
2085
+ font-size: 14px;
2086
+ margin-top: 10px;
2087
+ }
2088
+ .email-status.warning {
2089
+ color: var(--warning);
2090
+ font-weight: 700;
2091
+ }
2076
2092
  pre {
2077
2093
  white-space: pre-wrap;
2078
2094
  word-break: break-word;
@@ -2108,11 +2124,26 @@ function renderCheckoutSuccessPage(runtimeConfig) {
2108
2124
  color: var(--muted);
2109
2125
  font-size: 14px;
2110
2126
  }
2127
+ .brand-header {
2128
+ display: flex;
2129
+ align-items: center;
2130
+ gap: 10px;
2131
+ margin-bottom: 28px;
2132
+ text-decoration: none;
2133
+ color: var(--ink);
2134
+ font-weight: 700;
2135
+ font-size: 16px;
2136
+ letter-spacing: -0.01em;
2137
+ }
2138
+ .brand-header .logo-mark { width: 32px; height: 32px; display: block; }
2111
2139
  </style>
2140
+ <link rel="icon" type="image/png" href="/thumbgate-icon.png">
2141
+ <link rel="apple-touch-icon" href="/assets/brand/thumbgate-mark.svg">
2112
2142
  <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
2113
2143
  </head>
2114
2144
  <body>
2115
2145
  <main>
2146
+ <a href="/" class="brand-header"><img src="/assets/brand/thumbgate-mark-inline.svg" alt="ThumbGate" class="logo-mark" width="32" height="32"><span class="logo-text">ThumbGate</span></a>
2116
2147
  <span class="eyebrow">ThumbGate Pro</span>
2117
2148
  <h1>Your local Pro dashboard is ready.</h1>
2118
2149
  <p class="lead">This page verifies your Stripe session, provisions the key if needed, and gives you the exact command to save your license and launch your personal local dashboard.</p>
@@ -2120,6 +2151,7 @@ function renderCheckoutSuccessPage(runtimeConfig) {
2120
2151
  <div class="card">
2121
2152
  <div class="status" id="status">Verifying payment and provisioning your key...</div>
2122
2153
  <p class="muted" id="summary">Do not close this tab until the key appears.</p>
2154
+ <p class="email-status" id="email-status">Activation email pending checkout verification.</p>
2123
2155
  <pre id="key-block">Waiting for checkout session...</pre>
2124
2156
  </div>
2125
2157
 
@@ -2131,11 +2163,26 @@ function renderCheckoutSuccessPage(runtimeConfig) {
2131
2163
  </div>
2132
2164
 
2133
2165
  <div class="card">
2134
- <h2>Hosted API setup (optional)</h2>
2166
+ <h2>Use ThumbGate from CI, teammates, and remote agents (optional)</h2>
2167
+ <p>The Hosted API lets anything that can make an HTTP request &mdash; CI jobs, GitHub Actions, teammates' laptops, scheduled cron, or agents running in Docker or Lambda &mdash; push feedback into the same memory pool your local dashboard already reads from.</p>
2168
+
2169
+ <p><strong>When you need this:</strong></p>
2170
+ <ul>
2171
+ <li>You run agents in CI/CD, GitHub Actions, or Docker containers and want their failures captured automatically.</li>
2172
+ <li>Your team wants shared memory &mdash; every teammate's thumbs-down feeds the same prevention rules.</li>
2173
+ <li>You dispatch agents from servers, Lambdas, or scheduled jobs that never touch your laptop.</li>
2174
+ </ul>
2175
+
2176
+ <p><strong>When you can skip this:</strong></p>
2177
+ <ul>
2178
+ <li>You only use ThumbGate from your own laptop &mdash; the local dashboard already handles everything.</li>
2179
+ </ul>
2180
+
2181
+ <p><strong>How to set it up:</strong></p>
2135
2182
  <ol>
2136
- <li>Copy the environment block below into your workflow runner.</li>
2137
- <li>Use the curl example to confirm the hosted API captures an event.</li>
2138
- <li>Keep your key private and rotate by repurchasing or contacting support if needed.</li>
2183
+ <li>Copy the environment block below into your CI or server environment.</li>
2184
+ <li>Use the curl example to confirm the hosted API captures an event end-to-end.</li>
2185
+ <li>Treat the key like any other API secret &mdash; rotate via your billing portal if it leaks.</li>
2139
2186
  </ol>
2140
2187
  <pre id="env-block">Waiting for provisioning...</pre>
2141
2188
  <pre id="curl-block">Waiting for provisioning...</pre>
@@ -2154,6 +2201,7 @@ function renderCheckoutSuccessPage(runtimeConfig) {
2154
2201
  const telemetryEndpoint = '/v1/telemetry/ping';
2155
2202
  const statusEl = document.getElementById('status');
2156
2203
  const summaryEl = document.getElementById('summary');
2204
+ const emailStatusEl = document.getElementById('email-status');
2157
2205
  const keyBlock = document.getElementById('key-block');
2158
2206
  const envBlock = document.getElementById('env-block');
2159
2207
  const curlBlock = document.getElementById('curl-block');
@@ -2271,9 +2319,23 @@ function renderCheckoutSuccessPage(runtimeConfig) {
2271
2319
  sendTelemetryOnce('checkout_paid_confirmed');
2272
2320
  statusEl.textContent = 'ThumbGate Pro activated.';
2273
2321
  const resolvedTraceId = body.traceId || traceId || '';
2322
+ const emailStatus = body.trialEmail || {};
2323
+ const customerEmail = body.customerEmail || (emailStatus && emailStatus.customerEmail) || '';
2274
2324
  summaryEl.textContent = resolvedTraceId
2275
2325
  ? 'Your Pro key is ready. Save it once, launch your local dashboard, and keep the optional hosted snippet for team workflows. Trace: ' + resolvedTraceId + '.'
2276
2326
  : 'Your Pro key is ready. Save it once, launch your local dashboard, and keep the optional hosted snippet for team workflows.';
2327
+ if (emailStatus.status === 'sent' || emailStatus.status === 'already_sent') {
2328
+ emailStatusEl.className = 'email-status';
2329
+ emailStatusEl.textContent = customerEmail
2330
+ ? 'Activation email sent to ' + customerEmail + '.'
2331
+ : 'Activation email sent.';
2332
+ } else if (emailStatus.status === 'skipped' || emailStatus.status === 'failed') {
2333
+ emailStatusEl.className = 'email-status warning';
2334
+ emailStatusEl.textContent = 'Email delivery is not confirmed. Copy the key below now; this page is your activation source of truth.';
2335
+ } else {
2336
+ emailStatusEl.className = 'email-status warning';
2337
+ emailStatusEl.textContent = 'Email delivery status is unknown. Copy the key below now.';
2338
+ }
2277
2339
  keyBlock.textContent = body.apiKey || 'Provisioned, but no key was returned.';
2278
2340
  activateBlock.textContent = body.apiKey
2279
2341
  ? 'npx thumbgate pro --activate --key=' + body.apiKey
@@ -3479,6 +3541,16 @@ async function addContext(){
3479
3541
  return;
3480
3542
  }
3481
3543
 
3544
+ if (isGetLikeRequest && pathname === '/codex-plugin') {
3545
+ try {
3546
+ const html = fs.readFileSync(CODEX_PLUGIN_PAGE_PATH, 'utf-8');
3547
+ sendHtml(res, 200, html, {}, { headOnly: isHeadRequest });
3548
+ } catch {
3549
+ sendJson(res, 404, { error: 'Codex plugin page not found' });
3550
+ }
3551
+ return;
3552
+ }
3553
+
3482
3554
  if (isGetLikeRequest && pathname === '/compare') {
3483
3555
  try {
3484
3556
  const html = fs.readFileSync(COMPARE_PAGE_PATH, 'utf-8');
@@ -3575,6 +3647,7 @@ async function addContext(){
3575
3647
  if (isGetLikeRequest && (
3576
3648
  pathname === '/favicon.ico'
3577
3649
  || pathname === '/thumbgate-logo.png'
3650
+ || pathname === '/thumbgate-icon.png'
3578
3651
  || pathname === '/og.png'
3579
3652
  || pathname === '/apple-touch-icon.png'
3580
3653
  )) {
@@ -3589,7 +3662,7 @@ async function addContext(){
3589
3662
  version: pkg.version,
3590
3663
  status: 'ok',
3591
3664
  docs: 'https://github.com/IgorGanapolsky/ThumbGate',
3592
- endpoints: ['/health', '/dashboard', '/guide', '/compare', '/learn', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/feedback/summary', '/v1/lessons/search', '/v1/search', '/v1/documents', '/v1/documents/import', '/v1/documents/{documentId}', '/v1/dashboard', '/v1/dashboard/render-spec', '/v1/decisions/evaluate', '/v1/decisions/outcome', '/v1/decisions/metrics', '/v1/settings/status', '/v1/dpo/export', '/v1/jobs', '/v1/jobs/harness', '/v1/analytics/databricks/export'],
3665
+ endpoints: ['/health', '/dashboard', '/guide', '/codex-plugin', '/compare', '/learn', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/feedback/summary', '/v1/lessons/search', '/v1/search', '/v1/documents', '/v1/documents/import', '/v1/documents/{documentId}', '/v1/dashboard', '/v1/dashboard/render-spec', '/v1/decisions/evaluate', '/v1/decisions/outcome', '/v1/decisions/metrics', '/v1/settings/status', '/v1/dpo/export', '/v1/jobs', '/v1/jobs/harness', '/v1/analytics/databricks/export'],
3593
3666
  }, {}, {
3594
3667
  headOnly: isHeadRequest,
3595
3668
  });
@@ -3706,6 +3779,7 @@ async function addContext(){
3706
3779
  installId: bootstrapBody.installId,
3707
3780
  traceId,
3708
3781
  metadata: analyticsMetadata,
3782
+ appOrigin: hostedConfig.appOrigin,
3709
3783
  });
3710
3784
 
3711
3785
  if (result.url) {
@@ -4330,6 +4404,7 @@ async function addContext(){
4330
4404
  installId: body.installId,
4331
4405
  traceId,
4332
4406
  metadata: analyticsMetadata,
4407
+ appOrigin: hostedConfig.appOrigin,
4333
4408
  });
4334
4409
  sendJson(res, 200, {
4335
4410
  ...result,
@@ -5518,6 +5593,36 @@ async function addContext(){
5518
5593
  return;
5519
5594
  }
5520
5595
 
5596
+ // GET /v1/dashboard/review-state -- incremental review baseline and deltas
5597
+ if (req.method === 'GET' && pathname === '/v1/dashboard/review-state') {
5598
+ const reviewState = readDashboardReviewState(requestFeedbackDir);
5599
+ const data = generateDashboard(requestFeedbackDir, {
5600
+ reviewBaseline: reviewState,
5601
+ authContext: { tier: 'pro' },
5602
+ });
5603
+ sendJson(res, 200, {
5604
+ reviewState,
5605
+ reviewDelta: data.reviewDelta,
5606
+ });
5607
+ return;
5608
+ }
5609
+
5610
+ // POST /v1/dashboard/review-state -- mark current dashboard state as reviewed
5611
+ if (req.method === 'POST' && pathname === '/v1/dashboard/review-state') {
5612
+ const snapshot = buildReviewSnapshot(requestFeedbackDir);
5613
+ writeDashboardReviewState(requestFeedbackDir, snapshot);
5614
+ const data = generateDashboard(requestFeedbackDir, {
5615
+ reviewBaseline: snapshot,
5616
+ authContext: { tier: 'pro' },
5617
+ });
5618
+ sendJson(res, 200, {
5619
+ ok: true,
5620
+ reviewState: snapshot,
5621
+ reviewDelta: data.reviewDelta,
5622
+ });
5623
+ return;
5624
+ }
5625
+
5521
5626
  // GET /v1/dashboard/render-spec -- Constrained hosted dashboard JSON spec
5522
5627
  if (req.method === 'GET' && pathname === '/v1/dashboard/render-spec') {
5523
5628
  let summaryOptions;