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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/CHANGELOG.md +198 -0
- package/README.md +7 -6
- package/adapters/README.md +1 -1
- package/adapters/chatgpt/openapi.yaml +25 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +4 -4
- package/adapters/mcp/server-stdio.js +41 -2
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +100 -10
- package/openapi/openapi.yaml +25 -0
- package/package.json +13 -3
- package/public/codex-plugin.html +277 -0
- package/public/dashboard.html +141 -13
- package/public/index.html +92 -34
- package/public/learn.html +13 -2
- package/public/lessons.html +5 -2
- package/public/pro.html +8 -1
- package/scripts/auto-wire-hooks.js +10 -5
- package/scripts/billing.js +503 -8
- package/scripts/contextfs.js +1 -1
- package/scripts/dashboard.js +236 -0
- package/scripts/feedback-loop.js +22 -0
- package/scripts/gates-engine.js +461 -7
- package/scripts/hook-runtime.js +42 -0
- package/scripts/llm-client.js +25 -10
- package/scripts/mailer/index.js +13 -0
- package/scripts/mailer/resend-mailer.js +350 -0
- package/scripts/mcp-config.js +13 -0
- package/scripts/published-cli.js +8 -0
- package/scripts/seo-gsd.js +118 -4
- package/scripts/statusline.sh +8 -0
- package/scripts/vector-store.js +21 -7
- package/src/api/server.js +112 -7
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"
|
|
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>
|
|
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 — CI jobs, GitHub Actions, teammates' laptops, scheduled cron, or agents running in Docker or Lambda — 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 — 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 — 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
|
|
2137
|
-
<li>Use the curl example to confirm the hosted API captures an event.</li>
|
|
2138
|
-
<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 — 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;
|