thumbgate 1.27.16 → 1.27.18
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 +85 -0
- package/.claude-plugin/plugin.json +3 -4
- package/.well-known/llms.txt +12 -33
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +30 -215
- package/adapters/chatgpt/openapi.yaml +1695 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gemini/function-declarations.json +0 -1
- package/adapters/mcp/server-stdio.js +11 -263
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +122 -1421
- package/bin/postinstall.js +27 -11
- package/config/gate-templates.json +0 -216
- package/config/gates/claim-verification.json +0 -12
- package/config/gates/default.json +2 -31
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +13 -23
- package/config/merge-quality-checks.json +1 -0
- package/config/model-candidates.json +6 -121
- package/openapi/openapi.yaml +0 -12
- package/package.json +98 -228
- package/public/blog.html +4 -4
- package/public/codex-plugin.html +20 -72
- package/public/compare.html +8 -31
- package/public/dashboard.html +166 -930
- package/public/federal.html +2 -2
- package/public/guide.html +9 -33
- package/public/index.html +116 -495
- package/public/learn.html +18 -191
- package/public/lessons.html +10 -168
- package/public/numbers.html +7 -7
- package/public/pro.html +11 -34
- package/scripts/agent-memory-lifecycle.js +0 -211
- package/scripts/agent-readiness.js +3 -167
- package/scripts/agent-reward-model.js +1 -53
- package/scripts/auto-promote-gates.js +10 -82
- package/scripts/auto-wire-hooks.js +0 -14
- package/scripts/billing.js +1 -93
- package/scripts/build-metadata.js +10 -50
- package/scripts/cli-feedback.js +6 -18
- package/scripts/cli-schema.js +0 -90
- package/scripts/cli-telemetry.js +1 -6
- package/scripts/commercial-offer.js +2 -82
- package/scripts/context-manager.js +6 -74
- package/scripts/dashboard.js +2 -68
- package/scripts/durability/step.js +3 -3
- package/scripts/export-databricks-bundle.js +1 -5
- package/scripts/export-dpo-pairs.js +2 -7
- package/scripts/feedback-loop.js +1 -123
- package/scripts/feedback-quality.js +0 -87
- package/scripts/filesystem-search.js +10 -35
- package/scripts/gate-stats.js +0 -89
- package/scripts/gates-engine.js +85 -1105
- package/scripts/gemini-embedding-policy.js +1 -2
- package/scripts/hook-runtime.js +14 -20
- package/scripts/hook-thumbgate-cache-updater.js +2 -18
- package/scripts/hybrid-feedback-context.js +7 -142
- package/scripts/lesson-inference.js +3 -8
- package/scripts/lesson-search.js +1 -17
- package/scripts/license.js +1 -0
- package/scripts/llm-client.js +9 -390
- package/scripts/local-model-profile.js +8 -15
- package/scripts/memory-scope-readiness.js +0 -159
- package/scripts/meta-agent-loop.js +0 -36
- package/scripts/operational-integrity.js +5 -39
- package/scripts/oss-pr-opportunity-scout.js +5 -35
- package/scripts/plausible-server-events.js +6 -9
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +1 -42
- package/scripts/rate-limiter.js +13 -64
- package/scripts/secret-scanner.js +5 -44
- package/scripts/security-scanner.js +10 -260
- package/scripts/self-distill-agent.js +1 -3
- package/scripts/seo-gsd.js +2 -916
- package/scripts/statusline-cache-path.js +2 -17
- package/scripts/statusline-local-stats.js +1 -9
- package/scripts/statusline-meta.js +2 -28
- package/scripts/statusline.sh +4 -20
- package/scripts/telemetry-analytics.js +0 -357
- package/scripts/thompson-sampling.js +10 -31
- package/scripts/thumbgate-bench.js +1 -16
- package/scripts/thumbgate-search.js +19 -85
- package/scripts/tool-registry.js +1 -169
- package/scripts/vector-store.js +0 -45
- package/scripts/workflow-sentinel.js +53 -286
- package/scripts/workspace-evolver.js +2 -62
- package/src/api/server.js +290 -2578
- package/.claude/commands/dashboard.md +0 -15
- package/.claude/commands/thumbgate-blocked.md +0 -27
- package/.claude/commands/thumbgate-dashboard.md +0 -15
- package/.claude/commands/thumbgate-doctor.md +0 -30
- package/.claude/commands/thumbgate-guard.md +0 -36
- package/.claude/commands/thumbgate-protect.md +0 -30
- package/.claude/commands/thumbgate-rules.md +0 -30
- package/.well-known/agentic-verify.txt +0 -1
- package/adapters/gcp/dfcx-webhook-gate.js +0 -295
- package/adapters/letta/README.md +0 -41
- package/adapters/letta/thumbgate-letta-adapter.js +0 -133
- package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
- package/bench/observability-eval-suite.json +0 -26
- package/bin/dashboard-cli.js +0 -7
- package/commands/dashboard.md +0 -15
- package/commands/thumbgate-dashboard.md +0 -15
- package/config/builtin-lessons.json +0 -23
- package/config/gate-classifier-routing.json +0 -98
- package/config/post-deploy-marketing-pages.json +0 -61
- package/public/about.html +0 -162
- package/public/agent-manager.html +0 -179
- package/public/agents-cost-savings.html +0 -153
- package/public/ai-malpractice-prevention.html +0 -818
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
- package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
- package/public/assets/brand/thumbgate-mark-team.svg +0 -26
- package/public/assets/brand/thumbgate-mark.svg +0 -21
- package/public/assets/brand/thumbgate-wordmark.svg +0 -20
- package/public/assets/claude-thumbgate-statusbar.svg +0 -8
- package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
- package/public/assets/legal-intake-control-flow.svg +0 -66
- package/public/brand/thumbgate-mark.svg +0 -19
- package/public/brand/thumbgate-og.svg +0 -16
- package/public/chatgpt-app.html +0 -330
- package/public/codex-enterprise.html +0 -123
- package/public/js/buyer-intent.js +0 -672
- package/public/pricing.html +0 -399
- package/scripts/action-receipts.js +0 -324
- package/scripts/activation-quickstart.js +0 -187
- package/scripts/agent-operations-planner.js +0 -621
- package/scripts/ai-component-inventory.js +0 -367
- package/scripts/async-eval-observability.js +0 -236
- package/scripts/audit.js +0 -65
- package/scripts/aws-blocks-guardrails.js +0 -228
- package/scripts/classifier-routing.js +0 -130
- package/scripts/dashboard-chat.js +0 -332
- package/scripts/feedback-aggregate.js +0 -281
- package/scripts/feedback-sanitizer.js +0 -105
- package/scripts/hob-pack.js +0 -591
- package/scripts/hook-stop-anti-claim.js +0 -301
- package/scripts/install-shim.js +0 -87
- package/scripts/mcp-oauth.js +0 -293
- package/scripts/noop-detect.js +0 -285
- package/scripts/omlx-smoke.js +0 -192
- package/scripts/parallel-workflow-orchestrator.js +0 -293
- package/scripts/plan-gate.js +0 -243
- package/scripts/plausible-domain-config.js +0 -99
- package/scripts/qa-scenario-planner.js +0 -136
- package/scripts/repeat-metric.js +0 -137
- package/scripts/secret-fixture-tokens.js +0 -61
- package/scripts/secret-redaction.js +0 -166
- package/scripts/self-harness-optimizer.js +0 -141
- package/scripts/self-healing-check.js +0 -193
- package/scripts/silent-failure-cluster.js +0 -531
- package/scripts/statusline-cache-read.js +0 -57
- package/scripts/sync-telemetry-from-prod.js +0 -374
- package/scripts/tool-contract-validator.js +0 -76
- package/scripts/trajectory-scorer.js +0 -63
- package/scripts/verify-marketing-pages-deployed.js +0 -195
- package/scripts/visitor-journey.js +0 -172
package/bin/cli.js
CHANGED
|
@@ -23,17 +23,14 @@
|
|
|
23
23
|
* npx thumbgate background-governance # background-agent run report + risk check
|
|
24
24
|
* npx thumbgate cfo # local operational billing summary
|
|
25
25
|
* npx thumbgate pro # solo dashboard + exports side lane
|
|
26
|
-
* npx thumbgate audit <file> # audit an agent transcript for repeat-mistake token waste
|
|
27
26
|
*/
|
|
28
27
|
|
|
29
28
|
'use strict';
|
|
30
29
|
|
|
31
30
|
const fs = require('fs');
|
|
32
|
-
const os = require('os');
|
|
33
31
|
const path = require('path');
|
|
34
32
|
const crypto = require('crypto');
|
|
35
|
-
const
|
|
36
|
-
const { execSync, execFileSync, execFile, spawn, spawnSync } = require('child_process');
|
|
33
|
+
const { execSync, execFileSync } = require('child_process');
|
|
37
34
|
const {
|
|
38
35
|
codexAutoUpdateCliEntry,
|
|
39
36
|
codexAutoUpdateMcpEntry,
|
|
@@ -43,6 +40,13 @@ const {
|
|
|
43
40
|
resolveMcpEntry,
|
|
44
41
|
} = require(path.join(__dirname, '..', 'scripts', 'mcp-config'));
|
|
45
42
|
const { trackEvent } = require(path.join(__dirname, '..', 'scripts', 'cli-telemetry'));
|
|
43
|
+
const {
|
|
44
|
+
cacheUpdateHookCommand,
|
|
45
|
+
preToolHookCommand,
|
|
46
|
+
sessionStartHookCommand,
|
|
47
|
+
statuslineCommand,
|
|
48
|
+
userPromptHookCommand,
|
|
49
|
+
} = require(path.join(__dirname, '..', 'scripts', 'hook-runtime'));
|
|
46
50
|
const {
|
|
47
51
|
PRO_MONTHLY_PAYMENT_LINK,
|
|
48
52
|
PRO_PRICE_LABEL,
|
|
@@ -52,48 +56,8 @@ const COMMAND = process.argv[2];
|
|
|
52
56
|
const CWD = process.cwd();
|
|
53
57
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
54
58
|
|
|
55
|
-
const PRO_URL = 'https://thumbgate.
|
|
59
|
+
const PRO_URL = 'https://thumbgate-production.up.railway.app';
|
|
56
60
|
const PRO_CHECKOUT_URL = PRO_MONTHLY_PAYMENT_LINK;
|
|
57
|
-
// Mirrors scripts/rate-limiter.js TRIAL_DAYS — must stay in sync. Gitar
|
|
58
|
-
// caught a 14-vs-7 mismatch on PR #2337 (banner said "7-day trial" but
|
|
59
|
-
// the date label was computed 14 days out).
|
|
60
|
-
const TRIAL_DAYS = 7;
|
|
61
|
-
|
|
62
|
-
function checkoutUrlFor(source, content) {
|
|
63
|
-
try {
|
|
64
|
-
const base = content === 'capture_feedback'
|
|
65
|
-
? 'https://buy.stripe.com/7sYfZhaiE1eSbO99uj3sI0d'
|
|
66
|
-
: PRO_CHECKOUT_URL;
|
|
67
|
-
const url = new URL(base);
|
|
68
|
-
url.searchParams.set('utm_source', source || 'cli');
|
|
69
|
-
url.searchParams.set('utm_medium', 'cli');
|
|
70
|
-
url.searchParams.set('utm_campaign', 'pro_conversion');
|
|
71
|
-
if (content) url.searchParams.set('utm_content', content);
|
|
72
|
-
return url.toString();
|
|
73
|
-
} catch (_) {
|
|
74
|
-
return content === 'capture_feedback'
|
|
75
|
-
? 'https://buy.stripe.com/7sYfZhaiE1eSbO99uj3sI0d'
|
|
76
|
-
: PRO_CHECKOUT_URL;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function pricingUrlFor(source, content) {
|
|
81
|
-
try {
|
|
82
|
-
const url = new URL('/pricing', PRO_URL);
|
|
83
|
-
url.searchParams.set('utm_source', source || 'cli');
|
|
84
|
-
url.searchParams.set('utm_medium', 'cli');
|
|
85
|
-
url.searchParams.set('utm_campaign', 'pro_education');
|
|
86
|
-
if (content) url.searchParams.set('utm_content', content);
|
|
87
|
-
return url.toString();
|
|
88
|
-
} catch (_) {
|
|
89
|
-
return `${PRO_URL}/pricing`;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function trialDeadlineLabel(now = new Date()) {
|
|
94
|
-
const deadline = new Date(now.getTime() + (TRIAL_DAYS * 24 * 60 * 60 * 1000));
|
|
95
|
-
return deadline.toISOString().slice(0, 10);
|
|
96
|
-
}
|
|
97
61
|
|
|
98
62
|
function upgradeNudge() {
|
|
99
63
|
if (process.env.THUMBGATE_NO_NUDGE === '1') return;
|
|
@@ -101,13 +65,11 @@ function upgradeNudge() {
|
|
|
101
65
|
const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
|
|
102
66
|
if (isProTier()) return;
|
|
103
67
|
} catch (_) { return; }
|
|
104
|
-
const pricingUrl = pricingUrlFor('cli_upgrade_nudge', COMMAND || 'general');
|
|
105
68
|
process.stderr.write(
|
|
106
69
|
'\n Team rollout: start with the Workflow Hardening Sprint\n' +
|
|
107
|
-
' https://thumbgate.
|
|
70
|
+
' https://thumbgate-production.up.railway.app/#workflow-sprint-intake\n' +
|
|
108
71
|
`\n Solo side lane: Pro — ${PRO_PRICE_LABEL}\n` +
|
|
109
|
-
|
|
110
|
-
` ${pricingUrl}\n\n`
|
|
72
|
+
` ${PRO_CHECKOUT_URL}\n\n`
|
|
111
73
|
);
|
|
112
74
|
}
|
|
113
75
|
|
|
@@ -164,7 +126,7 @@ function telemetryPing(installId) {
|
|
|
164
126
|
timestamp: new Date().toISOString(),
|
|
165
127
|
};
|
|
166
128
|
appendLocalTelemetry(payloadObject);
|
|
167
|
-
const apiUrl = process.env.THUMBGATE_API_URL || 'https://thumbgate.
|
|
129
|
+
const apiUrl = process.env.THUMBGATE_API_URL || 'https://thumbgate-production.up.railway.app';
|
|
168
130
|
const payload = JSON.stringify(payloadObject);
|
|
169
131
|
try {
|
|
170
132
|
const url = new URL('/v1/telemetry/ping', apiUrl);
|
|
@@ -183,47 +145,25 @@ function proNudge(context) {
|
|
|
183
145
|
const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
|
|
184
146
|
if (isProTier()) return;
|
|
185
147
|
} catch (_) { /* if rate-limiter is unavailable, fall through and nudge */ }
|
|
186
|
-
const checkoutUrl = checkoutUrlFor('cli_nudge', context || COMMAND || 'general');
|
|
187
|
-
const pricingUrl = pricingUrlFor('cli_nudge', context || COMMAND || 'general');
|
|
188
148
|
const messages = [
|
|
189
|
-
`\n 💡 Pro (${PRO_PRICE_LABEL}):
|
|
190
|
-
`\n 💡
|
|
191
|
-
`\n 💡 ThumbGate Pro
|
|
149
|
+
`\n 💡 Unlock Pro (${PRO_PRICE_LABEL}): searchable dashboard, DPO export, multi-repo sync\n ${PRO_CHECKOUT_URL}\n`,
|
|
150
|
+
`\n 💡 Pro tip: export your feedback as DPO training pairs to improve your models.\n Get Pro: ${PRO_CHECKOUT_URL}\n`,
|
|
151
|
+
`\n 💡 ThumbGate Pro: search, edit, and sync lessons across repos. ${PRO_PRICE_LABEL}.\n ${PRO_CHECKOUT_URL}\n`,
|
|
192
152
|
];
|
|
193
153
|
// Rotate message daily — no Math.random (security policy)
|
|
194
154
|
const msg = messages[Math.floor(Date.now() / 86400000) % messages.length];
|
|
195
155
|
process.stderr.write(msg);
|
|
196
156
|
}
|
|
197
157
|
|
|
198
|
-
function limitNudge(action
|
|
158
|
+
function limitNudge(action) {
|
|
199
159
|
if (process.env.THUMBGATE_NO_NUDGE === '1') return;
|
|
200
|
-
const checkoutUrl = checkoutUrlFor('cli_limit', action);
|
|
201
|
-
const usageLine = Number.isFinite(limitResult.used) && Number.isFinite(limitResult.limit)
|
|
202
|
-
? ` Usage: ${limitResult.used}/${limitResult.limit} (${limitResult.limitType || 'limit'}).\n`
|
|
203
|
-
: '';
|
|
204
|
-
const reason = limitResult.message
|
|
205
|
-
? String(limitResult.message).split('\n')[0]
|
|
206
|
-
: 'Free tier limit reached.';
|
|
207
160
|
process.stderr.write(
|
|
208
|
-
`\n
|
|
209
|
-
|
|
210
|
-
`
|
|
211
|
-
` ${checkoutUrl}\n\n`
|
|
161
|
+
`\n ⚠️ Free tier limit reached. Upgrade to Pro for unlimited: https://thumbgate-production.up.railway.app/pro\n` +
|
|
162
|
+
` ${action} daily limit reached. Upgrade to Pro for unlimited usage — ${PRO_PRICE_LABEL}:\n` +
|
|
163
|
+
` ${PRO_CHECKOUT_URL}\n\n`
|
|
212
164
|
);
|
|
213
165
|
}
|
|
214
166
|
|
|
215
|
-
function printInitConversionPrompt(email) {
|
|
216
|
-
if (process.env.THUMBGATE_NO_NUDGE === '1') return;
|
|
217
|
-
const checkoutUrl = checkoutUrlFor('cli_init', email ? 'init_email' : 'init_no_email');
|
|
218
|
-
console.log('');
|
|
219
|
-
console.log(' ┌──────────────────────────────────────────────────────────┐');
|
|
220
|
-
console.log(` │ 7-day Pro trial active through ${trialDeadlineLabel()}. │`);
|
|
221
|
-
console.log(' │ Pro keeps lessons/rules/dashboard synced everywhere. │');
|
|
222
|
-
console.log(' │ Add onboarding: npx thumbgate init --email you@company.com │');
|
|
223
|
-
console.log(` │ Upgrade: ${checkoutUrl}`);
|
|
224
|
-
console.log(' └──────────────────────────────────────────────────────────┘');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
167
|
function parseArgs(argv) {
|
|
228
168
|
const args = {};
|
|
229
169
|
argv.forEach((arg, index) => {
|
|
@@ -245,73 +185,6 @@ function parseArgs(argv) {
|
|
|
245
185
|
return args;
|
|
246
186
|
}
|
|
247
187
|
|
|
248
|
-
function probeDash(port) {
|
|
249
|
-
return new Promise((resolve) => {
|
|
250
|
-
const req = http.get({ hostname: '127.0.0.1', port, path: '/health', timeout: 750 }, (res) => {
|
|
251
|
-
res.resume();
|
|
252
|
-
resolve(res.statusCode >= 200 && res.statusCode < 500);
|
|
253
|
-
});
|
|
254
|
-
req.on('error', () => resolve(false));
|
|
255
|
-
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function waitDash(port, timeoutMs = 8000) {
|
|
260
|
-
const deadline = Date.now() + timeoutMs;
|
|
261
|
-
while (Date.now() < deadline) {
|
|
262
|
-
if (await probeDash(port)) return true;
|
|
263
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
264
|
-
}
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function ensureDash(port) {
|
|
269
|
-
if (await probeDash(port)) {
|
|
270
|
-
return { started: false, pid: null };
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const serverPath = path.join(PKG_ROOT, 'src', 'api', 'server.js');
|
|
274
|
-
const child = spawn(process.execPath, [serverPath], {
|
|
275
|
-
cwd: PKG_ROOT,
|
|
276
|
-
detached: true,
|
|
277
|
-
env: { ...process.env, PORT: String(port), THUMBGATE_ALLOW_INSECURE: process.env.THUMBGATE_ALLOW_INSECURE || 'true' },
|
|
278
|
-
stdio: 'ignore',
|
|
279
|
-
});
|
|
280
|
-
child.unref();
|
|
281
|
-
|
|
282
|
-
if (!(await waitDash(port))) {
|
|
283
|
-
throw new Error('Dashboard API timeout');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return { started: true, pid: child.pid };
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function openBrowser(url) {
|
|
290
|
-
if (process.env.THUMBGATE_DASHBOARD_NO_OPEN === '1') {
|
|
291
|
-
console.log(`Ready: ${url}`);
|
|
292
|
-
return Promise.resolve();
|
|
293
|
-
}
|
|
294
|
-
const [command, args] = ({ darwin: ['open', [url]], win32: ['cmd', ['/c', 'start', '', url]] }[process.platform] || ['xdg-open', [url]]);
|
|
295
|
-
return new Promise((resolve, reject) => {
|
|
296
|
-
execFile(command, args, (err) => (err ? reject(err) : resolve()));
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function parseTtlMs(value, fallbackMs = 5 * 60 * 1000) {
|
|
301
|
-
if (value === undefined || value === null || value === true || value === '') return fallbackMs;
|
|
302
|
-
const raw = String(value).trim().toLowerCase();
|
|
303
|
-
const match = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
304
|
-
if (!match) return fallbackMs;
|
|
305
|
-
const amount = Number(match[1]);
|
|
306
|
-
if (!Number.isFinite(amount) || amount <= 0) return fallbackMs;
|
|
307
|
-
const unit = match[2] || 'ms';
|
|
308
|
-
const factor = unit === 'h' ? 60 * 60 * 1000
|
|
309
|
-
: unit === 'm' ? 60 * 1000
|
|
310
|
-
: unit === 's' ? 1000
|
|
311
|
-
: 1;
|
|
312
|
-
return Math.round(amount * factor);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
188
|
function readStdinText() {
|
|
316
189
|
try {
|
|
317
190
|
return fs.readFileSync(0, 'utf8');
|
|
@@ -522,24 +395,64 @@ function whichExists(cmd) {
|
|
|
522
395
|
|
|
523
396
|
function setupClaude() {
|
|
524
397
|
const mcpChanged = mergeMcpJson(path.join(CWD, '.mcp.json'), 'Claude Code', 'project');
|
|
525
|
-
const { wireHooks } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
|
|
526
|
-
const hookResult = wireHooks({ agent: 'claude-code' });
|
|
527
398
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
399
|
+
// Upsert Stop hook into .claude/settings.json for autonomous self-scoring
|
|
400
|
+
const settingsPath = path.join(CWD, '.claude', 'settings.json');
|
|
401
|
+
const stopHookCommand = 'bash scripts/hook-stop-self-score.sh';
|
|
402
|
+
|
|
403
|
+
let settings = { hooks: {} };
|
|
404
|
+
if (fs.existsSync(settingsPath)) {
|
|
405
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { /* fresh */ }
|
|
531
406
|
}
|
|
407
|
+
settings.hooks = settings.hooks || {};
|
|
532
408
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
409
|
+
const stopAlreadyPresent = (settings.hooks.Stop || [])
|
|
410
|
+
.some(entry => (entry.hooks || []).some(h => h.command === stopHookCommand));
|
|
411
|
+
|
|
412
|
+
let hooksChanged = false;
|
|
413
|
+
if (!stopAlreadyPresent) {
|
|
414
|
+
settings.hooks.Stop = settings.hooks.Stop || [];
|
|
415
|
+
settings.hooks.Stop.push({ hooks: [{ type: 'command', command: stopHookCommand }] });
|
|
416
|
+
hooksChanged = true;
|
|
417
|
+
console.log(' Claude Code: installed Stop hook');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Upsert PostToolUse hook for ThumbGate statusline cache updates
|
|
421
|
+
const cacheHookCommand = cacheUpdateHookCommand();
|
|
422
|
+
const originalPostToolUseCount = (settings.hooks.PostToolUse || []).length;
|
|
423
|
+
settings.hooks.PostToolUse = (settings.hooks.PostToolUse || []).filter(
|
|
424
|
+
(entry) => !(entry.hooks || []).some((h) => h.command && h.command !== cacheHookCommand && /(hook-thumbgate-cache-updater|cache-update\b)/.test(h.command))
|
|
425
|
+
);
|
|
426
|
+
if (settings.hooks.PostToolUse.length !== originalPostToolUseCount) {
|
|
427
|
+
hooksChanged = true;
|
|
428
|
+
}
|
|
429
|
+
const cacheAlreadyPresent = (settings.hooks.PostToolUse || [])
|
|
430
|
+
.some(entry => (entry.hooks || []).some(h => h.command === cacheHookCommand || (h.command && h.command.includes('cache-update'))));
|
|
431
|
+
|
|
432
|
+
if (!cacheAlreadyPresent) {
|
|
433
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
|
|
434
|
+
settings.hooks.PostToolUse.push({
|
|
435
|
+
matcher: 'mcp__thumbgate__feedback_stats|mcp__thumbgate__dashboard',
|
|
436
|
+
hooks: [{ type: 'command', command: cacheHookCommand }]
|
|
437
|
+
});
|
|
438
|
+
hooksChanged = true;
|
|
439
|
+
console.log(' Claude Code: installed ThumbGate cache updater hook');
|
|
540
440
|
}
|
|
541
441
|
|
|
542
|
-
|
|
442
|
+
// Upsert statusLine for ThumbGate feedback display
|
|
443
|
+
const statuslineScript = statuslineCommand();
|
|
444
|
+
if (!settings.statusLine || settings.statusLine.command !== statuslineScript) {
|
|
445
|
+
settings.statusLine = { type: 'command', command: statuslineScript };
|
|
446
|
+
hooksChanged = true;
|
|
447
|
+
console.log(' Claude Code: installed ThumbGate status line');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (hooksChanged) {
|
|
451
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
452
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return mcpChanged || hooksChanged;
|
|
543
456
|
}
|
|
544
457
|
|
|
545
458
|
function setupCodex() {
|
|
@@ -570,20 +483,6 @@ function setupCodex() {
|
|
|
570
483
|
}
|
|
571
484
|
|
|
572
485
|
function setupGemini() {
|
|
573
|
-
// Try to import custom commands as a Gemini plugin if the CLI is installed
|
|
574
|
-
const { execSync } = require('child_process');
|
|
575
|
-
let pluginImported = false;
|
|
576
|
-
for (const binName of ['agy', 'gemini']) {
|
|
577
|
-
try {
|
|
578
|
-
execSync(`${binName} plugin import "${PKG_ROOT}" --force`, { stdio: 'ignore' });
|
|
579
|
-
console.log(` Gemini: imported thumbgate plugin via ${binName}`);
|
|
580
|
-
pluginImported = true;
|
|
581
|
-
break;
|
|
582
|
-
} catch (err) {
|
|
583
|
-
// ignore errors if command doesn't exist or fails
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
486
|
const settingsPath = path.join(HOME, '.gemini', 'settings.json');
|
|
588
487
|
if (fs.existsSync(settingsPath)) {
|
|
589
488
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
@@ -604,14 +503,13 @@ function setupGemini() {
|
|
|
604
503
|
}
|
|
605
504
|
}
|
|
606
505
|
|
|
607
|
-
if (!changed) return
|
|
506
|
+
if (!changed) return false;
|
|
608
507
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
609
508
|
console.log(' Gemini: updated ~/.gemini/settings.json');
|
|
610
509
|
return true;
|
|
611
510
|
}
|
|
612
511
|
// Fallback: project-level .gemini/settings.json
|
|
613
|
-
|
|
614
|
-
return pluginImported || mcpChanged;
|
|
512
|
+
return mergeMcpJson(path.join(CWD, '.gemini', 'settings.json'), 'Gemini', 'project');
|
|
615
513
|
}
|
|
616
514
|
|
|
617
515
|
function setupAmp() {
|
|
@@ -699,104 +597,6 @@ function detectAgent(projectDir) {
|
|
|
699
597
|
return null;
|
|
700
598
|
}
|
|
701
599
|
|
|
702
|
-
async function setupVertex(options = {}) {
|
|
703
|
-
const { execSync } = require('child_process');
|
|
704
|
-
const dryRun = options.dryRun === true || options['dry-run'] === true;
|
|
705
|
-
console.log(`\nthumbgate setup-vertex v${pkgVersion()}`);
|
|
706
|
-
console.log(' Zero-friction Google Cloud & Vertex AI onboarding...');
|
|
707
|
-
if (dryRun) {
|
|
708
|
-
console.log(' Dry run: will detect gcloud account/project, but will not enable services or write .env.');
|
|
709
|
-
}
|
|
710
|
-
console.log('');
|
|
711
|
-
|
|
712
|
-
// 1. Detect gcloud CLI
|
|
713
|
-
let activeAccount = '';
|
|
714
|
-
let activeProject = '';
|
|
715
|
-
try {
|
|
716
|
-
activeAccount = execSync('gcloud config get-value account', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
717
|
-
activeProject = execSync('gcloud config get-value project', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
718
|
-
} catch (_) {
|
|
719
|
-
console.log(' ⚠️ Google Cloud SDK (gcloud CLI) not detected or not logged in.');
|
|
720
|
-
console.log(' To automate setup, install the Google Cloud CLI and run: gcloud auth login');
|
|
721
|
-
console.log(' Otherwise, manually set the following variables in your .env file:');
|
|
722
|
-
console.log(' THUMBGATE_PROVIDER_MODE=vertex');
|
|
723
|
-
console.log(' VERTEX_PROJECT_ID=<your-gcp-project-id>');
|
|
724
|
-
console.log('');
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (!activeAccount) {
|
|
729
|
-
console.log(' ⚠️ No active Google Cloud account set. Run: gcloud auth login');
|
|
730
|
-
return;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
console.log(` Active Account : \x1b[36m${activeAccount}\x1b[0m`);
|
|
734
|
-
if (!activeProject) {
|
|
735
|
-
console.log(' ⚠️ No active Google Cloud project set. Run: gcloud config set project <PROJECT_ID>');
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
console.log(` Active Project : \x1b[36m${activeProject}\x1b[0m`);
|
|
739
|
-
|
|
740
|
-
// Validate project ID matches GCP format before use in shell
|
|
741
|
-
if (!/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(activeProject)) {
|
|
742
|
-
console.log(' ⚠️ Invalid GCP project ID format. Aborting.');
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
if (dryRun) {
|
|
747
|
-
console.log(` DRY-RUN would enable Vertex AI API for project: ${activeProject}`);
|
|
748
|
-
console.log(` DRY-RUN would write THUMBGATE_PROVIDER_MODE=vertex and VERTEX_PROJECT_ID=${activeProject} to .env.`);
|
|
749
|
-
console.log('');
|
|
750
|
-
console.log(' Dry run complete. Re-run without --dry-run to apply these changes.');
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// 2. Auto-enable Vertex AI API
|
|
755
|
-
console.log(' ⚙️ Enabling Vertex AI API in your project (this can take a few seconds)...');
|
|
756
|
-
try {
|
|
757
|
-
execSync(`gcloud services enable aiplatform.googleapis.com --project=${activeProject}`, { stdio: 'inherit' });
|
|
758
|
-
console.log(' ✅ Vertex AI API successfully enabled.');
|
|
759
|
-
} catch (err) {
|
|
760
|
-
console.log(' ⚠️ Could not programmatically enable Vertex AI API. Please make sure your billing is open.');
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// 3. Write env config to .env
|
|
764
|
-
const envPath = path.join(process.cwd(), '.env');
|
|
765
|
-
let envContent = '';
|
|
766
|
-
if (fs.existsSync(envPath)) {
|
|
767
|
-
envContent = fs.readFileSync(envPath, 'utf8');
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Helper to update or append env values
|
|
771
|
-
function updateEnvKey(content, key, value) {
|
|
772
|
-
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
773
|
-
if (regex.test(content)) {
|
|
774
|
-
return content.replace(regex, `${key}=${value}`);
|
|
775
|
-
}
|
|
776
|
-
return content.trim() + `\n${key}=${value}\n`;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
let updatedContent = updateEnvKey(envContent, 'THUMBGATE_PROVIDER_MODE', 'vertex');
|
|
780
|
-
updatedContent = updateEnvKey(updatedContent, 'VERTEX_PROJECT_ID', activeProject);
|
|
781
|
-
|
|
782
|
-
fs.writeFileSync(envPath, updatedContent, 'utf8');
|
|
783
|
-
console.log(' ✅ Wrote configuration to local .env file.');
|
|
784
|
-
|
|
785
|
-
// 4. Print gorgeous success activation box
|
|
786
|
-
console.log('');
|
|
787
|
-
console.log(' ╭──────────────────────────────────────────────────────────╮');
|
|
788
|
-
console.log(' │ Vertex AI Setup Complete │');
|
|
789
|
-
console.log(' │ │');
|
|
790
|
-
console.log(' │ ThumbGate wrote local Vertex routing config. │');
|
|
791
|
-
console.log(' │ This does not create or verify a Dialogflow CX agent. │');
|
|
792
|
-
console.log(' │ Verify DFCX with the console or Dialogflow CX REST API. │');
|
|
793
|
-
console.log(' │ │');
|
|
794
|
-
console.log(' │ Try a test run: │');
|
|
795
|
-
console.log(' │ npx thumbgate feedback-self-test │');
|
|
796
|
-
console.log(' ╰──────────────────────────────────────────────────────────╯');
|
|
797
|
-
console.log('');
|
|
798
|
-
}
|
|
799
|
-
|
|
800
600
|
function quickStart() {
|
|
801
601
|
const qsArgs = parseArgs(process.argv.slice(3));
|
|
802
602
|
const projectDir = process.cwd();
|
|
@@ -857,28 +657,8 @@ function quickStart() {
|
|
|
857
657
|
console.log('');
|
|
858
658
|
}
|
|
859
659
|
|
|
860
|
-
// Activation walkthrough (guided first rule + live demonstrated block).
|
|
861
|
-
// Implementation lives in scripts/activation-quickstart.js so it can be unit
|
|
862
|
-
// tested without executing the CLI's top-level command switch. `init` is
|
|
863
|
-
// deliberately untouched — this is an additive, separate command.
|
|
864
|
-
function quickstart() {
|
|
865
|
-
return require(path.join(PKG_ROOT, 'scripts', 'activation-quickstart')).quickstart();
|
|
866
|
-
}
|
|
867
|
-
|
|
868
660
|
function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
869
661
|
const args = { ...cliArgs };
|
|
870
|
-
if (args.help || args.h) {
|
|
871
|
-
console.log('Usage: npx thumbgate init [--agent <name>] [--wire-hooks] [--email you@company.com]');
|
|
872
|
-
console.log('');
|
|
873
|
-
console.log('Scaffold ThumbGate in the current project and wire detected agent integrations.');
|
|
874
|
-
console.log('');
|
|
875
|
-
console.log('Options:');
|
|
876
|
-
console.log(' --agent <name> Wire a specific agent: claude-code, codex, gemini, amp, cursor, cline');
|
|
877
|
-
console.log(' --wire-hooks Wire hooks only; do not scaffold project files');
|
|
878
|
-
console.log(' --email <email> Subscribe installer to the setup guide and trial reminders');
|
|
879
|
-
console.log(' --dry-run Show hook changes without writing them');
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
662
|
|
|
883
663
|
// --wire-hooks only mode: skip scaffolding, just wire hooks
|
|
884
664
|
if (args['wire-hooks']) {
|
|
@@ -938,43 +718,12 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
938
718
|
// Always create .mcp.json (project-level MCP config used by Claude, Codex, Cursor)
|
|
939
719
|
mergeMcpJson(path.join(CWD, '.mcp.json'), 'MCP');
|
|
940
720
|
|
|
941
|
-
// Copy custom slash commands (.claude/commands/*.md) to the project's config directories
|
|
942
|
-
const pkgCommandsDir = path.join(PKG_ROOT, '.claude', 'commands');
|
|
943
|
-
if (fs.existsSync(pkgCommandsDir)) {
|
|
944
|
-
const targets = [
|
|
945
|
-
path.join(CWD, '.claude', 'commands'),
|
|
946
|
-
path.join(CWD, '.gemini', 'commands'),
|
|
947
|
-
path.join(CWD, '.antigravitycli', 'commands')
|
|
948
|
-
];
|
|
949
|
-
for (const projectCommandsDir of targets) {
|
|
950
|
-
if (!fs.existsSync(projectCommandsDir)) {
|
|
951
|
-
fs.mkdirSync(projectCommandsDir, { recursive: true });
|
|
952
|
-
}
|
|
953
|
-
try {
|
|
954
|
-
const files = fs.readdirSync(pkgCommandsDir);
|
|
955
|
-
for (const file of files) {
|
|
956
|
-
if (file.endsWith('.md')) {
|
|
957
|
-
fs.copyFileSync(path.join(pkgCommandsDir, file), path.join(projectCommandsDir, file));
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
} catch (err) {
|
|
961
|
-
console.log(` Failed to copy custom commands to ${path.relative(CWD, projectCommandsDir)}: ${err.message}`);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
console.log('Scaffolded custom slash commands directories (.claude, .gemini, .antigravitycli)');
|
|
965
|
-
}
|
|
966
|
-
|
|
967
721
|
// Auto-detect and configure platform-specific locations
|
|
968
722
|
console.log('');
|
|
969
723
|
console.log('Detecting platforms...');
|
|
970
724
|
let configured = 0;
|
|
971
725
|
|
|
972
726
|
const platforms = [
|
|
973
|
-
{ name: 'Claude Code', detect: [
|
|
974
|
-
() => whichExists('claude'),
|
|
975
|
-
() => fs.existsSync(path.join(HOME, '.claude')),
|
|
976
|
-
() => fs.existsSync(path.join(CWD, '.claude')),
|
|
977
|
-
], setup: setupClaude },
|
|
978
727
|
{ name: 'Codex', detect: [() => whichExists('codex'), () => fs.existsSync(path.join(HOME, '.codex'))], setup: setupCodex },
|
|
979
728
|
{ name: 'Gemini', detect: [() => whichExists('gemini'), () => fs.existsSync(path.join(HOME, '.gemini'))], setup: setupGemini },
|
|
980
729
|
{ name: 'Amp', detect: [() => whichExists('amp'), () => fs.existsSync(path.join(HOME, '.amp'))], setup: setupAmp },
|
|
@@ -997,11 +746,8 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
997
746
|
}
|
|
998
747
|
|
|
999
748
|
// ChatGPT — cannot be automated
|
|
1000
|
-
const chatgptSpec =
|
|
1001
|
-
|
|
1002
|
-
path.join(PKG_ROOT, 'adapters', 'chatgpt', 'openapi.yaml'),
|
|
1003
|
-
].find((candidate) => fs.existsSync(candidate));
|
|
1004
|
-
if (chatgptSpec) {
|
|
749
|
+
const chatgptSpec = path.join(PKG_ROOT, 'adapters', 'chatgpt', 'openapi.yaml');
|
|
750
|
+
if (fs.existsSync(chatgptSpec)) {
|
|
1005
751
|
const projectChatgptSpec = path.join(thumbgateDir, 'chatgpt-openapi.yaml');
|
|
1006
752
|
fs.copyFileSync(chatgptSpec, projectChatgptSpec);
|
|
1007
753
|
console.log(` ChatGPT: import ${path.relative(CWD, projectChatgptSpec)} in GPT Builder > Actions`);
|
|
@@ -1041,43 +787,16 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
1041
787
|
}
|
|
1042
788
|
|
|
1043
789
|
console.log('');
|
|
1044
|
-
console.log(
|
|
1045
|
-
|
|
1046
|
-
? args.email.trim()
|
|
1047
|
-
: (typeof args['onboarding-email'] === 'string' ? args['onboarding-email'].trim() : '');
|
|
1048
|
-
if (onboardingEmail) {
|
|
1049
|
-
try {
|
|
1050
|
-
execFileSync(process.execPath, [__filename, 'subscribe', onboardingEmail], {
|
|
1051
|
-
cwd: CWD,
|
|
1052
|
-
env: process.env,
|
|
1053
|
-
stdio: 'inherit',
|
|
1054
|
-
});
|
|
1055
|
-
trackEvent('cli_init_email_subscribed', { command: 'init', source: 'init_email' });
|
|
1056
|
-
} catch (_) {
|
|
1057
|
-
console.log(` Retry onboarding email: npx thumbgate subscribe ${onboardingEmail}`);
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
790
|
+
console.log(`thumbgate v${pkgVersion()} initialized.`);
|
|
791
|
+
console.log('Run: npx thumbgate help');
|
|
1060
792
|
trackEvent('cli_init', { command: 'init' });
|
|
1061
|
-
|
|
1062
|
-
// ---------------------------------------------------------------------------
|
|
1063
|
-
// Activation guide: the ONE thing the user should do next.
|
|
1064
|
-
// 98.5% of init users never promote their first prevention rule.
|
|
1065
|
-
// This is the funnel break — not conversion, not nudges — activation.
|
|
1066
|
-
// ---------------------------------------------------------------------------
|
|
1067
|
-
console.log('');
|
|
1068
|
-
console.log(' ╭──────────────────────────────────────────────────────────╮');
|
|
1069
|
-
console.log(' │ NEXT: Prove feedback capture works │');
|
|
1070
|
-
console.log(' │ │');
|
|
1071
|
-
console.log(' │ npx thumbgate feedback-self-test │');
|
|
1072
|
-
console.log(' │ │');
|
|
1073
|
-
console.log(' │ Then dogfood it in chat: │');
|
|
1074
|
-
console.log(' │ thumbs down: agent skipped verification │');
|
|
1075
|
-
console.log(' ╰──────────────────────────────────────────────────────────╯');
|
|
793
|
+
proNudge();
|
|
1076
794
|
console.log('');
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
795
|
+
console.log(' ┌──────────────────────────────────────────────────────────┐');
|
|
796
|
+
console.log(' │ Teams: shared enforcement, CI gates, audit trails │');
|
|
797
|
+
console.log(' │ One correction protects every agent on your team. │');
|
|
798
|
+
console.log(' │ https://thumbgate-production.up.railway.app/pro │');
|
|
799
|
+
console.log(' └──────────────────────────────────────────────────────────┘');
|
|
1081
800
|
|
|
1082
801
|
try {
|
|
1083
802
|
const { appendFunnelEvent } = require(path.join(PKG_ROOT, 'scripts', 'billing'));
|
|
@@ -1107,7 +826,7 @@ function capture() {
|
|
|
1107
826
|
const { getUsage } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
|
|
1108
827
|
const capLimit = checkLimit('capture_feedback');
|
|
1109
828
|
if (!capLimit.allowed) {
|
|
1110
|
-
limitNudge('capture_feedback'
|
|
829
|
+
limitNudge('capture_feedback');
|
|
1111
830
|
process.exit(1);
|
|
1112
831
|
}
|
|
1113
832
|
trackEvent('cli_capture', { command: 'capture' });
|
|
@@ -1122,88 +841,23 @@ function capture() {
|
|
|
1122
841
|
return;
|
|
1123
842
|
}
|
|
1124
843
|
|
|
1125
|
-
|
|
1126
|
-
const rawArgv = process.argv.slice(3);
|
|
1127
|
-
const positionalArgs = [];
|
|
1128
|
-
const BOOLEAN_FLAGS = new Set(['--json', '--verbose', '--quiet', '--dry-run', '--stats', '--summary', '--no-nudge', '--help']);
|
|
1129
|
-
for (let i = 0; i < rawArgv.length; i++) {
|
|
1130
|
-
const arg = rawArgv[i];
|
|
1131
|
-
if (arg.startsWith('--')) {
|
|
1132
|
-
if (!arg.includes('=') && !BOOLEAN_FLAGS.has(arg)) {
|
|
1133
|
-
i++;
|
|
1134
|
-
}
|
|
1135
|
-
} else {
|
|
1136
|
-
positionalArgs.push(arg);
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
let signal = (args.feedback || '').toLowerCase();
|
|
1141
|
-
let consumedSignalArgs = 0;
|
|
1142
|
-
if (!signal && positionalArgs[0]) {
|
|
1143
|
-
const { detectFeedbackSignal } = require(path.join(PKG_ROOT, 'scripts', 'feedback-quality'));
|
|
1144
|
-
const oneWord = positionalArgs[0];
|
|
1145
|
-
const twoWords = positionalArgs.slice(0, 2).join(' ');
|
|
1146
|
-
const detected = detectFeedbackSignal(twoWords) || detectFeedbackSignal(oneWord);
|
|
1147
|
-
if (detected) {
|
|
1148
|
-
signal = detected.signal;
|
|
1149
|
-
consumedSignalArgs = detectFeedbackSignal(twoWords) ? Math.min(2, positionalArgs.length) : 1;
|
|
1150
|
-
} else {
|
|
1151
|
-
const firstPos = positionalArgs[0].toLowerCase();
|
|
1152
|
-
if (['up', 'down', 'thumbsup', 'thumbsdown', 'thumbs_up', 'thumbs_down', 'positive', 'negative'].some(v => firstPos.includes(v))) {
|
|
1153
|
-
signal = firstPos;
|
|
1154
|
-
consumedSignalArgs = 1;
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
|
|
844
|
+
const signal = (args.feedback || '').toLowerCase();
|
|
1159
845
|
const normalized = ['up', 'thumbsup', 'thumbs_up', 'positive'].some(v => signal.includes(v)) ? 'up'
|
|
1160
846
|
: ['down', 'thumbsdown', 'thumbs_down', 'negative'].some(v => signal.includes(v)) ? 'down'
|
|
1161
847
|
: signal;
|
|
1162
848
|
|
|
1163
849
|
if (normalized !== 'up' && normalized !== 'down') {
|
|
1164
|
-
console.error('Missing or unrecognized --feedback=up|down
|
|
1165
|
-
process.exit(1);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
const gateAction = (args.action || '').toLowerCase();
|
|
1169
|
-
if (gateAction && !['block', 'approve', 'warn'].includes(gateAction)) {
|
|
1170
|
-
console.error('Unrecognized --action. Use: block (default), approve, or warn');
|
|
850
|
+
console.error('Missing or unrecognized --feedback=up|down');
|
|
1171
851
|
process.exit(1);
|
|
1172
852
|
}
|
|
1173
853
|
|
|
1174
|
-
let context = args.context || '';
|
|
1175
|
-
if (!context && consumedSignalArgs > 0) {
|
|
1176
|
-
context = positionalArgs.slice(consumedSignalArgs).join(' ');
|
|
1177
|
-
} else if (!context && positionalArgs[1]) {
|
|
1178
|
-
context = positionalArgs[1];
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
let whatWentWrong = args['what-went-wrong'];
|
|
1182
|
-
if (!whatWentWrong && consumedSignalArgs > 0 && positionalArgs.length > consumedSignalArgs + 1) {
|
|
1183
|
-
whatWentWrong = positionalArgs.slice(consumedSignalArgs + 1).join(' ');
|
|
1184
|
-
} else if (!whatWentWrong && positionalArgs[2]) {
|
|
1185
|
-
whatWentWrong = positionalArgs[2];
|
|
1186
|
-
} else if (!whatWentWrong && normalized === 'down' && context) {
|
|
1187
|
-
whatWentWrong = context;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
let whatToChange = args['what-to-change'];
|
|
1191
|
-
if (!whatToChange && consumedSignalArgs > 0 && positionalArgs.length > consumedSignalArgs + 2) {
|
|
1192
|
-
whatToChange = positionalArgs.slice(consumedSignalArgs + 2).join(' ');
|
|
1193
|
-
} else if (!whatToChange && positionalArgs[3]) {
|
|
1194
|
-
whatToChange = positionalArgs[3];
|
|
1195
|
-
} else if (!whatToChange && normalized === 'down' && context) {
|
|
1196
|
-
whatToChange = `avoid: ${context}`;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
854
|
const result = captureFeedback({
|
|
1200
855
|
signal: normalized,
|
|
1201
|
-
context: context,
|
|
1202
|
-
whatWentWrong:
|
|
1203
|
-
whatToChange:
|
|
856
|
+
context: args.context || '',
|
|
857
|
+
whatWentWrong: args['what-went-wrong'],
|
|
858
|
+
whatToChange: args['what-to-change'],
|
|
1204
859
|
whatWorked: args['what-worked'],
|
|
1205
860
|
tags: args.tags,
|
|
1206
|
-
gateAction: gateAction || undefined,
|
|
1207
861
|
});
|
|
1208
862
|
|
|
1209
863
|
if (result.accepted) {
|
|
@@ -1231,21 +885,10 @@ function capture() {
|
|
|
1231
885
|
const pct = Math.round((capLimit.used / capLimit.limit) * 100);
|
|
1232
886
|
console.log(` Usage : ${capLimit.used}/${capLimit.limit} captures today (${pct}%)`);
|
|
1233
887
|
if (capLimit.remaining <= 1) {
|
|
1234
|
-
console.log(` ⚠️ Free tier limit reached. Upgrade to Pro for unlimited: https://thumbgate.
|
|
888
|
+
console.log(` ⚠️ Free tier limit reached. Upgrade to Pro for unlimited: https://thumbgate-production.up.railway.app/pro`);
|
|
1235
889
|
}
|
|
1236
890
|
}
|
|
1237
891
|
console.log('');
|
|
1238
|
-
try {
|
|
1239
|
-
const { buildCaptureReceipt } = require(path.join(PKG_ROOT, 'scripts', 'commercial-offer'));
|
|
1240
|
-
console.log(buildCaptureReceipt({
|
|
1241
|
-
signal: normalized,
|
|
1242
|
-
feedbackId: ev.id,
|
|
1243
|
-
memoryId: mem.id,
|
|
1244
|
-
actionType: ev.actionType,
|
|
1245
|
-
}));
|
|
1246
|
-
} catch (_) {
|
|
1247
|
-
// Receipt is a conversion aid, not part of feedback persistence.
|
|
1248
|
-
}
|
|
1249
892
|
proNudge();
|
|
1250
893
|
} else {
|
|
1251
894
|
if (args.json) {
|
|
@@ -1263,175 +906,13 @@ function capture() {
|
|
|
1263
906
|
}
|
|
1264
907
|
}
|
|
1265
908
|
|
|
1266
|
-
function readJsonlEntries(filePath) {
|
|
1267
|
-
try {
|
|
1268
|
-
return fs.readFileSync(filePath, 'utf8')
|
|
1269
|
-
.split(/\r?\n/)
|
|
1270
|
-
.map((line) => line.trim())
|
|
1271
|
-
.filter(Boolean)
|
|
1272
|
-
.map((line) => JSON.parse(line));
|
|
1273
|
-
} catch (_) {
|
|
1274
|
-
return [];
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
function shellSingleQuote(value) {
|
|
1279
|
-
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
function feedbackSelfTest() {
|
|
1283
|
-
const args = parseArgs(process.argv.slice(3));
|
|
1284
|
-
const signalArg = String(args.feedback || args.signal || 'down').toLowerCase();
|
|
1285
|
-
const normalized = ['up', 'thumbsup', 'thumbs_up', 'positive'].some((v) => signalArg.includes(v)) ? 'up'
|
|
1286
|
-
: ['down', 'thumbsdown', 'thumbs_down', 'negative'].some((v) => signalArg.includes(v)) ? 'down'
|
|
1287
|
-
: signalArg;
|
|
1288
|
-
|
|
1289
|
-
if (normalized !== 'up' && normalized !== 'down') {
|
|
1290
|
-
console.error('feedback-self-test needs --feedback=up|down when overriding the default.');
|
|
1291
|
-
process.exit(1);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
const previousFeedbackDir = process.env.THUMBGATE_FEEDBACK_DIR;
|
|
1295
|
-
const previousNoNudge = process.env.THUMBGATE_NO_NUDGE;
|
|
1296
|
-
const feedbackDir = args['feedback-dir']
|
|
1297
|
-
? path.resolve(CWD, args['feedback-dir'])
|
|
1298
|
-
: args.persist
|
|
1299
|
-
? null
|
|
1300
|
-
: fs.mkdtempSync(path.join(os.tmpdir(), 'thumbgate-feedback-self-test-'));
|
|
1301
|
-
const isolated = Boolean(feedbackDir);
|
|
1302
|
-
|
|
1303
|
-
if (feedbackDir) process.env.THUMBGATE_FEEDBACK_DIR = feedbackDir;
|
|
1304
|
-
process.env.THUMBGATE_NO_NUDGE = '1';
|
|
1305
|
-
|
|
1306
|
-
try {
|
|
1307
|
-
const { captureFeedback, getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
1308
|
-
const context = args.context || `feedback self-test: typed thumbs ${normalized} reaches ThumbGate capture`;
|
|
1309
|
-
const result = captureFeedback({
|
|
1310
|
-
signal: normalized,
|
|
1311
|
-
context,
|
|
1312
|
-
whatWentWrong: normalized === 'down'
|
|
1313
|
-
? (args['what-went-wrong'] || 'Need proof that feedback capture is wired in this runtime')
|
|
1314
|
-
: undefined,
|
|
1315
|
-
whatToChange: normalized === 'down'
|
|
1316
|
-
? (args['what-to-change'] || 'Run a one-command self-test before claiming thumbs feedback is captured')
|
|
1317
|
-
: undefined,
|
|
1318
|
-
whatWorked: normalized === 'up'
|
|
1319
|
-
? (args['what-worked'] || 'Feedback capture persisted and was verified by a self-test')
|
|
1320
|
-
: undefined,
|
|
1321
|
-
tags: args.tags || 'self-test,dogfood,feedback-capture',
|
|
1322
|
-
});
|
|
1323
|
-
|
|
1324
|
-
const paths = getFeedbackPaths();
|
|
1325
|
-
const feedbackRows = readJsonlEntries(paths.FEEDBACK_LOG_PATH);
|
|
1326
|
-
const memoryRows = readJsonlEntries(paths.MEMORY_LOG_PATH);
|
|
1327
|
-
const feedbackId = result.feedbackEvent && result.feedbackEvent.id;
|
|
1328
|
-
const memoryId = result.memoryRecord && result.memoryRecord.id;
|
|
1329
|
-
const feedbackStored = Boolean(feedbackId && feedbackRows.some((row) => row.id === feedbackId));
|
|
1330
|
-
const memoryStored = Boolean(memoryId && memoryRows.some((row) => row.id === memoryId));
|
|
1331
|
-
|
|
1332
|
-
let hookProbe = null;
|
|
1333
|
-
if (args.hook) {
|
|
1334
|
-
const hookPrompt = args['hook-prompt'] || (
|
|
1335
|
-
normalized === 'down'
|
|
1336
|
-
? `thumbs down ${context}`
|
|
1337
|
-
: `thumbs up ${context}`
|
|
1338
|
-
);
|
|
1339
|
-
const hookResult = spawnSync(process.execPath, [__filename, 'hook-auto-capture'], {
|
|
1340
|
-
encoding: 'utf8',
|
|
1341
|
-
timeout: 10000,
|
|
1342
|
-
env: {
|
|
1343
|
-
...process.env,
|
|
1344
|
-
THUMBGATE_FEEDBACK_DIR: paths.FEEDBACK_DIR,
|
|
1345
|
-
THUMBGATE_NO_NUDGE: '1',
|
|
1346
|
-
CLAUDE_USER_PROMPT: hookPrompt,
|
|
1347
|
-
},
|
|
1348
|
-
});
|
|
1349
|
-
const hookFeedbackRows = readJsonlEntries(paths.FEEDBACK_LOG_PATH);
|
|
1350
|
-
const hookMemoryRows = readJsonlEntries(paths.MEMORY_LOG_PATH);
|
|
1351
|
-
const hookLatestFeedback = hookFeedbackRows[hookFeedbackRows.length - 1] || null;
|
|
1352
|
-
const hookLatestMemory = hookMemoryRows[hookMemoryRows.length - 1] || null;
|
|
1353
|
-
hookProbe = {
|
|
1354
|
-
ok: hookResult.status === 0
|
|
1355
|
-
&& Boolean(hookLatestFeedback)
|
|
1356
|
-
&& hookLatestFeedback.submittedContext === hookPrompt
|
|
1357
|
-
&& hookLatestFeedback.actionType !== 'no-action'
|
|
1358
|
-
&& Boolean(hookLatestMemory),
|
|
1359
|
-
prompt: hookPrompt,
|
|
1360
|
-
exitCode: hookResult.status,
|
|
1361
|
-
feedbackId: hookLatestFeedback ? hookLatestFeedback.id : null,
|
|
1362
|
-
memoryId: hookLatestMemory ? hookLatestMemory.id : null,
|
|
1363
|
-
actionType: hookLatestFeedback ? hookLatestFeedback.actionType : null,
|
|
1364
|
-
stdoutPreview: String(hookResult.stdout || '').slice(0, 500),
|
|
1365
|
-
stderrPreview: String(hookResult.stderr || '').slice(0, 500),
|
|
1366
|
-
};
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
const ok = Boolean(result.accepted && feedbackStored && memoryStored && (!hookProbe || hookProbe.ok));
|
|
1370
|
-
|
|
1371
|
-
const payload = {
|
|
1372
|
-
ok,
|
|
1373
|
-
command: 'feedback-self-test',
|
|
1374
|
-
signal: normalized,
|
|
1375
|
-
accepted: Boolean(result.accepted),
|
|
1376
|
-
feedbackId: feedbackId || null,
|
|
1377
|
-
memoryId: memoryId || null,
|
|
1378
|
-
feedbackStored,
|
|
1379
|
-
memoryStored,
|
|
1380
|
-
isolated,
|
|
1381
|
-
feedbackDir: paths.FEEDBACK_DIR,
|
|
1382
|
-
hookProbe,
|
|
1383
|
-
nextDogfoodCommand: `npx thumbgate capture --feedback=${normalized} --context=${shellSingleQuote(context)}`,
|
|
1384
|
-
};
|
|
1385
|
-
|
|
1386
|
-
if (args.json) {
|
|
1387
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
1388
|
-
} else if (ok) {
|
|
1389
|
-
console.log('\nThumbGate feedback self-test: PASS');
|
|
1390
|
-
console.log('─'.repeat(50));
|
|
1391
|
-
console.log(` Captured : ${normalized} (${feedbackId})`);
|
|
1392
|
-
console.log(` Stored lesson: ${memoryId}`);
|
|
1393
|
-
console.log(` Storage : ${paths.FEEDBACK_DIR}`);
|
|
1394
|
-
console.log(` Mode : ${isolated ? 'isolated test store' : 'active ThumbGate store'}`);
|
|
1395
|
-
if (hookProbe) {
|
|
1396
|
-
console.log(` Hook capture : PASS (${hookProbe.feedbackId}, ${hookProbe.memoryId})`);
|
|
1397
|
-
}
|
|
1398
|
-
console.log('\nDogfood in chat with:');
|
|
1399
|
-
console.log(` thumbs ${normalized}: ${context}`);
|
|
1400
|
-
} else {
|
|
1401
|
-
console.log('\nThumbGate feedback self-test: FAIL');
|
|
1402
|
-
console.log('─'.repeat(50));
|
|
1403
|
-
console.log(` Accepted : ${payload.accepted}`);
|
|
1404
|
-
console.log(` Feedback stored: ${feedbackStored}`);
|
|
1405
|
-
console.log(` Memory stored : ${memoryStored}`);
|
|
1406
|
-
if (hookProbe) {
|
|
1407
|
-
console.log(` Hook capture : ${hookProbe.ok ? 'true' : 'false'} (${hookProbe.actionType || 'no-action'})`);
|
|
1408
|
-
}
|
|
1409
|
-
console.log(` Storage : ${paths.FEEDBACK_DIR}`);
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
if (!ok) process.exit(2);
|
|
1413
|
-
} finally {
|
|
1414
|
-
if (previousFeedbackDir === undefined) delete process.env.THUMBGATE_FEEDBACK_DIR;
|
|
1415
|
-
else process.env.THUMBGATE_FEEDBACK_DIR = previousFeedbackDir;
|
|
1416
|
-
if (previousNoNudge === undefined) delete process.env.THUMBGATE_NO_NUDGE;
|
|
1417
|
-
else process.env.THUMBGATE_NO_NUDGE = previousNoNudge;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
909
|
function stats() {
|
|
1422
910
|
trackEvent('cli_stats', { command: 'stats' });
|
|
1423
911
|
const args = parseArgs(process.argv.slice(3));
|
|
1424
912
|
const { analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
1425
913
|
const data = analyzeFeedback();
|
|
1426
914
|
|
|
1427
|
-
// Gate enforcement stats — runtime intercepts + configured gates
|
|
1428
|
-
let gateData = { blocked: 0, warned: 0, passed: 0, byGate: {} };
|
|
1429
|
-
try { gateData = require(path.join(PKG_ROOT, 'scripts', 'gates-engine')).loadStats(); } catch {}
|
|
1430
|
-
let gateConfigData = { totalGates: 0, autoPromotedGates: 0, estimatedHoursSaved: '0.0', topBlocked: null, firstTimeFixRate: null };
|
|
1431
|
-
try { gateConfigData = require(path.join(PKG_ROOT, 'scripts', 'gate-stats')).calculateStats(); } catch {}
|
|
1432
|
-
|
|
1433
915
|
const avgCostOfMistake = 2.50;
|
|
1434
|
-
const totalInterceptions = gateData.blocked + gateData.warned;
|
|
1435
916
|
const payload = {
|
|
1436
917
|
total: data.total,
|
|
1437
918
|
positives: data.totalPositive,
|
|
@@ -1441,13 +922,6 @@ function stats() {
|
|
|
1441
922
|
revenueAtRisk: Number((data.totalNegative * avgCostOfMistake).toFixed(2)),
|
|
1442
923
|
topTags: data.topTags || [],
|
|
1443
924
|
recentActivity: data.recentActivity || [],
|
|
1444
|
-
gatesBlocked: gateData.blocked,
|
|
1445
|
-
gatesWarned: gateData.warned,
|
|
1446
|
-
totalGates: gateConfigData.totalGates,
|
|
1447
|
-
autoPromotedGates: gateConfigData.autoPromotedGates,
|
|
1448
|
-
estimatedHoursSaved: gateConfigData.estimatedHoursSaved,
|
|
1449
|
-
topBlockedGate: gateConfigData.topBlocked ? gateConfigData.topBlocked.id : null,
|
|
1450
|
-
firstTimeFixRate: gateConfigData.firstTimeFixRate,
|
|
1451
925
|
};
|
|
1452
926
|
|
|
1453
927
|
if (args.json) {
|
|
@@ -1461,36 +935,17 @@ function stats() {
|
|
|
1461
935
|
console.log(` Approval Rate : ${payload.approvalRate}%`);
|
|
1462
936
|
console.log(` Recent Trend : ${payload.recentTrend}%`);
|
|
1463
937
|
|
|
1464
|
-
// Gate enforcement — the high-ROI section
|
|
1465
|
-
if (totalInterceptions > 0 || payload.totalGates > 0) {
|
|
1466
|
-
console.log('\n🛡️ PRE-ACTION GATE ENFORCEMENT');
|
|
1467
|
-
console.log(` Actions blocked : ${payload.gatesBlocked}`);
|
|
1468
|
-
console.log(` Actions warned : ${payload.gatesWarned}`);
|
|
1469
|
-
console.log(` Active gates : ${payload.totalGates} (${payload.autoPromotedGates} auto-promoted)`);
|
|
1470
|
-
if (payload.topBlockedGate) console.log(` Top blocker : ${payload.topBlockedGate}`);
|
|
1471
|
-
console.log(` Est. time saved : ~${payload.estimatedHoursSaved} hours`);
|
|
1472
|
-
const { formatFirstTimeFixRate } = require(path.join(PKG_ROOT, 'scripts', 'gate-stats'));
|
|
1473
|
-
console.log(` First-time fix : ${formatFirstTimeFixRate(payload.firstTimeFixRate)}`);
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
938
|
if (payload.negatives > 0) {
|
|
1477
939
|
console.log('\n⚠️ REVENUE-AT-RISK ANALYSIS');
|
|
1478
940
|
console.log(` Repeated Failures detected: ${payload.negatives}`);
|
|
1479
941
|
console.log(` Estimated Operational Loss: $${payload.revenueAtRisk}`);
|
|
1480
942
|
console.log(' Action Required: Run "npx thumbgate rules" to generate guardrails.');
|
|
1481
943
|
console.log(' Strategic Recommendation: if this is a shared workflow problem, start the Workflow Hardening Sprint.');
|
|
1482
|
-
console.log(' Team intake: https://thumbgate.
|
|
944
|
+
console.log(' Team intake: https://thumbgate-production.up.railway.app/#workflow-sprint-intake');
|
|
1483
945
|
console.log(' Solo side lane: npx thumbgate pro');
|
|
1484
946
|
} else {
|
|
1485
947
|
console.log('\n✅ System is currently high-reliability. No immediate revenue loss detected.');
|
|
1486
948
|
}
|
|
1487
|
-
try {
|
|
1488
|
-
const { buildStatsReceipt } = require(path.join(PKG_ROOT, 'scripts', 'commercial-offer'));
|
|
1489
|
-
const receipt = buildStatsReceipt(payload);
|
|
1490
|
-
if (receipt) console.log(receipt);
|
|
1491
|
-
} catch (_) {
|
|
1492
|
-
// Keep stats resilient if the receipt helper is unavailable in old installs.
|
|
1493
|
-
}
|
|
1494
949
|
proNudge();
|
|
1495
950
|
}
|
|
1496
951
|
|
|
@@ -1596,14 +1051,13 @@ function pro() {
|
|
|
1596
1051
|
} = require(path.join(PKG_ROOT, 'scripts', 'pro-local-dashboard'));
|
|
1597
1052
|
|
|
1598
1053
|
function printProInfo() {
|
|
1599
|
-
const hostedUrl = 'https://thumbgate.
|
|
1054
|
+
const hostedUrl = 'https://thumbgate-production.up.railway.app';
|
|
1600
1055
|
const truthUrl = 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/COMMERCIAL_TRUTH.md';
|
|
1601
1056
|
console.log('\nThumbGate Pro — Local Dashboard');
|
|
1602
1057
|
console.log('─'.repeat(50));
|
|
1603
1058
|
console.log('Self-serve side lane today: Pro ($19/mo or $149/yr).');
|
|
1604
1059
|
console.log('Every licensed Pro user gets a personal local dashboard on localhost.');
|
|
1605
1060
|
console.log('\nWhat is available:');
|
|
1606
|
-
console.log(' - Hosted sync: keep lessons, rules, and dashboard state aligned across laptops, CI, containers, and agent runtimes');
|
|
1607
1061
|
console.log(' - Local Pro dashboard: your own browser dashboard for search, gates, and DPO export');
|
|
1608
1062
|
console.log(' - Team rollout path: shared hosted lessons, org visibility, and workflow proof');
|
|
1609
1063
|
console.log(' - Commercial truth doc: source of truth for traction, pricing, and proof claims');
|
|
@@ -1741,13 +1195,6 @@ function lessons() {
|
|
|
1741
1195
|
const tags = String(args.tags || '').split(',').map((t) => t.trim()).filter(Boolean);
|
|
1742
1196
|
const query = args.query || process.argv.slice(3).find((a) => !a.startsWith('--')) || '';
|
|
1743
1197
|
const limit = Number(args.limit || 10);
|
|
1744
|
-
const { checkLimit } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
|
|
1745
|
-
const searchLimit = checkLimit('search_lessons');
|
|
1746
|
-
if (!searchLimit.allowed) {
|
|
1747
|
-
trackEvent('cli_upgrade_prompt', { command: 'lessons', action: 'search_lessons', limitType: searchLimit.limitType || null });
|
|
1748
|
-
limitNudge('search_lessons', searchLimit);
|
|
1749
|
-
process.exit(1);
|
|
1750
|
-
}
|
|
1751
1198
|
|
|
1752
1199
|
// --remote: fetch from hosted Railway instance
|
|
1753
1200
|
if (args.remote) {
|
|
@@ -1914,7 +1361,6 @@ function modelCandidatesCmd() {
|
|
|
1914
1361
|
const maxCandidates = args.max ? Number(args.max) : undefined;
|
|
1915
1362
|
const { reportPath, report } = writeModelCandidatesReport(undefined, {
|
|
1916
1363
|
workload: args.workload,
|
|
1917
|
-
workloadFile: args['workload-file'] || args.workloadFile,
|
|
1918
1364
|
provider: args.provider,
|
|
1919
1365
|
family: args.family,
|
|
1920
1366
|
gateway: args.gateway,
|
|
@@ -2015,10 +1461,13 @@ function risk() {
|
|
|
2015
1461
|
}
|
|
2016
1462
|
|
|
2017
1463
|
function exportDpo() {
|
|
2018
|
-
const {
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
1464
|
+
const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
|
|
1465
|
+
if (!isProTier(null)) {
|
|
1466
|
+
process.stderr.write(
|
|
1467
|
+
`\n 🔒 DPO Export requires Pro (${PRO_PRICE_LABEL}).\n` +
|
|
1468
|
+
` Your feedback would generate valuable training pairs.\n` +
|
|
1469
|
+
` Upgrade: ${PRO_CHECKOUT_URL}\n\n`
|
|
1470
|
+
);
|
|
2022
1471
|
process.exit(1);
|
|
2023
1472
|
}
|
|
2024
1473
|
const extraArgs = process.argv.slice(3).join(' ');
|
|
@@ -2035,10 +1484,13 @@ function exportDpo() {
|
|
|
2035
1484
|
}
|
|
2036
1485
|
|
|
2037
1486
|
function exportDatabricks() {
|
|
2038
|
-
const {
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
1487
|
+
const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
|
|
1488
|
+
if (!isProTier(null)) {
|
|
1489
|
+
process.stderr.write(
|
|
1490
|
+
`\n 🔒 Databricks Export requires Pro (${PRO_PRICE_LABEL}).\n` +
|
|
1491
|
+
` Export feedback logs + proof artifacts for analytics.\n` +
|
|
1492
|
+
` Upgrade: ${PRO_CHECKOUT_URL}\n\n`
|
|
1493
|
+
);
|
|
2042
1494
|
process.exit(1);
|
|
2043
1495
|
}
|
|
2044
1496
|
const extraArgs = process.argv.slice(3).join(' ');
|
|
@@ -2233,26 +1685,6 @@ function pulse() {
|
|
|
2233
1685
|
});
|
|
2234
1686
|
}
|
|
2235
1687
|
|
|
2236
|
-
function checkUpdateCmd() {
|
|
2237
|
-
const { checkUpdate } = require(path.join(PKG_ROOT, 'scripts', 'check-update'));
|
|
2238
|
-
const args = parseArgs(process.argv.slice(3));
|
|
2239
|
-
checkUpdate({ verbose: !args.json, force: args.force }).then((res) => {
|
|
2240
|
-
if (args.json) {
|
|
2241
|
-
console.log(JSON.stringify(res, null, 2));
|
|
2242
|
-
}
|
|
2243
|
-
process.exit(0);
|
|
2244
|
-
}).catch((err) => {
|
|
2245
|
-
console.error(err && err.message ? err.message : err);
|
|
2246
|
-
process.exit(1);
|
|
2247
|
-
});
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
|
-
function selfUpdateCmd() {
|
|
2251
|
-
const { selfUpdate } = require(path.join(PKG_ROOT, 'scripts', 'check-update'));
|
|
2252
|
-
const success = selfUpdate();
|
|
2253
|
-
process.exit(success ? 0 : 1);
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
1688
|
function dispatchBrief() {
|
|
2257
1689
|
const args = parseArgs(process.argv.slice(3));
|
|
2258
1690
|
const {
|
|
@@ -2286,25 +1718,6 @@ function gateStats() {
|
|
|
2286
1718
|
console.log('\n' + formatStats(stats) + '\n');
|
|
2287
1719
|
}
|
|
2288
1720
|
|
|
2289
|
-
function contextPacks() {
|
|
2290
|
-
const args = parseArgs(process.argv.slice(3));
|
|
2291
|
-
const { generateAutoContextPacks } = require(path.join(PKG_ROOT, 'scripts', 'auto-context-packs'));
|
|
2292
|
-
const result = generateAutoContextPacks();
|
|
2293
|
-
if (args.json) {
|
|
2294
|
-
console.log(JSON.stringify(result, null, 2));
|
|
2295
|
-
return;
|
|
2296
|
-
}
|
|
2297
|
-
console.log(`\nGenerated ${result.packCount} context pack(s):`);
|
|
2298
|
-
for (const p of result.packs) {
|
|
2299
|
-
console.log(` [${p.type}] ${p.name}`);
|
|
2300
|
-
console.log(` -> ${p.filePath}`);
|
|
2301
|
-
}
|
|
2302
|
-
if (result.packCount === 0) {
|
|
2303
|
-
console.log(' (No failure patterns found yet — capture some feedback first.)');
|
|
2304
|
-
}
|
|
2305
|
-
console.log('');
|
|
2306
|
-
}
|
|
2307
|
-
|
|
2308
1721
|
function harnessAudit() {
|
|
2309
1722
|
const args = parseArgs(process.argv.slice(3));
|
|
2310
1723
|
const {
|
|
@@ -2585,7 +1998,7 @@ function backgroundGovernance() {
|
|
|
2585
1998
|
console.log(' - Run --check before dispatching an unattended PR job.');
|
|
2586
1999
|
console.log(' - Route protected branches and large blast-radius jobs to human review.');
|
|
2587
2000
|
console.log(' - Convert CI failures into thumbs-down lessons so repeats become Pre-Action Checks.');
|
|
2588
|
-
console.log('\nGuide: https://thumbgate.
|
|
2001
|
+
console.log('\nGuide: https://thumbgate-production.up.railway.app/guides/background-agent-governance\n');
|
|
2589
2002
|
}
|
|
2590
2003
|
|
|
2591
2004
|
function optimize() {
|
|
@@ -2593,52 +2006,6 @@ function optimize() {
|
|
|
2593
2006
|
doOptimize();
|
|
2594
2007
|
}
|
|
2595
2008
|
|
|
2596
|
-
function syncGcp() {
|
|
2597
|
-
const { syncToGcp } = require(path.join(PKG_ROOT, 'adapters', 'gcp', 'sync.js'));
|
|
2598
|
-
syncToGcp();
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
function cleanup() {
|
|
2602
|
-
console.log('Cleaning up ThumbGate processes...');
|
|
2603
|
-
try {
|
|
2604
|
-
const { execSync } = require('child_process');
|
|
2605
|
-
// Kill all 'thumbgate serve' and 'thumbgate dashboard' processes except this one
|
|
2606
|
-
const pids = execSync("ps aux | grep -E 'thumbgate (serve|dashboard|mcp)' | grep -v 'grep' | grep -v 'cleanup' | awk '{print $2}'", { encoding: 'utf8' })
|
|
2607
|
-
.split('\n')
|
|
2608
|
-
.filter(Boolean)
|
|
2609
|
-
.map(Number)
|
|
2610
|
-
.filter(pid => pid !== process.pid);
|
|
2611
|
-
|
|
2612
|
-
if (pids.length > 0) {
|
|
2613
|
-
console.log(`Killing ${pids.length} process(es): ${pids.join(', ')}`);
|
|
2614
|
-
pids.forEach(pid => {
|
|
2615
|
-
try { process.kill(pid, 'SIGTERM'); } catch (_) {}
|
|
2616
|
-
});
|
|
2617
|
-
// Give them a moment to die
|
|
2618
|
-
execSync('sleep 1');
|
|
2619
|
-
} else {
|
|
2620
|
-
console.log('No other ThumbGate processes found.');
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
// Check port 3456 specifically
|
|
2624
|
-
try {
|
|
2625
|
-
const portPids = execSync("lsof -ti :3456", { encoding: 'utf8' })
|
|
2626
|
-
.split('\n')
|
|
2627
|
-
.map(s => s.trim())
|
|
2628
|
-
.filter(Boolean)
|
|
2629
|
-
.map(Number);
|
|
2630
|
-
portPids.forEach(pid => {
|
|
2631
|
-
console.log(`Killing process ${pid} holding port 3456`);
|
|
2632
|
-
try { process.kill(pid, 'SIGKILL'); } catch (_) {}
|
|
2633
|
-
});
|
|
2634
|
-
} catch (_) { /* port already free */ }
|
|
2635
|
-
|
|
2636
|
-
console.log('✅ Cleanup complete. Run "npx thumbgate pro" to restart the dashboard.');
|
|
2637
|
-
} catch (err) {
|
|
2638
|
-
console.error(`Cleanup failed: ${err.message}`);
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
|
-
|
|
2642
2009
|
function serve() {
|
|
2643
2010
|
try {
|
|
2644
2011
|
const { repairCodexHooks } = require(path.join(PKG_ROOT, 'scripts', 'codex-self-heal'));
|
|
@@ -2675,34 +2042,11 @@ function install() {
|
|
|
2675
2042
|
}
|
|
2676
2043
|
|
|
2677
2044
|
async function gateCheck() {
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
if (process.env.THUMBGATE_HOTFIX_BYPASS === '1') {
|
|
2684
|
-
process.stdout.write(JSON.stringify({
|
|
2685
|
-
decision: 'approve',
|
|
2686
|
-
reason: 'hotfix-bypass-opt-in',
|
|
2687
|
-
hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: '' }
|
|
2688
|
-
}) + '\n');
|
|
2689
|
-
return;
|
|
2690
|
-
}
|
|
2691
|
-
try {
|
|
2692
|
-
const payload = readStdinText();
|
|
2693
|
-
const input = payload ? JSON.parse(payload) : {};
|
|
2694
|
-
const gatesEngine = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
2695
|
-
const output = await gatesEngine.runAsync(input);
|
|
2696
|
-
process.stdout.write(output + '\n');
|
|
2697
|
-
} catch (err) {
|
|
2698
|
-
process.stderr.write(`gate-check error: ${err.message}\n`);
|
|
2699
|
-
process.stdout.write(JSON.stringify({
|
|
2700
|
-
hookSpecificOutput: {
|
|
2701
|
-
hookEventName: 'PreToolUse',
|
|
2702
|
-
additionalContext: `[ThumbGate Error] ${err.message}`,
|
|
2703
|
-
}
|
|
2704
|
-
}) + '\n');
|
|
2705
|
-
}
|
|
2045
|
+
const payload = readStdinText();
|
|
2046
|
+
const input = payload ? JSON.parse(payload) : {};
|
|
2047
|
+
const gatesEngine = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
2048
|
+
const output = await gatesEngine.runAsync(input);
|
|
2049
|
+
process.stdout.write(output + '\n');
|
|
2706
2050
|
}
|
|
2707
2051
|
|
|
2708
2052
|
function cacheUpdate() {
|
|
@@ -2729,14 +2073,9 @@ function statuslineRender() {
|
|
|
2729
2073
|
|
|
2730
2074
|
function hookAutoCapture() {
|
|
2731
2075
|
syncActiveProjectContext();
|
|
2732
|
-
const prompt = process.env.CLAUDE_USER_PROMPT
|
|
2733
|
-
|| process.env.THUMBGATE_USER_PROMPT
|
|
2734
|
-
|| process.env.CODEX_USER_PROMPT
|
|
2735
|
-
|| process.env.USER_PROMPT
|
|
2736
|
-
|| readStdinText().trim();
|
|
2076
|
+
const prompt = process.env.CLAUDE_USER_PROMPT || process.env.THUMBGATE_USER_PROMPT || readStdinText().trim();
|
|
2737
2077
|
const { evaluatePromptGuard } = require(path.join(PKG_ROOT, 'scripts', 'prompt-guard'));
|
|
2738
2078
|
const { processInlineFeedback, formatCliOutput } = require(path.join(PKG_ROOT, 'scripts', 'cli-feedback'));
|
|
2739
|
-
const { detectFeedbackSignal, isGenericFeedbackText } = require(path.join(PKG_ROOT, 'scripts', 'feedback-quality'));
|
|
2740
2079
|
const { loadOptionalModule } = require(path.join(PKG_ROOT, 'scripts', 'private-core-boundary'));
|
|
2741
2080
|
const { recordConversationEntry, readRecentConversationWindow } = loadOptionalModule(
|
|
2742
2081
|
path.join(PKG_ROOT, 'scripts', 'feedback-history-distiller'),
|
|
@@ -2758,13 +2097,14 @@ function hookAutoCapture() {
|
|
|
2758
2097
|
return;
|
|
2759
2098
|
}
|
|
2760
2099
|
|
|
2761
|
-
const
|
|
2762
|
-
|
|
2100
|
+
const lower = prompt.toLowerCase();
|
|
2101
|
+
const isUp = /(thumbs?\s*up|that worked|looks good|nice work|perfect|good job)/i.test(lower);
|
|
2102
|
+
const isDown = /(thumbs?\s*down|that failed|that was wrong|fix this)/i.test(lower);
|
|
2103
|
+
if (!isUp && !isDown) {
|
|
2763
2104
|
return;
|
|
2764
2105
|
}
|
|
2765
2106
|
|
|
2766
|
-
const signal =
|
|
2767
|
-
const genericPrompt = isGenericFeedbackText(prompt, signal === 'down' ? 'negative' : 'positive');
|
|
2107
|
+
const signal = isDown ? 'down' : 'up';
|
|
2768
2108
|
const conversationWindow = readRecentConversationWindow({ limit: 8 });
|
|
2769
2109
|
const result = processInlineFeedback({
|
|
2770
2110
|
signal,
|
|
@@ -2772,8 +2112,8 @@ function hookAutoCapture() {
|
|
|
2772
2112
|
chatHistory: signal === 'down'
|
|
2773
2113
|
? conversationWindow.map((entry) => ({ role: entry.author === 'assistant' ? 'assistant' : 'user', content: entry.text || '' }))
|
|
2774
2114
|
: undefined,
|
|
2775
|
-
whatWentWrong: signal === 'down'
|
|
2776
|
-
whatWorked: signal === 'up'
|
|
2115
|
+
whatWentWrong: signal === 'down' ? prompt : undefined,
|
|
2116
|
+
whatWorked: signal === 'up' ? prompt : undefined,
|
|
2777
2117
|
});
|
|
2778
2118
|
process.stdout.write(formatCliOutput(result) + '\n');
|
|
2779
2119
|
}
|
|
@@ -2845,27 +2185,6 @@ function installMcp() {
|
|
|
2845
2185
|
|
|
2846
2186
|
function dashboard() {
|
|
2847
2187
|
const args = parseArgs(process.argv.slice(3));
|
|
2848
|
-
if (args.open || args.web) {
|
|
2849
|
-
const { resolveProjectDir } = require(path.join(PKG_ROOT, 'scripts', 'feedback-paths'));
|
|
2850
|
-
const projectDir = resolveProjectDir({ cwd: process.cwd(), env: process.env });
|
|
2851
|
-
const port = process.env.PORT || 3456;
|
|
2852
|
-
const url = `http://127.0.0.1:${port}/dashboard?project=${encodeURIComponent(projectDir)}`;
|
|
2853
|
-
|
|
2854
|
-
ensureDash(Number(port))
|
|
2855
|
-
.then((server) => {
|
|
2856
|
-
if (server.started) {
|
|
2857
|
-
console.log(`API ${port} pid ${server.pid}`);
|
|
2858
|
-
}
|
|
2859
|
-
return openBrowser(url);
|
|
2860
|
-
})
|
|
2861
|
-
.then(() => process.exit(0))
|
|
2862
|
-
.catch((err) => {
|
|
2863
|
-
console.error(err && err.message ? err.message : err);
|
|
2864
|
-
process.exit(1);
|
|
2865
|
-
});
|
|
2866
|
-
return;
|
|
2867
|
-
}
|
|
2868
|
-
|
|
2869
2188
|
const { printDashboard } = require(path.join(PKG_ROOT, 'scripts', 'dashboard'));
|
|
2870
2189
|
const { getOperationalDashboard } = require(path.join(PKG_ROOT, 'scripts', 'operational-dashboard'));
|
|
2871
2190
|
|
|
@@ -2985,64 +2304,6 @@ function startApi() {
|
|
|
2985
2304
|
}
|
|
2986
2305
|
}
|
|
2987
2306
|
|
|
2988
|
-
function breakGlass() {
|
|
2989
|
-
const args = parseArgs(process.argv.slice(3));
|
|
2990
|
-
const positionalReason = process.argv.slice(3).find((arg) => !arg.startsWith('--'));
|
|
2991
|
-
const reason = String(args.reason || positionalReason || '').trim();
|
|
2992
|
-
if (!reason) {
|
|
2993
|
-
console.error('Usage: npx thumbgate break-glass --reason "why this recovery is needed" [--ttl=5m] [--json]');
|
|
2994
|
-
process.exit(1);
|
|
2995
|
-
}
|
|
2996
|
-
|
|
2997
|
-
const ttlMs = parseTtlMs(args.ttl, 5 * 60 * 1000);
|
|
2998
|
-
const { breakGlassEmergency } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
2999
|
-
const result = breakGlassEmergency({ reason, ttlMs });
|
|
3000
|
-
if (args.json) {
|
|
3001
|
-
console.log(JSON.stringify(result, null, 2));
|
|
3002
|
-
return;
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3005
|
-
console.log('ThumbGate break-glass active.');
|
|
3006
|
-
console.log(` Reason : ${result.reason}`);
|
|
3007
|
-
console.log(` Expires : ${result.expiresAt}`);
|
|
3008
|
-
console.log(' Unlocked : hook settings edits, pr_create_allowed, pr_threads_checked');
|
|
3009
|
-
console.log(' Still gated: local-only scope, force-push, protected branch push, unsafe chmod, broad rm -rf');
|
|
3010
|
-
}
|
|
3011
|
-
|
|
3012
|
-
function aiInventory() {
|
|
3013
|
-
const args = parseArgs(process.argv.slice(3));
|
|
3014
|
-
const {
|
|
3015
|
-
scanAiComponents,
|
|
3016
|
-
buildCycloneDxMlBom,
|
|
3017
|
-
formatInventoryText,
|
|
3018
|
-
writeOutput,
|
|
3019
|
-
} = require(path.join(PKG_ROOT, 'scripts', 'ai-component-inventory'));
|
|
3020
|
-
const rootDir = path.resolve(String(args.root || args.cwd || CWD));
|
|
3021
|
-
const format = String(args.format || (args.json ? 'json' : 'summary')).toLowerCase();
|
|
3022
|
-
const inventory = scanAiComponents({
|
|
3023
|
-
rootDir,
|
|
3024
|
-
maxFiles: args['max-files'] ? Number(args['max-files']) : undefined,
|
|
3025
|
-
includeSnippets: args.snippets !== false,
|
|
3026
|
-
});
|
|
3027
|
-
|
|
3028
|
-
let payload;
|
|
3029
|
-
if (format === 'cyclonedx' || format === 'ml-bom' || format === 'mlbom') {
|
|
3030
|
-
payload = JSON.stringify(buildCycloneDxMlBom(inventory, { version: pkgVersion() }), null, 2);
|
|
3031
|
-
} else if (format === 'json') {
|
|
3032
|
-
payload = JSON.stringify(inventory, null, 2);
|
|
3033
|
-
} else {
|
|
3034
|
-
payload = formatInventoryText(inventory);
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
if (args.output) {
|
|
3038
|
-
writeOutput(path.resolve(String(args.output)), `${payload}\n`);
|
|
3039
|
-
console.log(`Wrote AI inventory evidence to ${path.resolve(String(args.output))}`);
|
|
3040
|
-
return;
|
|
3041
|
-
}
|
|
3042
|
-
|
|
3043
|
-
console.log(payload);
|
|
3044
|
-
}
|
|
3045
|
-
|
|
3046
2307
|
function help() {
|
|
3047
2308
|
const v = pkgVersion();
|
|
3048
2309
|
const helpArgs = process.argv.slice(3);
|
|
@@ -3058,18 +2319,13 @@ function help() {
|
|
|
3058
2319
|
console.log('');
|
|
3059
2320
|
console.log('Common commands:');
|
|
3060
2321
|
console.log(' init Detect agent and wire ThumbGate hooks');
|
|
3061
|
-
console.log(' feedback-self-test Prove thumbs capture works locally');
|
|
3062
2322
|
console.log(' capture --feedback=up|down --context="<text>" Capture a thumbs signal as a stored lesson');
|
|
3063
2323
|
console.log(' stats Approval rate, recent trend, blocked-pattern count');
|
|
3064
2324
|
console.log(' lessons [query] Search promoted lessons');
|
|
3065
2325
|
console.log(' explore Interactive TUI for lessons, gates, stats');
|
|
3066
2326
|
console.log(' dashboard Open the local ThumbGate dashboard');
|
|
3067
|
-
console.log(' ai-inventory Scan AI/ML components and export ML-BOM evidence');
|
|
3068
2327
|
console.log(' doctor Audit runtime isolation + bootstrap context');
|
|
3069
|
-
console.log(' break-glass --reason="..." Short TTL recovery if gates over-fire');
|
|
3070
|
-
console.log(' brain [--write] Build the agent-readable context brain (lessons + rules + gates)');
|
|
3071
2328
|
console.log(' pro ThumbGate Pro (dashboard, exports, sync)');
|
|
3072
|
-
console.log(' subscribe <email> Get the 5-min setup guide + weekly tips by email');
|
|
3073
2329
|
console.log('');
|
|
3074
2330
|
console.log('More:');
|
|
3075
2331
|
console.log(' thumbgate help all Full subcommand surface (~70 commands)');
|
|
@@ -3115,11 +2371,7 @@ function help() {
|
|
|
3115
2371
|
|
|
3116
2372
|
// Legacy and specialist commands kept visible until they graduate into the schema.
|
|
3117
2373
|
console.log('Also available:');
|
|
3118
|
-
console.log(' install-mcp Install MCP server
|
|
3119
|
-
console.log(' default: machine-wide (~/.claude/settings.json — shared dashboard)');
|
|
3120
|
-
console.log(' --project: per-repo (<cwd>/.claude/settings.json — isolated dashboard)');
|
|
3121
|
-
console.log(' --no-hooks: MCP only, skip hook wiring');
|
|
3122
|
-
console.log(' break-glass Short TTL recovery if gates over-fire');
|
|
2374
|
+
console.log(' install-mcp Install MCP server into Claude Code settings (--project for local)');
|
|
3123
2375
|
console.log(' cfo Hosted billing summary (local fallback JSON)');
|
|
3124
2376
|
console.log(' billing:setup Generate operator key + print Railway setup instructions');
|
|
3125
2377
|
console.log(' repair-github-marketplace Repair legacy GitHub Marketplace amount mappings');
|
|
@@ -3140,7 +2392,6 @@ function help() {
|
|
|
3140
2392
|
console.log(' proxy-pointer-rag-guardrails Map visual document RAG signals to Document RAG Safety gates');
|
|
3141
2393
|
console.log(' rag-precision-guardrails Map retrieval tuning regressions to Document RAG Safety gates');
|
|
3142
2394
|
console.log(' ai-engineering-stack-guardrails Map gateway, MCP, AGENTS.md, LLM wiki, reviewer, and sandbox gaps to stack gates');
|
|
3143
|
-
console.log(' ai-inventory Scan AI/ML components and export JSON or CycloneDX ML-BOM evidence');
|
|
3144
2395
|
console.log(' upstream-contributions Find dependency issues worth fixing without promotional PRs');
|
|
3145
2396
|
console.log(' long-running-agent-context-guardrails Map structured-memory gaps to long-running agent gates');
|
|
3146
2397
|
console.log(' reasoning-efficiency-guardrails Map reasoning compression signals to efficiency gates');
|
|
@@ -3165,7 +2416,6 @@ function help() {
|
|
|
3165
2416
|
|
|
3166
2417
|
console.log('Examples:');
|
|
3167
2418
|
console.log(' npx thumbgate init');
|
|
3168
|
-
console.log(' npx thumbgate feedback-self-test');
|
|
3169
2419
|
console.log(' npx thumbgate status --json');
|
|
3170
2420
|
console.log(' npx thumbgate explore lessons --json');
|
|
3171
2421
|
console.log(' npx thumbgate explore gates --json');
|
|
@@ -3175,7 +2425,6 @@ function help() {
|
|
|
3175
2425
|
console.log(' npx thumbgate proxy-pointer-rag-guardrails --tree-path=.rag/tree.json --image-pointers=paper-1/figures/fig2.png --documents=paper-1 --visual-claims --json');
|
|
3176
2426
|
console.log(' npx thumbgate rag-precision-guardrails --baseline-recall=0.86 --new-recall=0.72 --threshold-change --agentic --structural-near-misses --json');
|
|
3177
2427
|
console.log(' npx thumbgate ai-engineering-stack-guardrails --mcp-tool-count=182 --direct-provider-keys --llm-wiki-pages=24 --context-freshness-days=30 --background-agents --json');
|
|
3178
|
-
console.log(' npx thumbgate ai-inventory --format=cyclonedx --output=.thumbgate/ai-mlbom.json');
|
|
3179
2428
|
console.log(' npx thumbgate long-running-agent-context-guardrails --request-count=80 --output-mb=3 --raw-chat-only --json');
|
|
3180
2429
|
console.log(' npx thumbgate reasoning-efficiency-guardrails --baseline-tokens=1200 --compressed-tokens=980 --baseline-accuracy=0.84 --compressed-accuracy=0.85 --verifier --json');
|
|
3181
2430
|
console.log(' npx thumbgate deepseek-v4-runtime-guardrails --context-tokens=900000 --hybrid-attention --speculative-decoding --accept-length=1.4 --precision-mode=fp8 --json');
|
|
@@ -3197,270 +2446,11 @@ if (COMMAND === 'daemon' || COMMAND === 'serve-daemon') {
|
|
|
3197
2446
|
process.exit(0);
|
|
3198
2447
|
}
|
|
3199
2448
|
|
|
3200
|
-
// ---------------------------------------------------------------------------
|
|
3201
|
-
// Global --help interceptor: `thumbgate <cmd> --help` shows per-command help
|
|
3202
|
-
// instead of running the command. Commands with their own --help guard (init)
|
|
3203
|
-
// handle it internally; everything else falls through here.
|
|
3204
|
-
// ---------------------------------------------------------------------------
|
|
3205
|
-
const _cliSubArgs = process.argv.slice(3);
|
|
3206
|
-
const _wantsHelp = _cliSubArgs.includes('--help') || _cliSubArgs.includes('-h');
|
|
3207
|
-
|
|
3208
|
-
const SUBCOMMAND_HELP = {
|
|
3209
|
-
capture: 'Usage: npx thumbgate capture --feedback=up|down --context="..." [--what-worked="..."] [--what-went-wrong="..."] [--what-to-change="..."] [--tags=a,b]',
|
|
3210
|
-
feedback: 'Usage: npx thumbgate feedback --feedback=up|down --context="..." [--what-worked="..."] [--what-went-wrong="..."] [--what-to-change="..."] [--tags=a,b]',
|
|
3211
|
-
'feedback-self-test': 'Usage: npx thumbgate feedback-self-test [--json] [--persist] [--hook] [--feedback=up|down]\n\nCapture a synthetic thumbs signal and verify feedback-log + memory-log writes. Defaults to an isolated test store; use --hook to also prove UserPromptSubmit-style raw prompt auto-capture.',
|
|
3212
|
-
dogfood: 'Usage: npx thumbgate dogfood [--json] [--persist] [--feedback=up|down]\n\nAlias for feedback-self-test.',
|
|
3213
|
-
stats: 'Usage: npx thumbgate stats\n\nShow gate enforcement statistics: blocked/warned counts, active gates, time saved.',
|
|
3214
|
-
trial: 'Usage: npx thumbgate trial\n\nShow Pro trial status, remaining days, and upgrade path.',
|
|
3215
|
-
pro: 'Usage: npx thumbgate pro [--activate <key>]\n\nLaunch the local Pro dashboard or activate a Pro license key.',
|
|
3216
|
-
subscribe: 'Usage: npx thumbgate subscribe <email>\n\nSubscribe to the 5-minute setup guide + trial reminders.',
|
|
3217
|
-
lessons: 'Usage: npx thumbgate lessons [--query="..."] [--limit=N]\n\nSearch the lesson database (Pro feature).',
|
|
3218
|
-
search: 'Usage: npx thumbgate search <query>\n\nSearch ThumbGate knowledge base (Pro feature).',
|
|
3219
|
-
'gate-check': 'Usage: npx thumbgate gate-check\n\nPreToolUse hook interface: reads tool call JSON from stdin, outputs gate verdict.',
|
|
3220
|
-
'hermes-gate': 'Usage: npx thumbgate hermes-gate\n\nNous Research Hermes Agent pre_tool_call shell hook: reads Hermes tool-call JSON from stdin, runs the ThumbGate gate pipeline (strict by default), and outputs {"decision":"block","reason":...} to veto or {} to allow. Gates terminal/patch/skill_manage etc. See adapters/hermes/config.yaml.',
|
|
3221
|
-
'break-glass': 'Usage: npx thumbgate break-glass --reason="why" [--ttl=5m] [--json]\n\nShort-lived recovery path for over-firing gates. Allows hook settings edits and satisfies PR-create/thread-check gates without disabling core destructive-action protections.',
|
|
3222
|
-
serve: 'Usage: npx thumbgate serve\n\nStart the MCP stdio server. This is for agent runtimes, not the local HTTP dashboard.',
|
|
3223
|
-
mcp: 'Usage: npx thumbgate mcp\n\nAlias for `thumbgate serve`.',
|
|
3224
|
-
dashboard: 'Usage: npx thumbgate dashboard [--window=today|7d|30d] [--open]\n\nPrint the operational dashboard summary or open the browser HTTP dashboard (use --open). Defaults to PORT=3456.',
|
|
3225
|
-
'start-api': 'Usage: npx thumbgate start-api\n\nStart the local ThumbGate HTTP API/dashboard. Defaults to PORT=8787; use PORT=3456 for statusline localhost links.',
|
|
3226
|
-
'export-dpo': 'Usage: npx thumbgate export-dpo [--format=jsonl|csv]\n\nExport feedback as DPO training pairs (Pro feature).',
|
|
3227
|
-
status: 'Usage: npx thumbgate status\n\nShow ThumbGate system health and active configuration.',
|
|
3228
|
-
watch: 'Usage: npx thumbgate watch\n\nWatch for feedback changes and auto-regenerate prevention rules.',
|
|
3229
|
-
compact: 'Usage: npx thumbgate compact\n\nCompact the lesson database and reclaim disk space.',
|
|
3230
|
-
'context-packs': 'Usage: npx thumbgate context-packs\n\nGenerate context packs from top failure patterns.',
|
|
3231
|
-
suggest: 'Usage: npx thumbgate suggest <gate-id>\n\nSuggest fixes for a specific gate based on lesson history.',
|
|
3232
|
-
cost: 'Usage: npx thumbgate cost [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nShow cumulative $ and tokens saved by PreToolUse gate blocks. Reads ~/.thumbgate/gate-stats.json.',
|
|
3233
|
-
savings: 'Usage: npx thumbgate savings [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nAlias for `thumbgate cost`.',
|
|
3234
|
-
'setup-vertex': 'Usage: npx thumbgate setup-vertex [--dry-run]\n\nAuto-enable Vertex AI API on GCP and write local Vertex routing config to .env. With --dry-run, only detect the active account/project and print the planned changes. This does not create or verify a Dialogflow CX agent; use the Dialogflow CX REST API or console for live-agent evidence.',
|
|
3235
|
-
'ai-inventory': 'Usage: npx thumbgate ai-inventory [--root <dir>] [--format=summary|json|cyclonedx] [--output <path>] [--max-files=N]\n\nScan source/manifests/model artifacts for AI, ML, agent-framework, vector DB, Vertex, Gemini, and Dialogflow CX components. Use --format=cyclonedx to produce exportable ML-BOM evidence for enterprise reviews.',
|
|
3236
|
-
brain: 'Usage: npx thumbgate brain [--write] [--json] [--limit=N]\n\nBuild the agent-readable "context brain" — a single artifact consolidating this\nrepo\'s lessons, prevention rules, active gates, and project context for a coding\nagent to read BEFORE acting. --write saves it to .thumbgate/BRAIN.md (versioned,\ndeterministic). --json emits the structured model. --limit caps lessons (default 15).',
|
|
3237
|
-
'team-sync': 'Usage: npx thumbgate team-sync\n\nSynchronize prevention rules and context brain with your team\'s git repository (git pull --rebase & git push), then auto-rebuild the local brain.',
|
|
3238
|
-
};
|
|
3239
|
-
|
|
3240
|
-
if (_wantsHelp && COMMAND && SUBCOMMAND_HELP[COMMAND]) {
|
|
3241
|
-
console.log(SUBCOMMAND_HELP[COMMAND]);
|
|
3242
|
-
process.exit(0);
|
|
3243
|
-
}
|
|
3244
|
-
|
|
3245
|
-
// -----------------------------------------------------------------------------
|
|
3246
|
-
// brain — consolidate ThumbGate's institutional memory (lessons + prevention
|
|
3247
|
-
// rules + active gates + project context) into a single, versioned,
|
|
3248
|
-
// agent-readable "context brain". The persistent context a coding agent should
|
|
3249
|
-
// read BEFORE acting, so it stops repeating mistakes. Composes the existing
|
|
3250
|
-
// explore-subcommands primitives; output is deterministic (no volatile
|
|
3251
|
-
// timestamp) so `.thumbgate/BRAIN.md` diffs only when the underlying memory
|
|
3252
|
-
// changes.
|
|
3253
|
-
// -----------------------------------------------------------------------------
|
|
3254
|
-
function buildBrainModel(opts = {}) {
|
|
3255
|
-
const { exploreLessons, exploreRules, exploreGates } = require(path.join(PKG_ROOT, 'scripts', 'explore-subcommands'));
|
|
3256
|
-
let feedbackDir;
|
|
3257
|
-
try {
|
|
3258
|
-
const { getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
3259
|
-
feedbackDir = getFeedbackPaths().FEEDBACK_DIR;
|
|
3260
|
-
} catch (_) { feedbackDir = path.join(CWD, '.thumbgate'); }
|
|
3261
|
-
const limit = Math.max(1, Number(opts.limit) || 15);
|
|
3262
|
-
const safe = (fn, fallback) => { try { return fn(); } catch (_) { return fallback; } };
|
|
3263
|
-
const lessons = safe(() => exploreLessons({ feedbackDir, pkgRoot: PKG_ROOT, limit, json: true }), { lessons: [], total: 0 });
|
|
3264
|
-
const rules = safe(() => exploreRules({ feedbackDir, pkgRoot: PKG_ROOT, json: true }), { rules: [], total: 0 });
|
|
3265
|
-
const gates = safe(() => exploreGates({ feedbackDir, pkgRoot: PKG_ROOT, json: true }), { gates: [], total: 0 });
|
|
3266
|
-
const instructionFiles = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md', '.cursorrules', '.thumbgate/config.json']
|
|
3267
|
-
.filter((f) => { try { return fs.existsSync(path.join(CWD, f)); } catch (_) { return false; } });
|
|
3268
|
-
return { lessons, rules, gates, project: { root: CWD, instructionFiles } };
|
|
3269
|
-
}
|
|
3270
|
-
|
|
3271
|
-
function renderBrainMarkdown(model) {
|
|
3272
|
-
const out = [];
|
|
3273
|
-
out.push('# ThumbGate Context Brain');
|
|
3274
|
-
out.push('');
|
|
3275
|
-
out.push('<!-- The persistent context a coding agent should read BEFORE acting in this repo.');
|
|
3276
|
-
out.push(' Generated by `npx thumbgate brain`. Rebuild after capturing feedback. -->');
|
|
3277
|
-
out.push('');
|
|
3278
|
-
out.push('> Institutional memory for AI coding agents working here: what has been');
|
|
3279
|
-
out.push('> learned, what to avoid, and what is enforced. Read this first.');
|
|
3280
|
-
out.push('');
|
|
3281
|
-
|
|
3282
|
-
out.push('## What this codebase taught its agents (lessons)');
|
|
3283
|
-
out.push('');
|
|
3284
|
-
const lessons = (model.lessons && model.lessons.lessons) || [];
|
|
3285
|
-
if (!lessons.length) {
|
|
3286
|
-
out.push('_No lessons captured yet. Run `npx thumbgate capture --feedback=down --context="what failed"`._');
|
|
3287
|
-
} else {
|
|
3288
|
-
for (const l of lessons) {
|
|
3289
|
-
const sig = String(l.signal || '').toLowerCase();
|
|
3290
|
-
const mark = (sig.includes('positive') || sig === 'up') ? '✅' : ((sig.includes('negative') || sig === 'down') ? '⛔' : '•');
|
|
3291
|
-
const enforced = l.confidence === 'active' ? ' _(enforced)_' : '';
|
|
3292
|
-
const ctx = String(l.context || '').replace(/\s+/g, ' ').trim().slice(0, 220);
|
|
3293
|
-
if (ctx) out.push(`- ${mark} ${ctx}${enforced}`);
|
|
3294
|
-
}
|
|
3295
|
-
}
|
|
3296
|
-
out.push('');
|
|
3297
|
-
|
|
3298
|
-
out.push('## Guardrails — do NOT repeat these (prevention rules)');
|
|
3299
|
-
out.push('');
|
|
3300
|
-
const rules = (model.rules && model.rules.rules) || [];
|
|
3301
|
-
if (!rules.length) {
|
|
3302
|
-
out.push('_No prevention rules yet._');
|
|
3303
|
-
} else {
|
|
3304
|
-
for (const r of rules) {
|
|
3305
|
-
out.push(`- **${String(r.title || '').trim()}**`);
|
|
3306
|
-
const body = String(r.body || '').split('\n')[0].trim();
|
|
3307
|
-
if (body && body !== r.title) out.push(` - ${body.slice(0, 200)}`);
|
|
3308
|
-
}
|
|
3309
|
-
}
|
|
3310
|
-
out.push('');
|
|
3311
|
-
|
|
3312
|
-
out.push('## Active enforcement (gates)');
|
|
3313
|
-
out.push('');
|
|
3314
|
-
const gates = (model.gates && model.gates.gates) || [];
|
|
3315
|
-
if (!gates.length) {
|
|
3316
|
-
out.push('_No active gates._');
|
|
3317
|
-
} else {
|
|
3318
|
-
for (const g of gates) {
|
|
3319
|
-
const occ = g.occurrences ? ` (${g.occurrences}×)` : '';
|
|
3320
|
-
out.push(`- \`${g.pattern || g.id}\` → **${g.action}**${occ}`);
|
|
3321
|
-
}
|
|
3322
|
-
}
|
|
3323
|
-
out.push('');
|
|
3324
|
-
|
|
3325
|
-
out.push('## Project context');
|
|
3326
|
-
out.push('');
|
|
3327
|
-
out.push(`- Repo root: \`${model.project.root}\``);
|
|
3328
|
-
const files = model.project.instructionFiles;
|
|
3329
|
-
out.push(`- Agent instruction files: ${files.length ? files.map((f) => '`' + f + '`').join(', ') : '_none detected_'}`);
|
|
3330
|
-
out.push('');
|
|
3331
|
-
out.push('---');
|
|
3332
|
-
const lt = (model.lessons && model.lessons.total) || 0;
|
|
3333
|
-
const rt = (model.rules && model.rules.total) || 0;
|
|
3334
|
-
const gt = (model.gates && model.gates.total) || 0;
|
|
3335
|
-
out.push(`_${lt} lessons · ${rt} rules · ${gt} gates. Rebuild with \`npx thumbgate brain --write\`._`);
|
|
3336
|
-
out.push('');
|
|
3337
|
-
return out.join('\n');
|
|
3338
|
-
}
|
|
3339
|
-
|
|
3340
|
-
function cmdBrain(args = {}) {
|
|
3341
|
-
const model = buildBrainModel({ limit: args.limit });
|
|
3342
|
-
if (args.json) { console.log(JSON.stringify(model, null, 2)); return 0; }
|
|
3343
|
-
const md = renderBrainMarkdown(model);
|
|
3344
|
-
if (args.write) {
|
|
3345
|
-
const dir = path.join(CWD, '.thumbgate');
|
|
3346
|
-
const target = path.join(dir, 'BRAIN.md');
|
|
3347
|
-
try {
|
|
3348
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
3349
|
-
fs.writeFileSync(target, md);
|
|
3350
|
-
} catch (err) {
|
|
3351
|
-
// Surface a clean, actionable error instead of an uncaught stack trace
|
|
3352
|
-
// (e.g. permission denied, read-only filesystem).
|
|
3353
|
-
console.error(`Could not write ${target}: ${err && err.message ? err.message : err}`);
|
|
3354
|
-
return 1;
|
|
3355
|
-
}
|
|
3356
|
-
const lt = (model.lessons && model.lessons.total) || 0;
|
|
3357
|
-
const rt = (model.rules && model.rules.total) || 0;
|
|
3358
|
-
const gt = (model.gates && model.gates.total) || 0;
|
|
3359
|
-
console.log(`\u{1f9e0} Wrote context brain to .thumbgate/BRAIN.md (${lt} lessons · ${rt} rules · ${gt} gates).`);
|
|
3360
|
-
console.log(' Point your agent at it: add "Read .thumbgate/BRAIN.md first" to CLAUDE.md / AGENTS.md.');
|
|
3361
|
-
return 0;
|
|
3362
|
-
}
|
|
3363
|
-
process.stdout.write(md);
|
|
3364
|
-
return 0;
|
|
3365
|
-
}
|
|
3366
|
-
|
|
3367
|
-
async function teamSync() {
|
|
3368
|
-
const { execSync } = require('child_process');
|
|
3369
|
-
|
|
3370
|
-
// Verify we are in a Git repo
|
|
3371
|
-
try {
|
|
3372
|
-
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore', cwd: CWD });
|
|
3373
|
-
} catch (err) {
|
|
3374
|
-
console.error('❌ Error: The current directory is not a Git repository.');
|
|
3375
|
-
process.exit(1);
|
|
3376
|
-
}
|
|
3377
|
-
|
|
3378
|
-
console.log('🔄 Checking shared prevention rules status...');
|
|
3379
|
-
|
|
3380
|
-
const rulesRelative = '.thumbgate/prevention-rules.md';
|
|
3381
|
-
const brainRelative = '.thumbgate/BRAIN.md';
|
|
3382
|
-
const rulesPath = path.join(CWD, rulesRelative);
|
|
3383
|
-
|
|
3384
|
-
if (!fs.existsSync(rulesPath)) {
|
|
3385
|
-
console.log('⚠️ No local prevention rules file found to sync.');
|
|
3386
|
-
}
|
|
3387
|
-
|
|
3388
|
-
let statusOutput = '';
|
|
3389
|
-
try {
|
|
3390
|
-
statusOutput = execSync('git status --porcelain', { encoding: 'utf8', cwd: CWD }) || '';
|
|
3391
|
-
} catch (_) {}
|
|
3392
|
-
const hasLocalChanges = statusOutput.includes(rulesRelative) || statusOutput.includes(brainRelative) || statusOutput.includes('.thumbgate/');
|
|
3393
|
-
|
|
3394
|
-
if (hasLocalChanges) {
|
|
3395
|
-
console.log('📝 Local changes detected in prevention rules. Committing locally...');
|
|
3396
|
-
try {
|
|
3397
|
-
const filesToAdd = [rulesRelative, brainRelative].filter(f => fs.existsSync(path.join(CWD, f)));
|
|
3398
|
-
if (filesToAdd.length > 0) {
|
|
3399
|
-
const filesStr = filesToAdd.map(f => `"${f}"`).join(' ');
|
|
3400
|
-
execSync(`git add -f ${filesStr}`, { cwd: CWD });
|
|
3401
|
-
execSync('git commit -m "chore(thumbgate): update shared prevention rules [skip ci]"', { cwd: CWD });
|
|
3402
|
-
console.log('✅ Local rules committed successfully.');
|
|
3403
|
-
} else {
|
|
3404
|
-
console.log('✨ No local rules files exist to commit.');
|
|
3405
|
-
}
|
|
3406
|
-
} catch (e) {
|
|
3407
|
-
console.log('✨ No changes to commit (already staged/clean).');
|
|
3408
|
-
}
|
|
3409
|
-
} else {
|
|
3410
|
-
console.log('✨ No local rules changes to commit.');
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
// Pull from remote
|
|
3414
|
-
console.log('📥 Pulling rules from teammate remote (git pull --rebase)...');
|
|
3415
|
-
try {
|
|
3416
|
-
execSync('git pull --rebase', { stdio: 'inherit', cwd: CWD });
|
|
3417
|
-
} catch (pullErr) {
|
|
3418
|
-
console.error('❌ Git pull failed. Please resolve conflicts manually.');
|
|
3419
|
-
process.exit(1);
|
|
3420
|
-
}
|
|
3421
|
-
|
|
3422
|
-
// Push to remote
|
|
3423
|
-
console.log('📤 Pushing rules to teammate remote (git push)...');
|
|
3424
|
-
try {
|
|
3425
|
-
execSync('git push', { stdio: 'inherit', cwd: CWD });
|
|
3426
|
-
} catch (pushErr) {
|
|
3427
|
-
console.error('❌ Git push failed. You may need to run git push manually.');
|
|
3428
|
-
process.exit(1);
|
|
3429
|
-
}
|
|
3430
|
-
|
|
3431
|
-
// Rebuild the context brain (.thumbgate/BRAIN.md) from the newly merged rules
|
|
3432
|
-
console.log('🧠 Rebuilding local context brain from merged rules...');
|
|
3433
|
-
try {
|
|
3434
|
-
const brainArgs = { write: true };
|
|
3435
|
-
cmdBrain(brainArgs);
|
|
3436
|
-
} catch (brainErr) {
|
|
3437
|
-
console.warn(`⚠️ Failed to rebuild context brain: ${brainErr.message}`);
|
|
3438
|
-
}
|
|
3439
|
-
|
|
3440
|
-
console.log('\n🚀 Team rules synchronization complete! Your agents now share team-wide learning.');
|
|
3441
|
-
}
|
|
3442
|
-
|
|
3443
2449
|
switch (COMMAND) {
|
|
3444
|
-
case 'team-sync':
|
|
3445
|
-
case 'git-sync':
|
|
3446
|
-
teamSync().catch((err) => {
|
|
3447
|
-
console.error(err && err.message ? err.message : err);
|
|
3448
|
-
process.exit(1);
|
|
3449
|
-
});
|
|
3450
|
-
break;
|
|
3451
|
-
case '--version':
|
|
3452
|
-
case '-v':
|
|
3453
|
-
case 'version':
|
|
3454
|
-
console.log(pkgVersion());
|
|
3455
|
-
break;
|
|
3456
2450
|
case 'init':
|
|
3457
2451
|
init();
|
|
3458
2452
|
upgradeNudge();
|
|
3459
2453
|
break;
|
|
3460
|
-
case 'quickstart':
|
|
3461
|
-
case 'first-rule':
|
|
3462
|
-
quickstart();
|
|
3463
|
-
break;
|
|
3464
2454
|
case 'quick-start':
|
|
3465
2455
|
quickStart();
|
|
3466
2456
|
break;
|
|
@@ -3474,12 +2464,6 @@ switch (COMMAND) {
|
|
|
3474
2464
|
case 'mcp':
|
|
3475
2465
|
serve();
|
|
3476
2466
|
break;
|
|
3477
|
-
case 'cleanup':
|
|
3478
|
-
cleanup();
|
|
3479
|
-
break;
|
|
3480
|
-
case 'sync-gcp':
|
|
3481
|
-
syncGcp();
|
|
3482
|
-
break;
|
|
3483
2467
|
case 'gate-check':
|
|
3484
2468
|
gateCheck().catch((err) => {
|
|
3485
2469
|
console.error(err && err.message ? err.message : err);
|
|
@@ -3503,16 +2487,6 @@ switch (COMMAND) {
|
|
|
3503
2487
|
capture();
|
|
3504
2488
|
upgradeNudge();
|
|
3505
2489
|
break;
|
|
3506
|
-
case 'feedback-self-test':
|
|
3507
|
-
case 'dogfood':
|
|
3508
|
-
feedbackSelfTest();
|
|
3509
|
-
break;
|
|
3510
|
-
case 'setup-vertex':
|
|
3511
|
-
setupVertex(parseArgs(process.argv.slice(3))).catch((err) => {
|
|
3512
|
-
console.error(err && err.message ? err.message : err);
|
|
3513
|
-
process.exit(1);
|
|
3514
|
-
});
|
|
3515
|
-
break;
|
|
3516
2490
|
case 'stats':
|
|
3517
2491
|
stats();
|
|
3518
2492
|
upgradeNudge();
|
|
@@ -3521,27 +2495,6 @@ switch (COMMAND) {
|
|
|
3521
2495
|
case 'revenue':
|
|
3522
2496
|
cfo();
|
|
3523
2497
|
break;
|
|
3524
|
-
case 'cost':
|
|
3525
|
-
case 'savings':
|
|
3526
|
-
case 'costs': {
|
|
3527
|
-
const { main: costMain } = require(path.join(PKG_ROOT, 'scripts', 'cost-cli'));
|
|
3528
|
-
process.exit(costMain(process.argv.slice(3)));
|
|
3529
|
-
// process.exit doesn't return, but keep an explicit break so the switch
|
|
3530
|
-
// cannot accidentally fall through to case 'billing:setup' if a future
|
|
3531
|
-
// refactor wraps costMain in try/finally that intercepts the exit, or
|
|
3532
|
-
// a test runner stubs process.exit (flagged by gitar-bot on PR #2281).
|
|
3533
|
-
break;
|
|
3534
|
-
}
|
|
3535
|
-
case 'brain': {
|
|
3536
|
-
const sub = process.argv.slice(3).find((arg) => !arg.startsWith('--'));
|
|
3537
|
-
if (sub && ['init', 'context', 'remember', 'check', 'cleanup', 'status'].includes(sub)) {
|
|
3538
|
-
brain();
|
|
3539
|
-
} else {
|
|
3540
|
-
const brainArgs = parseArgs(process.argv.slice(3));
|
|
3541
|
-
process.exitCode = cmdBrain(brainArgs);
|
|
3542
|
-
}
|
|
3543
|
-
break;
|
|
3544
|
-
}
|
|
3545
2498
|
case 'billing:setup':
|
|
3546
2499
|
require(path.join(PKG_ROOT, 'scripts', 'billing-setup'));
|
|
3547
2500
|
break;
|
|
@@ -3558,11 +2511,6 @@ switch (COMMAND) {
|
|
|
3558
2511
|
case 'search-lessons':
|
|
3559
2512
|
lessons();
|
|
3560
2513
|
break;
|
|
3561
|
-
case 'notes': {
|
|
3562
|
-
const { cli: notesCli } = require(path.join(PKG_ROOT, 'scripts', 'implementation-notes'));
|
|
3563
|
-
notesCli(process.argv.slice(3));
|
|
3564
|
-
break;
|
|
3565
|
-
}
|
|
3566
2514
|
case 'lesson-health':
|
|
3567
2515
|
case 'stale': {
|
|
3568
2516
|
const { initDB } = require(path.join(PKG_ROOT, 'scripts', 'lesson-db'));
|
|
@@ -3704,9 +2652,6 @@ switch (COMMAND) {
|
|
|
3704
2652
|
case 'rules':
|
|
3705
2653
|
rules();
|
|
3706
2654
|
break;
|
|
3707
|
-
case 'context-packs':
|
|
3708
|
-
contextPacks();
|
|
3709
|
-
break;
|
|
3710
2655
|
case 'harness-audit':
|
|
3711
2656
|
case 'harness':
|
|
3712
2657
|
harnessAudit();
|
|
@@ -3736,12 +2681,6 @@ switch (COMMAND) {
|
|
|
3736
2681
|
case 'llm-wiki-guardrails':
|
|
3737
2682
|
aiEngineeringStackGuardrails();
|
|
3738
2683
|
break;
|
|
3739
|
-
case 'ai-inventory':
|
|
3740
|
-
case 'ai-component-inventory':
|
|
3741
|
-
case 'ml-bom':
|
|
3742
|
-
case 'mlbom':
|
|
3743
|
-
aiInventory();
|
|
3744
|
-
break;
|
|
3745
2684
|
case 'deepseek-v4-runtime-guardrails':
|
|
3746
2685
|
case 'deepseek-runtime-guardrails':
|
|
3747
2686
|
case 'sparse-attention-runtime-guardrails':
|
|
@@ -3806,82 +2745,6 @@ switch (COMMAND) {
|
|
|
3806
2745
|
case 'self-heal':
|
|
3807
2746
|
selfHeal();
|
|
3808
2747
|
break;
|
|
3809
|
-
case 'workflow':
|
|
3810
|
-
case 'swarm': {
|
|
3811
|
-
const args = parseArgs(process.argv.slice(3));
|
|
3812
|
-
let objective = args.objective;
|
|
3813
|
-
if (!objective) {
|
|
3814
|
-
const firstPositional = process.argv.slice(3).find((a, idx, arr) => {
|
|
3815
|
-
if (a.startsWith('--')) return false;
|
|
3816
|
-
const prev = arr[idx - 1];
|
|
3817
|
-
if (prev && prev.startsWith('--') && !prev.includes('=')) return false;
|
|
3818
|
-
return true;
|
|
3819
|
-
});
|
|
3820
|
-
if (firstPositional) objective = firstPositional;
|
|
3821
|
-
}
|
|
3822
|
-
if (!objective) {
|
|
3823
|
-
console.error('Error: objective is required. Run with --objective="your objective" or provide it as a positional argument.');
|
|
3824
|
-
process.exit(1);
|
|
3825
|
-
}
|
|
3826
|
-
const { executeWorkflow } = require(path.join(PKG_ROOT, 'scripts', 'parallel-workflow-orchestrator'));
|
|
3827
|
-
const concurrency = args.concurrency ? Number(args.concurrency) : undefined;
|
|
3828
|
-
const timeoutMs = args.timeoutMs ? Number(args.timeoutMs) : undefined;
|
|
3829
|
-
executeWorkflow(objective, { concurrency, timeoutMs, cwd: CWD })
|
|
3830
|
-
.then((res) => {
|
|
3831
|
-
if (args.json) {
|
|
3832
|
-
console.log(JSON.stringify(res, null, 2));
|
|
3833
|
-
} else {
|
|
3834
|
-
console.log(`\n✅ Parallel workflow execution complete.`);
|
|
3835
|
-
console.log(` Workflow ID: ${res.workflowId}`);
|
|
3836
|
-
console.log(` Objective : ${res.objective}`);
|
|
3837
|
-
console.log(` Duration : ${(res.durationMs / 1000).toFixed(2)}s`);
|
|
3838
|
-
console.log(` Report Path: ${res.reportPath}`);
|
|
3839
|
-
console.log(`\nReport Summary:\n`);
|
|
3840
|
-
console.log(fs.readFileSync(res.reportPath, 'utf8'));
|
|
3841
|
-
}
|
|
3842
|
-
process.exit(0);
|
|
3843
|
-
})
|
|
3844
|
-
.catch((err) => {
|
|
3845
|
-
console.error('Workflow execution failed:', err.message);
|
|
3846
|
-
process.exit(1);
|
|
3847
|
-
});
|
|
3848
|
-
break;
|
|
3849
|
-
}
|
|
3850
|
-
case 'trial': {
|
|
3851
|
-
// Show trial status — connects the 4K monthly npm installers to checkout
|
|
3852
|
-
const { isProTier, isInTrialPeriod, trialDaysRemaining, getInstallAgeDays } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
|
|
3853
|
-
const ageDays = getInstallAgeDays();
|
|
3854
|
-
const inTrial = isInTrialPeriod();
|
|
3855
|
-
const remaining = trialDaysRemaining();
|
|
3856
|
-
const isPro = isProTier();
|
|
3857
|
-
console.log('');
|
|
3858
|
-
console.log(' ThumbGate Pro Trial');
|
|
3859
|
-
console.log(' ──────────────────');
|
|
3860
|
-
if (isPro && !inTrial) {
|
|
3861
|
-
console.log(' Status: ✅ Pro (active license or API key)');
|
|
3862
|
-
} else if (inTrial) {
|
|
3863
|
-
const expiryDate = new Date(Date.now() + remaining * 86400000).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
|
3864
|
-
console.log(` Status: 🎁 Active (${remaining} day${remaining === 1 ? '' : 's'} remaining)`);
|
|
3865
|
-
console.log(` Expires: ${expiryDate}`);
|
|
3866
|
-
console.log('');
|
|
3867
|
-
console.log(' All Pro features unlocked:');
|
|
3868
|
-
console.log(' • Unlimited prevention rules (free tier: 5)');
|
|
3869
|
-
console.log(' • Recall, lesson search, DPO export');
|
|
3870
|
-
console.log(' • Cross-machine sync via API key');
|
|
3871
|
-
} else {
|
|
3872
|
-
console.log(' Status: ⏰ Expired');
|
|
3873
|
-
if (ageDays !== null) {
|
|
3874
|
-
console.log(` Installed: ${Math.floor(ageDays)} days ago`);
|
|
3875
|
-
}
|
|
3876
|
-
console.log('');
|
|
3877
|
-
console.log(' Free tier: 5 active rules, no recall/search/export');
|
|
3878
|
-
}
|
|
3879
|
-
console.log('');
|
|
3880
|
-
console.log(` Keep Pro: ${PRO_CHECKOUT_URL}`);
|
|
3881
|
-
console.log(` Or set: THUMBGATE_API_KEY=<your-key>`);
|
|
3882
|
-
console.log('');
|
|
3883
|
-
break;
|
|
3884
|
-
}
|
|
3885
2748
|
case 'pro':
|
|
3886
2749
|
pro();
|
|
3887
2750
|
break;
|
|
@@ -3899,24 +2762,12 @@ switch (COMMAND) {
|
|
|
3899
2762
|
case 'status':
|
|
3900
2763
|
status();
|
|
3901
2764
|
break;
|
|
3902
|
-
case 'break-glass':
|
|
3903
|
-
case 'breakglass':
|
|
3904
|
-
breakGlass();
|
|
3905
|
-
break;
|
|
3906
2765
|
case 'funnel':
|
|
3907
2766
|
funnel();
|
|
3908
2767
|
break;
|
|
3909
2768
|
case 'pulse':
|
|
3910
2769
|
pulse();
|
|
3911
2770
|
break;
|
|
3912
|
-
case 'check-update':
|
|
3913
|
-
case 'upgrade-check':
|
|
3914
|
-
checkUpdateCmd();
|
|
3915
|
-
break;
|
|
3916
|
-
case 'self-update':
|
|
3917
|
-
case 'upgrade-cli':
|
|
3918
|
-
selfUpdateCmd();
|
|
3919
|
-
break;
|
|
3920
2771
|
case 'dispatch':
|
|
3921
2772
|
case 'dispatch-brief':
|
|
3922
2773
|
dispatchBrief();
|
|
@@ -3942,53 +2793,6 @@ switch (COMMAND) {
|
|
|
3942
2793
|
});
|
|
3943
2794
|
break;
|
|
3944
2795
|
}
|
|
3945
|
-
case 'hermes-gate': {
|
|
3946
|
-
// Nous Research Hermes Agent `pre_tool_call` shell hook.
|
|
3947
|
-
// Hermes pipes each pending tool call as JSON to stdin and reads a decision from stdout;
|
|
3948
|
-
// {"decision":"block","reason":...} vetoes the call. We reuse the SAME gate pipeline as
|
|
3949
|
-
// `gate-check` (runAsync → secret guard, security scan, force-push / skill_manage / learned
|
|
3950
|
-
// prevention rules) and translate the verdict into Hermes's format.
|
|
3951
|
-
//
|
|
3952
|
-
// Hermes `pre_tool_call` is binary (block or allow) with no warn channel, and the whole point
|
|
3953
|
-
// of wiring it is to gate, so we run STRICT enforcement by default — otherwise ThumbGate's
|
|
3954
|
-
// warn-by-default posture would pass every deny through and the hook would block nothing.
|
|
3955
|
-
// Opt out with THUMBGATE_HERMES_WARN_ONLY=1; THUMBGATE_HOTFIX_BYPASS=1 still disables checks.
|
|
3956
|
-
// Wire it in ~/.hermes/config.yaml — see adapters/hermes/config.yaml.
|
|
3957
|
-
if (process.env.THUMBGATE_HERMES_WARN_ONLY !== '1' && process.env.THUMBGATE_HOTFIX_BYPASS !== '1') {
|
|
3958
|
-
process.env.THUMBGATE_STRICT_ENFORCEMENT = '1';
|
|
3959
|
-
}
|
|
3960
|
-
const { runAsync: hermesGateRun } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
3961
|
-
let hermesStdin = '';
|
|
3962
|
-
process.stdin.setEncoding('utf8');
|
|
3963
|
-
process.stdin.on('data', (chunk) => { hermesStdin += chunk; });
|
|
3964
|
-
process.stdin.on('end', async () => {
|
|
3965
|
-
try {
|
|
3966
|
-
const payload = JSON.parse(hermesStdin);
|
|
3967
|
-
// Hermes sends snake_case tool_name/tool_input — gates-engine reads these directly.
|
|
3968
|
-
const verdict = await hermesGateRun({ tool_name: payload.tool_name, tool_input: payload.tool_input });
|
|
3969
|
-
let parsed = {};
|
|
3970
|
-
try { parsed = JSON.parse(verdict); } catch (_e) { parsed = {}; }
|
|
3971
|
-
const hso = parsed.hookSpecificOutput || {};
|
|
3972
|
-
if (hso.permissionDecision === 'deny') {
|
|
3973
|
-
process.stdout.write(JSON.stringify({
|
|
3974
|
-
decision: 'block',
|
|
3975
|
-
reason: hso.permissionDecisionReason || 'Blocked by ThumbGate prevention rule.',
|
|
3976
|
-
}) + '\n');
|
|
3977
|
-
} else {
|
|
3978
|
-
// warn / no match → allow. The gate engine already logged the decision.
|
|
3979
|
-
process.stdout.write(JSON.stringify({}) + '\n');
|
|
3980
|
-
}
|
|
3981
|
-
process.exit(0);
|
|
3982
|
-
} catch (err) {
|
|
3983
|
-
// Hermes hooks fail OPEN on error/timeout — emit an explicit allow so a gate fault
|
|
3984
|
-
// never wedges the agent (reliability ≈ enforcement; keep this fast).
|
|
3985
|
-
process.stderr.write(`hermes-gate error: ${err.message}\n`);
|
|
3986
|
-
process.stdout.write(JSON.stringify({}) + '\n');
|
|
3987
|
-
process.exit(0);
|
|
3988
|
-
}
|
|
3989
|
-
});
|
|
3990
|
-
break;
|
|
3991
|
-
}
|
|
3992
2796
|
case 'gate-stats':
|
|
3993
2797
|
gateStats();
|
|
3994
2798
|
break;
|
|
@@ -4043,29 +2847,6 @@ switch (COMMAND) {
|
|
|
4043
2847
|
}
|
|
4044
2848
|
break;
|
|
4045
2849
|
}
|
|
4046
|
-
case 'audit': {
|
|
4047
|
-
const auditFile = process.argv[3];
|
|
4048
|
-
if (!auditFile) {
|
|
4049
|
-
console.error('Usage: npx thumbgate audit <path-to-transcript.txt>');
|
|
4050
|
-
process.exit(1);
|
|
4051
|
-
}
|
|
4052
|
-
const { runAudit } = require(path.join(PKG_ROOT, 'scripts', 'audit'));
|
|
4053
|
-
const { results, totalWaste, error } = runAudit(auditFile);
|
|
4054
|
-
if (error) {
|
|
4055
|
-
console.error(error);
|
|
4056
|
-
process.exit(1);
|
|
4057
|
-
}
|
|
4058
|
-
console.log('\n🔍 AI Bill Audit Results\n');
|
|
4059
|
-
if (results.length === 0) {
|
|
4060
|
-
console.log('✅ No repeat-offender patterns found. Your sessions are efficient!');
|
|
4061
|
-
} else {
|
|
4062
|
-
console.table(results);
|
|
4063
|
-
console.log('\n💰 Total estimated monthly waste: $' + totalWaste);
|
|
4064
|
-
console.log('\nBlock these mistakes permanently with ThumbGate Pro:');
|
|
4065
|
-
console.log(PRO_CHECKOUT_URL);
|
|
4066
|
-
}
|
|
4067
|
-
break;
|
|
4068
|
-
}
|
|
4069
2850
|
case 'dashboard':
|
|
4070
2851
|
dashboard();
|
|
4071
2852
|
break;
|
|
@@ -4089,86 +2870,6 @@ switch (COMMAND) {
|
|
|
4089
2870
|
case 'compact':
|
|
4090
2871
|
compact();
|
|
4091
2872
|
break;
|
|
4092
|
-
case 'subscribe': {
|
|
4093
|
-
// Capture an installer's email so we can send the 5-minute setup
|
|
4094
|
-
// guide + weekly tips. Drops to /v1/marketing/install-email on the
|
|
4095
|
-
// hosted endpoint; the server fires sendNewsletterWelcomeEmail via
|
|
4096
|
-
// Resend and persists the address for follow-up.
|
|
4097
|
-
//
|
|
4098
|
-
// Usage:
|
|
4099
|
-
// npx thumbgate subscribe you@company.com
|
|
4100
|
-
// npx thumbgate subscribe --email=you@company.com
|
|
4101
|
-
//
|
|
4102
|
-
// Never prompts interactively — postinstall must stay non-interactive
|
|
4103
|
-
// for CI safety. This is the deliberate opt-in path the banner points
|
|
4104
|
-
// at; if the operator runs it, they want the email.
|
|
4105
|
-
const args = process.argv.slice(3);
|
|
4106
|
-
let email = '';
|
|
4107
|
-
for (const arg of args) {
|
|
4108
|
-
if (arg.startsWith('--email=')) email = arg.slice('--email='.length).trim();
|
|
4109
|
-
else if (!arg.startsWith('--')) email = arg.trim();
|
|
4110
|
-
}
|
|
4111
|
-
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4112
|
-
console.error('Usage: npx thumbgate subscribe <email>');
|
|
4113
|
-
console.error(' npx thumbgate subscribe --email=you@company.com');
|
|
4114
|
-
console.error('');
|
|
4115
|
-
console.error('We send a 5-minute setup guide + weekly tips. One-click unsubscribe.');
|
|
4116
|
-
process.exit(1);
|
|
4117
|
-
}
|
|
4118
|
-
const endpoint = process.env.THUMBGATE_INSTALL_EMAIL_ENDPOINT
|
|
4119
|
-
|| 'https://thumbgate.ai/v1/marketing/install-email';
|
|
4120
|
-
const payload = JSON.stringify({
|
|
4121
|
-
email,
|
|
4122
|
-
source: 'cli_subscribe',
|
|
4123
|
-
installId: (() => {
|
|
4124
|
-
try {
|
|
4125
|
-
const configPath = path.join(CWD, '.thumbgate', 'config.json');
|
|
4126
|
-
if (!fs.existsSync(configPath)) return null;
|
|
4127
|
-
return (JSON.parse(fs.readFileSync(configPath, 'utf8')).installId) || null;
|
|
4128
|
-
} catch { return null; }
|
|
4129
|
-
})(),
|
|
4130
|
-
cliVersion: pkgVersion(),
|
|
4131
|
-
});
|
|
4132
|
-
const url = new URL(endpoint);
|
|
4133
|
-
const transport = url.protocol === 'https:' ? require('node:https') : require('node:http');
|
|
4134
|
-
const req = transport.request({
|
|
4135
|
-
method: 'POST',
|
|
4136
|
-
hostname: url.hostname,
|
|
4137
|
-
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
4138
|
-
path: url.pathname + url.search,
|
|
4139
|
-
headers: {
|
|
4140
|
-
'Content-Type': 'application/json',
|
|
4141
|
-
'Content-Length': Buffer.byteLength(payload),
|
|
4142
|
-
'User-Agent': `thumbgate-cli/${pkgVersion()}`,
|
|
4143
|
-
},
|
|
4144
|
-
timeout: 8000,
|
|
4145
|
-
}, (res) => {
|
|
4146
|
-
let body = '';
|
|
4147
|
-
res.on('data', (chunk) => { body += chunk; });
|
|
4148
|
-
res.on('end', () => {
|
|
4149
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
4150
|
-
console.log(`✓ Subscribed ${email}.`);
|
|
4151
|
-
console.log(' Check your inbox in ~30 seconds for the setup guide.');
|
|
4152
|
-
console.log(' Unsubscribe link is in every email.');
|
|
4153
|
-
process.exit(0);
|
|
4154
|
-
} else {
|
|
4155
|
-
console.error(`✗ Subscribe failed: HTTP ${res.statusCode}${body ? ` — ${body.slice(0, 200)}` : ''}`);
|
|
4156
|
-
process.exit(2);
|
|
4157
|
-
}
|
|
4158
|
-
});
|
|
4159
|
-
});
|
|
4160
|
-
req.on('error', (err) => {
|
|
4161
|
-
console.error(`✗ Subscribe failed: ${err.message}`);
|
|
4162
|
-
console.error(' Network issue? Try again, or email igor.ganapolsky@gmail.com directly.');
|
|
4163
|
-
process.exit(3);
|
|
4164
|
-
});
|
|
4165
|
-
req.on('timeout', () => {
|
|
4166
|
-
req.destroy(new Error('timeout after 8000ms'));
|
|
4167
|
-
});
|
|
4168
|
-
req.write(payload);
|
|
4169
|
-
req.end();
|
|
4170
|
-
break;
|
|
4171
|
-
}
|
|
4172
2873
|
case 'checkin': {
|
|
4173
2874
|
// User check-in command — asks how it's going after install
|
|
4174
2875
|
const thumbgateDir = path.join(CWD, '.thumbgate');
|