thumbgate 1.27.12 → 1.27.14
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/plugin.json +1 -1
- package/.well-known/llms.txt +2 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +2 -4
- package/adapters/claude/.mcp.json +2 -2
- 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/bin/cli.js +78 -259
- package/config/gate-templates.json +0 -228
- package/config/gates/claim-verification.json +0 -18
- package/package.json +35 -25
- package/public/assets/brand/thumbgate-logo-transparent.svg +22 -0
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +19 -0
- package/public/assets/brand/thumbgate-mark.svg +11 -5
- package/public/blog.html +0 -30
- package/public/brand/thumbgate-mark.svg +9 -5
- package/public/chatgpt-app.html +2 -2
- package/public/compare.html +2 -1
- package/public/dashboard.html +1 -1
- package/public/federal.html +1 -1
- package/public/index.html +95 -216
- package/public/learn.html +59 -35
- package/public/lessons.html +1 -1
- package/public/numbers.html +2 -2
- package/public/pro.html +7 -7
- package/scripts/agent-readiness.js +142 -0
- package/scripts/aws-blocks-guardrails.js +228 -0
- package/scripts/cli-schema.js +22 -10
- package/scripts/dashboard-chat.js +2 -1
- package/scripts/document-intake.js +1 -49
- package/scripts/durability/step.js +3 -3
- package/scripts/gate-stats.js +5 -11
- package/scripts/gates-engine.js +0 -49
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-stop-anti-claim.js +116 -184
- package/scripts/hosted-config.js +0 -12
- package/scripts/lesson-search.js +1 -15
- package/scripts/llm-client.js +187 -5
- package/scripts/plausible-domain-config.js +3 -1
- package/scripts/seo-gsd.js +240 -1
- package/scripts/tool-registry.js +2 -2
- package/scripts/vector-store.js +44 -0
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +340 -131
- package/public/assets/brand/thumbgate-mark-inline.svg +0 -15
- package/public/compare/adopt-ai.html +0 -219
- package/public/compare/agentix-labs.html +0 -197
- package/public/compare/ai-experience-orchestration.html +0 -216
- package/public/compare/anthropic-claude-for-legal.html +0 -260
- package/public/compare/anthropic-containment.html +0 -280
- package/public/compare/arcade.html +0 -175
- package/public/compare/arcjet.html +0 -239
- package/public/compare/bumblebee.html +0 -307
- package/public/compare/claude-code-hooks.html +0 -294
- package/public/compare/databricks-unity-ai-gateway.html +0 -215
- package/public/compare/fallow.html +0 -351
- package/public/compare/heidi.html +0 -233
- package/public/compare/mem0.html +0 -342
- package/public/compare/oak-and-sparrow-gatekeeper.html +0 -289
- package/public/compare/rein.html +0 -236
- package/public/compare/sigmashake.html +0 -256
- package/public/compare/speclock.html +0 -342
- package/public/guides/agent-harness-optimization.html +0 -342
- package/public/guides/agentic-web-governance.html +0 -406
- package/public/guides/ai-agent-governance-sprint.html +0 -415
- package/public/guides/ai-agent-pre-action-approval-gates.html +0 -401
- package/public/guides/ai-agent-workflow-migration-checklist.html +0 -392
- package/public/guides/ai-deployment-readiness.html +0 -415
- package/public/guides/ai-mode-ads-agent-governance.html +0 -401
- package/public/guides/ai-search-topical-presence.html +0 -342
- package/public/guides/autoresearch-agent-safety.html +0 -342
- package/public/guides/background-agent-governance.html +0 -358
- package/public/guides/best-tools-stop-ai-agents-breaking-production.html +0 -363
- package/public/guides/browser-automation-safety.html +0 -342
- package/public/guides/chatgpt-ads-trust.html +0 -353
- package/public/guides/claude-code-feedback.html +0 -339
- package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
- package/public/guides/claude-code-skills-guardrails.html +0 -343
- package/public/guides/claude-desktop.html +0 -356
- package/public/guides/code-knowledge-graph-guardrails.html +0 -365
- package/public/guides/codex-cli-guardrails.html +0 -339
- package/public/guides/cursor-agent-guardrails.html +0 -339
- package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
- package/public/guides/database-agent-safety.html +0 -406
- package/public/guides/deepseek-v4-runtime-guardrails.html +0 -346
- package/public/guides/developer-machine-supply-chain-guardrails.html +0 -358
- package/public/guides/gcp-mcp-guardrails.html +0 -147
- package/public/guides/gemini-cli-feedback-memory.html +0 -339
- package/public/guides/gpt-5-5-model-evaluation.html +0 -358
- package/public/guides/internal-ai-engineering-stack-guardrails.html +0 -348
- package/public/guides/long-running-agent-context-management.html +0 -346
- package/public/guides/mcp-tool-governance.html +0 -401
- package/public/guides/multica-thumbgate-setup.html +0 -134
- package/public/guides/native-messaging-host-security.html +0 -342
- package/public/guides/policy-engine-pre-action-gates.html +0 -346
- package/public/guides/pre-action-checks.html +0 -342
- package/public/guides/pretooluse-hooks-vs-advisory-prompt-rules.html +0 -342
- package/public/guides/prompt-tricks-to-workflow-rules.html +0 -365
- package/public/guides/proxy-pointer-rag-guardrails.html +0 -352
- package/public/guides/rag-precision-tuning-guardrails.html +0 -352
- package/public/guides/reasoning-compression-guardrails.html +0 -346
- package/public/guides/relational-knowledge-ai-recommendations.html +0 -342
- package/public/guides/roo-code-alternative-cline.html +0 -339
- package/public/guides/semantic-programmatic-seo-guardrails.html +0 -352
- package/public/guides/seo-agent-skills-guardrails.html +0 -344
- package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -342
- package/public/learn/ac-dc-runtime-enforcement.html +0 -277
- package/public/learn/agent-harness-pattern.html +0 -181
- package/public/learn/agent-identity-connector-governance.html +0 -146
- package/public/learn/agent-swarms-shared-gates.html +0 -173
- package/public/learn/agentic-enterprise-context-brain.html +0 -117
- package/public/learn/agentic-os-team-governance.html +0 -146
- package/public/learn/ai-agent-governance.html +0 -158
- package/public/learn/ai-agent-persistent-memory.html +0 -211
- package/public/learn/anthropomorphic-claim-gates.html +0 -180
- package/public/learn/background-agent-control-layer.html +0 -184
- package/public/learn/claude-code-goal-with-rubrics.html +0 -205
- package/public/learn/codex-role-plugins-need-governance.html +0 -125
- package/public/learn/cost-aware-agent-gate-routing.html +0 -173
- package/public/learn/databricks-unity-ai-gateway-runtime-governance.html +0 -157
- package/public/learn/deterministic-agent-workflows.html +0 -185
- package/public/learn/feedback-loop-vs-decision-layer.html +0 -283
- package/public/learn/from-prototype-to-production.html +0 -223
- package/public/learn/learn.css +0 -51
- package/public/learn/mcp-pre-action-checks-explained.html +0 -172
- package/public/learn/pretix-stripe-connect-marketplaces.html +0 -161
- package/public/learn/regulated-agent-execution-boundary.html +0 -196
- package/public/learn/spec-driven-development.html +0 -168
- package/public/learn/stop-ai-agent-force-push.html +0 -134
- package/public/learn/vibe-coding-safety-net.html +0 -142
- package/scripts/reddit-browser-notification-watch.js +0 -230
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const { chromium } = require('playwright-core');
|
|
7
|
-
|
|
8
|
-
const DEFAULT_CDP_ENDPOINT = 'http://127.0.0.1:9222';
|
|
9
|
-
const DEFAULT_STATE_FILE = path.resolve(__dirname, '..', '.thumbgate', 'reddit-browser-notification-state.json');
|
|
10
|
-
const DEFAULT_EVENTS_FILE = path.resolve(__dirname, '..', '.thumbgate', 'reddit-browser-notifications.jsonl');
|
|
11
|
-
const REDDIT_NOTIFICATIONS_URL = 'https://www.reddit.com/notifications';
|
|
12
|
-
|
|
13
|
-
function resolveRuntimeFile(envName, defaultPath) {
|
|
14
|
-
const configured = process.env[envName];
|
|
15
|
-
return configured ? path.resolve(configured) : defaultPath;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function loadJson(filePath, fallback) {
|
|
19
|
-
try {
|
|
20
|
-
if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
21
|
-
} catch {
|
|
22
|
-
// Ignore corrupt transient state; a later write will repair it.
|
|
23
|
-
}
|
|
24
|
-
return fallback;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function writeJson(filePath, value) {
|
|
28
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
29
|
-
fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function appendJsonl(filePath, rows) {
|
|
33
|
-
if (rows.length === 0) return;
|
|
34
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
35
|
-
fs.appendFileSync(filePath, `${rows.map((row) => JSON.stringify(row)).join('\n')}\n`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function fingerprintNotification(notification) {
|
|
39
|
-
return [
|
|
40
|
-
notification.author || '',
|
|
41
|
-
notification.kind || '',
|
|
42
|
-
notification.subreddit || '',
|
|
43
|
-
notification.preview || '',
|
|
44
|
-
notification.age || '',
|
|
45
|
-
].join('|').toLowerCase();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function scoreNotification(notification) {
|
|
49
|
-
const text = `${notification.author || ''} ${notification.kind || ''} ${notification.preview || ''}`.toLowerCase();
|
|
50
|
-
let score = 0;
|
|
51
|
-
const reasons = [];
|
|
52
|
-
|
|
53
|
-
if (/accepted your chat invite|chat invite/i.test(text)) {
|
|
54
|
-
score += 5;
|
|
55
|
-
reasons.push('chat_accepted');
|
|
56
|
-
}
|
|
57
|
-
if (/\b(interested|try|paid|diagnostic|workflow|failure|gate|thumbgate|thubgate)\b/i.test(text)) {
|
|
58
|
-
score += 4;
|
|
59
|
-
reasons.push('buyer_signal');
|
|
60
|
-
}
|
|
61
|
-
if (/\b(replied|mentioned)\b/i.test(text)) {
|
|
62
|
-
score += 2;
|
|
63
|
-
reasons.push('reply_or_mention');
|
|
64
|
-
}
|
|
65
|
-
if (/\b(spam|slop|bot|report|ignore all previous instructions)\b/i.test(text)) {
|
|
66
|
-
score -= 5;
|
|
67
|
-
reasons.push('hostile_or_meta');
|
|
68
|
-
}
|
|
69
|
-
if (/automoderator|mod-bot|minimum karma|removed|reviewed shortly/i.test(text)) {
|
|
70
|
-
score -= 1;
|
|
71
|
-
reasons.push('platform_moderation');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return { score, reasons };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function ageMinutes(age) {
|
|
78
|
-
const text = String(age || '').trim().toLowerCase();
|
|
79
|
-
if (!text || text === 'just now') return 0;
|
|
80
|
-
const match = /^(\d+)\s*([mhdw])\s+ago$/.exec(text);
|
|
81
|
-
if (!match) return Number.POSITIVE_INFINITY;
|
|
82
|
-
const value = Number(match[1]);
|
|
83
|
-
const unit = match[2];
|
|
84
|
-
if (unit === 'm') return value;
|
|
85
|
-
if (unit === 'h') return value * 60;
|
|
86
|
-
if (unit === 'd') return value * 24 * 60;
|
|
87
|
-
return value * 7 * 24 * 60;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isAgeLine(line) {
|
|
91
|
-
return /^(?:just now|\d+\s*[mhdw]\s+ago)$/i.test(String(line || '').trim());
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function isRecentNotification(notification, maxAgeMinutes = 48 * 60) {
|
|
95
|
-
return ageMinutes(notification.age) <= maxAgeMinutes;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function parseNotificationBlocks(bodyText) {
|
|
99
|
-
const lines = String(bodyText || '')
|
|
100
|
-
.split('\n')
|
|
101
|
-
.map((line) => line.trim())
|
|
102
|
-
.filter(Boolean);
|
|
103
|
-
const notifications = [];
|
|
104
|
-
|
|
105
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
106
|
-
let author = lines[index];
|
|
107
|
-
let kind = lines[index + 1] || '';
|
|
108
|
-
let kindIndex = index + 1;
|
|
109
|
-
if (isAgeLine(author)) continue;
|
|
110
|
-
|
|
111
|
-
if (/\b(replied to|mentioned you|new mentions)\b/i.test(author)) {
|
|
112
|
-
kind = author;
|
|
113
|
-
kindIndex = index;
|
|
114
|
-
const authorMatch = /^u\/([^\s]+)/i.exec(kind);
|
|
115
|
-
author = authorMatch ? authorMatch[1] : author;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!kind || !/\b(accepted your chat invite|replied to|mentioned you|new mentions)\b/i.test(kind)) continue;
|
|
119
|
-
|
|
120
|
-
const hasPreview = !/accepted your chat invite|new mentions/i.test(kind);
|
|
121
|
-
const preview = hasPreview ? (lines[kindIndex + 1] || '') : '';
|
|
122
|
-
const age = hasPreview ? (lines[kindIndex + 2] || '') : (lines[kindIndex + 1] || '');
|
|
123
|
-
const subredditMatch = /\bin\s+r\/([A-Za-z0-9_]+)/.exec(kind);
|
|
124
|
-
const notification = {
|
|
125
|
-
author,
|
|
126
|
-
kind,
|
|
127
|
-
subreddit: subredditMatch ? subredditMatch[1] : null,
|
|
128
|
-
preview,
|
|
129
|
-
age,
|
|
130
|
-
};
|
|
131
|
-
const scored = scoreNotification(notification);
|
|
132
|
-
notifications.push({
|
|
133
|
-
...notification,
|
|
134
|
-
...scored,
|
|
135
|
-
ageMinutes: ageMinutes(notification.age),
|
|
136
|
-
fingerprint: fingerprintNotification(notification),
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return notifications;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function readRedditNotifications({
|
|
144
|
-
cdpEndpoint = process.env.THUMBGATE_CHROME_CDP_ENDPOINT || DEFAULT_CDP_ENDPOINT,
|
|
145
|
-
timeoutMs = Number(process.env.THUMBGATE_REDDIT_BROWSER_TIMEOUT_MS || 15000),
|
|
146
|
-
} = {}) {
|
|
147
|
-
const browser = await chromium.connectOverCDP(cdpEndpoint);
|
|
148
|
-
const context = browser.contexts()[0] || await browser.newContext();
|
|
149
|
-
const page = await context.newPage();
|
|
150
|
-
try {
|
|
151
|
-
await page.goto(REDDIT_NOTIFICATIONS_URL, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
|
|
152
|
-
await page.waitForTimeout(3000);
|
|
153
|
-
const bodyText = await page.locator('body').innerText({ timeout: timeoutMs });
|
|
154
|
-
return parseNotificationBlocks(bodyText);
|
|
155
|
-
} finally {
|
|
156
|
-
await page.close().catch(() => {});
|
|
157
|
-
await browser.close().catch(() => {});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function run({ dryRun = false, now = new Date().toISOString() } = {}) {
|
|
162
|
-
const stateFile = resolveRuntimeFile('THUMBGATE_REDDIT_BROWSER_STATE_FILE', DEFAULT_STATE_FILE);
|
|
163
|
-
const eventsFile = resolveRuntimeFile('THUMBGATE_REDDIT_BROWSER_EVENTS_FILE', DEFAULT_EVENTS_FILE);
|
|
164
|
-
const state = loadJson(stateFile, { seen: {} });
|
|
165
|
-
const notifications = await readRedditNotifications();
|
|
166
|
-
const fresh = notifications.filter((notification) => !state.seen[notification.fingerprint]);
|
|
167
|
-
const actionable = fresh.filter((notification) => notification.score > 0 && isRecentNotification(notification));
|
|
168
|
-
const rows = actionable.map((notification) => ({
|
|
169
|
-
checkedAt: now,
|
|
170
|
-
platform: 'reddit',
|
|
171
|
-
source: 'browser_notifications',
|
|
172
|
-
status: 'pending_review',
|
|
173
|
-
...notification,
|
|
174
|
-
}));
|
|
175
|
-
|
|
176
|
-
for (const notification of fresh) {
|
|
177
|
-
state.seen[notification.fingerprint] = { seenAt: now, score: notification.score };
|
|
178
|
-
}
|
|
179
|
-
state.lastCheck = now;
|
|
180
|
-
|
|
181
|
-
if (!dryRun) {
|
|
182
|
-
writeJson(stateFile, state);
|
|
183
|
-
appendJsonl(eventsFile, rows);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
notifications: notifications.length,
|
|
188
|
-
fresh: fresh.length,
|
|
189
|
-
actionable: actionable.length,
|
|
190
|
-
eventsFile,
|
|
191
|
-
actionableItems: actionable,
|
|
192
|
-
dryRun,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function parseArgs(argv = process.argv.slice(2)) {
|
|
197
|
-
return {
|
|
198
|
-
dryRun: argv.includes('--dry-run'),
|
|
199
|
-
json: argv.includes('--json'),
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (require.main === module) {
|
|
204
|
-
const args = parseArgs();
|
|
205
|
-
run({ dryRun: args.dryRun })
|
|
206
|
-
.then((result) => {
|
|
207
|
-
if (args.json) {
|
|
208
|
-
console.log(JSON.stringify(result, null, 2));
|
|
209
|
-
} else {
|
|
210
|
-
console.log(`[reddit-browser-watch] notifications=${result.notifications} fresh=${result.fresh} actionable=${result.actionable} dryRun=${result.dryRun}`);
|
|
211
|
-
for (const item of result.actionableItems) {
|
|
212
|
-
console.log(`- score=${item.score} author=${item.author} kind=${item.kind} preview=${item.preview.slice(0, 120)}`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
.catch((err) => {
|
|
217
|
-
console.error(`[reddit-browser-watch] ${err.message}`);
|
|
218
|
-
process.exitCode = 1;
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
module.exports = {
|
|
223
|
-
fingerprintNotification,
|
|
224
|
-
ageMinutes,
|
|
225
|
-
isRecentNotification,
|
|
226
|
-
parseNotificationBlocks,
|
|
227
|
-
readRedditNotifications,
|
|
228
|
-
run,
|
|
229
|
-
scoreNotification,
|
|
230
|
-
};
|