thumbgate 1.27.19 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/dashboard.md +15 -0
- package/.claude/commands/thumbgate-blocked.md +27 -0
- package/.claude/commands/thumbgate-dashboard.md +15 -0
- package/.claude/commands/thumbgate-doctor.md +30 -0
- package/.claude/commands/thumbgate-guard.md +36 -0
- package/.claude/commands/thumbgate-protect.md +30 -0
- package/.claude/commands/thumbgate-rules.md +30 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +63 -45
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gcp/dfcx-webhook-gate.js +295 -0
- package/adapters/letta/README.md +41 -0
- package/adapters/letta/thumbgate-letta-adapter.js +133 -0
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
- package/bench/observability-eval-suite.json +26 -0
- package/bin/cli.js +52 -9
- package/bin/dashboard-cli.js +7 -0
- package/bin/postinstall.js +14 -23
- package/commands/dashboard.md +15 -0
- package/commands/thumbgate-dashboard.md +15 -0
- package/config/entitlement-public-keys.json +6 -0
- package/config/gates/default.json +1 -1
- package/config/github-about.json +2 -2
- package/config/merge-quality-checks.json +3 -0
- package/package.json +233 -100
- package/public/about.html +159 -0
- package/public/agent-manager.html +179 -0
- package/public/agents-cost-savings.html +153 -0
- package/public/ai-malpractice-prevention.html +818 -0
- 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 +28 -0
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +18 -0
- package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
- package/public/assets/brand/thumbgate-mark-team.svg +26 -0
- package/public/assets/brand/thumbgate-mark.svg +21 -0
- package/public/assets/brand/thumbgate-wordmark.svg +20 -0
- package/public/assets/claude-thumbgate-statusbar.svg +8 -0
- package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
- package/public/assets/legal-intake-control-flow.svg +66 -0
- package/public/blog.html +12 -9
- package/public/brand/thumbgate-mark.svg +19 -0
- package/public/brand/thumbgate-og.svg +16 -0
- package/public/chatgpt-app.html +330 -0
- package/public/codex-enterprise.html +123 -0
- package/public/codex-plugin.html +5 -5
- package/public/compare.html +10 -10
- package/public/dashboard.html +1 -1
- package/public/diagnostic.html +345 -0
- package/public/federal.html +2 -2
- package/public/guide.html +14 -14
- package/public/index.html +81 -70
- package/public/install.html +193 -0
- package/public/js/buyer-intent.js +672 -0
- package/public/learn.html +4 -17
- package/public/numbers.html +2 -2
- package/public/pricing.html +392 -0
- package/public/pro.html +2 -2
- package/scripts/action-receipts.js +324 -0
- package/scripts/activation-quickstart.js +187 -0
- package/scripts/agent-operations-planner.js +621 -0
- package/scripts/agent-reward-model.js +13 -0
- package/scripts/ai-component-inventory.js +367 -0
- package/scripts/async-eval-observability.js +236 -0
- package/scripts/audit.js +65 -0
- package/scripts/aws-blocks-guardrails.js +272 -0
- package/scripts/bayes-optimal-gate.js +6 -1
- package/scripts/classifier-routing.js +130 -0
- package/scripts/cli-feedback.js +17 -1
- package/scripts/commercial-offer.js +3 -3
- package/scripts/dashboard-chat.js +332 -0
- package/scripts/entitlement.js +250 -0
- package/scripts/export-databricks-bundle.js +5 -0
- package/scripts/export-dpo-pairs.js +6 -0
- package/scripts/export-hf-dataset.js +5 -0
- package/scripts/feedback-aggregate.js +281 -0
- package/scripts/feedback-sanitizer.js +105 -0
- package/scripts/gates-engine.js +15 -3
- package/scripts/hook-stop-anti-claim.js +301 -0
- package/scripts/imperative-detector.js +85 -0
- package/scripts/install-shim.js +87 -0
- package/scripts/intervention-policy.js +13 -0
- package/scripts/mcp-oauth.js +293 -0
- package/scripts/noop-detect.js +285 -0
- package/scripts/parallel-workflow-orchestrator.js +293 -0
- package/scripts/plan-gate.js +243 -0
- package/scripts/plausible-domain-config.js +99 -0
- package/scripts/pr-manager.js +9 -22
- package/scripts/pro-local-dashboard.js +198 -0
- package/scripts/qa-scenario-planner.js +136 -0
- package/scripts/repeat-metric.js +137 -0
- package/scripts/risk-scorer.js +6 -0
- package/scripts/secret-fixture-tokens.js +61 -0
- package/scripts/secret-redaction.js +166 -0
- package/scripts/self-harness-optimizer.js +141 -0
- package/scripts/self-healing-check.js +193 -0
- package/scripts/self-protection.js +90 -0
- package/scripts/seo-gsd.js +2 -2
- package/scripts/silent-failure-cluster.js +531 -0
- package/scripts/statusline-cache-read.js +57 -0
- package/scripts/sync-telemetry-from-prod.js +374 -0
- package/scripts/thompson-sampling.js +11 -2
- package/scripts/tool-contract-validator.js +76 -0
- package/scripts/trajectory-scorer.js +63 -0
- package/scripts/verify-marketing-pages-deployed.js +212 -0
- package/scripts/visitor-journey.js +172 -0
- package/src/api/server.js +81 -10
- package/.claude-plugin/marketplace.json +0 -85
- package/adapters/chatgpt/openapi.yaml +0 -1707
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { normalizeProviderAction } = require('../../scripts/provider-action-normalizer');
|
|
5
|
+
|
|
6
|
+
const BLOCK_DECISIONS = new Set([
|
|
7
|
+
'block',
|
|
8
|
+
'blocked',
|
|
9
|
+
'deny',
|
|
10
|
+
'denied',
|
|
11
|
+
'disallow',
|
|
12
|
+
'disallowed',
|
|
13
|
+
'fail',
|
|
14
|
+
'failed',
|
|
15
|
+
'forbid',
|
|
16
|
+
'forbidden',
|
|
17
|
+
'reject',
|
|
18
|
+
'rejected',
|
|
19
|
+
'unsafe',
|
|
20
|
+
'violation',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const REVIEW_DECISIONS = new Set([
|
|
24
|
+
'approval',
|
|
25
|
+
'approval-required',
|
|
26
|
+
'approval_required',
|
|
27
|
+
'approve',
|
|
28
|
+
'human-review',
|
|
29
|
+
'human_review',
|
|
30
|
+
'manual-review',
|
|
31
|
+
'manual_review',
|
|
32
|
+
'review',
|
|
33
|
+
'requires-approval',
|
|
34
|
+
'requires_approval',
|
|
35
|
+
'requires-review',
|
|
36
|
+
'requires_review',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const ALLOW_DECISIONS = new Set([
|
|
40
|
+
'accept',
|
|
41
|
+
'accepted',
|
|
42
|
+
'allow',
|
|
43
|
+
'allowed',
|
|
44
|
+
'ok',
|
|
45
|
+
'pass',
|
|
46
|
+
'passed',
|
|
47
|
+
'permit',
|
|
48
|
+
'permitted',
|
|
49
|
+
'safe',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
function asObject(value) {
|
|
53
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function asArray(value) {
|
|
57
|
+
return Array.isArray(value) ? value : [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function firstString(...values) {
|
|
61
|
+
for (const value of values) {
|
|
62
|
+
const text = String(value || '').trim();
|
|
63
|
+
if (text) return text;
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeDecisionToken(value) {
|
|
69
|
+
return String(value || '')
|
|
70
|
+
.trim()
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
.replace(/\s+/g, '-');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeEvidence(value) {
|
|
76
|
+
const direct = asArray(value.evidence);
|
|
77
|
+
const citations = asArray(value.citations);
|
|
78
|
+
const violations = asArray(value.violations);
|
|
79
|
+
const reasons = asArray(value.reasons);
|
|
80
|
+
const reasoning = asArray(value.reasoning);
|
|
81
|
+
const threatTypes = asArray(value.threat_types || value.threatTypes)
|
|
82
|
+
.map((threatType) => ({
|
|
83
|
+
code: String(threatType || '').trim(),
|
|
84
|
+
message: `Threat type: ${String(threatType || '').trim()}`,
|
|
85
|
+
source: 'guardian',
|
|
86
|
+
severity: firstString(value.threat_level, value.threatLevel),
|
|
87
|
+
}));
|
|
88
|
+
return [...direct, ...citations, ...violations, ...reasons, ...reasoning, ...threatTypes]
|
|
89
|
+
.map((entry) => {
|
|
90
|
+
if (typeof entry === 'string') return { text: entry };
|
|
91
|
+
const object = asObject(entry);
|
|
92
|
+
if (!Object.keys(object).length) return null;
|
|
93
|
+
return {
|
|
94
|
+
id: firstString(object.id, object.ruleId, object.rule_id, object.code),
|
|
95
|
+
text: firstString(object.text, object.reason, object.message, object.description, object.title),
|
|
96
|
+
source: firstString(object.source, object.provider, object.policy),
|
|
97
|
+
severity: firstString(object.severity, object.level),
|
|
98
|
+
raw: object,
|
|
99
|
+
};
|
|
100
|
+
})
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractPolicyDecision(input = {}) {
|
|
105
|
+
const event = asObject(input);
|
|
106
|
+
for (const candidate of [
|
|
107
|
+
event.policyDecision,
|
|
108
|
+
event.policy_decision,
|
|
109
|
+
event.guardrailResult,
|
|
110
|
+
event.guardrail_result,
|
|
111
|
+
event.result,
|
|
112
|
+
]) {
|
|
113
|
+
const object = asObject(candidate);
|
|
114
|
+
if (Object.keys(object).length) return object;
|
|
115
|
+
}
|
|
116
|
+
return event;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function classifyPolicyDecision(input = {}) {
|
|
120
|
+
const value = extractPolicyDecision(input);
|
|
121
|
+
const token = normalizeDecisionToken(firstString(
|
|
122
|
+
value.decision,
|
|
123
|
+
value.action,
|
|
124
|
+
value.status,
|
|
125
|
+
value.result,
|
|
126
|
+
value.verdict,
|
|
127
|
+
value.outcome,
|
|
128
|
+
value.effect,
|
|
129
|
+
value.recommended_action,
|
|
130
|
+
value.recommendedAction,
|
|
131
|
+
));
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
value.allowed === false
|
|
135
|
+
|| value.is_safe === false
|
|
136
|
+
|| value.isSafe === false
|
|
137
|
+
|| value.accepted === false
|
|
138
|
+
|| value.blocked === true
|
|
139
|
+
|| value.denied === true
|
|
140
|
+
|| BLOCK_DECISIONS.has(token)
|
|
141
|
+
) {
|
|
142
|
+
return 'block';
|
|
143
|
+
}
|
|
144
|
+
if (
|
|
145
|
+
value.requiresApproval === true
|
|
146
|
+
|| value.requires_approval === true
|
|
147
|
+
|| value.reviewRequired === true
|
|
148
|
+
|| value.review_required === true
|
|
149
|
+
|| REVIEW_DECISIONS.has(token)
|
|
150
|
+
) {
|
|
151
|
+
return 'approval_required';
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
value.allowed === true
|
|
155
|
+
|| value.is_safe === true
|
|
156
|
+
|| value.isSafe === true
|
|
157
|
+
|| value.accepted === true
|
|
158
|
+
|| ALLOW_DECISIONS.has(token)
|
|
159
|
+
) {
|
|
160
|
+
return 'allow';
|
|
161
|
+
}
|
|
162
|
+
return 'unknown';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function normalizePolicyDecision(input = {}, options = {}) {
|
|
166
|
+
const value = extractPolicyDecision(input);
|
|
167
|
+
const decision = classifyPolicyDecision(value);
|
|
168
|
+
const source = firstString(
|
|
169
|
+
options.source,
|
|
170
|
+
value.source,
|
|
171
|
+
value.provider,
|
|
172
|
+
value.engine,
|
|
173
|
+
value.policyEngine,
|
|
174
|
+
value.policy_engine,
|
|
175
|
+
'policy-engine'
|
|
176
|
+
);
|
|
177
|
+
const reason = firstString(
|
|
178
|
+
value.reason,
|
|
179
|
+
value.message,
|
|
180
|
+
value.explanation,
|
|
181
|
+
value.summary,
|
|
182
|
+
asArray(value.reasoning).join('; '),
|
|
183
|
+
asArray(value.reasons).join('; '),
|
|
184
|
+
decision === 'unknown' ? 'Policy engine returned an unknown decision; approval required before execution.' : ''
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
allowed: decision === 'allow',
|
|
189
|
+
blocked: decision === 'block',
|
|
190
|
+
approvalRequired: decision === 'approval_required' || decision === 'unknown',
|
|
191
|
+
decision,
|
|
192
|
+
reason,
|
|
193
|
+
source,
|
|
194
|
+
confidence: Number.isFinite(Number(value.confidence)) ? Number(value.confidence) : null,
|
|
195
|
+
policyId: firstString(value.policyId, value.policy_id, value.ruleId, value.rule_id, value.id),
|
|
196
|
+
severity: firstString(value.severity, value.level, value.threat_level, value.threatLevel),
|
|
197
|
+
score: Number.isFinite(Number(value.score))
|
|
198
|
+
? Number(value.score)
|
|
199
|
+
: (Number.isFinite(Number(value.threat_score)) ? Number(value.threat_score) : null),
|
|
200
|
+
evidence: normalizeEvidence(value),
|
|
201
|
+
raw: value,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizePolicyAction(input = {}) {
|
|
206
|
+
const event = asObject(input);
|
|
207
|
+
return {
|
|
208
|
+
...normalizeProviderAction({
|
|
209
|
+
...event,
|
|
210
|
+
provider: firstString(event.provider, event.agentRuntime, event.runtime, 'policy-engine'),
|
|
211
|
+
toolName: firstString(event.toolName, event.tool_name, event.name),
|
|
212
|
+
input: asObject(event.toolInput || event.input || event.arguments || event.args),
|
|
213
|
+
}),
|
|
214
|
+
policyContext: asObject(event.policyContext || event.policy_context),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function createPolicyEngineGuard({
|
|
219
|
+
policyCheck,
|
|
220
|
+
executeTool,
|
|
221
|
+
gateCheck,
|
|
222
|
+
onDecision,
|
|
223
|
+
source = 'policy-engine',
|
|
224
|
+
} = {}) {
|
|
225
|
+
if (typeof policyCheck !== 'function') {
|
|
226
|
+
throw new TypeError('createPolicyEngineGuard requires a policyCheck function');
|
|
227
|
+
}
|
|
228
|
+
if (typeof executeTool !== 'function') {
|
|
229
|
+
throw new TypeError('createPolicyEngineGuard requires an executeTool function');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return async function guardedPolicyTool(input = {}) {
|
|
233
|
+
const normalizedAction = normalizePolicyAction(input);
|
|
234
|
+
const policyDecision = normalizePolicyDecision(await policyCheck(normalizedAction), { source });
|
|
235
|
+
const gateDecision = typeof gateCheck === 'function'
|
|
236
|
+
? normalizePolicyDecision(await gateCheck({ normalizedAction, policyDecision }), { source: 'thumbgate' })
|
|
237
|
+
: null;
|
|
238
|
+
const effectiveDecision = gateDecision && !gateDecision.allowed ? gateDecision : policyDecision;
|
|
239
|
+
|
|
240
|
+
if (typeof onDecision === 'function') {
|
|
241
|
+
await onDecision({ normalizedAction, policyDecision, gateDecision, effectiveDecision });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!effectiveDecision.allowed) {
|
|
245
|
+
const error = new Error(effectiveDecision.reason || 'ThumbGate blocked this action before execution.');
|
|
246
|
+
error.code = effectiveDecision.approvalRequired ? 'THUMBGATE_APPROVAL_REQUIRED' : 'THUMBGATE_BLOCKED';
|
|
247
|
+
error.thumbgate = { normalizedAction, policyDecision, gateDecision, effectiveDecision };
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return executeTool(input, { normalizedAction, policyDecision, gateDecision, effectiveDecision });
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = {
|
|
256
|
+
createPolicyEngineGuard,
|
|
257
|
+
extractPolicyDecision,
|
|
258
|
+
normalizePolicyAction,
|
|
259
|
+
normalizePolicyDecision,
|
|
260
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cases": [
|
|
3
|
+
{
|
|
4
|
+
"id": "checkout-link-grounding",
|
|
5
|
+
"traceId": "trace_checkout_001",
|
|
6
|
+
"question": "Is the Pro checkout link working?",
|
|
7
|
+
"response": "The Pro checkout link is working because the checkout diagnostic confirms Stripe Pro checkout is reachable.",
|
|
8
|
+
"retrievedContexts": [
|
|
9
|
+
"Stripe Pro checkout diagnostic confirms the Pro checkout link is reachable and returns a valid checkout target.",
|
|
10
|
+
"The workflow sprint checkout is intake-led and should not expose a stale payment link."
|
|
11
|
+
],
|
|
12
|
+
"reference": "The Pro checkout link is reachable when the Stripe diagnostic passes."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "letta-tool-gate",
|
|
16
|
+
"traceId": "trace_letta_001",
|
|
17
|
+
"question": "Should Letta execute a force push tool call?",
|
|
18
|
+
"response": "ThumbGate blocks force push before execution for Letta tool calls.",
|
|
19
|
+
"retrievedContexts": [
|
|
20
|
+
"ThumbGate's Letta adapter wraps Letta tool calls and runs gate-check before the executor is called.",
|
|
21
|
+
"Force push to main is a blocked high-risk git action."
|
|
22
|
+
],
|
|
23
|
+
"reference": "ThumbGate blocks high-risk Letta tool calls before execution."
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
package/bin/cli.js
CHANGED
|
@@ -134,7 +134,7 @@ function upgradeNudge() {
|
|
|
134
134
|
'\n Team rollout: start with the $499 Workflow Hardening Diagnostic\n' +
|
|
135
135
|
` ${diagnosticUrl}\n` +
|
|
136
136
|
`\n Solo side lane: Pro — ${PRO_PRICE_LABEL}\n` +
|
|
137
|
-
'
|
|
137
|
+
' Removes solo caps; adds personal recall, dashboard proof, exports, and managed adapters.\n' +
|
|
138
138
|
` ${pricingUrl}\n\n`
|
|
139
139
|
);
|
|
140
140
|
}
|
|
@@ -234,9 +234,9 @@ function proNudge(context) {
|
|
|
234
234
|
const checkoutUrl = checkoutUrlFor('cli_nudge', context || COMMAND || 'general');
|
|
235
235
|
const pricingUrl = pricingUrlFor('cli_nudge', context || COMMAND || 'general');
|
|
236
236
|
const messages = [
|
|
237
|
-
`\n 💡 Pro (${PRO_PRICE_LABEL}):
|
|
238
|
-
`\n 💡 You just taught ThumbGate something locally. Pro keeps
|
|
239
|
-
`\n 💡 ThumbGate Pro
|
|
237
|
+
`\n 💡 Pro (${PRO_PRICE_LABEL}): personal recall, dashboard proof, exports, and managed adapters.\n See pricing: ${pricingUrl}\n`,
|
|
238
|
+
`\n 💡 You just taught ThumbGate something locally. Pro keeps personal lessons searchable and exportable without free-tier caps.\n See pricing: ${pricingUrl}\n`,
|
|
239
|
+
`\n 💡 ThumbGate Pro maintains adapter coverage across Claude, Codex, Cursor, containers, and CI. ${PRO_PRICE_LABEL}.\n Start Pro: ${checkoutUrl}\n`,
|
|
240
240
|
];
|
|
241
241
|
// Rotate message daily — no Math.random (security policy)
|
|
242
242
|
const msg = messages[Math.floor(Date.now() / 86400000) % messages.length];
|
|
@@ -921,7 +921,7 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
921
921
|
console.log('Scaffold ThumbGate in the current project and wire detected agent integrations.');
|
|
922
922
|
console.log('');
|
|
923
923
|
console.log('Options:');
|
|
924
|
-
console.log(' --agent <name> Wire a specific agent: claude-code, codex, gemini, amp, cursor, cline');
|
|
924
|
+
console.log(' --agent <name> Wire a specific agent: claude-code, codex, gemini, amp, cursor, cline, opencode');
|
|
925
925
|
console.log(' --wire-hooks Wire hooks only; do not scaffold project files');
|
|
926
926
|
console.log(' --email <email> Subscribe installer to the setup guide and trial reminders');
|
|
927
927
|
console.log(' --dry-run Show hook changes without writing them');
|
|
@@ -1593,6 +1593,7 @@ function pro() {
|
|
|
1593
1593
|
trackEvent('cli_pro_view', { command: 'pro' });
|
|
1594
1594
|
const args = parseArgs(process.argv.slice(3));
|
|
1595
1595
|
const {
|
|
1596
|
+
notifyHostedProActivation,
|
|
1596
1597
|
resolveProKey,
|
|
1597
1598
|
saveLicense,
|
|
1598
1599
|
startLocalProDashboard,
|
|
@@ -1606,9 +1607,10 @@ function pro() {
|
|
|
1606
1607
|
console.log('Self-serve side lane today: Pro ($19/mo or $149/yr).');
|
|
1607
1608
|
console.log('Every licensed Pro user gets a personal local dashboard on localhost.');
|
|
1608
1609
|
console.log('\nWhat is available:');
|
|
1609
|
-
console.log(' -
|
|
1610
|
+
console.log(' - Personal recall: search lessons, rules, and proof');
|
|
1610
1611
|
console.log(' - Local Pro dashboard: your own browser dashboard for search, gates, and DPO export');
|
|
1611
|
-
console.log(' -
|
|
1612
|
+
console.log(' - Managed adapters: Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode');
|
|
1613
|
+
console.log(' - Team rollout path: Enterprise adds shared hosted lessons, org visibility, workflow proof');
|
|
1612
1614
|
console.log(' - Commercial truth doc: source of truth for traction, pricing, and proof claims');
|
|
1613
1615
|
console.log('\nLinks:');
|
|
1614
1616
|
console.log(` Buy Pro : ${PRO_CHECKOUT_URL}`);
|
|
@@ -1661,7 +1663,22 @@ function pro() {
|
|
|
1661
1663
|
console.log('\n✅ Pro license activated!');
|
|
1662
1664
|
console.log(` Key saved to: ${licensePath}`);
|
|
1663
1665
|
console.log(' Launching your personal local dashboard...\n');
|
|
1664
|
-
return
|
|
1666
|
+
return notifyHostedProActivation({
|
|
1667
|
+
key: license.key,
|
|
1668
|
+
source: 'cli_pro_activate',
|
|
1669
|
+
version: license.version,
|
|
1670
|
+
})
|
|
1671
|
+
.then((notificationResult) => {
|
|
1672
|
+
appendLocalTelemetry({
|
|
1673
|
+
eventType: 'pro_activation_alert',
|
|
1674
|
+
version: license.version,
|
|
1675
|
+
timestamp: new Date().toISOString(),
|
|
1676
|
+
notified: Boolean(notificationResult && notificationResult.notified),
|
|
1677
|
+
sent: Boolean(notificationResult && notificationResult.alert && notificationResult.alert.sent),
|
|
1678
|
+
reason: notificationResult && notificationResult.reason ? notificationResult.reason : null,
|
|
1679
|
+
});
|
|
1680
|
+
return launchDashboard(license.key, 'pro_activate');
|
|
1681
|
+
});
|
|
1665
1682
|
}
|
|
1666
1683
|
|
|
1667
1684
|
if (args.upgrade) {
|
|
@@ -3934,6 +3951,7 @@ switch (COMMAND) {
|
|
|
3934
3951
|
// PreToolUse hook interface: reads tool call JSON from stdin, outputs gate verdict
|
|
3935
3952
|
// Used by: generate-pretool-hook.sh → npx thumbgate gate-check
|
|
3936
3953
|
const { run: gateRun, runAsync: gateRunAsync } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
3954
|
+
const { evaluateSelfProtection } = require(path.join(PKG_ROOT, 'scripts', 'self-protection'));
|
|
3937
3955
|
let stdinData = '';
|
|
3938
3956
|
process.stdin.setEncoding('utf8');
|
|
3939
3957
|
process.stdin.on('data', (chunk) => { stdinData += chunk; });
|
|
@@ -3941,7 +3959,32 @@ switch (COMMAND) {
|
|
|
3941
3959
|
try {
|
|
3942
3960
|
const input = JSON.parse(stdinData);
|
|
3943
3961
|
const output = await gateRunAsync(input);
|
|
3944
|
-
|
|
3962
|
+
// Self-protection overlay (2026-07-08): the gate engine only denies edits
|
|
3963
|
+
// to ThumbGate's own governance files under strict enforcement; by default
|
|
3964
|
+
// such edits pass as a clean ALLOW. Surface them (or block in strict) so an
|
|
3965
|
+
// agent can't silently disable the firewall. Only overlays when the engine
|
|
3966
|
+
// did not already produce a hard deny.
|
|
3967
|
+
let verdict = output;
|
|
3968
|
+
try {
|
|
3969
|
+
const parsed = JSON.parse(output || '{}');
|
|
3970
|
+
const alreadyDeny = (parsed.hookSpecificOutput && parsed.hookSpecificOutput.permissionDecision === 'deny')
|
|
3971
|
+
|| parsed.decision === 'block';
|
|
3972
|
+
if (!alreadyDeny) {
|
|
3973
|
+
const sp = evaluateSelfProtection(input.tool_name, input.tool_input);
|
|
3974
|
+
if (sp && sp.action === 'block') {
|
|
3975
|
+
verdict = JSON.stringify({
|
|
3976
|
+
decision: 'block',
|
|
3977
|
+
reason: sp.message,
|
|
3978
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: sp.message },
|
|
3979
|
+
});
|
|
3980
|
+
} else if (sp && sp.action === 'warn') {
|
|
3981
|
+
verdict = JSON.stringify({
|
|
3982
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: sp.message },
|
|
3983
|
+
});
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
} catch (_e) { /* non-JSON engine output: pass through unchanged */ }
|
|
3987
|
+
process.stdout.write(verdict + '\n');
|
|
3945
3988
|
process.exit(0);
|
|
3946
3989
|
} catch (err) {
|
|
3947
3990
|
process.stderr.write(`gate-check error: ${err.message}\n`);
|
package/bin/postinstall.js
CHANGED
|
@@ -15,41 +15,32 @@ if (isCI || isQuiet) process.exit(0);
|
|
|
15
15
|
const {
|
|
16
16
|
PRO_MONTHLY_PAYMENT_LINK,
|
|
17
17
|
PRO_PRICE_LABEL,
|
|
18
|
+
TEAM_PRICE_LABEL,
|
|
18
19
|
} = require('../scripts/commercial-offer');
|
|
19
20
|
|
|
20
21
|
// Tracked click-through path: /go/pro → /checkout/pro → Stripe.
|
|
22
|
+
// This captures UTM attribution in our funnel before handing off to Stripe.
|
|
21
23
|
const PRO_CTA_URL = 'https://thumbgate.ai/go/pro?utm_source=npm&utm_medium=postinstall&utm_campaign=first_dollar';
|
|
24
|
+
const WORKFLOW_SPRINT_URL = 'https://thumbgate.ai/#workflow-sprint-intake';
|
|
22
25
|
const DASHBOARD_URL = 'https://thumbgate.ai/dashboard?utm_source=npm&utm_medium=postinstall&utm_campaign=dashboard_nudge';
|
|
23
26
|
|
|
24
27
|
process.stderr.write(`
|
|
25
28
|
╭─────────────────────────────────────────────────────╮
|
|
26
|
-
│ ThumbGate installed.
|
|
29
|
+
│ ThumbGate installed — 7-day Pro trial is live. │
|
|
27
30
|
│ │
|
|
28
|
-
│
|
|
29
|
-
│
|
|
30
|
-
│ BEFORE the model sees them — zero tokens spent │
|
|
31
|
-
│ on mistakes you've already corrected. │
|
|
31
|
+
│ Start now: npx thumbgate init │
|
|
32
|
+
│ Updates: npx thumbgate subscribe you@company.com│
|
|
32
33
|
│ │
|
|
33
|
-
│
|
|
34
|
-
│
|
|
35
|
-
│ npx thumbgate stats │
|
|
36
|
-
│ │
|
|
37
|
-
│ See your gates firing live: │
|
|
38
|
-
│ ${DASHBOARD_URL.slice(0, 47).padEnd(47, ' ')} │
|
|
34
|
+
│ Free after trial: 3 rules, 2 captures/day. │
|
|
35
|
+
│ Pro ($19/mo): unlimited everything. │
|
|
39
36
|
╰─────────────────────────────────────────────────────╯
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Org dashboard. DPO export. 24×7 ops.
|
|
46
|
-
Upgrade: ${PRO_CTA_URL}
|
|
47
|
-
Direct: ${PRO_MONTHLY_PAYMENT_LINK}
|
|
48
|
-
|
|
49
|
-
⚡ LAUNCH PROMO: 50% off first 3 months with code THUMB50
|
|
50
|
-
One-click (discount applied): https://buy.stripe.com/eVqeVd62o0aO19v7mb3sI3g
|
|
51
|
-
Limited to first 50 customers. July 2026
|
|
38
|
+
Trial unlocks: unlimited rules, lesson search, DPO export,
|
|
39
|
+
and personal dashboard proof. After 7 days, free tier limits apply.
|
|
40
|
+
Subscribe for the 5-min setup guide + weekly tips:
|
|
41
|
+
npx thumbgate subscribe you@company.com
|
|
52
42
|
|
|
53
|
-
|
|
43
|
+
Dashboard: ${DASHBOARD_URL}
|
|
44
|
+
Keep Pro: ${PRO_CTA_URL}
|
|
54
45
|
|
|
55
46
|
`);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dashboard
|
|
3
|
+
description: Open the local HTTP dashboard for the current project in your web browser.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Open Dashboard
|
|
7
|
+
|
|
8
|
+
Open the local HTTP dashboard for the current project in your web browser.
|
|
9
|
+
|
|
10
|
+
## Instructions
|
|
11
|
+
Execute the following command in the project directory to open the browser dashboard scoped to the current repository:
|
|
12
|
+
```bash
|
|
13
|
+
thumbgate-dashboard
|
|
14
|
+
```
|
|
15
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: thumbgate-dashboard
|
|
3
|
+
description: Open the local HTTP dashboard for the current project in your web browser.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Open Scoped ThumbGate Dashboard
|
|
7
|
+
|
|
8
|
+
Open the local HTTP dashboard for the current project in your web browser.
|
|
9
|
+
|
|
10
|
+
## Instructions
|
|
11
|
+
Execute the following command in the project directory to open the browser dashboard:
|
|
12
|
+
```bash
|
|
13
|
+
thumbgate-dashboard
|
|
14
|
+
```
|
|
15
|
+
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
{
|
|
211
211
|
"id": "deny-network-egress",
|
|
212
212
|
"layer": "Cloud",
|
|
213
|
-
"pattern": "
|
|
213
|
+
"pattern": "fetch\\(|https?://(?!(?:github\\.com|registry\\.npmjs\\.org|api\\.anthropic\\.com|localhost|127\\.0\\.0\\.1|\\[::1\\]|0\\.0\\.0\\.0)(?:[:/?#]|$))|(?:curl|wget)\\s+(?:-\\S+\\s+)*(?!https?://|(?:github\\.com|registry\\.npmjs\\.org|api\\.anthropic\\.com|localhost|127\\.0\\.0\\.1|\\[::1\\]|0\\.0\\.0\\.0)(?:[:/?#]|$))[\\w.-]+\\.[a-z]{2,}",
|
|
214
214
|
"action": "warn",
|
|
215
215
|
"unless": "egress_approved",
|
|
216
216
|
"message": "Potential unauthorized network egress detected.",
|
package/config/github-about.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"repo": "IgorGanapolsky/ThumbGate",
|
|
3
3
|
"repositoryUrl": "https://github.com/IgorGanapolsky/ThumbGate",
|
|
4
4
|
"homepageUrl": "https://thumbgate.ai",
|
|
5
|
-
"githubDescription": "Agent governance
|
|
6
|
-
"metaDescription": "
|
|
5
|
+
"githubDescription": "Agent governance: thumbs become Pre-Action Checks that flag repeat mistakes and hard-block catastrophic classes before code or systems change.",
|
|
6
|
+
"metaDescription": "ThumbGate is the pre-action governance runtime for AI coding agents (Claude Code, Cursor, Codex, Gemini CLI, Amp, Cline, OpenCode): 👍 thumbs up and 👎 thumbs down feedback become history-aware lessons stored locally; Enterprise adds shared lessons and org visibility. PreToolUse checks flag and log risky tool calls — hard-blocking catastrophic classes by default and warning on the rest, with strict mode to hard-block every rule.",
|
|
7
7
|
"topics": [
|
|
8
8
|
"thumbgate",
|
|
9
9
|
"pre-action-checks",
|