thumbgate 0.9.9

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 (369) hide show
  1. package/.claude-plugin/README.md +134 -0
  2. package/.claude-plugin/bundle/icon.png +0 -0
  3. package/.claude-plugin/bundle/icon.svg +18 -0
  4. package/.claude-plugin/bundle/server/index.js +24 -0
  5. package/.claude-plugin/marketplace.json +36 -0
  6. package/.claude-plugin/plugin.json +21 -0
  7. package/.well-known/mcp/server-card.json +231 -0
  8. package/LICENSE +21 -0
  9. package/README.md +375 -0
  10. package/adapters/README.md +9 -0
  11. package/adapters/amp/skills/rlhf-feedback/SKILL.md +22 -0
  12. package/adapters/chatgpt/INSTALL.md +83 -0
  13. package/adapters/chatgpt/openapi.yaml +1281 -0
  14. package/adapters/claude/.mcp.json +14 -0
  15. package/adapters/codex/config.toml +9 -0
  16. package/adapters/gemini/function-declarations.json +224 -0
  17. package/adapters/mcp/server-stdio.js +788 -0
  18. package/adapters/opencode/opencode.json +15 -0
  19. package/bin/cli.js +1483 -0
  20. package/bin/memory.sh +64 -0
  21. package/bin/obsidian-sync.sh +20 -0
  22. package/bin/postinstall.js +37 -0
  23. package/config/build-metadata.json +4 -0
  24. package/config/e2e-critical-flows.json +45 -0
  25. package/config/gate-templates.json +77 -0
  26. package/config/gates/claim-verification.json +29 -0
  27. package/config/gates/computer-use.json +39 -0
  28. package/config/gates/default.json +117 -0
  29. package/config/github-about.json +25 -0
  30. package/config/mcp-allowlists.json +135 -0
  31. package/config/model-tiers.json +33 -0
  32. package/config/partner-routing.json +132 -0
  33. package/config/policy-bundles/constrained-v1.json +64 -0
  34. package/config/policy-bundles/default-v1.json +91 -0
  35. package/config/rubrics/default-v1.json +52 -0
  36. package/config/skill-packs/react-testing.json +23 -0
  37. package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
  38. package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
  39. package/config/skill-specs/pr-reviewer.json +9 -0
  40. package/config/skill-specs/release-status.json +9 -0
  41. package/config/skill-specs/ticket-triage.json +9 -0
  42. package/config/subagent-profiles.json +32 -0
  43. package/config/tessl-tiles.json +29 -0
  44. package/config/thumbgate-settings.managed.json +12 -0
  45. package/openapi/openapi.yaml +1281 -0
  46. package/package.json +286 -0
  47. package/plugins/amp-skill/INSTALL.md +52 -0
  48. package/plugins/amp-skill/SKILL.md +64 -0
  49. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
  50. package/plugins/claude-codex-bridge/.mcp.json +12 -0
  51. package/plugins/claude-codex-bridge/INSTALL.md +43 -0
  52. package/plugins/claude-codex-bridge/README.md +46 -0
  53. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
  54. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
  55. package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
  56. package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
  57. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
  58. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
  59. package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
  60. package/plugins/claude-skill/INSTALL.md +55 -0
  61. package/plugins/claude-skill/SKILL.md +46 -0
  62. package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
  63. package/plugins/codex-profile/.mcp.json +12 -0
  64. package/plugins/codex-profile/AGENTS.md +20 -0
  65. package/plugins/codex-profile/INSTALL.md +66 -0
  66. package/plugins/codex-profile/README.md +37 -0
  67. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
  68. package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
  69. package/plugins/cursor-marketplace/LICENSE +21 -0
  70. package/plugins/cursor-marketplace/README.md +124 -0
  71. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
  72. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  73. package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
  74. package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
  75. package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
  76. package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
  77. package/plugins/cursor-marketplace/mcp.json +12 -0
  78. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
  79. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
  80. package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
  81. package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
  82. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
  83. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
  84. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
  85. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
  86. package/plugins/gemini-extension/INSTALL.md +92 -0
  87. package/plugins/gemini-extension/gemini_prompt.txt +14 -0
  88. package/plugins/gemini-extension/tool_contract.json +45 -0
  89. package/plugins/opencode-profile/INSTALL.md +57 -0
  90. package/public/assets/instagram-card.png +0 -0
  91. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  92. package/public/blog.html +400 -0
  93. package/public/dashboard.html +1093 -0
  94. package/public/guide.html +317 -0
  95. package/public/index.html +1195 -0
  96. package/public/learn/agent-harness-pattern.html +180 -0
  97. package/public/learn/ai-agent-persistent-memory.html +202 -0
  98. package/public/learn/learn.css +45 -0
  99. package/public/learn/mcp-pre-action-gates-explained.html +172 -0
  100. package/public/learn/stop-ai-agent-force-push.html +134 -0
  101. package/public/learn/vibe-coding-safety-net.html +142 -0
  102. package/public/learn.html +213 -0
  103. package/public/lessons.html +650 -0
  104. package/public/vercel.json +8 -0
  105. package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
  106. package/scripts/a2ui-engine.js +73 -0
  107. package/scripts/access-anomaly-detector.js +12 -0
  108. package/scripts/adk-consolidator.js +266 -0
  109. package/scripts/agent-readiness.js +220 -0
  110. package/scripts/agent-security-hardening.js +227 -0
  111. package/scripts/agentic-data-pipeline.js +847 -0
  112. package/scripts/analytics-report.js +328 -0
  113. package/scripts/analytics-window.js +158 -0
  114. package/scripts/async-job-runner.js +1001 -0
  115. package/scripts/audit-trail.js +398 -0
  116. package/scripts/auto-promote-gates.js +293 -0
  117. package/scripts/auto-wire-hooks.js +316 -0
  118. package/scripts/autonomous-sales-agent.js +39 -0
  119. package/scripts/autoresearch-runner.js +216 -0
  120. package/scripts/background-agent-governance.js +237 -0
  121. package/scripts/behavioral-extraction.js +93 -0
  122. package/scripts/belief-update.js +84 -0
  123. package/scripts/billing.js +2438 -0
  124. package/scripts/bot-detector.js +50 -0
  125. package/scripts/budget-guard.js +173 -0
  126. package/scripts/build-claude-mcpb.js +189 -0
  127. package/scripts/build-metadata.js +97 -0
  128. package/scripts/check-congruence.js +322 -0
  129. package/scripts/cli-feedback.js +135 -0
  130. package/scripts/cli-telemetry.js +87 -0
  131. package/scripts/cloudflare-dynamic-sandbox.js +315 -0
  132. package/scripts/code-reasoning.js +350 -0
  133. package/scripts/codegraph-context.js +466 -0
  134. package/scripts/commercial-offer.js +56 -0
  135. package/scripts/computer-use-firewall.js +250 -0
  136. package/scripts/context-engine.js +694 -0
  137. package/scripts/contextfs.js +1287 -0
  138. package/scripts/conversation-context.js +119 -0
  139. package/scripts/creator-campaigns.js +239 -0
  140. package/scripts/daemon-manager.js +108 -0
  141. package/scripts/daily-digest.js +11 -0
  142. package/scripts/dashboard-render-spec.js +395 -0
  143. package/scripts/dashboard.js +1058 -0
  144. package/scripts/data-governance.js +173 -0
  145. package/scripts/delegation-runtime.js +900 -0
  146. package/scripts/deploy-gcp.sh +44 -0
  147. package/scripts/deploy-policy.js +231 -0
  148. package/scripts/disagreement-mining.js +315 -0
  149. package/scripts/dispatch-brief.js +159 -0
  150. package/scripts/distribution-surfaces.js +44 -0
  151. package/scripts/dpo-optimizer.js +206 -0
  152. package/scripts/ensure-repo-bootstrap.js +129 -0
  153. package/scripts/ephemeral-agent-store.js +219 -0
  154. package/scripts/eval-harness.js +56 -0
  155. package/scripts/evolution-state.js +241 -0
  156. package/scripts/experiment-tracker.js +267 -0
  157. package/scripts/export-databricks-bundle.js +242 -0
  158. package/scripts/export-dpo-pairs.js +344 -0
  159. package/scripts/export-kto-pairs.js +309 -0
  160. package/scripts/export-training.js +450 -0
  161. package/scripts/failure-diagnostics.js +558 -0
  162. package/scripts/feedback-attribution.js +313 -0
  163. package/scripts/feedback-fallback.js +110 -0
  164. package/scripts/feedback-history-distiller.js +391 -0
  165. package/scripts/feedback-inbox-read.js +162 -0
  166. package/scripts/feedback-loop.js +1887 -0
  167. package/scripts/feedback-paths.js +145 -0
  168. package/scripts/feedback-quality.js +139 -0
  169. package/scripts/feedback-root-consolidator.js +238 -0
  170. package/scripts/feedback-schema.js +426 -0
  171. package/scripts/feedback-session.js +286 -0
  172. package/scripts/feedback-to-memory.js +185 -0
  173. package/scripts/feedback-to-rules.js +164 -0
  174. package/scripts/filesystem-search.js +405 -0
  175. package/scripts/funnel-analytics.js +35 -0
  176. package/scripts/gate-satisfy.js +42 -0
  177. package/scripts/gate-stats.js +116 -0
  178. package/scripts/gate-templates.js +70 -0
  179. package/scripts/gates-engine.js +816 -0
  180. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  181. package/scripts/generate-pretool-hook.sh +40 -0
  182. package/scripts/github-about.js +350 -0
  183. package/scripts/github-outreach.js +65 -0
  184. package/scripts/gtm-revenue-loop.js +520 -0
  185. package/scripts/hallucination-detector.js +226 -0
  186. package/scripts/hf-papers.js +317 -0
  187. package/scripts/history-distiller.js +200 -0
  188. package/scripts/hook-auto-capture.sh +100 -0
  189. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  190. package/scripts/hook-stop-self-score.sh +51 -0
  191. package/scripts/hook-stop-verify-deploy.sh +31 -0
  192. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  193. package/scripts/hook-verify-before-done.sh +20 -0
  194. package/scripts/hosted-config.js +156 -0
  195. package/scripts/hybrid-feedback-context.js +675 -0
  196. package/scripts/install-mcp.js +159 -0
  197. package/scripts/intent-router.js +392 -0
  198. package/scripts/internal-agent-bootstrap.js +490 -0
  199. package/scripts/jsonl-watcher.js +155 -0
  200. package/scripts/lesson-db.js +613 -0
  201. package/scripts/lesson-inference.js +310 -0
  202. package/scripts/lesson-retrieval.js +95 -0
  203. package/scripts/lesson-rotation.js +137 -0
  204. package/scripts/lesson-search.js +644 -0
  205. package/scripts/lesson-synthesis.js +196 -0
  206. package/scripts/license.js +50 -0
  207. package/scripts/local-model-profile.js +384 -0
  208. package/scripts/markdown-escape.js +12 -0
  209. package/scripts/marketing-experiment.js +671 -0
  210. package/scripts/mcp-config.js +149 -0
  211. package/scripts/mcp-policy.js +99 -0
  212. package/scripts/memalign-recall.js +111 -0
  213. package/scripts/memory-firewall.js +222 -0
  214. package/scripts/memory-migration.js +296 -0
  215. package/scripts/meta-policy.js +190 -0
  216. package/scripts/metered-billing.js +16 -0
  217. package/scripts/model-tier-router.js +301 -0
  218. package/scripts/money-watcher.js +71 -0
  219. package/scripts/multi-hop-recall.js +240 -0
  220. package/scripts/natural-language-harness.js +330 -0
  221. package/scripts/obsidian-export.js +713 -0
  222. package/scripts/operational-dashboard.js +103 -0
  223. package/scripts/operational-summary.js +93 -0
  224. package/scripts/optimize-context.js +17 -0
  225. package/scripts/org-dashboard.js +201 -0
  226. package/scripts/partner-orchestration.js +146 -0
  227. package/scripts/per-step-scoring.js +165 -0
  228. package/scripts/perplexity-marketing.js +466 -0
  229. package/scripts/pii-scanner.js +153 -0
  230. package/scripts/plan-gate.js +154 -0
  231. package/scripts/post-everywhere.js +308 -0
  232. package/scripts/post-to-x-retry.sh +22 -0
  233. package/scripts/post-to-x.js +369 -0
  234. package/scripts/pr-manager.js +236 -0
  235. package/scripts/predictive-insights.js +356 -0
  236. package/scripts/principle-extractor.js +162 -0
  237. package/scripts/pro-features.js +40 -0
  238. package/scripts/pro-local-dashboard.js +174 -0
  239. package/scripts/problem-detail.js +53 -0
  240. package/scripts/product-feedback.js +134 -0
  241. package/scripts/profile-router.js +245 -0
  242. package/scripts/prompt-dlp.js +221 -0
  243. package/scripts/prompt-guard.js +83 -0
  244. package/scripts/prove-adapters.js +863 -0
  245. package/scripts/prove-attribution.js +365 -0
  246. package/scripts/prove-automation.js +653 -0
  247. package/scripts/prove-autoresearch.js +304 -0
  248. package/scripts/prove-claim-verification.js +277 -0
  249. package/scripts/prove-cloudflare-sandbox.js +163 -0
  250. package/scripts/prove-data-pipeline.js +410 -0
  251. package/scripts/prove-data-quality.js +227 -0
  252. package/scripts/prove-evolution.js +352 -0
  253. package/scripts/prove-harnesses.js +287 -0
  254. package/scripts/prove-intelligence.js +259 -0
  255. package/scripts/prove-lancedb.js +371 -0
  256. package/scripts/prove-local-intelligence.js +342 -0
  257. package/scripts/prove-loop-closure.js +263 -0
  258. package/scripts/prove-predictive-insights.js +357 -0
  259. package/scripts/prove-runtime.js +350 -0
  260. package/scripts/prove-seo-gsd.js +234 -0
  261. package/scripts/prove-settings.js +279 -0
  262. package/scripts/prove-subway-upgrades.js +277 -0
  263. package/scripts/prove-tessl.js +229 -0
  264. package/scripts/prove-training-export.js +327 -0
  265. package/scripts/prove-workflow-contract.js +116 -0
  266. package/scripts/prove-xmemory.js +332 -0
  267. package/scripts/publish-decision.js +133 -0
  268. package/scripts/pulse.js +80 -0
  269. package/scripts/rate-limiter.js +125 -0
  270. package/scripts/reddit-dm-outreach.js +182 -0
  271. package/scripts/reddit-monitor-cron.sh +26 -0
  272. package/scripts/reflector-agent.js +221 -0
  273. package/scripts/reminder-engine.js +132 -0
  274. package/scripts/revenue-status.js +472 -0
  275. package/scripts/risk-scorer.js +459 -0
  276. package/scripts/rlaif-self-audit.js +129 -0
  277. package/scripts/rlhf_session_start.sh +32 -0
  278. package/scripts/rubric-engine.js +230 -0
  279. package/scripts/schedule-manager.js +251 -0
  280. package/scripts/secret-scanner.js +414 -0
  281. package/scripts/self-heal.js +147 -0
  282. package/scripts/self-healing-check.js +188 -0
  283. package/scripts/semantic-layer.js +98 -0
  284. package/scripts/seo-gsd.js +1153 -0
  285. package/scripts/settings-hierarchy.js +214 -0
  286. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  287. package/scripts/skill-exporter.js +262 -0
  288. package/scripts/skill-generator.js +446 -0
  289. package/scripts/skill-materializer.js +134 -0
  290. package/scripts/skill-packs.js +136 -0
  291. package/scripts/skill-proposer.js +99 -0
  292. package/scripts/skill-quality-tracker.js +282 -0
  293. package/scripts/slo-alert-engine.js +14 -0
  294. package/scripts/slow-loop.js +72 -0
  295. package/scripts/social-analytics/db/schema.sql +32 -0
  296. package/scripts/social-analytics/db/social-analytics.db +0 -0
  297. package/scripts/social-analytics/digest.js +256 -0
  298. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  299. package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
  300. package/scripts/social-analytics/load-env.js +46 -0
  301. package/scripts/social-analytics/mcp-server.js +289 -0
  302. package/scripts/social-analytics/normalizer.js +580 -0
  303. package/scripts/social-analytics/notify.js +162 -0
  304. package/scripts/social-analytics/poll-all.js +92 -0
  305. package/scripts/social-analytics/pollers/github.js +195 -0
  306. package/scripts/social-analytics/pollers/instagram.js +253 -0
  307. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  308. package/scripts/social-analytics/pollers/plausible.js +247 -0
  309. package/scripts/social-analytics/pollers/reddit.js +306 -0
  310. package/scripts/social-analytics/pollers/threads.js +233 -0
  311. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  312. package/scripts/social-analytics/pollers/x.js +227 -0
  313. package/scripts/social-analytics/pollers/youtube.js +304 -0
  314. package/scripts/social-analytics/pollers/zernio.js +183 -0
  315. package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
  316. package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
  317. package/scripts/social-analytics/publishers/devto.js +122 -0
  318. package/scripts/social-analytics/publishers/instagram.js +317 -0
  319. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  320. package/scripts/social-analytics/publishers/reddit.js +390 -0
  321. package/scripts/social-analytics/publishers/threads.js +275 -0
  322. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  323. package/scripts/social-analytics/publishers/x.js +259 -0
  324. package/scripts/social-analytics/publishers/youtube.js +223 -0
  325. package/scripts/social-analytics/publishers/zernio.js +378 -0
  326. package/scripts/social-analytics/run-digest.js +34 -0
  327. package/scripts/social-analytics/store.js +257 -0
  328. package/scripts/social-analytics/utm.js +143 -0
  329. package/scripts/social-pipeline.js +2628 -0
  330. package/scripts/social-quality-gate.js +18 -0
  331. package/scripts/social-reply-monitor.js +445 -0
  332. package/scripts/status-dashboard.js +155 -0
  333. package/scripts/statusline-lesson.js +16 -0
  334. package/scripts/statusline-tower.js +8 -0
  335. package/scripts/statusline.sh +116 -0
  336. package/scripts/stripe-live-status.js +115 -0
  337. package/scripts/subagent-profiles.js +79 -0
  338. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  339. package/scripts/sync-github-about.js +52 -0
  340. package/scripts/sync-version.js +447 -0
  341. package/scripts/synthetic-dpo.js +234 -0
  342. package/scripts/telemetry-analytics.js +821 -0
  343. package/scripts/tessl-export.js +371 -0
  344. package/scripts/test-coverage.js +120 -0
  345. package/scripts/thompson-sampling.js +417 -0
  346. package/scripts/thumbgate-search.js +189 -0
  347. package/scripts/tool-kpi-tracker.js +12 -0
  348. package/scripts/tool-registry.js +811 -0
  349. package/scripts/train_from_feedback.py +933 -0
  350. package/scripts/user-profile.js +78 -0
  351. package/scripts/validate-feedback.js +581 -0
  352. package/scripts/validate-workflow-contract.js +287 -0
  353. package/scripts/vector-store.js +197 -0
  354. package/scripts/verification-loop.js +291 -0
  355. package/scripts/verify-obsidian-setup.sh +269 -0
  356. package/scripts/verify-run.js +269 -0
  357. package/scripts/webhook-delivery.js +62 -0
  358. package/scripts/weekly-auto-post.js +124 -0
  359. package/scripts/workflow-runs.js +154 -0
  360. package/scripts/workflow-sprint-intake.js +475 -0
  361. package/scripts/workspace-evolver.js +374 -0
  362. package/scripts/x-autonomous-marketing.js +139 -0
  363. package/scripts/xmemory-lite.js +405 -0
  364. package/skills/agent-memory/SKILL.md +97 -0
  365. package/skills/rlhf-feedback/SKILL.md +49 -0
  366. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  367. package/skills/solve-architecture-autonomy/tool.js +33 -0
  368. package/skills/thumbgate/SKILL.md +114 -0
  369. package/src/api/server.js +4206 -0
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * reddit-dm-outreach.js
6
+ * Send direct messages to engaged Reddit users via OAuth2 password grant flow.
7
+ *
8
+ * Usage:
9
+ * node scripts/reddit-dm-outreach.js
10
+ *
11
+ * Requires env vars:
12
+ * REDDIT_CLIENT_ID
13
+ * REDDIT_CLIENT_SECRET
14
+ * REDDIT_USERNAME
15
+ * REDDIT_PASSWORD
16
+ */
17
+
18
+ const https = require('https');
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ // Parse .env credentials line-by-line
23
+ function parseEnv(filePath) {
24
+ const content = fs.readFileSync(filePath, 'utf8');
25
+ const result = {};
26
+ const lines = content.split('\n');
27
+
28
+ for (const line of lines) {
29
+ const trimmed = line.trim();
30
+ if (!trimmed || trimmed.startsWith('#')) continue;
31
+
32
+ const eqIdx = trimmed.indexOf('=');
33
+ if (eqIdx === -1) continue;
34
+
35
+ const key = trimmed.substring(0, eqIdx).trim();
36
+ const value = trimmed.substring(eqIdx + 1).trim();
37
+ result[key] = value;
38
+ }
39
+
40
+ return result;
41
+ }
42
+
43
+ const envPath = path.join(__dirname, '..', '.env');
44
+ const env = parseEnv(envPath);
45
+
46
+ const REDDIT_CLIENT_ID = env.REDDIT_CLIENT_ID;
47
+ const REDDIT_CLIENT_SECRET = env.REDDIT_CLIENT_SECRET;
48
+ const REDDIT_USERNAME = env.REDDIT_USERNAME;
49
+ const REDDIT_PASSWORD = env.REDDIT_PASSWORD;
50
+
51
+ if (!REDDIT_CLIENT_ID || !REDDIT_CLIENT_SECRET || !REDDIT_USERNAME || !REDDIT_PASSWORD) {
52
+ console.error('❌ Missing Reddit credentials in .env');
53
+ process.exit(1);
54
+ }
55
+
56
+ console.log('📧 Reddit DM Outreach');
57
+ console.log('---');
58
+
59
+ // OAuth2 password grant flow
60
+ function authenticate() {
61
+ return new Promise((resolve, reject) => {
62
+ const authHeader = 'Basic ' + Buffer.from(
63
+ REDDIT_CLIENT_ID + ':' + REDDIT_CLIENT_SECRET
64
+ ).toString('base64');
65
+
66
+ const postData = [
67
+ 'grant_type=password',
68
+ `username=${encodeURIComponent(REDDIT_USERNAME)}`,
69
+ `password=${encodeURIComponent(REDDIT_PASSWORD)}`,
70
+ 'duration=permanent'
71
+ ].join('&');
72
+
73
+ const authOptions = {
74
+ hostname: 'www.reddit.com',
75
+ port: 443,
76
+ path: '/api/v1/access_token',
77
+ method: 'POST',
78
+ headers: {
79
+ 'Authorization': authHeader,
80
+ 'Content-Type': 'application/x-www-form-urlencoded',
81
+ 'User-Agent': 'ThumbGate-Testimonial/1.0',
82
+ 'Content-Length': Buffer.byteLength(postData)
83
+ }
84
+ };
85
+
86
+ const authReq = https.request(authOptions, (res) => {
87
+ let data = '';
88
+ res.on('data', chunk => { data += chunk; });
89
+ res.on('end', () => {
90
+ try {
91
+ const parsed = JSON.parse(data);
92
+ if (parsed.access_token) {
93
+ console.log('✅ Authenticated as', REDDIT_USERNAME);
94
+ resolve(parsed.access_token);
95
+ } else {
96
+ reject(new Error('Auth failed: ' + JSON.stringify(parsed)));
97
+ }
98
+ } catch (e) {
99
+ reject(new Error('Parse error: ' + e.message));
100
+ }
101
+ });
102
+ });
103
+
104
+ authReq.on('error', reject);
105
+ authReq.write(postData);
106
+ authReq.end();
107
+ });
108
+ }
109
+
110
+ // Send a single DM
111
+ function sendDM(accessToken, to, subject, text) {
112
+ return new Promise((resolve, reject) => {
113
+ const dmData = JSON.stringify({ to, subject, text });
114
+
115
+ const dmOptions = {
116
+ hostname: 'oauth.reddit.com',
117
+ port: 443,
118
+ path: '/api/compose',
119
+ method: 'POST',
120
+ headers: {
121
+ 'Authorization': 'Bearer ' + accessToken,
122
+ 'Content-Type': 'application/json',
123
+ 'User-Agent': 'ThumbGate-Testimonial/1.0',
124
+ 'Content-Length': Buffer.byteLength(dmData)
125
+ }
126
+ };
127
+
128
+ const dmReq = https.request(dmOptions, (res) => {
129
+ let data = '';
130
+ res.on('data', chunk => { data += chunk; });
131
+ res.on('end', () => {
132
+ if (res.statusCode === 200) {
133
+ resolve();
134
+ } else {
135
+ reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 200)}`));
136
+ }
137
+ });
138
+ });
139
+
140
+ dmReq.on('error', reject);
141
+ dmReq.write(dmData);
142
+ dmReq.end();
143
+ });
144
+ }
145
+
146
+ // Main
147
+ async function main() {
148
+ try {
149
+ const accessToken = await authenticate();
150
+
151
+ const messages = [
152
+ {
153
+ to: 'game-of-kton',
154
+ subject: 'ThumbGate Pro — Try it free, get the hook',
155
+ text: 'Hey, thanks for the thoughtful comments on the Cursor thread about agent memory. You clearly get the hooks-based enforcement approach. Would you be open to trying ThumbGate Pro for free? I\'d love to get your honest take — if you like it, a one-sentence quote I can use on the landing page would be huge. No strings attached either way. Here\'s the repo: https://github.com/IgorGanapolsky/ThumbGate'
156
+ },
157
+ {
158
+ to: 'Deep_Ad1959',
159
+ subject: 'ThumbGate Pro — Context-dependent blocking (your idea)',
160
+ text: 'Hey, your point about context-dependent blocking was spot on — it\'s exactly why we use Thompson Sampling instead of hard binary blocks. Would you be interested in trying ThumbGate Pro for free? If you find it useful, I\'d appreciate a quick testimonial quote for the site. No obligation. Repo: https://github.com/IgorGanapolsky/ThumbGate'
161
+ }
162
+ ];
163
+
164
+ console.log(`\n📨 Sending ${messages.length} direct messages...\n`);
165
+
166
+ for (const msg of messages) {
167
+ try {
168
+ await sendDM(accessToken, msg.to, msg.subject, msg.text);
169
+ console.log(`✅ DM sent to u/${msg.to}`);
170
+ } catch (err) {
171
+ console.error(`❌ Failed to send DM to u/${msg.to}:`, err.message);
172
+ }
173
+ }
174
+
175
+ console.log(`\n✅ Outreach complete (${messages.length}/${messages.length} sent)`);
176
+ } catch (err) {
177
+ console.error('❌ Error:', err.message);
178
+ process.exit(1);
179
+ }
180
+ }
181
+
182
+ main();
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_DIR="/Users/igorganapolsky/workspace/git/igor/rlhf"
5
+ LOG_FILE="${REPO_DIR}/.thumbgate/reddit-monitor.log"
6
+
7
+ mkdir -p "${REPO_DIR}/.rlhf"
8
+
9
+ # Load environment
10
+ if [ -f "${REPO_DIR}/.env" ]; then
11
+ while IFS='=' read -r key value; do
12
+ # Skip comments and empty lines
13
+ [[ -z "$key" || "$key" =~ ^# ]] && continue
14
+ # Strip leading/trailing whitespace from key
15
+ key=$(echo "$key" | xargs)
16
+ # Export the variable (value preserved as-is)
17
+ export "$key=$value"
18
+ done < "${REPO_DIR}/.env"
19
+ fi
20
+
21
+ echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] Reddit monitor run starting" >> "$LOG_FILE"
22
+
23
+ cd "$REPO_DIR"
24
+ /opt/homebrew/bin/node scripts/social-reply-monitor.js >> "$LOG_FILE" 2>&1
25
+
26
+ echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] Reddit monitor run complete" >> "$LOG_FILE"
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Reflector Agent — Self-Healing Brain
3
+ *
4
+ * On negative feedback, analyzes the conversation window to:
5
+ * 1. Identify what the assistant did wrong
6
+ * 2. Check if this is a recurring mistake
7
+ * 3. Propose a specific, actionable rule
8
+ * 4. Return the proposal for user confirmation
9
+ *
10
+ * This transforms ThumbGate from "Manual Guardrail" to "Self-Healing Brain"
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const { retrieveRelevantLessons } = require('./lesson-retrieval');
16
+ const {
17
+ extractFilePaths,
18
+ extractToolCalls,
19
+ extractErrors,
20
+ normalizeConversationWindow,
21
+ } = require('./conversation-context');
22
+
23
+ /**
24
+ * Run a post-mortem analysis on a negative feedback event.
25
+ * @param {object} params
26
+ * @param {Array} params.conversationWindow - Last N conversation turns
27
+ * @param {string} params.context - One-line context from the caller
28
+ * @param {string} params.whatWentWrong - What the caller said went wrong
29
+ * @param {object} params.structuredRule - IF/THEN rule from lesson-inference
30
+ * @param {object} params.feedbackEvent - The stored feedback event
31
+ * @returns {object} Reflection result with proposed rule and recurrence info
32
+ */
33
+ function reflect(params) {
34
+ const { conversationWindow, context, whatWentWrong, structuredRule, feedbackEvent } = params;
35
+
36
+ // 1. Extract what happened from the conversation
37
+ const analysis = analyzeConversation(conversationWindow || []);
38
+
39
+ // 2. Check for recurrence — has this mistake happened before?
40
+ const recurrence = checkRecurrence(analysis, feedbackEvent);
41
+
42
+ // 3. Generate a human-readable proposed rule
43
+ const proposedRule = generateProposedRule(analysis, structuredRule, recurrence, whatWentWrong);
44
+
45
+ // 4. Determine severity based on recurrence
46
+ const severity = recurrence.count >= 3 ? 'critical' : recurrence.count >= 1 ? 'warning' : 'info';
47
+
48
+ return {
49
+ status: 'reflection_complete',
50
+ analysis: {
51
+ userIntent: analysis.userIntent,
52
+ assistantAction: analysis.assistantAction,
53
+ errorDetected: analysis.errorDetected,
54
+ toolsUsed: analysis.toolsUsed,
55
+ filesInvolved: analysis.filesInvolved,
56
+ },
57
+ recurrence: {
58
+ isRecurring: recurrence.count > 0,
59
+ count: recurrence.count,
60
+ previousLessons: recurrence.previousLessons.map(l => ({
61
+ id: l.id,
62
+ title: l.title,
63
+ timestamp: l.timestamp,
64
+ })),
65
+ },
66
+ proposedRule: proposedRule,
67
+ severity: severity,
68
+ message: formatReflectionMessage(proposedRule, recurrence, analysis),
69
+ };
70
+ }
71
+
72
+ function analyzeConversation(window) {
73
+ const normalizedWindow = normalizeConversationWindow(window);
74
+ const userMsgs = normalizedWindow.filter(m => m.role === 'user');
75
+ const assistantMsgs = normalizedWindow.filter(m => m.role === 'assistant');
76
+
77
+ const lastUser = userMsgs[userMsgs.length - 1]?.content || '';
78
+ const lastAssistant = assistantMsgs[assistantMsgs.length - 1]?.content || '';
79
+ const corrections = extractCorrections(userMsgs);
80
+ const toolsUsed = extractToolCalls(normalizedWindow);
81
+ const filesInvolved = extractFilePaths(normalizedWindow).slice(0, 10);
82
+ const errors = extractErrors(normalizedWindow).slice(0, 5);
83
+
84
+ return {
85
+ userIntent: lastUser.slice(0, 300),
86
+ assistantAction: lastAssistant.slice(0, 300),
87
+ corrections,
88
+ errorDetected: errors.length > 0,
89
+ errors,
90
+ toolsUsed,
91
+ filesInvolved,
92
+ messageCount: normalizedWindow.length,
93
+ };
94
+ }
95
+
96
+ function checkRecurrence(analysis, feedbackEvent) {
97
+ // Search existing lessons for similar mistakes
98
+ let previousLessons = [];
99
+ try {
100
+ const context = `${analysis.userIntent} ${analysis.assistantAction} ${analysis.corrections.join(' ')}`;
101
+ const toolName = analysis.toolsUsed[0] || 'unknown';
102
+ previousLessons = retrieveRelevantLessons(toolName, context, { maxResults: 5 });
103
+ // Filter to only negative lessons
104
+ previousLessons = previousLessons.filter(l => l.signal === 'negative');
105
+ } catch (_err) {
106
+ // Non-critical — recurrence check is best-effort
107
+ }
108
+
109
+ return {
110
+ count: previousLessons.length,
111
+ previousLessons,
112
+ };
113
+ }
114
+
115
+ function generateProposedRule(analysis, structuredRule, recurrence, whatWentWrong) {
116
+ // Build the most specific rule we can from available data
117
+ const parts = [];
118
+
119
+ // Use corrections from conversation as the strongest signal
120
+ if (analysis.corrections.length > 0) {
121
+ const correction = analysis.corrections[0];
122
+ parts.push({
123
+ type: 'constraint',
124
+ rule: `NEVER ${correction}`,
125
+ source: 'user-correction',
126
+ confidence: 0.95,
127
+ });
128
+ }
129
+
130
+ // Use structured rule if available
131
+ if (structuredRule?.trigger && structuredRule?.action) {
132
+ parts.push({
133
+ type: structuredRule.action.type === 'avoid' ? 'constraint' : 'preference',
134
+ rule: `IF ${structuredRule.trigger.condition} THEN ${structuredRule.action.description}`,
135
+ source: 'inferred',
136
+ confidence: structuredRule.confidence || 0.7,
137
+ });
138
+ }
139
+
140
+ // Use whatWentWrong as fallback
141
+ if (parts.length === 0 && whatWentWrong) {
142
+ parts.push({
143
+ type: 'lesson',
144
+ rule: `AVOID: ${whatWentWrong}`,
145
+ source: 'user-provided',
146
+ confidence: 0.8,
147
+ });
148
+ }
149
+
150
+ // Use analysis as last resort
151
+ if (parts.length === 0) {
152
+ parts.push({
153
+ type: 'observation',
154
+ rule: `Review approach when: ${analysis.userIntent.slice(0, 80)}`,
155
+ source: 'conversation-analysis',
156
+ confidence: 0.5,
157
+ });
158
+ }
159
+
160
+ // Pick highest confidence rule
161
+ const best = parts.sort((a, b) => b.confidence - a.confidence)[0];
162
+
163
+ return {
164
+ ...best,
165
+ isRecurring: recurrence.count > 0,
166
+ recurrenceCount: recurrence.count,
167
+ scope: analysis.filesInvolved.length > 0 ? 'project' : 'global',
168
+ };
169
+ }
170
+
171
+ function formatReflectionMessage(proposedRule, recurrence, analysis) {
172
+ const prefix = recurrence.count > 0
173
+ ? `I've made this mistake ${recurrence.count + 1} time(s) now. `
174
+ : '';
175
+
176
+ const ruleText = proposedRule.rule;
177
+
178
+ const correction = analysis.corrections.length > 0
179
+ ? ` I noticed you corrected me: "${analysis.corrections[0].slice(0, 80)}".`
180
+ : '';
181
+
182
+ const fileContext = analysis.filesInvolved.length > 0
183
+ ? ` (in ${analysis.filesInvolved.slice(0, 3).join(', ')})`
184
+ : '';
185
+
186
+ return `${prefix}${correction} I've recorded a rule${fileContext}: "${ruleText}". Correct?`;
187
+ }
188
+
189
+ function extractCorrections(userMessages) {
190
+ const results = [];
191
+ const phraseSets = [
192
+ ['don\'t ', 'do not ', 'never ', 'stop '],
193
+ ['wrong ', 'incorrect ', 'that\'s not ', 'no, '],
194
+ ['i said ', 'i told you ', 'i already '],
195
+ ['use ', 'switch to ', 'change to '],
196
+ ];
197
+
198
+ for (const message of userMessages) {
199
+ const content = String(message.content || '').trim();
200
+ const lower = content.toLowerCase();
201
+ if (!lower) continue;
202
+
203
+ for (const phrases of phraseSets) {
204
+ for (const phrase of phrases) {
205
+ const index = lower.indexOf(phrase);
206
+ if (index === -1) continue;
207
+ let detail = content.slice(index + phrase.length).trim();
208
+ const insteadIndex = detail.toLowerCase().indexOf(' instead');
209
+ if (insteadIndex >= 0) {
210
+ detail = detail.slice(0, insteadIndex).trim();
211
+ }
212
+ if (detail) results.push(detail.slice(0, 100));
213
+ break;
214
+ }
215
+ }
216
+ }
217
+
218
+ return results;
219
+ }
220
+
221
+ module.exports = { reflect, analyzeConversation, checkRecurrence, generateProposedRule, formatReflectionMessage };
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const PROJECT_ROOT = path.join(__dirname, '..');
7
+ const DEFAULT_STATE_PATH = path.join(PROJECT_ROOT, '.rlhf', 'reminder-state.json');
8
+
9
+ const REMINDER_TEMPLATES = {
10
+ guardrail_spike: 'Safety guardrails triggered {{count}} times. Re-apply rule: {{rule}}',
11
+ iteration_limit: 'Approaching max iterations ({{count}}/{{limit}}). Prioritize essential actions only.',
12
+ tool_misuse: 'Tool misuse detected {{count}} times for: {{tools}}. Verify tool schemas before calling.',
13
+ error_cascade: 'Repeated errors ({{count}}). Switch strategy: {{suggestion}}',
14
+ };
15
+
16
+ const DEFAULT_THRESHOLDS = {
17
+ guardrail_spike: 3,
18
+ iteration_limit: 1,
19
+ tool_misuse: 2,
20
+ error_cascade: 3,
21
+ };
22
+
23
+ function getStatePath(stateFile) {
24
+ return stateFile || DEFAULT_STATE_PATH;
25
+ }
26
+
27
+ function loadState(stateFile) {
28
+ const p = getStatePath(stateFile);
29
+ try {
30
+ if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'));
31
+ } catch {
32
+ // corrupted — start fresh
33
+ }
34
+ return { counts: {} };
35
+ }
36
+
37
+ function saveState(state, stateFile) {
38
+ const p = getStatePath(stateFile);
39
+ fs.mkdirSync(path.dirname(p), { recursive: true });
40
+ fs.writeFileSync(p, JSON.stringify(state, null, 2));
41
+ }
42
+
43
+ /**
44
+ * Increment the event counter for a given event type.
45
+ * @param {string} eventType - One of the keys in REMINDER_TEMPLATES
46
+ * @param {string} [stateFile] - Path to state JSON (default: .thumbgate/reminder-state.json)
47
+ * @returns {number} New count after incrementing
48
+ */
49
+ function trackEvent(eventType, stateFile) {
50
+ const state = loadState(stateFile);
51
+ state.counts[eventType] = (state.counts[eventType] || 0) + 1;
52
+ saveState(state, stateFile);
53
+ return state.counts[eventType];
54
+ }
55
+
56
+ /**
57
+ * Get the current event count without modifying state.
58
+ * @param {string} eventType
59
+ * @param {string} [stateFile]
60
+ * @returns {number}
61
+ */
62
+ function getEventCount(eventType, stateFile) {
63
+ return loadState(stateFile).counts[eventType] || 0;
64
+ }
65
+
66
+ /**
67
+ * Return true if the event count meets or exceeds its threshold.
68
+ * @param {string} eventType
69
+ * @param {number} [threshold] - Defaults to DEFAULT_THRESHOLDS[eventType] or 3
70
+ * @param {string} [stateFile]
71
+ * @returns {boolean}
72
+ */
73
+ function shouldInjectReminder(eventType, threshold, stateFile) {
74
+ const t = typeof threshold === 'number' ? threshold : (DEFAULT_THRESHOLDS[eventType] || 3);
75
+ return getEventCount(eventType, stateFile) >= t;
76
+ }
77
+
78
+ /**
79
+ * Render a reminder template with context variable substitution.
80
+ * @param {string} eventType
81
+ * @param {object} ctx - Variables to substitute into {{var}} placeholders
82
+ * @returns {string}
83
+ */
84
+ function renderTemplate(eventType, ctx) {
85
+ const template = REMINDER_TEMPLATES[eventType];
86
+ if (!template) return `[Reminder] Event: ${eventType}`;
87
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => (ctx && ctx[key] !== undefined ? ctx[key] : `{${key}}`));
88
+ }
89
+
90
+ /**
91
+ * Append a system reminder to a turns array without modifying state.
92
+ * Callers are responsible for calling trackEvent before/after as needed.
93
+ * @param {object[]} turns - Existing turns array
94
+ * @param {string} eventType
95
+ * @param {object} ctx - Template variables (count will be added automatically)
96
+ * @param {string} [stateFile]
97
+ * @returns {object[]} New turns array with reminder appended
98
+ */
99
+ function injectReminder(turns, eventType, ctx, stateFile) {
100
+ const count = getEventCount(eventType, stateFile);
101
+ const message = renderTemplate(eventType, { ...ctx, count });
102
+ const reminder = {
103
+ role: 'user',
104
+ content: `[System Reminder] ${message}`,
105
+ injectedAt: new Date().toISOString(),
106
+ eventType,
107
+ };
108
+ return [...turns, reminder];
109
+ }
110
+
111
+ /**
112
+ * Reset the event counter for a given event type (e.g., after a reminder is acted on).
113
+ * @param {string} eventType
114
+ * @param {string} [stateFile]
115
+ */
116
+ function resetEvent(eventType, stateFile) {
117
+ const state = loadState(stateFile);
118
+ state.counts[eventType] = 0;
119
+ saveState(state, stateFile);
120
+ }
121
+
122
+ module.exports = {
123
+ REMINDER_TEMPLATES,
124
+ DEFAULT_THRESHOLDS,
125
+ DEFAULT_STATE_PATH,
126
+ trackEvent,
127
+ getEventCount,
128
+ shouldInjectReminder,
129
+ renderTemplate,
130
+ injectReminder,
131
+ resetEvent,
132
+ };