thumbgate 0.9.10

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 (364) 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/thumbgate-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 +1484 -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 +283 -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 +1014 -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-312.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 +299 -0
  117. package/scripts/auto-wire-hooks.js +312 -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 +97 -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 +263 -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 +209 -0
  152. package/scripts/ephemeral-agent-store.js +219 -0
  153. package/scripts/eval-harness.js +56 -0
  154. package/scripts/evolution-state.js +241 -0
  155. package/scripts/experiment-tracker.js +267 -0
  156. package/scripts/export-databricks-bundle.js +242 -0
  157. package/scripts/export-dpo-pairs.js +345 -0
  158. package/scripts/export-kto-pairs.js +310 -0
  159. package/scripts/export-training.js +448 -0
  160. package/scripts/failure-diagnostics.js +558 -0
  161. package/scripts/feedback-attribution.js +313 -0
  162. package/scripts/feedback-fallback.js +111 -0
  163. package/scripts/feedback-history-distiller.js +391 -0
  164. package/scripts/feedback-inbox-read.js +162 -0
  165. package/scripts/feedback-loop.js +1887 -0
  166. package/scripts/feedback-paths.js +145 -0
  167. package/scripts/feedback-quality.js +139 -0
  168. package/scripts/feedback-root-consolidator.js +238 -0
  169. package/scripts/feedback-schema.js +426 -0
  170. package/scripts/feedback-session.js +286 -0
  171. package/scripts/feedback-to-memory.js +185 -0
  172. package/scripts/feedback-to-rules.js +163 -0
  173. package/scripts/filesystem-search.js +404 -0
  174. package/scripts/funnel-analytics.js +35 -0
  175. package/scripts/gate-satisfy.js +42 -0
  176. package/scripts/gate-stats.js +116 -0
  177. package/scripts/gate-templates.js +70 -0
  178. package/scripts/gates-engine.js +816 -0
  179. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  180. package/scripts/generate-pretool-hook.sh +40 -0
  181. package/scripts/github-about.js +350 -0
  182. package/scripts/github-outreach.js +65 -0
  183. package/scripts/gtm-revenue-loop.js +520 -0
  184. package/scripts/hallucination-detector.js +226 -0
  185. package/scripts/hf-papers.js +317 -0
  186. package/scripts/history-distiller.js +200 -0
  187. package/scripts/hook-auto-capture.sh +95 -0
  188. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  189. package/scripts/hook-stop-self-score.sh +51 -0
  190. package/scripts/hook-stop-verify-deploy.sh +31 -0
  191. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  192. package/scripts/hook-verify-before-done.sh +20 -0
  193. package/scripts/hosted-config.js +170 -0
  194. package/scripts/hybrid-feedback-context.js +676 -0
  195. package/scripts/install-mcp.js +159 -0
  196. package/scripts/intent-router.js +392 -0
  197. package/scripts/internal-agent-bootstrap.js +490 -0
  198. package/scripts/jsonl-watcher.js +155 -0
  199. package/scripts/lesson-db.js +613 -0
  200. package/scripts/lesson-inference.js +315 -0
  201. package/scripts/lesson-retrieval.js +95 -0
  202. package/scripts/lesson-rotation.js +137 -0
  203. package/scripts/lesson-search.js +644 -0
  204. package/scripts/lesson-synthesis.js +196 -0
  205. package/scripts/license.js +50 -0
  206. package/scripts/local-model-profile.js +383 -0
  207. package/scripts/markdown-escape.js +12 -0
  208. package/scripts/marketing-experiment.js +671 -0
  209. package/scripts/mcp-config.js +149 -0
  210. package/scripts/mcp-policy.js +99 -0
  211. package/scripts/memalign-recall.js +111 -0
  212. package/scripts/memory-firewall.js +222 -0
  213. package/scripts/memory-migration.js +296 -0
  214. package/scripts/meta-policy.js +194 -0
  215. package/scripts/metered-billing.js +16 -0
  216. package/scripts/model-tier-router.js +301 -0
  217. package/scripts/money-watcher.js +71 -0
  218. package/scripts/multi-hop-recall.js +240 -0
  219. package/scripts/natural-language-harness.js +330 -0
  220. package/scripts/obsidian-export.js +712 -0
  221. package/scripts/operational-dashboard.js +103 -0
  222. package/scripts/operational-summary.js +93 -0
  223. package/scripts/optimize-context.js +17 -0
  224. package/scripts/org-dashboard.js +201 -0
  225. package/scripts/partner-orchestration.js +146 -0
  226. package/scripts/per-step-scoring.js +165 -0
  227. package/scripts/perplexity-marketing.js +466 -0
  228. package/scripts/pii-scanner.js +153 -0
  229. package/scripts/plan-gate.js +154 -0
  230. package/scripts/post-everywhere.js +308 -0
  231. package/scripts/post-to-x-retry.sh +22 -0
  232. package/scripts/post-to-x.js +369 -0
  233. package/scripts/pr-manager.js +236 -0
  234. package/scripts/predictive-insights.js +356 -0
  235. package/scripts/principle-extractor.js +162 -0
  236. package/scripts/pro-features.js +40 -0
  237. package/scripts/pro-local-dashboard.js +174 -0
  238. package/scripts/problem-detail.js +53 -0
  239. package/scripts/product-feedback.js +134 -0
  240. package/scripts/profile-router.js +245 -0
  241. package/scripts/prompt-dlp.js +221 -0
  242. package/scripts/prompt-guard.js +83 -0
  243. package/scripts/prove-adapters.js +863 -0
  244. package/scripts/prove-attribution.js +365 -0
  245. package/scripts/prove-automation.js +653 -0
  246. package/scripts/prove-autoresearch.js +304 -0
  247. package/scripts/prove-claim-verification.js +277 -0
  248. package/scripts/prove-cloudflare-sandbox.js +163 -0
  249. package/scripts/prove-data-pipeline.js +410 -0
  250. package/scripts/prove-data-quality.js +227 -0
  251. package/scripts/prove-evolution.js +352 -0
  252. package/scripts/prove-harnesses.js +287 -0
  253. package/scripts/prove-intelligence.js +259 -0
  254. package/scripts/prove-lancedb.js +371 -0
  255. package/scripts/prove-local-intelligence.js +342 -0
  256. package/scripts/prove-loop-closure.js +263 -0
  257. package/scripts/prove-predictive-insights.js +357 -0
  258. package/scripts/prove-runtime.js +350 -0
  259. package/scripts/prove-seo-gsd.js +234 -0
  260. package/scripts/prove-settings.js +279 -0
  261. package/scripts/prove-subway-upgrades.js +277 -0
  262. package/scripts/prove-tessl.js +229 -0
  263. package/scripts/prove-training-export.js +327 -0
  264. package/scripts/prove-workflow-contract.js +116 -0
  265. package/scripts/prove-xmemory.js +332 -0
  266. package/scripts/publish-decision.js +133 -0
  267. package/scripts/pulse.js +80 -0
  268. package/scripts/rate-limiter.js +125 -0
  269. package/scripts/reddit-dm-outreach.js +182 -0
  270. package/scripts/reddit-monitor-cron.sh +26 -0
  271. package/scripts/reflector-agent.js +221 -0
  272. package/scripts/reminder-engine.js +132 -0
  273. package/scripts/revenue-status.js +472 -0
  274. package/scripts/risk-scorer.js +458 -0
  275. package/scripts/rlaif-self-audit.js +129 -0
  276. package/scripts/rubric-engine.js +230 -0
  277. package/scripts/schedule-manager.js +251 -0
  278. package/scripts/secret-scanner.js +414 -0
  279. package/scripts/self-heal.js +147 -0
  280. package/scripts/self-healing-check.js +188 -0
  281. package/scripts/semantic-layer.js +98 -0
  282. package/scripts/seo-gsd.js +1153 -0
  283. package/scripts/settings-hierarchy.js +214 -0
  284. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  285. package/scripts/skill-exporter.js +262 -0
  286. package/scripts/skill-generator.js +446 -0
  287. package/scripts/skill-materializer.js +134 -0
  288. package/scripts/skill-packs.js +136 -0
  289. package/scripts/skill-proposer.js +99 -0
  290. package/scripts/skill-quality-tracker.js +284 -0
  291. package/scripts/slo-alert-engine.js +14 -0
  292. package/scripts/slow-loop.js +72 -0
  293. package/scripts/social-analytics/db/schema.sql +32 -0
  294. package/scripts/social-analytics/digest.js +256 -0
  295. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  296. package/scripts/social-analytics/instagram-thumbgate-post.js +73 -0
  297. package/scripts/social-analytics/mcp-server.js +289 -0
  298. package/scripts/social-analytics/normalizer.js +580 -0
  299. package/scripts/social-analytics/notify.js +162 -0
  300. package/scripts/social-analytics/poll-all.js +107 -0
  301. package/scripts/social-analytics/pollers/github.js +195 -0
  302. package/scripts/social-analytics/pollers/instagram.js +253 -0
  303. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  304. package/scripts/social-analytics/pollers/plausible.js +247 -0
  305. package/scripts/social-analytics/pollers/reddit.js +306 -0
  306. package/scripts/social-analytics/pollers/threads.js +233 -0
  307. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  308. package/scripts/social-analytics/pollers/x.js +227 -0
  309. package/scripts/social-analytics/pollers/youtube.js +304 -0
  310. package/scripts/social-analytics/pollers/zernio.js +180 -0
  311. package/scripts/social-analytics/publish-instagram-thumbgate.js +85 -0
  312. package/scripts/social-analytics/publishers/devto.js +122 -0
  313. package/scripts/social-analytics/publishers/instagram.js +317 -0
  314. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  315. package/scripts/social-analytics/publishers/reddit.js +390 -0
  316. package/scripts/social-analytics/publishers/threads.js +275 -0
  317. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  318. package/scripts/social-analytics/publishers/x.js +259 -0
  319. package/scripts/social-analytics/publishers/youtube.js +223 -0
  320. package/scripts/social-analytics/publishers/zernio.js +209 -0
  321. package/scripts/social-analytics/run-digest.js +34 -0
  322. package/scripts/social-analytics/store.js +257 -0
  323. package/scripts/social-analytics/utm.js +143 -0
  324. package/scripts/social-pipeline.js +2628 -0
  325. package/scripts/social-quality-gate.js +18 -0
  326. package/scripts/social-reply-monitor.js +445 -0
  327. package/scripts/status-dashboard.js +155 -0
  328. package/scripts/statusline-lesson.js +16 -0
  329. package/scripts/statusline-tower.js +8 -0
  330. package/scripts/statusline.sh +116 -0
  331. package/scripts/stripe-live-status.js +115 -0
  332. package/scripts/subagent-profiles.js +79 -0
  333. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  334. package/scripts/sync-github-about.js +52 -0
  335. package/scripts/sync-version.js +451 -0
  336. package/scripts/synthetic-dpo.js +234 -0
  337. package/scripts/telemetry-analytics.js +821 -0
  338. package/scripts/tessl-export.js +371 -0
  339. package/scripts/test-coverage.js +120 -0
  340. package/scripts/thompson-sampling.js +417 -0
  341. package/scripts/thumbgate-search.js +189 -0
  342. package/scripts/tool-kpi-tracker.js +12 -0
  343. package/scripts/tool-registry.js +811 -0
  344. package/scripts/train_from_feedback.py +910 -0
  345. package/scripts/user-profile.js +78 -0
  346. package/scripts/validate-feedback.js +580 -0
  347. package/scripts/validate-workflow-contract.js +287 -0
  348. package/scripts/vector-store.js +198 -0
  349. package/scripts/verification-loop.js +291 -0
  350. package/scripts/verify-obsidian-setup.sh +269 -0
  351. package/scripts/verify-run.js +269 -0
  352. package/scripts/webhook-delivery.js +62 -0
  353. package/scripts/weekly-auto-post.js +124 -0
  354. package/scripts/workflow-runs.js +154 -0
  355. package/scripts/workflow-sprint-intake.js +475 -0
  356. package/scripts/workspace-evolver.js +374 -0
  357. package/scripts/x-autonomous-marketing.js +139 -0
  358. package/scripts/xmemory-lite.js +405 -0
  359. package/skills/agent-memory/SKILL.md +97 -0
  360. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  361. package/skills/solve-architecture-autonomy/tool.js +33 -0
  362. package/skills/thumbgate/SKILL.md +114 -0
  363. package/skills/thumbgate-feedback/SKILL.md +49 -0
  364. package/src/api/server.js +4208 -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, '.thumbgate', '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,73 @@
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
+ publishToAllPlatforms,
16
+ getConnectedAccounts,
17
+ } = require('./publishers/zernio');
18
+
19
+ const THUMBGATE_CAPTION = `Your AI coding agent has amnesia. It forgets everything between sessions.
20
+
21
+ ThumbGate gives it memory that survives restarts. Thumbs-down a mistake → it becomes a prevention rule → the agent can't repeat it.
22
+
23
+ One command: npx thumbgate init
24
+
25
+ Works with Claude Code, Cursor, Codex, Gemini.
26
+
27
+ #AIAgents #DeveloperTools #CodingAgents #MCP #OpenSource #ClaudeCode #ThumbGate`;
28
+
29
+ async function postThumbGateToInstagram() {
30
+ try {
31
+ console.log('[instagram] Fetching Zernio connected accounts...');
32
+ const accounts = await getConnectedAccounts();
33
+
34
+ // Find Instagram account
35
+ const instagramAccount = accounts.find((a) => a.platform === 'instagram');
36
+ if (!instagramAccount) {
37
+ throw new Error('No Instagram account found in Zernio. Please connect Instagram first.');
38
+ }
39
+
40
+ console.log(`[instagram] Found Instagram account: ${instagramAccount.accountId}`);
41
+
42
+ const platforms = [
43
+ {
44
+ platform: 'instagram',
45
+ accountId: instagramAccount.accountId,
46
+ },
47
+ ];
48
+
49
+ console.log('[instagram] Publishing ThumbGate caption to Instagram...');
50
+ const result = await publishPost(THUMBGATE_CAPTION, platforms);
51
+
52
+ console.log('✅ Post published successfully!');
53
+ console.log(`Post ID: ${result.id || result.data?.id || 'unknown'}`);
54
+ return result;
55
+ } catch (err) {
56
+ console.error(`❌ Failed to post to Instagram: ${err.message}`);
57
+ throw err;
58
+ }
59
+ }
60
+
61
+ // CLI execution
62
+ if (require.main === module) {
63
+ (async () => {
64
+ try {
65
+ await postThumbGateToInstagram();
66
+ process.exit(0);
67
+ } catch (err) {
68
+ process.exit(1);
69
+ }
70
+ })();
71
+ }
72
+
73
+ module.exports = { postThumbGateToInstagram, THUMBGATE_CAPTION };
@@ -0,0 +1,289 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * MCP stdio server for social analytics.
5
+ *
6
+ * Exposes social metrics from SQLite as queryable tools for Claude.
7
+ * Uses a simple JSON-RPC 2.0 over stdio pattern — no external MCP SDK required.
8
+ *
9
+ * Protocol:
10
+ * - Reads newline-delimited JSON-RPC 2.0 requests from stdin.
11
+ * - Writes newline-delimited JSON-RPC 2.0 responses to stdout.
12
+ * - Supported methods: tools/list, tools/call
13
+ */
14
+
15
+ const readline = require('readline');
16
+
17
+ const { initDb, queryMetrics, topContent, getFollowerHistory } = require('./store');
18
+ const { generateDigest } = require('./digest');
19
+
20
+ const VALID_PLATFORMS = ['instagram', 'tiktok', 'github', 'all'];
21
+
22
+ // Tool definitions exposed to Claude.
23
+ const TOOLS = [
24
+ {
25
+ name: 'query_social_metrics',
26
+ description:
27
+ 'Returns aggregated engagement metrics from the social analytics database for the given platform and time window.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ platform: {
32
+ type: 'string',
33
+ enum: VALID_PLATFORMS,
34
+ description: 'Platform to query. Use "all" for all platforms combined.',
35
+ },
36
+ days: {
37
+ type: 'number',
38
+ description: 'Number of past days to include in the query.',
39
+ default: 7,
40
+ },
41
+ },
42
+ required: ['platform'],
43
+ },
44
+ },
45
+ {
46
+ name: 'top_social_content',
47
+ description:
48
+ 'Returns top content items ranked by total engagement (likes + comments + shares + saves) over the past N days.',
49
+ inputSchema: {
50
+ type: 'object',
51
+ properties: {
52
+ days: {
53
+ type: 'number',
54
+ description: 'Number of past days to consider.',
55
+ default: 7,
56
+ },
57
+ limit: {
58
+ type: 'number',
59
+ description: 'Maximum number of results to return.',
60
+ default: 10,
61
+ },
62
+ },
63
+ required: [],
64
+ },
65
+ },
66
+ {
67
+ name: 'follower_growth',
68
+ description: 'Returns follower count snapshots for a platform over the past N days.',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ platform: {
73
+ type: 'string',
74
+ enum: ['instagram', 'tiktok', 'github'],
75
+ description: 'Platform to query follower history for.',
76
+ },
77
+ days: {
78
+ type: 'number',
79
+ description: 'Number of past days to include.',
80
+ default: 7,
81
+ },
82
+ },
83
+ required: ['platform'],
84
+ },
85
+ },
86
+ {
87
+ name: 'social_digest',
88
+ description:
89
+ 'Returns a full digest object summarising all social metrics, follower deltas, and top content for the past N days.',
90
+ inputSchema: {
91
+ type: 'object',
92
+ properties: {
93
+ days: {
94
+ type: 'number',
95
+ description: 'Number of past days to summarise.',
96
+ default: 7,
97
+ },
98
+ },
99
+ required: [],
100
+ },
101
+ },
102
+ ];
103
+
104
+ /**
105
+ * Validates that a value is a positive integer (or coerces a numeric string).
106
+ *
107
+ * @param {*} value
108
+ * @param {number} defaultVal
109
+ * @returns {number}
110
+ */
111
+ function asPositiveInt(value, defaultVal) {
112
+ const n = value == null ? defaultVal : Number(value);
113
+ if (!Number.isFinite(n) || n <= 0) {
114
+ throw new Error(`Expected a positive number, got: ${value}`);
115
+ }
116
+ return Math.floor(n);
117
+ }
118
+
119
+ /**
120
+ * Dispatches a tools/call request and returns the result payload.
121
+ *
122
+ * @param {import('better-sqlite3').Database} db
123
+ * @param {string} toolName
124
+ * @param {object} toolArgs
125
+ * @returns {object}
126
+ */
127
+ function callTool(db, toolName, toolArgs) {
128
+ const args = toolArgs || {};
129
+
130
+ switch (toolName) {
131
+ case 'query_social_metrics': {
132
+ const platform = args.platform;
133
+ if (!VALID_PLATFORMS.includes(platform)) {
134
+ throw new Error(`Invalid platform "${platform}". Must be one of: ${VALID_PLATFORMS.join(', ')}`);
135
+ }
136
+ const days = asPositiveInt(args.days, 7);
137
+ const opts = platform === 'all' ? { days } : { platform, days };
138
+ const rows = queryMetrics(db, opts);
139
+ return { rows };
140
+ }
141
+
142
+ case 'top_social_content': {
143
+ const days = asPositiveInt(args.days, 7);
144
+ const limit = asPositiveInt(args.limit, 10);
145
+ const rows = topContent(db, { days, limit });
146
+ return { rows };
147
+ }
148
+
149
+ case 'follower_growth': {
150
+ const platform = args.platform;
151
+ if (!platform || !['instagram', 'tiktok', 'github'].includes(platform)) {
152
+ throw new Error(`Invalid platform "${platform}". Must be one of: instagram, tiktok, github`);
153
+ }
154
+ const days = asPositiveInt(args.days, 7);
155
+ const rows = getFollowerHistory(db, { platform, days });
156
+ return { rows };
157
+ }
158
+
159
+ case 'social_digest': {
160
+ const days = asPositiveInt(args.days, 7);
161
+ const digest = generateDigest(db, { days });
162
+ return digest;
163
+ }
164
+
165
+ default:
166
+ throw new Error(`Unknown tool: ${toolName}`);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Builds a JSON-RPC 2.0 success response.
172
+ *
173
+ * @param {string|number|null} id
174
+ * @param {*} result
175
+ * @returns {object}
176
+ */
177
+ function successResponse(id, result) {
178
+ return { jsonrpc: '2.0', id, result };
179
+ }
180
+
181
+ /**
182
+ * Builds a JSON-RPC 2.0 error response.
183
+ *
184
+ * @param {string|number|null} id
185
+ * @param {number} code
186
+ * @param {string} message
187
+ * @returns {object}
188
+ */
189
+ function errorResponse(id, code, message) {
190
+ return { jsonrpc: '2.0', id, error: { code, message } };
191
+ }
192
+
193
+ /**
194
+ * Handles a single parsed JSON-RPC request object and returns a response object.
195
+ *
196
+ * @param {import('better-sqlite3').Database} db
197
+ * @param {object} request
198
+ * @returns {object}
199
+ */
200
+ function handleRequest(db, request) {
201
+ const { id = null, method, params } = request;
202
+
203
+ try {
204
+ if (method === 'tools/list') {
205
+ return successResponse(id, { tools: TOOLS });
206
+ }
207
+
208
+ if (method === 'tools/call') {
209
+ const { name: toolName, arguments: toolArgs } = params || {};
210
+ if (!toolName) {
211
+ return errorResponse(id, -32602, 'Missing required param: name');
212
+ }
213
+ const result = callTool(db, toolName, toolArgs);
214
+ return successResponse(id, {
215
+ content: [
216
+ {
217
+ type: 'text',
218
+ text: JSON.stringify(result, null, 2),
219
+ },
220
+ ],
221
+ });
222
+ }
223
+
224
+ // Method not found.
225
+ return errorResponse(id, -32601, `Method not found: ${method}`);
226
+ } catch (err) {
227
+ return errorResponse(id, -32603, err.message);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Starts the MCP stdio server. Reads newline-delimited JSON from stdin,
233
+ * writes newline-delimited JSON to stdout.
234
+ */
235
+ function startServer() {
236
+ const db = initDb();
237
+
238
+ const rl = readline.createInterface({
239
+ input: process.stdin,
240
+ output: null,
241
+ terminal: false,
242
+ });
243
+
244
+ process.stderr.write('[social-analytics mcp-server] Listening on stdin...\n');
245
+
246
+ rl.on('line', (line) => {
247
+ const trimmed = line.trim();
248
+ if (!trimmed) return;
249
+
250
+ let request;
251
+ try {
252
+ request = JSON.parse(trimmed);
253
+ } catch (_) {
254
+ const resp = errorResponse(null, -32700, 'Parse error: invalid JSON');
255
+ process.stdout.write(JSON.stringify(resp) + '\n');
256
+ return;
257
+ }
258
+
259
+ const response = handleRequest(db, request);
260
+ process.stdout.write(JSON.stringify(response) + '\n');
261
+ });
262
+
263
+ rl.on('close', () => {
264
+ db.close();
265
+ process.stderr.write('[social-analytics mcp-server] stdin closed, shutting down.\n');
266
+ });
267
+
268
+ process.on('SIGINT', () => {
269
+ db.close();
270
+ process.exit(0);
271
+ });
272
+
273
+ process.on('SIGTERM', () => {
274
+ db.close();
275
+ process.exit(0);
276
+ });
277
+ }
278
+
279
+ module.exports = {
280
+ TOOLS,
281
+ callTool,
282
+ handleRequest,
283
+ startServer,
284
+ };
285
+
286
+ // Run as main: start the stdio server.
287
+ if (require.main === module) {
288
+ startServer();
289
+ }