thumbgate 1.27.19 → 1.27.20

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.
Files changed (87) hide show
  1. package/.claude/commands/dashboard.md +15 -0
  2. package/.claude/commands/thumbgate-blocked.md +27 -0
  3. package/.claude/commands/thumbgate-dashboard.md +15 -0
  4. package/.claude/commands/thumbgate-doctor.md +30 -0
  5. package/.claude/commands/thumbgate-guard.md +36 -0
  6. package/.claude/commands/thumbgate-protect.md +30 -0
  7. package/.claude/commands/thumbgate-rules.md +30 -0
  8. package/.claude-plugin/plugin.json +1 -1
  9. package/.well-known/mcp/server-card.json +1 -1
  10. package/README.md +0 -3
  11. package/adapters/claude/.mcp.json +2 -2
  12. package/adapters/gcp/dfcx-webhook-gate.js +295 -0
  13. package/adapters/letta/README.md +41 -0
  14. package/adapters/letta/thumbgate-letta-adapter.js +133 -0
  15. package/adapters/mcp/server-stdio.js +1 -1
  16. package/adapters/opencode/opencode.json +1 -1
  17. package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
  18. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
  19. package/bench/observability-eval-suite.json +26 -0
  20. package/bin/cli.js +27 -1
  21. package/bin/dashboard-cli.js +7 -0
  22. package/bin/postinstall.js +14 -23
  23. package/commands/dashboard.md +15 -0
  24. package/commands/thumbgate-dashboard.md +15 -0
  25. package/package.json +225 -100
  26. package/public/about.html +162 -0
  27. package/public/agent-manager.html +179 -0
  28. package/public/agents-cost-savings.html +153 -0
  29. package/public/ai-malpractice-prevention.html +818 -0
  30. package/public/assets/brand/github-social-preview.png +0 -0
  31. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  32. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  33. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  34. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  35. package/public/assets/brand/thumbgate-logo-transparent.svg +28 -0
  36. package/public/assets/brand/thumbgate-mark-inline-v3.svg +18 -0
  37. package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
  38. package/public/assets/brand/thumbgate-mark-team.svg +26 -0
  39. package/public/assets/brand/thumbgate-mark.svg +21 -0
  40. package/public/assets/brand/thumbgate-wordmark.svg +20 -0
  41. package/public/assets/claude-thumbgate-statusbar.svg +8 -0
  42. package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
  43. package/public/assets/legal-intake-control-flow.svg +66 -0
  44. package/public/brand/thumbgate-mark.svg +19 -0
  45. package/public/brand/thumbgate-og.svg +16 -0
  46. package/public/chatgpt-app.html +330 -0
  47. package/public/codex-enterprise.html +123 -0
  48. package/public/diagnostic.html +345 -0
  49. package/public/index.html +2 -2
  50. package/public/install.html +193 -0
  51. package/public/js/buyer-intent.js +672 -0
  52. package/public/numbers.html +2 -2
  53. package/public/pricing.html +399 -0
  54. package/scripts/action-receipts.js +324 -0
  55. package/scripts/activation-quickstart.js +187 -0
  56. package/scripts/agent-operations-planner.js +621 -0
  57. package/scripts/ai-component-inventory.js +367 -0
  58. package/scripts/async-eval-observability.js +236 -0
  59. package/scripts/audit.js +65 -0
  60. package/scripts/aws-blocks-guardrails.js +272 -0
  61. package/scripts/classifier-routing.js +130 -0
  62. package/scripts/dashboard-chat.js +332 -0
  63. package/scripts/feedback-aggregate.js +281 -0
  64. package/scripts/feedback-sanitizer.js +105 -0
  65. package/scripts/hook-stop-anti-claim.js +301 -0
  66. package/scripts/install-shim.js +87 -0
  67. package/scripts/mcp-oauth.js +293 -0
  68. package/scripts/noop-detect.js +285 -0
  69. package/scripts/parallel-workflow-orchestrator.js +293 -0
  70. package/scripts/plan-gate.js +243 -0
  71. package/scripts/plausible-domain-config.js +99 -0
  72. package/scripts/qa-scenario-planner.js +136 -0
  73. package/scripts/repeat-metric.js +137 -0
  74. package/scripts/secret-fixture-tokens.js +61 -0
  75. package/scripts/secret-redaction.js +166 -0
  76. package/scripts/self-harness-optimizer.js +141 -0
  77. package/scripts/self-healing-check.js +193 -0
  78. package/scripts/self-protection.js +90 -0
  79. package/scripts/silent-failure-cluster.js +531 -0
  80. package/scripts/statusline-cache-read.js +57 -0
  81. package/scripts/sync-telemetry-from-prod.js +374 -0
  82. package/scripts/tool-contract-validator.js +76 -0
  83. package/scripts/trajectory-scorer.js +63 -0
  84. package/scripts/verify-marketing-pages-deployed.js +212 -0
  85. package/scripts/visitor-journey.js +172 -0
  86. package/.claude-plugin/marketplace.json +0 -85
  87. package/adapters/chatgpt/openapi.yaml +0 -1707
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { normalizeProviderAction } = require('../../scripts/provider-action-normalizer');
5
+
6
+ function asObject(value) {
7
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
8
+ }
9
+
10
+ function firstString(...values) {
11
+ for (const value of values) {
12
+ const text = String(value || '').trim();
13
+ if (text) return text;
14
+ }
15
+ return '';
16
+ }
17
+
18
+ function extractLettaToolCall(input = {}) {
19
+ const event = asObject(input);
20
+ const toolCall = asObject(event.toolCall || event.tool_call || event.lettaToolCall);
21
+ const functionCall = asObject(toolCall.function || event.function);
22
+ const mcp = asObject(event.mcp || event.mcpToolCall);
23
+ const clientTool = asObject(event.clientTool || event.client_tool);
24
+ const params = asObject(event.params);
25
+
26
+ const name = firstString(
27
+ event.toolName,
28
+ event.name,
29
+ toolCall.name,
30
+ functionCall.name,
31
+ clientTool.name,
32
+ mcp.name,
33
+ params.name
34
+ );
35
+ const args = asObject(
36
+ event.arguments
37
+ || event.args
38
+ || event.input
39
+ || toolCall.arguments
40
+ || toolCall.input
41
+ || functionCall.arguments
42
+ || clientTool.arguments
43
+ || clientTool.input
44
+ || mcp.arguments
45
+ || params.arguments
46
+ );
47
+
48
+ return {
49
+ id: firstString(event.id, event.toolCallId, event.tool_call_id, toolCall.id, clientTool.id),
50
+ name,
51
+ arguments: args,
52
+ server: firstString(event.mcpServer, mcp.server, params.server),
53
+ surface: firstString(event.surface, event.executionSurface, clientTool.name ? 'client-tool' : mcp.name || params.name ? 'mcp-tool' : 'server-tool'),
54
+ };
55
+ }
56
+
57
+ function normalizeLettaAction(input = {}) {
58
+ const event = asObject(input);
59
+ const toolCall = extractLettaToolCall(event);
60
+
61
+ const normalized = normalizeProviderAction({
62
+ ...event,
63
+ provider: 'letta',
64
+ toolCall: {
65
+ id: toolCall.id,
66
+ name: toolCall.name,
67
+ input: toolCall.arguments,
68
+ },
69
+ toolName: toolCall.name,
70
+ input: toolCall.arguments,
71
+ mcpServer: toolCall.server,
72
+ });
73
+
74
+ return {
75
+ ...normalized,
76
+ provider: 'letta',
77
+ agentRuntime: 'letta',
78
+ letta: {
79
+ agentId: firstString(event.agentId, event.agent_id),
80
+ messageId: firstString(event.messageId, event.message_id),
81
+ surface: toolCall.surface,
82
+ toolCallId: toolCall.id,
83
+ },
84
+ };
85
+ }
86
+
87
+ function normalizeGateDecision(decision = {}) {
88
+ const value = asObject(decision);
89
+ const raw = firstString(value.decision, value.mode, value.action, value.status).toLowerCase();
90
+ const blocked = value.allowed === false
91
+ || value.accepted === false
92
+ || value.blocked === true
93
+ || ['block', 'deny', 'denied', 'reject', 'rejected'].includes(raw);
94
+ const approvalRequired = value.requiresApproval === true || raw === 'approve';
95
+ return {
96
+ allowed: !blocked && !approvalRequired,
97
+ blocked,
98
+ approvalRequired,
99
+ reason: firstString(value.reason, value.message, Array.isArray(value.reasons) ? value.reasons.join('; ') : ''),
100
+ raw: value,
101
+ };
102
+ }
103
+
104
+ function createLettaToolGuard({ gateCheck, executeTool, onDecision } = {}) {
105
+ if (typeof gateCheck !== 'function') {
106
+ throw new TypeError('createLettaToolGuard requires a gateCheck function');
107
+ }
108
+ if (typeof executeTool !== 'function') {
109
+ throw new TypeError('createLettaToolGuard requires an executeTool function');
110
+ }
111
+
112
+ return async function guardedLettaTool(input = {}) {
113
+ const normalizedAction = normalizeLettaAction(input);
114
+ const decision = normalizeGateDecision(await gateCheck(normalizedAction));
115
+ if (typeof onDecision === 'function') {
116
+ await onDecision({ normalizedAction, decision });
117
+ }
118
+ if (!decision.allowed) {
119
+ const error = new Error(decision.reason || 'ThumbGate blocked this Letta tool call before execution.');
120
+ error.code = decision.approvalRequired ? 'THUMBGATE_APPROVAL_REQUIRED' : 'THUMBGATE_BLOCKED';
121
+ error.thumbgate = { normalizedAction, decision };
122
+ throw error;
123
+ }
124
+ return executeTool(input, { normalizedAction, decision });
125
+ };
126
+ }
127
+
128
+ module.exports = {
129
+ createLettaToolGuard,
130
+ extractLettaToolCall,
131
+ normalizeGateDecision,
132
+ normalizeLettaAction,
133
+ };
@@ -231,7 +231,7 @@ const {
231
231
  finalizeSession: finalizeFeedbackSession,
232
232
  } = require('../../scripts/feedback-session');
233
233
 
234
- const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.27.15' };
234
+ const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.27.20' };
235
235
  const COMMERCE_CATEGORIES = [
236
236
  'product_recommendation',
237
237
  'brand_compliance',
@@ -7,7 +7,7 @@
7
7
  "npx",
8
8
  "--yes",
9
9
  "--package",
10
- "thumbgate@1.27.15",
10
+ "thumbgate@1.27.20",
11
11
  "thumbgate",
12
12
  "serve"
13
13
  ],
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const DEFAULT_ENDPOINT = 'https://api.oraclestechnologies.com/v1/guardian/analyze';
5
+
6
+ function requireApiKey(env = process.env) {
7
+ const key = env.ETHICORE_API_KEY || env.GUARDIAN_API_KEY || env.ORACLES_GUARDIAN_API_KEY;
8
+ if (!key) {
9
+ throw new Error('ETHICORE_API_KEY env var is required');
10
+ }
11
+ return key;
12
+ }
13
+
14
+ async function analyzeText(text, options = {}) {
15
+ if (!String(text || '').trim()) {
16
+ throw new Error('analyzeText requires text');
17
+ }
18
+
19
+ const env = options.env || process.env;
20
+ const endpoint = options.endpoint || env.ETHICORE_GUARDIAN_ENDPOINT || DEFAULT_ENDPOINT;
21
+ const apiKey = options.apiKey || requireApiKey(env);
22
+ const fetchImpl = options.fetch || fetch;
23
+
24
+ const response = await fetchImpl(endpoint, {
25
+ method: 'POST',
26
+ headers: {
27
+ Authorization: `Bearer ${apiKey}`,
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ body: JSON.stringify({ text }),
31
+ });
32
+
33
+ const bodyText = await response.text();
34
+ let body = bodyText;
35
+ try {
36
+ body = bodyText ? JSON.parse(bodyText) : {};
37
+ } catch {
38
+ // Keep non-JSON body for diagnostics.
39
+ }
40
+
41
+ if (!response.ok) {
42
+ throw new Error(`Ethicore Guardian API ${response.status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
43
+ }
44
+
45
+ return body;
46
+ }
47
+
48
+ function createEthicorePolicyCheck(options = {}) {
49
+ return async function ethicorePolicyCheck(action = {}) {
50
+ const toolText = [
51
+ action.toolName,
52
+ action.actionType,
53
+ action.command,
54
+ action.path,
55
+ action.url,
56
+ action.input ? JSON.stringify(action.input) : '',
57
+ ].filter(Boolean).join('\n');
58
+
59
+ return analyzeText(toolText || JSON.stringify(action), options);
60
+ };
61
+ }
62
+
63
+ module.exports = {
64
+ DEFAULT_ENDPOINT,
65
+ analyzeText,
66
+ createEthicorePolicyCheck,
67
+ requireApiKey,
68
+ };
@@ -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
@@ -3934,6 +3934,7 @@ switch (COMMAND) {
3934
3934
  // PreToolUse hook interface: reads tool call JSON from stdin, outputs gate verdict
3935
3935
  // Used by: generate-pretool-hook.sh → npx thumbgate gate-check
3936
3936
  const { run: gateRun, runAsync: gateRunAsync } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
3937
+ const { evaluateSelfProtection } = require(path.join(PKG_ROOT, 'scripts', 'self-protection'));
3937
3938
  let stdinData = '';
3938
3939
  process.stdin.setEncoding('utf8');
3939
3940
  process.stdin.on('data', (chunk) => { stdinData += chunk; });
@@ -3941,7 +3942,32 @@ switch (COMMAND) {
3941
3942
  try {
3942
3943
  const input = JSON.parse(stdinData);
3943
3944
  const output = await gateRunAsync(input);
3944
- process.stdout.write(output + '\n');
3945
+ // Self-protection overlay (2026-07-08): the gate engine only denies edits
3946
+ // to ThumbGate's own governance files under strict enforcement; by default
3947
+ // such edits pass as a clean ALLOW. Surface them (or block in strict) so an
3948
+ // agent can't silently disable the firewall. Only overlays when the engine
3949
+ // did not already produce a hard deny.
3950
+ let verdict = output;
3951
+ try {
3952
+ const parsed = JSON.parse(output || '{}');
3953
+ const alreadyDeny = (parsed.hookSpecificOutput && parsed.hookSpecificOutput.permissionDecision === 'deny')
3954
+ || parsed.decision === 'block';
3955
+ if (!alreadyDeny) {
3956
+ const sp = evaluateSelfProtection(input.tool_name, input.tool_input);
3957
+ if (sp && sp.action === 'block') {
3958
+ verdict = JSON.stringify({
3959
+ decision: 'block',
3960
+ reason: sp.message,
3961
+ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: sp.message },
3962
+ });
3963
+ } else if (sp && sp.action === 'warn') {
3964
+ verdict = JSON.stringify({
3965
+ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: sp.message },
3966
+ });
3967
+ }
3968
+ }
3969
+ } catch (_e) { /* non-JSON engine output: pass through unchanged */ }
3970
+ process.stdout.write(verdict + '\n');
3945
3971
  process.exit(0);
3946
3972
  } catch (err) {
3947
3973
  process.stderr.write(`gate-check error: ${err.message}\n`);
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Insert 'dashboard' and '--open' as the subcommands/arguments
5
+ process.argv.splice(2, 0, 'dashboard', '--open');
6
+
7
+ require('./cli.js');
@@ -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
- Every repeat-mistake your agent makes costs
29
- tokens. ThumbGate blocks known-bad tool calls
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
- Start free (local-only, never expires):
34
- npx thumbgate init
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
- Pro ${PRO_PRICE_LABEL}
42
- Hosted lesson sync across all your machines.
43
- Adapter matrix kept current for Claude Code,
44
- Cursor, Codex, Gemini, Amp, Cline, OpenCode.
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
+ hosted dashboard. 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
- Or run: npx thumbgate pro
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
+