thumbgate 1.5.3 → 1.5.8
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/adapters/README.md +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/package.json +11 -3
- package/public/blog.html +474 -0
- package/public/dashboard.html +52 -0
- package/public/index.html +65 -21
- package/public/learn.html +274 -0
- package/public/pro.html +1087 -0
- package/scripts/auto-wire-hooks.js +61 -1
- package/scripts/dashboard.js +13 -0
- package/src/api/server.js +15 -17
|
@@ -71,6 +71,7 @@ function detectAgent(flagAgent) {
|
|
|
71
71
|
if (['codex'].includes(normalized)) return 'codex';
|
|
72
72
|
if (['gemini'].includes(normalized)) return 'gemini';
|
|
73
73
|
if (['forge', 'forgecode', 'forge-code'].includes(normalized)) return 'forge';
|
|
74
|
+
if (['cursor'].includes(normalized)) return 'cursor';
|
|
74
75
|
return null;
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -80,9 +81,65 @@ function detectAgent(flagAgent) {
|
|
|
80
81
|
if (fs.existsSync(path.join(home, '.codex'))) return 'codex';
|
|
81
82
|
if (fs.existsSync(path.join(home, '.gemini'))) return 'gemini';
|
|
82
83
|
if (fs.existsSync(path.join(process.cwd(), 'forge.yaml'))) return 'forge';
|
|
84
|
+
if (fs.existsSync(path.join(process.cwd(), '.cursor'))) return 'cursor';
|
|
83
85
|
return null;
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
// --- Cursor wiring ---
|
|
89
|
+
// Cursor uses .cursor/mcp.json in the project root. We write the ThumbGate MCP
|
|
90
|
+
// server config there so Cursor picks up the server on next restart. Cursor's
|
|
91
|
+
// native hook model is different from Claude Code's — we rely on the MCP
|
|
92
|
+
// server's PreToolUse-equivalent enforcement via the gate-check tool.
|
|
93
|
+
|
|
94
|
+
function cursorMcpConfigPath() {
|
|
95
|
+
return path.join(process.cwd(), '.cursor', 'mcp.json');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function wireCursorHooks(options = {}) {
|
|
99
|
+
const mcpPath = cursorMcpConfigPath();
|
|
100
|
+
const dir = path.dirname(mcpPath);
|
|
101
|
+
const thumbgateServer = {
|
|
102
|
+
command: 'npx',
|
|
103
|
+
args: ['--yes', '--package', 'thumbgate@latest', 'thumbgate', 'serve'],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
let existing = { mcpServers: {} };
|
|
107
|
+
if (fs.existsSync(mcpPath)) {
|
|
108
|
+
try {
|
|
109
|
+
existing = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
110
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
111
|
+
} catch {
|
|
112
|
+
return { changed: false, error: `Could not parse ${mcpPath}` };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const before = JSON.stringify(existing.mcpServers.thumbgate || null);
|
|
117
|
+
existing.mcpServers.thumbgate = thumbgateServer;
|
|
118
|
+
const after = JSON.stringify(existing.mcpServers.thumbgate);
|
|
119
|
+
|
|
120
|
+
const addedEntry = {
|
|
121
|
+
lifecycle: 'mcpServers.thumbgate',
|
|
122
|
+
command: `${thumbgateServer.command} ${thumbgateServer.args.join(' ')}`,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (options.dryRun) {
|
|
126
|
+
return {
|
|
127
|
+
changed: before !== after,
|
|
128
|
+
dryRun: true,
|
|
129
|
+
settingsPath: mcpPath,
|
|
130
|
+
added: before === after ? [] : [addedEntry],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (before === after) {
|
|
135
|
+
return { changed: false, settingsPath: mcpPath, added: [] };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
139
|
+
fs.writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + '\n');
|
|
140
|
+
return { changed: true, settingsPath: mcpPath, added: [addedEntry] };
|
|
141
|
+
}
|
|
142
|
+
|
|
86
143
|
// --- Claude Code wiring ---
|
|
87
144
|
|
|
88
145
|
function claudeSettingsPath() {
|
|
@@ -543,7 +600,7 @@ function wireHooks(options) {
|
|
|
543
600
|
const agent = detectAgent(options.agent);
|
|
544
601
|
if (!agent) {
|
|
545
602
|
return {
|
|
546
|
-
error: 'Could not detect AI agent. Use --agent=claude-code|codex|gemini|forge',
|
|
603
|
+
error: 'Could not detect AI agent. Use --agent=claude-code|codex|gemini|forge|cursor',
|
|
547
604
|
agent: null,
|
|
548
605
|
changed: false,
|
|
549
606
|
};
|
|
@@ -563,6 +620,9 @@ function wireHooks(options) {
|
|
|
563
620
|
case 'forge':
|
|
564
621
|
result = wireForgeHooks(options);
|
|
565
622
|
break;
|
|
623
|
+
case 'cursor':
|
|
624
|
+
result = wireCursorHooks(options);
|
|
625
|
+
break;
|
|
566
626
|
default:
|
|
567
627
|
return { error: `Unsupported agent: ${agent}`, agent, changed: false };
|
|
568
628
|
}
|
package/scripts/dashboard.js
CHANGED
|
@@ -971,6 +971,18 @@ function generateDashboard(feedbackDir, options = {}) {
|
|
|
971
971
|
const feedbackTimeSeries = computeFeedbackTimeSeries(entries, 30);
|
|
972
972
|
const lessonPipeline = computeLessonPipeline(feedbackDir, entries, gateStats);
|
|
973
973
|
|
|
974
|
+
// Estimated token savings — computed from gate blocked counts using the
|
|
975
|
+
// conservative methodology in scripts/token-savings.js. This is the ONLY
|
|
976
|
+
// place "$ saved" appears that's backed by real gate-block data; the landing
|
|
977
|
+
// page hero uses a hardcoded sample number disclosed as "Sample".
|
|
978
|
+
let tokenSavings = null;
|
|
979
|
+
try {
|
|
980
|
+
const { computeTokenSavings } = require('./token-savings');
|
|
981
|
+
tokenSavings = computeTokenSavings({
|
|
982
|
+
blockedCalls: Number(gateStats.blocked) || 0,
|
|
983
|
+
});
|
|
984
|
+
} catch { /* module missing — skip */ }
|
|
985
|
+
|
|
974
986
|
// Merge lesson counts into feedbackTimeSeries days
|
|
975
987
|
for (const day of feedbackTimeSeries.days) {
|
|
976
988
|
day.lessons = lessonPipeline.lessonsByDay.get(day.dayKey) || 0;
|
|
@@ -1018,6 +1030,7 @@ function generateDashboard(feedbackDir, options = {}) {
|
|
|
1018
1030
|
liveMetrics,
|
|
1019
1031
|
predictive,
|
|
1020
1032
|
feedbackTimeSeries,
|
|
1033
|
+
tokenSavings,
|
|
1021
1034
|
lessonPipeline: {
|
|
1022
1035
|
stages: lessonPipeline.stages,
|
|
1023
1036
|
rates: lessonPipeline.rates,
|
package/src/api/server.js
CHANGED
|
@@ -1896,7 +1896,9 @@ function renderRobotsTxt(runtimeConfig) {
|
|
|
1896
1896
|
function renderSitemapXml(runtimeConfig) {
|
|
1897
1897
|
const entries = [
|
|
1898
1898
|
{ path: '/', changefreq: 'weekly', priority: '1.0' },
|
|
1899
|
-
|
|
1899
|
+
// /pro consolidated into /#pro-pitch (2026-04-16) — removed from sitemap
|
|
1900
|
+
// so search engines don't chase the 301 instead of indexing the canonical
|
|
1901
|
+
// homepage directly.
|
|
1900
1902
|
{ path: '/llm-context.md', changefreq: 'weekly', priority: '0.8' },
|
|
1901
1903
|
...THUMBGATE_SEO_SITEMAP_ENTRIES,
|
|
1902
1904
|
];
|
|
@@ -3453,21 +3455,17 @@ async function addContext(){
|
|
|
3453
3455
|
}
|
|
3454
3456
|
|
|
3455
3457
|
if (isGetLikeRequest && pathname === '/pro') {
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
});
|
|
3468
|
-
} catch (err) {
|
|
3469
|
-
sendText(res, 500, err.message || 'Pro page unavailable');
|
|
3470
|
-
}
|
|
3458
|
+
// Consolidated: /pro content now lives inline on `/` as the #pro-pitch
|
|
3459
|
+
// strip (hero-adjacent pricing card). 301 so external links (README,
|
|
3460
|
+
// plugin manifests, guides, compare pages) pass link equity onto the
|
|
3461
|
+
// single canonical landing page. Query string is preserved so UTM
|
|
3462
|
+
// tracking from inbound campaigns still reaches GA/PostHog on `/`.
|
|
3463
|
+
const redirectTarget = `/#pro-pitch${parsed.search || ''}`;
|
|
3464
|
+
res.writeHead(301, {
|
|
3465
|
+
Location: redirectTarget,
|
|
3466
|
+
'Cache-Control': 'public, max-age=3600',
|
|
3467
|
+
});
|
|
3468
|
+
res.end();
|
|
3471
3469
|
return;
|
|
3472
3470
|
}
|
|
3473
3471
|
|
|
@@ -3591,7 +3589,7 @@ async function addContext(){
|
|
|
3591
3589
|
version: pkg.version,
|
|
3592
3590
|
status: 'ok',
|
|
3593
3591
|
docs: 'https://github.com/IgorGanapolsky/ThumbGate',
|
|
3594
|
-
endpoints: ['/health', '/dashboard', '/guide', '/compare', '/learn', '/
|
|
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'],
|
|
3595
3593
|
}, {}, {
|
|
3596
3594
|
headOnly: isHeadRequest,
|
|
3597
3595
|
});
|