thumbgate 1.5.4 → 1.6.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 +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +61 -5
- package/openapi/openapi.yaml +25 -0
- package/package.json +16 -3
- package/public/codex-plugin.html +277 -0
- package/public/dashboard.html +193 -13
- package/public/index.html +150 -48
- package/public/learn.html +13 -2
- package/public/lessons.html +5 -2
- package/public/pro.html +8 -1
- package/scripts/auto-wire-hooks.js +71 -6
- package/scripts/billing.js +503 -8
- package/scripts/contextfs.js +1 -1
- package/scripts/dashboard.js +249 -0
- package/scripts/gates-engine.js +153 -2
- 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 +126 -23
|
@@ -17,6 +17,11 @@ const fs = require('fs');
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const {
|
|
19
19
|
cacheUpdateHookCommand,
|
|
20
|
+
codexCacheUpdateHookCommand,
|
|
21
|
+
codexPreToolHookCommand,
|
|
22
|
+
codexSessionStartHookCommand,
|
|
23
|
+
codexStatuslineCommand,
|
|
24
|
+
codexUserPromptHookCommand,
|
|
20
25
|
preToolHookCommand,
|
|
21
26
|
sessionStartHookCommand,
|
|
22
27
|
statuslineCommand,
|
|
@@ -48,17 +53,17 @@ const CLAUDE_HOOKS = {
|
|
|
48
53
|
const CODEX_HOOKS = {
|
|
49
54
|
PreToolUse: {
|
|
50
55
|
matcher: 'Bash',
|
|
51
|
-
hooks: [{ type: 'command', command:
|
|
56
|
+
hooks: [{ type: 'command', command: codexPreToolHookCommand() }],
|
|
52
57
|
},
|
|
53
58
|
UserPromptSubmit: {
|
|
54
|
-
hooks: [{ type: 'command', command:
|
|
59
|
+
hooks: [{ type: 'command', command: codexUserPromptHookCommand() }],
|
|
55
60
|
},
|
|
56
61
|
PostToolUse: {
|
|
57
62
|
matcher: 'mcp__thumbgate__feedback_stats|mcp__thumbgate__dashboard',
|
|
58
|
-
hooks: [{ type: 'command', command:
|
|
63
|
+
hooks: [{ type: 'command', command: codexCacheUpdateHookCommand() }],
|
|
59
64
|
},
|
|
60
65
|
SessionStart: {
|
|
61
|
-
hooks: [{ type: 'command', command:
|
|
66
|
+
hooks: [{ type: 'command', command: codexSessionStartHookCommand() }],
|
|
62
67
|
},
|
|
63
68
|
};
|
|
64
69
|
|
|
@@ -71,6 +76,7 @@ function detectAgent(flagAgent) {
|
|
|
71
76
|
if (['codex'].includes(normalized)) return 'codex';
|
|
72
77
|
if (['gemini'].includes(normalized)) return 'gemini';
|
|
73
78
|
if (['forge', 'forgecode', 'forge-code'].includes(normalized)) return 'forge';
|
|
79
|
+
if (['cursor'].includes(normalized)) return 'cursor';
|
|
74
80
|
return null;
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -80,9 +86,65 @@ function detectAgent(flagAgent) {
|
|
|
80
86
|
if (fs.existsSync(path.join(home, '.codex'))) return 'codex';
|
|
81
87
|
if (fs.existsSync(path.join(home, '.gemini'))) return 'gemini';
|
|
82
88
|
if (fs.existsSync(path.join(process.cwd(), 'forge.yaml'))) return 'forge';
|
|
89
|
+
if (fs.existsSync(path.join(process.cwd(), '.cursor'))) return 'cursor';
|
|
83
90
|
return null;
|
|
84
91
|
}
|
|
85
92
|
|
|
93
|
+
// --- Cursor wiring ---
|
|
94
|
+
// Cursor uses .cursor/mcp.json in the project root. We write the ThumbGate MCP
|
|
95
|
+
// server config there so Cursor picks up the server on next restart. Cursor's
|
|
96
|
+
// native hook model is different from Claude Code's — we rely on the MCP
|
|
97
|
+
// server's PreToolUse-equivalent enforcement via the gate-check tool.
|
|
98
|
+
|
|
99
|
+
function cursorMcpConfigPath() {
|
|
100
|
+
return path.join(process.cwd(), '.cursor', 'mcp.json');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function wireCursorHooks(options = {}) {
|
|
104
|
+
const mcpPath = cursorMcpConfigPath();
|
|
105
|
+
const dir = path.dirname(mcpPath);
|
|
106
|
+
const thumbgateServer = {
|
|
107
|
+
command: 'npx',
|
|
108
|
+
args: ['--yes', '--package', 'thumbgate@latest', 'thumbgate', 'serve'],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let existing = { mcpServers: {} };
|
|
112
|
+
if (fs.existsSync(mcpPath)) {
|
|
113
|
+
try {
|
|
114
|
+
existing = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
115
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
116
|
+
} catch {
|
|
117
|
+
return { changed: false, error: `Could not parse ${mcpPath}` };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const before = JSON.stringify(existing.mcpServers.thumbgate || null);
|
|
122
|
+
existing.mcpServers.thumbgate = thumbgateServer;
|
|
123
|
+
const after = JSON.stringify(existing.mcpServers.thumbgate);
|
|
124
|
+
|
|
125
|
+
const addedEntry = {
|
|
126
|
+
lifecycle: 'mcpServers.thumbgate',
|
|
127
|
+
command: `${thumbgateServer.command} ${thumbgateServer.args.join(' ')}`,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (options.dryRun) {
|
|
131
|
+
return {
|
|
132
|
+
changed: before !== after,
|
|
133
|
+
dryRun: true,
|
|
134
|
+
settingsPath: mcpPath,
|
|
135
|
+
added: before === after ? [] : [addedEntry],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (before === after) {
|
|
140
|
+
return { changed: false, settingsPath: mcpPath, added: [] };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
144
|
+
fs.writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + '\n');
|
|
145
|
+
return { changed: true, settingsPath: mcpPath, added: [addedEntry] };
|
|
146
|
+
}
|
|
147
|
+
|
|
86
148
|
// --- Claude Code wiring ---
|
|
87
149
|
|
|
88
150
|
function claudeSettingsPath() {
|
|
@@ -402,7 +464,7 @@ function syncCodexStatusLine(config, desiredStatusLine) {
|
|
|
402
464
|
function wireCodexHooks(options) {
|
|
403
465
|
const configPath = options.settingsPath || codexConfigPath();
|
|
404
466
|
const dryRun = options.dryRun || false;
|
|
405
|
-
const desiredStatusLine =
|
|
467
|
+
const desiredStatusLine = codexStatuslineCommand();
|
|
406
468
|
|
|
407
469
|
let config = loadJsonFile(configPath) || {};
|
|
408
470
|
config.hooks = config.hooks || {};
|
|
@@ -543,7 +605,7 @@ function wireHooks(options) {
|
|
|
543
605
|
const agent = detectAgent(options.agent);
|
|
544
606
|
if (!agent) {
|
|
545
607
|
return {
|
|
546
|
-
error: 'Could not detect AI agent. Use --agent=claude-code|codex|gemini|forge',
|
|
608
|
+
error: 'Could not detect AI agent. Use --agent=claude-code|codex|gemini|forge|cursor',
|
|
547
609
|
agent: null,
|
|
548
610
|
changed: false,
|
|
549
611
|
};
|
|
@@ -563,6 +625,9 @@ function wireHooks(options) {
|
|
|
563
625
|
case 'forge':
|
|
564
626
|
result = wireForgeHooks(options);
|
|
565
627
|
break;
|
|
628
|
+
case 'cursor':
|
|
629
|
+
result = wireCursorHooks(options);
|
|
630
|
+
break;
|
|
566
631
|
default:
|
|
567
632
|
return { error: `Unsupported agent: ${agent}`, agent, changed: false };
|
|
568
633
|
}
|