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,256 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const { initDb, queryMetrics, topContent, getFollowerHistory } = require('./store');
7
+
8
+ const PLATFORMS = ['instagram', 'tiktok', 'github'];
9
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
10
+ const DEFAULT_OUTPUT_DIR = path.join(REPO_ROOT, '.artifacts', 'social', 'digests');
11
+
12
+ /**
13
+ * Formats a Date to YYYY-MM-DD in local time.
14
+ * @param {Date} date
15
+ * @returns {string}
16
+ */
17
+ function toDateString(date) {
18
+ return date.toISOString().slice(0, 10);
19
+ }
20
+
21
+ /**
22
+ * Calculates the follower delta for each platform over the given window.
23
+ * Returns the difference between the most recent snapshot and the oldest
24
+ * snapshot within the window. Returns 0 if fewer than 2 snapshots exist.
25
+ *
26
+ * @param {import('better-sqlite3').Database} db
27
+ * @param {number} days
28
+ * @returns {{ instagram: number, tiktok: number, github: number }}
29
+ */
30
+ function buildFollowerDelta(db, days) {
31
+ const delta = {};
32
+ for (const platform of PLATFORMS) {
33
+ const history = getFollowerHistory(db, { platform, days });
34
+ if (history.length >= 2) {
35
+ delta[platform] = history[history.length - 1].follower_count - history[0].follower_count;
36
+ } else if (history.length === 1) {
37
+ delta[platform] = 0;
38
+ } else {
39
+ delta[platform] = 0;
40
+ }
41
+ }
42
+ return delta;
43
+ }
44
+
45
+ /**
46
+ * Builds per-platform aggregated metrics map.
47
+ *
48
+ * @param {object[]} rows - Rows from queryMetrics (all platforms).
49
+ * @returns {object}
50
+ */
51
+ function buildByPlatform(rows) {
52
+ const byPlatform = {};
53
+ for (const platform of PLATFORMS) {
54
+ const row = rows.find((r) => r.platform === platform);
55
+ if (row) {
56
+ byPlatform[platform] = {
57
+ post_count: row.post_count,
58
+ total_impressions: row.total_impressions,
59
+ total_reach: row.total_reach,
60
+ total_likes: row.total_likes,
61
+ total_comments: row.total_comments,
62
+ total_shares: row.total_shares,
63
+ total_saves: row.total_saves,
64
+ total_clicks: row.total_clicks,
65
+ total_video_views: row.total_video_views,
66
+ avg_impressions: row.avg_impressions,
67
+ avg_likes: row.avg_likes,
68
+ };
69
+ } else {
70
+ byPlatform[platform] = {
71
+ post_count: 0,
72
+ total_impressions: 0,
73
+ total_reach: 0,
74
+ total_likes: 0,
75
+ total_comments: 0,
76
+ total_shares: 0,
77
+ total_saves: 0,
78
+ total_clicks: 0,
79
+ total_video_views: 0,
80
+ avg_impressions: 0,
81
+ avg_likes: 0,
82
+ };
83
+ }
84
+ }
85
+ return byPlatform;
86
+ }
87
+
88
+ /**
89
+ * Generates a weekly digest object from the SQLite database.
90
+ *
91
+ * @param {import('better-sqlite3').Database} db
92
+ * @param {{ days?: number }} options
93
+ * @returns {object} digest
94
+ */
95
+ function generateDigest(db, { days = 7 } = {}) {
96
+ const end = new Date();
97
+ const start = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
98
+
99
+ const allMetrics = queryMetrics(db, { days });
100
+ const top = topContent(db, { days, limit: 10 });
101
+ const followerDelta = buildFollowerDelta(db, days);
102
+ const byPlatform = buildByPlatform(allMetrics);
103
+
104
+ // Aggregate totals across all platforms.
105
+ let totalImpressions = 0;
106
+ let totalLikes = 0;
107
+ let totalComments = 0;
108
+ let totalShares = 0;
109
+
110
+ for (const row of allMetrics) {
111
+ totalImpressions += row.total_impressions || 0;
112
+ totalLikes += row.total_likes || 0;
113
+ totalComments += row.total_comments || 0;
114
+ totalShares += row.total_shares || 0;
115
+ }
116
+
117
+ const engagementRaw =
118
+ totalImpressions > 0
119
+ ? (((totalLikes + totalComments + totalShares) / totalImpressions) * 100).toFixed(1)
120
+ : '0.0';
121
+
122
+ const topContent_ = top.map((row) => ({
123
+ platform: row.platform,
124
+ post_id: row.post_id,
125
+ post_url: row.post_url || null,
126
+ total_engagement: row.total_engagement,
127
+ impressions: row.total_impressions,
128
+ }));
129
+
130
+ return {
131
+ period: {
132
+ start: toDateString(start),
133
+ end: toDateString(end),
134
+ },
135
+ summary: {
136
+ total_impressions: totalImpressions,
137
+ total_likes: totalLikes,
138
+ total_comments: totalComments,
139
+ total_shares: totalShares,
140
+ engagement_rate: `${engagementRaw}%`,
141
+ follower_delta: followerDelta,
142
+ },
143
+ by_platform: byPlatform,
144
+ top_content: topContent_,
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Converts a digest object to a readable markdown string.
150
+ *
151
+ * @param {object} digest
152
+ * @returns {string}
153
+ */
154
+ function renderDigestMarkdown(digest) {
155
+ const { period, summary, by_platform, top_content } = digest;
156
+
157
+ const lines = [];
158
+
159
+ lines.push(`# Weekly Social Digest: ${period.start} → ${period.end}`);
160
+ lines.push('');
161
+ lines.push('## Summary');
162
+ lines.push('');
163
+ lines.push(`| Metric | Value |`);
164
+ lines.push(`|--------|-------|`);
165
+ lines.push(`| Total Impressions | ${summary.total_impressions.toLocaleString()} |`);
166
+ lines.push(`| Total Likes | ${summary.total_likes.toLocaleString()} |`);
167
+ lines.push(`| Total Comments | ${summary.total_comments.toLocaleString()} |`);
168
+ lines.push(`| Total Shares | ${summary.total_shares.toLocaleString()} |`);
169
+ lines.push(`| Engagement Rate | ${summary.engagement_rate} |`);
170
+ lines.push('');
171
+ lines.push('### Follower Delta');
172
+ lines.push('');
173
+ lines.push(`| Platform | Change |`);
174
+ lines.push(`|----------|--------|`);
175
+ for (const [platform, delta] of Object.entries(summary.follower_delta)) {
176
+ const sign = delta >= 0 ? '+' : '';
177
+ lines.push(`| ${platform} | ${sign}${delta} |`);
178
+ }
179
+
180
+ lines.push('');
181
+ lines.push('## By Platform');
182
+ lines.push('');
183
+
184
+ for (const [platform, stats] of Object.entries(by_platform)) {
185
+ lines.push(`### ${platform.charAt(0).toUpperCase() + platform.slice(1)}`);
186
+ lines.push('');
187
+ lines.push(`| Metric | Value |`);
188
+ lines.push(`|--------|-------|`);
189
+ lines.push(`| Posts | ${stats.post_count} |`);
190
+ lines.push(`| Impressions | ${stats.total_impressions.toLocaleString()} |`);
191
+ lines.push(`| Reach | ${stats.total_reach.toLocaleString()} |`);
192
+ lines.push(`| Likes | ${stats.total_likes.toLocaleString()} |`);
193
+ lines.push(`| Comments | ${stats.total_comments.toLocaleString()} |`);
194
+ lines.push(`| Shares | ${stats.total_shares.toLocaleString()} |`);
195
+ lines.push(`| Saves | ${stats.total_saves.toLocaleString()} |`);
196
+ lines.push(`| Clicks | ${stats.total_clicks.toLocaleString()} |`);
197
+ lines.push(`| Video Views | ${stats.total_video_views.toLocaleString()} |`);
198
+ lines.push('');
199
+ }
200
+
201
+ lines.push('## Top Content');
202
+ lines.push('');
203
+
204
+ if (top_content.length === 0) {
205
+ lines.push('_No content recorded in this period._');
206
+ } else {
207
+ lines.push(`| # | Platform | Post ID | Engagement | Impressions | URL |`);
208
+ lines.push(`|---|----------|---------|------------|-------------|-----|`);
209
+ top_content.forEach((item, idx) => {
210
+ const url = item.post_url ? `[link](${item.post_url})` : '—';
211
+ lines.push(
212
+ `| ${idx + 1} | ${item.platform} | ${item.post_id} | ${item.total_engagement} | ${item.impressions.toLocaleString()} | ${url} |`
213
+ );
214
+ });
215
+ }
216
+
217
+ lines.push('');
218
+ lines.push(`_Generated at ${new Date().toISOString()}_`);
219
+
220
+ return lines.join('\n');
221
+ }
222
+
223
+ /**
224
+ * Writes digest.json and digest.md to the given output directory.
225
+ *
226
+ * @param {object} digest
227
+ * @param {string} outputDir
228
+ * @returns {{ jsonPath: string, mdPath: string }}
229
+ */
230
+ function writeDigest(digest, outputDir) {
231
+ fs.mkdirSync(outputDir, { recursive: true });
232
+
233
+ const jsonPath = path.join(outputDir, 'digest.json');
234
+ const mdPath = path.join(outputDir, 'digest.md');
235
+
236
+ fs.writeFileSync(jsonPath, JSON.stringify(digest, null, 2), 'utf8');
237
+ fs.writeFileSync(mdPath, renderDigestMarkdown(digest), 'utf8');
238
+
239
+ return { jsonPath, mdPath };
240
+ }
241
+
242
+ module.exports = {
243
+ generateDigest,
244
+ renderDigestMarkdown,
245
+ writeDigest,
246
+ };
247
+
248
+ // Run as main: generate and write a 7-day digest.
249
+ if (require.main === module) {
250
+ const db = initDb();
251
+ const digest = generateDigest(db, { days: 7 });
252
+ const { jsonPath, mdPath } = writeDigest(digest, DEFAULT_OUTPUT_DIR);
253
+ console.log(`Digest written:\n JSON: ${jsonPath}\n MD: ${mdPath}`);
254
+ console.log(`\nSummary: ${JSON.stringify(digest.summary, null, 2)}`);
255
+ db.close();
256
+ }
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * generate-instagram-card.js
6
+ * Creates a 1080x1080 ThumbGate Instagram card using sharp.
7
+ *
8
+ * Usage:
9
+ * node generate-instagram-card.js [--output=path/to/output.png]
10
+ *
11
+ * Output: .thumbgate/instagram-card.png (default)
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ let sharp;
17
+ try { sharp = require('sharp'); } catch { /* optional dependency */ }
18
+
19
+ const REPO_ROOT = path.resolve(__dirname, '../..');
20
+ const DEFAULT_OUTPUT = path.join(REPO_ROOT, '.rlhf', 'instagram-card.png');
21
+
22
+ async function generateInstagramCard(outputPath = DEFAULT_OUTPUT) {
23
+ const width = 1080;
24
+ const height = 1080;
25
+
26
+ // Create SVG markup for the card
27
+ const svg = `
28
+ <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
29
+ <!-- Background -->
30
+ <rect width="${width}" height="${height}" fill="#0d1117"/>
31
+
32
+ <!-- Main heading -->
33
+ <text x="${width / 2}" y="400" font-size="72" font-weight="bold" text-anchor="middle" fill="#ffffff" font-family="Arial, sans-serif">
34
+ Your AI agent
35
+ </text>
36
+ <text x="${width / 2}" y="490" font-size="72" font-weight="bold" text-anchor="middle" fill="#ffffff" font-family="Arial, sans-serif">
37
+ has amnesia.
38
+ </text>
39
+
40
+ <!-- Subheading -->
41
+ <text x="${width / 2}" y="620" font-size="48" text-anchor="middle" fill="#888888" font-family="Arial, sans-serif">
42
+ Give it memory that
43
+ </text>
44
+ <text x="${width / 2}" y="680" font-size="48" text-anchor="middle" fill="#888888" font-family="Arial, sans-serif">
45
+ survives restarts.
46
+ </text>
47
+
48
+ <!-- Brand -->
49
+ <text x="${width / 2}" y="950" font-size="56" font-weight="bold" text-anchor="middle" fill="#00d9ff" font-family="Arial, sans-serif">
50
+ ThumbGate
51
+ </text>
52
+ </svg>
53
+ `;
54
+
55
+ if (!sharp) {
56
+ throw new Error('sharp is not installed. Run: npm install sharp');
57
+ }
58
+
59
+ try {
60
+ // Ensure output directory exists
61
+ const outputDir = path.dirname(outputPath);
62
+ if (!fs.existsSync(outputDir)) {
63
+ fs.mkdirSync(outputDir, { recursive: true });
64
+ }
65
+
66
+ // Generate PNG from SVG
67
+ await sharp(Buffer.from(svg))
68
+ .png()
69
+ .toFile(outputPath);
70
+
71
+ console.log(`✅ Instagram card generated: ${outputPath}`);
72
+ console.log(` Size: 1080x1080 PNG`);
73
+ console.log(` File size: ${fs.statSync(outputPath).size} bytes`);
74
+ return outputPath;
75
+ } catch (err) {
76
+ console.error(`❌ Image generation failed: ${err.message}`);
77
+ throw err;
78
+ }
79
+ }
80
+
81
+ // CLI execution
82
+ if (require.main === module) {
83
+ const args = process.argv.slice(2);
84
+ const outputArg = args.find((a) => a.startsWith('--output='));
85
+ const outputPath = outputArg ? outputArg.slice(9) : DEFAULT_OUTPUT;
86
+
87
+ (async () => {
88
+ try {
89
+ await generateInstagramCard(outputPath);
90
+ process.exit(0);
91
+ } catch (err) {
92
+ process.exit(1);
93
+ }
94
+ })();
95
+ }
96
+
97
+ module.exports = { generateInstagramCard, DEFAULT_OUTPUT };
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * instagram-thumbgate-post.js
6
+ * Posts ThumbGate Instagram card and caption via Zernio API.
7
+ *
8
+ * Usage:
9
+ * node instagram-thumbgate-post.js [--image-path=path/to/image.png]
10
+ */
11
+
12
+ const path = require('path');
13
+ const {
14
+ publishPost,
15
+ schedulePost,
16
+ getConnectedAccounts,
17
+ uploadLocalMedia,
18
+ } = require('./publishers/zernio');
19
+
20
+ const THUMBGATE_CAPTION = `Your AI coding agent has amnesia. It forgets everything between sessions.
21
+
22
+ ThumbGate gives it memory that survives restarts. Thumbs-down a mistake → it becomes a prevention rule → the agent can't repeat it.
23
+
24
+ One command: npx thumbgate init
25
+
26
+ Works with Claude Code, Cursor, Codex, Gemini.
27
+
28
+ #AIAgents #DeveloperTools #ClaudeCode #ThumbGate`;
29
+
30
+ async function postThumbGateToInstagram(options = {}) {
31
+ const caption = String(options.caption || THUMBGATE_CAPTION).trim();
32
+ const imagePath = options.imagePath ? path.resolve(options.imagePath) : '';
33
+ const schedule = String(options.schedule || '').trim();
34
+ const timezone = String(options.timezone || 'America/New_York').trim() || 'America/New_York';
35
+
36
+ try {
37
+ console.log('[instagram] Fetching Zernio connected accounts...');
38
+ const accounts = await getConnectedAccounts();
39
+
40
+ // Find Instagram account
41
+ const instagramAccount = accounts.find((a) => a.platform === 'instagram');
42
+ if (!instagramAccount) {
43
+ throw new Error('No Instagram account found in Zernio. Please connect Instagram first.');
44
+ }
45
+
46
+ console.log(`[instagram] Found Instagram account: ${instagramAccount.accountId}`);
47
+
48
+ const platforms = [
49
+ {
50
+ platform: 'instagram',
51
+ accountId: instagramAccount.accountId,
52
+ },
53
+ ];
54
+
55
+ if (!imagePath) {
56
+ throw new Error('Instagram posts require an imagePath because Zernio requires media content for Instagram posts.');
57
+ }
58
+
59
+ console.log(`[instagram] Uploading Instagram media from ${imagePath}...`);
60
+ const mediaItem = await uploadLocalMedia(imagePath);
61
+
62
+ const publishOptions = {
63
+ mediaItems: [mediaItem],
64
+ utm: options.utm,
65
+ };
66
+
67
+ let result;
68
+ if (schedule) {
69
+ console.log(`[instagram] Scheduling Instagram post for ${schedule} (${timezone})...`);
70
+ result = await schedulePost(caption, platforms, schedule, timezone, publishOptions);
71
+ } else {
72
+ console.log('[instagram] Publishing ThumbGate caption to Instagram...');
73
+ result = await publishPost(caption, platforms, publishOptions);
74
+ }
75
+ if (result && result.blocked) {
76
+ const reasons = Array.isArray(result.reasons)
77
+ ? result.reasons.map((reason) => reason.reason || reason.id || String(reason)).join(', ')
78
+ : 'quality gate blocked the caption';
79
+ throw new Error(`Instagram post blocked: ${reasons}`);
80
+ }
81
+
82
+ console.log('✅ Post published successfully!');
83
+ console.log(`Post ID: ${result.id || result.data?.id || 'unknown'}`);
84
+ return result;
85
+ } catch (err) {
86
+ console.error(`❌ Failed to post to Instagram: ${err.message}`);
87
+ throw err;
88
+ }
89
+ }
90
+
91
+ // CLI execution
92
+ if (require.main === module) {
93
+ const args = process.argv.slice(2);
94
+ const imageArg = args.find((arg) => arg.startsWith('--image-path='));
95
+ const imagePath = imageArg ? imageArg.slice('--image-path='.length) : '';
96
+
97
+ (async () => {
98
+ try {
99
+ await postThumbGateToInstagram({ imagePath });
100
+ process.exit(0);
101
+ } catch (err) {
102
+ process.exit(1);
103
+ }
104
+ })();
105
+ }
106
+
107
+ module.exports = { postThumbGateToInstagram, THUMBGATE_CAPTION };
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const dotenv = require('dotenv');
6
+
7
+ const DEFAULT_ENV_PATH = path.resolve(__dirname, '..', '..', '.env');
8
+
9
+ function resolveEnvPath(envPath = DEFAULT_ENV_PATH) {
10
+ return path.isAbsolute(envPath) ? envPath : path.resolve(process.cwd(), envPath);
11
+ }
12
+
13
+ function loadLocalEnv(options = {}) {
14
+ const resolvedPath = resolveEnvPath(options.envPath);
15
+ if (!fs.existsSync(resolvedPath)) {
16
+ return {
17
+ exists: false,
18
+ loadedKeys: [],
19
+ path: resolvedPath,
20
+ };
21
+ }
22
+
23
+ const parsed = dotenv.parse(fs.readFileSync(resolvedPath, 'utf8'));
24
+ const loadedKeys = [];
25
+ const override = options.override === true;
26
+
27
+ for (const [key, value] of Object.entries(parsed)) {
28
+ if (!override && process.env[key] !== undefined) {
29
+ continue;
30
+ }
31
+ process.env[key] = value;
32
+ loadedKeys.push(key);
33
+ }
34
+
35
+ return {
36
+ exists: true,
37
+ loadedKeys,
38
+ path: resolvedPath,
39
+ };
40
+ }
41
+
42
+ module.exports = {
43
+ DEFAULT_ENV_PATH,
44
+ loadLocalEnv,
45
+ resolveEnvPath,
46
+ };