thumbgate 1.27.20 → 1.28.1
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 +2 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +124 -129
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +18 -33
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +95 -78
- package/bin/postinstall.js +1 -1
- package/config/entitlement-public-keys.json +6 -0
- package/config/gates/default.json +3 -3
- package/config/github-about.json +2 -5
- package/config/merge-quality-checks.json +3 -0
- package/config/post-deploy-marketing-pages.json +1 -1
- package/hooks/hooks.json +38 -0
- package/package.json +28 -10
- package/public/about.html +1 -4
- package/public/agent-manager.html +6 -6
- package/public/agents-cost-savings.html +3 -3
- package/public/ai-malpractice-prevention.html +1 -1
- package/public/blog.html +12 -9
- package/public/chatgpt-app.html +3 -3
- package/public/codex-enterprise.html +3 -3
- package/public/codex-plugin.html +5 -5
- package/public/compare.html +10 -10
- package/public/dashboard.html +1 -1
- package/public/diagnostic.html +23 -1
- package/public/federal.html +2 -2
- package/public/guide.html +14 -14
- package/public/index.html +202 -220
- package/public/install.html +14 -14
- package/public/learn.html +4 -17
- package/public/numbers.html +2 -2
- package/public/partner-intake.html +198 -0
- package/public/pricing.html +61 -31
- package/public/pro.html +2 -2
- package/scripts/agent-reward-model.js +13 -0
- package/scripts/bayes-optimal-gate.js +6 -1
- package/scripts/billing.js +815 -137
- package/scripts/checkout-attribution-reference.js +85 -0
- package/scripts/claude-feedback-sync.js +23 -6
- package/scripts/cli-feedback.js +33 -8
- package/scripts/commercial-offer.js +3 -3
- package/scripts/entitlement.js +250 -0
- package/scripts/export-databricks-bundle.js +5 -0
- package/scripts/export-dpo-pairs.js +6 -0
- package/scripts/export-hf-dataset.js +5 -0
- package/scripts/feedback-loop.js +290 -1
- package/scripts/feedback-quality.js +25 -26
- package/scripts/feedback-sanitizer.js +151 -3
- package/scripts/gates-engine.js +134 -43
- package/scripts/hosted-config.js +9 -2
- package/scripts/imperative-detector.js +85 -0
- package/scripts/intervention-policy.js +13 -0
- package/scripts/mailer/resend-mailer.js +11 -5
- package/scripts/pr-manager.js +9 -22
- package/scripts/pro-local-dashboard.js +198 -0
- package/scripts/risk-scorer.js +6 -0
- package/scripts/secret-scanner.js +363 -68
- package/scripts/self-protection.js +21 -36
- package/scripts/seo-gsd.js +2 -2
- package/scripts/thompson-sampling.js +11 -2
- package/src/api/server.js +202 -22
- package/public/assets/brand/github-social-preview.png +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const crypto = require('crypto');
|
|
4
5
|
const os = require('os');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
|
|
7
8
|
const DEFAULT_PRO_API = 'https://thumbgate-production.up.railway.app';
|
|
9
|
+
const DEFAULT_PRO_ACTIVATION_ALERT_EMAIL = 'igor.ganapolsky@gmail.com';
|
|
8
10
|
const CREATOR_BYPASS_VALUE = process.env.THUMBGATE_DEV_SECRET || '';
|
|
9
11
|
const CREATOR_BYPASS_ENV = 'THUMBGATE_DEV_BYPASS';
|
|
10
12
|
const CREATOR_SYNTHETIC_KEY = process.env.THUMBGATE_DEV_KEY || '';
|
|
@@ -55,6 +57,195 @@ function getLicensePath(homeDir = process.env.HOME || process.env.USERPROFILE ||
|
|
|
55
57
|
return path.join(getLicenseDir(homeDir), 'license.json');
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
function isTruthyEnv(value) {
|
|
61
|
+
return /^(1|true|yes|on)$/i.test(String(value || '').trim());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeText(value) {
|
|
65
|
+
return String(value || '').trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function escapeHtml(value) {
|
|
69
|
+
return String(value || '')
|
|
70
|
+
.replace(/&/g, '&')
|
|
71
|
+
.replace(/</g, '<')
|
|
72
|
+
.replace(/>/g, '>')
|
|
73
|
+
.replace(/"/g, '"')
|
|
74
|
+
.replace(/'/g, ''');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function fingerprintProKey(key) {
|
|
78
|
+
const normalized = normalizeText(key);
|
|
79
|
+
if (!normalized) return '';
|
|
80
|
+
return `sha256:${crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 12)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveProActivationAlertRecipient(env = process.env) {
|
|
84
|
+
return normalizeText(
|
|
85
|
+
env.THUMBGATE_PRO_ACTIVATION_ALERT_EMAIL ||
|
|
86
|
+
env.THUMBGATE_OPERATOR_ALERT_EMAIL ||
|
|
87
|
+
env.THUMBGATE_SUPPORT_EMAIL ||
|
|
88
|
+
DEFAULT_PRO_ACTIVATION_ALERT_EMAIL
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function canSendProActivationAlert({ env = process.env, sendEmailImpl } = {}) {
|
|
93
|
+
if (isTruthyEnv(env.THUMBGATE_DISABLE_PRO_ACTIVATION_ALERTS)) return false;
|
|
94
|
+
if (!resolveProActivationAlertRecipient(env)) return false;
|
|
95
|
+
if (sendEmailImpl) return true;
|
|
96
|
+
return Boolean(normalizeText(env.RESEND_API_KEY || env.THUMBGATE_RESEND_API_KEY));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function renderProActivationAlertBodies({
|
|
100
|
+
keyFingerprint,
|
|
101
|
+
source,
|
|
102
|
+
version,
|
|
103
|
+
activatedAt,
|
|
104
|
+
hostname,
|
|
105
|
+
platform,
|
|
106
|
+
arch,
|
|
107
|
+
nodeVersion,
|
|
108
|
+
customerId,
|
|
109
|
+
installId,
|
|
110
|
+
usageCount,
|
|
111
|
+
} = {}) {
|
|
112
|
+
const occurredAt = activatedAt || new Date().toISOString();
|
|
113
|
+
const runtimePlatform = platform || process.platform;
|
|
114
|
+
const runtimeArch = arch || process.arch;
|
|
115
|
+
const runtimeNode = nodeVersion || process.version;
|
|
116
|
+
const text = [
|
|
117
|
+
'ThumbGate Pro activation detected.',
|
|
118
|
+
'',
|
|
119
|
+
`Activated at: ${occurredAt}`,
|
|
120
|
+
`Key fingerprint: ${keyFingerprint || 'unknown'}`,
|
|
121
|
+
`Source: ${source || 'unknown'}`,
|
|
122
|
+
`Version: ${version || 'unknown'}`,
|
|
123
|
+
`Customer ID: ${customerId || 'unknown'}`,
|
|
124
|
+
`Install ID: ${installId || 'unknown'}`,
|
|
125
|
+
`Usage count: ${usageCount ?? 'unknown'}`,
|
|
126
|
+
`Host: ${hostname || 'unknown'}`,
|
|
127
|
+
`Runtime: ${runtimePlatform}/${runtimeArch} on ${runtimeNode}`,
|
|
128
|
+
'',
|
|
129
|
+
'Secret hygiene: this alert intentionally does not include the raw Pro key.',
|
|
130
|
+
].join('\n');
|
|
131
|
+
const html = `<!doctype html>
|
|
132
|
+
<html>
|
|
133
|
+
<body style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;color:#17212b;line-height:1.5;">
|
|
134
|
+
<h1 style="font-size:20px;margin:0 0 12px;">ThumbGate Pro activation detected</h1>
|
|
135
|
+
<table role="presentation" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
|
|
136
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Activated at</td><td style="padding:4px 0;">${escapeHtml(occurredAt)}</td></tr>
|
|
137
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Key fingerprint</td><td style="padding:4px 0;"><code>${escapeHtml(keyFingerprint || 'unknown')}</code></td></tr>
|
|
138
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Source</td><td style="padding:4px 0;">${escapeHtml(source || 'unknown')}</td></tr>
|
|
139
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Version</td><td style="padding:4px 0;">${escapeHtml(version || 'unknown')}</td></tr>
|
|
140
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Customer ID</td><td style="padding:4px 0;">${escapeHtml(customerId || 'unknown')}</td></tr>
|
|
141
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Install ID</td><td style="padding:4px 0;">${escapeHtml(installId || 'unknown')}</td></tr>
|
|
142
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Usage count</td><td style="padding:4px 0;">${escapeHtml(usageCount ?? 'unknown')}</td></tr>
|
|
143
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Host</td><td style="padding:4px 0;">${escapeHtml(hostname || 'unknown')}</td></tr>
|
|
144
|
+
<tr><td style="padding:4px 14px 4px 0;color:#64748b;">Runtime</td><td style="padding:4px 0;">${escapeHtml(runtimePlatform)}/${escapeHtml(runtimeArch)} on ${escapeHtml(runtimeNode)}</td></tr>
|
|
145
|
+
</table>
|
|
146
|
+
<p style="margin-top:16px;color:#64748b;">Secret hygiene: this alert intentionally does not include the raw Pro key.</p>
|
|
147
|
+
</body>
|
|
148
|
+
</html>`;
|
|
149
|
+
return { text, html };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function sendProActivationAlert({
|
|
153
|
+
key,
|
|
154
|
+
source = 'unknown',
|
|
155
|
+
version,
|
|
156
|
+
customerId,
|
|
157
|
+
installId,
|
|
158
|
+
usageCount,
|
|
159
|
+
env = process.env,
|
|
160
|
+
sendEmailImpl,
|
|
161
|
+
} = {}) {
|
|
162
|
+
const keyFingerprint = fingerprintProKey(key);
|
|
163
|
+
if (!keyFingerprint) return { sent: false, reason: 'missing_key' };
|
|
164
|
+
if (!canSendProActivationAlert({ env, sendEmailImpl })) {
|
|
165
|
+
return { sent: false, reason: 'activation_alert_disabled_or_unconfigured' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const to = resolveProActivationAlertRecipient(env);
|
|
169
|
+
const { html, text } = renderProActivationAlertBodies({
|
|
170
|
+
keyFingerprint,
|
|
171
|
+
source,
|
|
172
|
+
version,
|
|
173
|
+
customerId,
|
|
174
|
+
installId,
|
|
175
|
+
usageCount,
|
|
176
|
+
activatedAt: new Date().toISOString(),
|
|
177
|
+
hostname: os.hostname(),
|
|
178
|
+
platform: process.platform,
|
|
179
|
+
arch: process.arch,
|
|
180
|
+
nodeVersion: process.version,
|
|
181
|
+
});
|
|
182
|
+
const sender = sendEmailImpl || require('./mailer').sendEmail;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
return await sender({
|
|
186
|
+
to,
|
|
187
|
+
subject: 'ThumbGate Pro activated',
|
|
188
|
+
html,
|
|
189
|
+
text,
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return {
|
|
193
|
+
sent: false,
|
|
194
|
+
reason: 'exception',
|
|
195
|
+
error: error && error.message ? error.message : String(error),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function notifyHostedProActivation({
|
|
201
|
+
key,
|
|
202
|
+
source = 'cli_pro_activate',
|
|
203
|
+
version,
|
|
204
|
+
apiBaseUrl = process.env.THUMBGATE_API_BASE_URL || DEFAULT_PRO_API,
|
|
205
|
+
fetchImpl = globalThis.fetch,
|
|
206
|
+
} = {}) {
|
|
207
|
+
const normalizedKey = normalizeText(key);
|
|
208
|
+
if (!normalizedKey) return { notified: false, reason: 'missing_key' };
|
|
209
|
+
if (typeof fetchImpl !== 'function') return { notified: false, reason: 'no_fetch' };
|
|
210
|
+
|
|
211
|
+
const keyFingerprint = fingerprintProKey(normalizedKey);
|
|
212
|
+
const endpoint = new URL('/v1/billing/pro-activation', apiBaseUrl).toString();
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetchImpl(endpoint, {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: {
|
|
217
|
+
authorization: `Bearer ${normalizedKey}`,
|
|
218
|
+
'content-type': 'application/json',
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({
|
|
221
|
+
keyFingerprint,
|
|
222
|
+
source,
|
|
223
|
+
version: version || null,
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
const body = await response.json().catch(() => ({}));
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
return {
|
|
229
|
+
notified: false,
|
|
230
|
+
reason: body && body.detail ? body.detail : `http_${response.status}`,
|
|
231
|
+
status: response.status,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
notified: true,
|
|
236
|
+
status: response.status,
|
|
237
|
+
alert: body.alert || null,
|
|
238
|
+
keyFingerprint: body.keyFingerprint || keyFingerprint,
|
|
239
|
+
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
notified: false,
|
|
243
|
+
reason: 'exception',
|
|
244
|
+
error: error && error.message ? error.message : String(error),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
58
249
|
function readLicense({ homeDir } = {}) {
|
|
59
250
|
try {
|
|
60
251
|
return JSON.parse(fs.readFileSync(getLicensePath(homeDir), 'utf8'));
|
|
@@ -162,12 +353,19 @@ module.exports = {
|
|
|
162
353
|
CREATOR_BYPASS_VALUE,
|
|
163
354
|
CREATOR_SYNTHETIC_KEY,
|
|
164
355
|
DEFAULT_PRO_API,
|
|
356
|
+
DEFAULT_PRO_ACTIVATION_ALERT_EMAIL,
|
|
357
|
+
canSendProActivationAlert,
|
|
358
|
+
fingerprintProKey,
|
|
165
359
|
getLicenseDir,
|
|
166
360
|
getLicensePath,
|
|
167
361
|
hasDevOverride,
|
|
168
362
|
isCreatorDev,
|
|
169
363
|
readLicense,
|
|
364
|
+
renderProActivationAlertBodies,
|
|
365
|
+
notifyHostedProActivation,
|
|
366
|
+
resolveProActivationAlertRecipient,
|
|
170
367
|
saveLicense,
|
|
368
|
+
sendProActivationAlert,
|
|
171
369
|
resolveProKey,
|
|
172
370
|
validateProKey,
|
|
173
371
|
startLocalProDashboard,
|
package/scripts/risk-scorer.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { resolveFeedbackDir: resolveSharedFeedbackDir } = require('./feedback-paths');
|
|
7
|
+
const { requireLearnedModelsEntitlement } = require('./entitlement');
|
|
7
8
|
|
|
8
9
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
9
10
|
const DEFAULT_FEEDBACK_DIR = resolveSharedFeedbackDir();
|
|
@@ -268,6 +269,10 @@ function buildPatternSummary(rows) {
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
function trainRiskModel(rows, options = {}) {
|
|
272
|
+
requireLearnedModelsEntitlement({
|
|
273
|
+
...(options.entitlement || {}),
|
|
274
|
+
label: 'risk-scorer AdaBoost training',
|
|
275
|
+
});
|
|
271
276
|
const registry = buildFeatureRegistry(rows, options);
|
|
272
277
|
const examples = rows.map((row) => ({
|
|
273
278
|
row,
|
|
@@ -419,6 +424,7 @@ function trainAndPersistRiskModel(feedbackDir, options = {}) {
|
|
|
419
424
|
}
|
|
420
425
|
|
|
421
426
|
function getRiskSummary(feedbackDir) {
|
|
427
|
+
requireLearnedModelsEntitlement({ label: 'risk-scorer summary' });
|
|
422
428
|
const resolvedDir = resolveFeedbackDir(feedbackDir);
|
|
423
429
|
const rows = readJSONL(sequencePathFor(resolvedDir));
|
|
424
430
|
if (rows.length === 0) return null;
|