thumbgate 1.21.1 → 1.22.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/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +109 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +95 -81
- package/config/mcp-allowlists.json +12 -6
- package/package.json +15 -5
- package/public/agent-manager.html +1 -1
- package/public/codex-enterprise.html +123 -0
- package/public/dashboard.html +15 -2
- package/public/index.html +12 -5
- package/public/lessons.html +12 -0
- package/public/numbers.html +2 -2
- package/scripts/audit.js +65 -0
- package/scripts/auto-wire-hooks.js +14 -0
- package/scripts/build-metadata.js +32 -13
- package/scripts/cli-schema.js +8 -0
- package/scripts/gate-stats.js +89 -0
- package/scripts/gates-engine.js +14 -0
- package/scripts/hook-runtime.js +20 -14
- package/scripts/statusline.sh +41 -30
- package/scripts/tool-registry.js +18 -0
- package/src/api/server.js +59 -0
package/scripts/statusline.sh
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
8
8
|
case "$SCRIPT_DIR" in *[!a-zA-Z0-9/_.-]*) echo "ThumbGate: invalid script path"; exit 1;; esac
|
|
9
9
|
LOCAL_API_ORIGIN="${THUMBGATE_LOCAL_API_ORIGIN:-http://localhost:3456}"
|
|
10
|
+
STATUSLINE_VERBOSE="${THUMBGATE_STATUSLINE_VERBOSE:-0}"
|
|
10
11
|
|
|
11
12
|
# ── Parse Claude Code session JSON from stdin ─────────────────────
|
|
12
13
|
eval "$(cat | jq -r '
|
|
@@ -92,17 +93,19 @@ fi
|
|
|
92
93
|
LINK_STATE="offline"
|
|
93
94
|
UP_URL=""; DOWN_URL=""; DASHBOARD_URL=""; LESSONS_URL=""
|
|
94
95
|
DASHBOARD_LABEL="Dashboard"; LESSONS_LABEL="Lessons"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
97
|
+
_LINKS_JSON=$(node "${SCRIPT_DIR}/statusline-links.js" 2>/dev/null)
|
|
98
|
+
if [ -n "$_LINKS_JSON" ]; then
|
|
99
|
+
eval "$(echo "$_LINKS_JSON" | jq -r '
|
|
100
|
+
@sh "LINK_STATE=\(.state // "offline")",
|
|
101
|
+
@sh "UP_URL=\(.upUrl // "")",
|
|
102
|
+
@sh "DOWN_URL=\(.downUrl // "")",
|
|
103
|
+
@sh "DASHBOARD_URL=\(.dashboardUrl // "")",
|
|
104
|
+
@sh "LESSONS_URL=\(.lessonsUrl // "")",
|
|
105
|
+
@sh "DASHBOARD_LABEL=\(.dashboardLabel // "Dashboard")",
|
|
106
|
+
@sh "LESSONS_LABEL=\(.lessonsLabel // "Lessons")"
|
|
107
|
+
' 2>/dev/null)"
|
|
108
|
+
fi
|
|
106
109
|
fi
|
|
107
110
|
|
|
108
111
|
# ── ThumbGate package metadata ────────────────────────────────────────
|
|
@@ -117,13 +120,15 @@ fi
|
|
|
117
120
|
|
|
118
121
|
# ── Repo context (branch / work item / PR) ───────────────────────────
|
|
119
122
|
BRANCH_NAME=""; WORK_ITEM_LABEL=""; PR_LABEL=""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
124
|
+
_CONTEXT_JSON=$(node "${SCRIPT_DIR}/statusline-context.js" 2>/dev/null)
|
|
125
|
+
if [[ -n "$_CONTEXT_JSON" ]]; then
|
|
126
|
+
eval "$(echo "$_CONTEXT_JSON" | jq -r '
|
|
127
|
+
@sh "BRANCH_NAME=\(.branchName // "")",
|
|
128
|
+
@sh "WORK_ITEM_LABEL=\(.workItemLabel // "")",
|
|
129
|
+
@sh "PR_LABEL=\(.prLabel // "")"
|
|
130
|
+
' 2>/dev/null)"
|
|
131
|
+
fi
|
|
127
132
|
fi
|
|
128
133
|
|
|
129
134
|
# ── Control Tower stats ──────────────────────────────────────────
|
|
@@ -139,14 +144,16 @@ fi
|
|
|
139
144
|
|
|
140
145
|
# ── Latest lesson (data available for extensions; not rendered in statusbar) ──
|
|
141
146
|
LESSON_TEXT=""; LESSON_ID=""; LESSON_LABEL=""; LESSON_LINK=""
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
148
|
+
_LESSON_JSON=$(node "${SCRIPT_DIR}/statusline-lesson.js" 2>/dev/null)
|
|
149
|
+
if [[ -n "$_LESSON_JSON" ]]; then
|
|
150
|
+
eval "$(echo "$_LESSON_JSON" | jq -r '
|
|
151
|
+
@sh "LESSON_TEXT=\(.text // "")",
|
|
152
|
+
@sh "LESSON_ID=\(.lessonId // "")",
|
|
153
|
+
@sh "LESSON_LABEL=\(.label // "")",
|
|
154
|
+
@sh "LESSON_LINK=\(.link // "")"
|
|
155
|
+
' 2>/dev/null)"
|
|
156
|
+
fi
|
|
150
157
|
fi
|
|
151
158
|
|
|
152
159
|
# ── Colors ────────────────────────────────────────────────────────
|
|
@@ -199,8 +206,10 @@ LINE="${LINE:+${LINE} · }ThumbGate v${TG_VERSION} · ${TG_TIER}"
|
|
|
199
206
|
if [[ "$UP" = "0" && "$DOWN" = "0" ]]; then
|
|
200
207
|
LINE="${D}${LINE}${RST} · no feedback yet"
|
|
201
208
|
[[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
210
|
+
LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
|
|
211
|
+
[[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
|
|
212
|
+
fi
|
|
204
213
|
printf '%b\n' "$LINE"
|
|
205
214
|
else
|
|
206
215
|
LINE="${LINE} · ${G}${BD}${UP}${RST}${UP_LINK} ${R}${BD}${DOWN}${RST}${DOWN_LINK} ${ARROW}"
|
|
@@ -210,8 +219,10 @@ else
|
|
|
210
219
|
[[ "${AT_RISK:-0}" -gt 0 ]] && LINE="${LINE} ${R}${AT_RISK}⚠${RST}"
|
|
211
220
|
[[ "${ANOMALIES:-0}" -gt 0 ]] && LINE="${LINE} ${R}${ANOMALIES}☠${RST}"
|
|
212
221
|
[[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
223
|
+
LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
|
|
224
|
+
[[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
|
|
225
|
+
fi
|
|
215
226
|
|
|
216
227
|
printf '%b\n' "$LINE"
|
|
217
228
|
fi
|
package/scripts/tool-registry.js
CHANGED
|
@@ -397,6 +397,24 @@ const TOOLS = [
|
|
|
397
397
|
},
|
|
398
398
|
},
|
|
399
399
|
}),
|
|
400
|
+
readOnlyTool({
|
|
401
|
+
name: 'suggest_fix',
|
|
402
|
+
description: 'Suggest corrective actions for a described failure by searching the lesson DB and prevention rules. Returns up to 3 ranked suggestions with their source. Call this when something goes wrong and you need guidance on what to do next.',
|
|
403
|
+
inputSchema: {
|
|
404
|
+
type: 'object',
|
|
405
|
+
required: ['context'],
|
|
406
|
+
properties: {
|
|
407
|
+
context: {
|
|
408
|
+
type: 'string',
|
|
409
|
+
description: 'Description of what went wrong or what the agent is trying to fix.',
|
|
410
|
+
},
|
|
411
|
+
limit: {
|
|
412
|
+
type: 'number',
|
|
413
|
+
description: 'Maximum number of suggestions to return (default 3, max 5).',
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
}),
|
|
400
418
|
readOnlyTool({
|
|
401
419
|
name: 'infer_lesson_from_history',
|
|
402
420
|
description: 'Perform autonomous inference on chat history to identify why a failure occurred and what rule should be recorded.',
|
package/src/api/server.js
CHANGED
|
@@ -2504,6 +2504,7 @@ function renderSitemapXml(runtimeConfig) {
|
|
|
2504
2504
|
{ path: '/agent-manager', changefreq: 'weekly', priority: '0.9' },
|
|
2505
2505
|
{ path: '/llm-context.md', changefreq: 'weekly', priority: '0.8' },
|
|
2506
2506
|
{ path: '/codex-plugin', changefreq: 'weekly', priority: '0.75' },
|
|
2507
|
+
{ path: '/codex-enterprise', changefreq: 'weekly', priority: '0.85' },
|
|
2507
2508
|
...THUMBGATE_SEO_SITEMAP_ENTRIES,
|
|
2508
2509
|
];
|
|
2509
2510
|
return [
|
|
@@ -4520,6 +4521,30 @@ async function addContext(){
|
|
|
4520
4521
|
return;
|
|
4521
4522
|
}
|
|
4522
4523
|
|
|
4524
|
+
if (isGetLikeRequest && (pathname === '/codex-enterprise' || pathname === '/codex-enterprise.html')) {
|
|
4525
|
+
// Landing page riding the 2026-05-20 OpenAI×Dell Codex Enterprise
|
|
4526
|
+
// partnership announcement. Dell-distributed Codex expands the TAM
|
|
4527
|
+
// for ThumbGate's governance layer — capture every agent decision,
|
|
4528
|
+
// promote repeat failures to PreToolUse gates, ship the audit trail
|
|
4529
|
+
// procurement requires. Routed through servePublicMarketingPage so
|
|
4530
|
+
// arrivals via the partnership news cycle capture UTM attribution
|
|
4531
|
+
// and landing_page_view telemetry with pageType: 'codex_enterprise'.
|
|
4532
|
+
try {
|
|
4533
|
+
servePublicMarketingPage({
|
|
4534
|
+
req,
|
|
4535
|
+
res,
|
|
4536
|
+
parsed,
|
|
4537
|
+
hostedConfig,
|
|
4538
|
+
isHeadRequest,
|
|
4539
|
+
renderHtml: () => fs.readFileSync(path.join(PUBLIC_DIR, 'codex-enterprise.html'), 'utf-8'),
|
|
4540
|
+
extraTelemetry: { pageType: 'codex_enterprise' },
|
|
4541
|
+
});
|
|
4542
|
+
} catch {
|
|
4543
|
+
sendJson(res, 404, { error: 'Codex Enterprise page not found' });
|
|
4544
|
+
}
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4523
4548
|
if (isGetLikeRequest && pathname === '/learn/learn.css') {
|
|
4524
4549
|
try {
|
|
4525
4550
|
const cssPath = path.join(LEARN_DIR, 'learn.css');
|
|
@@ -7729,6 +7754,7 @@ function startServer({ port, host } = {}) {
|
|
|
7729
7754
|
const listenPort = Number(port ?? process.env.PORT ?? 8787);
|
|
7730
7755
|
const listenHost = String(host ?? process.env.HOST ?? '0.0.0.0').trim() || '0.0.0.0';
|
|
7731
7756
|
const server = createApiServer();
|
|
7757
|
+
registerGracefulShutdown(server);
|
|
7732
7758
|
return new Promise((resolve) => {
|
|
7733
7759
|
server.listen(listenPort, listenHost, () => {
|
|
7734
7760
|
const address = server.address();
|
|
@@ -7744,6 +7770,39 @@ function startServer({ port, host } = {}) {
|
|
|
7744
7770
|
});
|
|
7745
7771
|
}
|
|
7746
7772
|
|
|
7773
|
+
// Railway / Cloud Run / Kubernetes deploy rotations send SIGTERM to swap
|
|
7774
|
+
// containers. Without a handler, Node exits immediately — in-flight requests
|
|
7775
|
+
// are killed and the orchestrator may mark the container as "crashed" (instead
|
|
7776
|
+
// of "gracefully stopped"), wasting its restart-policy budget on a healthy
|
|
7777
|
+
// shutdown. Drain HTTP, give a deadline, then force-exit if anything hangs.
|
|
7778
|
+
function registerGracefulShutdown(server, { gracePeriodMs = 25_000 } = {}) {
|
|
7779
|
+
if (server[GRACEFUL_SHUTDOWN_KEY]) return;
|
|
7780
|
+
server[GRACEFUL_SHUTDOWN_KEY] = true;
|
|
7781
|
+
let shuttingDown = false;
|
|
7782
|
+
const stop = (signal) => {
|
|
7783
|
+
if (shuttingDown) return;
|
|
7784
|
+
shuttingDown = true;
|
|
7785
|
+
console.log(`[shutdown] ${signal} received — draining connections (deadline ${gracePeriodMs}ms)`);
|
|
7786
|
+
const forceTimer = setTimeout(() => {
|
|
7787
|
+
console.error('[shutdown] grace period elapsed — forcing exit');
|
|
7788
|
+
process.exit(1);
|
|
7789
|
+
}, gracePeriodMs);
|
|
7790
|
+
if (typeof forceTimer.unref === 'function') forceTimer.unref();
|
|
7791
|
+
server.close((err) => {
|
|
7792
|
+
if (err) {
|
|
7793
|
+
console.error('[shutdown] server.close error:', err.message);
|
|
7794
|
+
process.exit(1);
|
|
7795
|
+
}
|
|
7796
|
+
console.log('[shutdown] drained cleanly');
|
|
7797
|
+
process.exit(0);
|
|
7798
|
+
});
|
|
7799
|
+
};
|
|
7800
|
+
process.on('SIGTERM', () => stop('SIGTERM'));
|
|
7801
|
+
process.on('SIGINT', () => stop('SIGINT'));
|
|
7802
|
+
}
|
|
7803
|
+
|
|
7804
|
+
const GRACEFUL_SHUTDOWN_KEY = Symbol.for('thumbgate.gracefulShutdownRegistered');
|
|
7805
|
+
|
|
7747
7806
|
module.exports = {
|
|
7748
7807
|
createApiServer,
|
|
7749
7808
|
startServer,
|