thumbgate 1.4.3 → 1.4.5

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 (270) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/llms.txt +12 -8
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +18 -8
  6. package/adapters/README.md +1 -1
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/codex/config.toml +2 -2
  9. package/adapters/mcp/server-stdio.js +1 -1
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/config/github-about.json +2 -2
  12. package/package.json +158 -10
  13. package/scripts/billing.js +5 -2
  14. package/scripts/statusline.sh +1 -0
  15. package/src/api/server.js +113 -16
  16. package/src/index.js +3 -0
  17. package/.claude-plugin/bundle/icon.png +0 -0
  18. package/.claude-plugin/bundle/icon.svg +0 -18
  19. package/.claude-plugin/bundle/server/index.js +0 -24
  20. package/adapters/chatgpt/INSTALL.md +0 -158
  21. package/adapters/perplexity/.mcp.json +0 -36
  22. package/adapters/perplexity/config.toml +0 -16
  23. package/adapters/perplexity/opencode.json +0 -29
  24. package/bin/memory.sh +0 -64
  25. package/bin/obsidian-sync.sh +0 -20
  26. package/plugins/amp-skill/INSTALL.md +0 -52
  27. package/plugins/amp-skill/SKILL.md +0 -64
  28. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
  29. package/plugins/claude-codex-bridge/.mcp.json +0 -14
  30. package/plugins/claude-codex-bridge/INSTALL.md +0 -43
  31. package/plugins/claude-codex-bridge/README.md +0 -46
  32. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
  33. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
  34. package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
  35. package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
  36. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
  37. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
  38. package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
  39. package/plugins/claude-skill/INSTALL.md +0 -55
  40. package/plugins/claude-skill/SKILL.md +0 -46
  41. package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
  42. package/plugins/codex-profile/.mcp.json +0 -14
  43. package/plugins/codex-profile/AGENTS.md +0 -20
  44. package/plugins/codex-profile/INSTALL.md +0 -89
  45. package/plugins/codex-profile/README.md +0 -61
  46. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
  47. package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
  48. package/plugins/cursor-marketplace/LICENSE +0 -21
  49. package/plugins/cursor-marketplace/README.md +0 -124
  50. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
  51. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  52. package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
  53. package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
  54. package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
  55. package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
  56. package/plugins/cursor-marketplace/mcp.json +0 -14
  57. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
  58. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
  59. package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
  60. package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
  61. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
  62. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
  63. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
  64. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
  65. package/plugins/gemini-extension/INSTALL.md +0 -92
  66. package/plugins/gemini-extension/gemini_prompt.txt +0 -14
  67. package/plugins/gemini-extension/tool_contract.json +0 -45
  68. package/plugins/opencode-profile/INSTALL.md +0 -57
  69. package/public/assets/instagram-card.png +0 -0
  70. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  71. package/public/blog.html +0 -474
  72. package/public/compare/mem0.html +0 -189
  73. package/public/compare/speclock.html +0 -180
  74. package/public/compare.html +0 -310
  75. package/public/dashboard.html +0 -1100
  76. package/public/guide.html +0 -317
  77. package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
  78. package/public/guides/codex-cli-guardrails.html +0 -158
  79. package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
  80. package/public/guides/pre-action-gates.html +0 -162
  81. package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
  82. package/public/index.html +0 -1225
  83. package/public/js/buyer-intent.js +0 -252
  84. package/public/learn/agent-harness-pattern.html +0 -180
  85. package/public/learn/ai-agent-persistent-memory.html +0 -203
  86. package/public/learn/learn.css +0 -45
  87. package/public/learn/mcp-pre-action-gates-explained.html +0 -172
  88. package/public/learn/stop-ai-agent-force-push.html +0 -134
  89. package/public/learn/vibe-coding-safety-net.html +0 -142
  90. package/public/learn.html +0 -274
  91. package/public/lessons.html +0 -967
  92. package/public/llm-context.md +0 -156
  93. package/public/pro.html +0 -1087
  94. package/public/vercel.json +0 -8
  95. package/scripts/a2ui-engine.js +0 -73
  96. package/scripts/adk-consolidator.js +0 -274
  97. package/scripts/agent-security-hardening.js +0 -225
  98. package/scripts/ai-search-visibility.js +0 -116
  99. package/scripts/autonomous-sales-agent.js +0 -39
  100. package/scripts/autoresearch-runner.js +0 -216
  101. package/scripts/background-agent-governance.js +0 -229
  102. package/scripts/behavioral-extraction.js +0 -93
  103. package/scripts/budget-enforcer.js +0 -173
  104. package/scripts/budget-guard.js +0 -173
  105. package/scripts/build-claude-mcpb.js +0 -255
  106. package/scripts/build-codex-plugin.js +0 -152
  107. package/scripts/capture-railway-diagnostics.sh +0 -97
  108. package/scripts/changeset-check.js +0 -372
  109. package/scripts/check-congruence.js +0 -443
  110. package/scripts/computer-use-firewall.js +0 -280
  111. package/scripts/content-engine/linkedin-content-generator.js +0 -154
  112. package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
  113. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
  114. package/scripts/content-engine/reddit-thread-finder.js +0 -154
  115. package/scripts/context-engine.js +0 -710
  116. package/scripts/daily-digest.js +0 -11
  117. package/scripts/data-governance.js +0 -173
  118. package/scripts/deploy-gcp.sh +0 -44
  119. package/scripts/deploy-policy.js +0 -249
  120. package/scripts/disagreement-mining.js +0 -315
  121. package/scripts/dpo-optimizer.js +0 -206
  122. package/scripts/ensure-repo-bootstrap.js +0 -130
  123. package/scripts/ephemeral-agent-store.js +0 -212
  124. package/scripts/eval-harness.js +0 -56
  125. package/scripts/export-kto-pairs.js +0 -309
  126. package/scripts/export-training.js +0 -446
  127. package/scripts/feedback-fallback.js +0 -111
  128. package/scripts/feedback-inbox-read.js +0 -162
  129. package/scripts/feedback-root-consolidator.js +0 -233
  130. package/scripts/feedback-to-memory.js +0 -185
  131. package/scripts/gate-satisfy.js +0 -42
  132. package/scripts/generate-paperbanana-diagrams.sh +0 -99
  133. package/scripts/generate-pretool-hook.sh +0 -40
  134. package/scripts/github-about.js +0 -430
  135. package/scripts/github-outreach.js +0 -65
  136. package/scripts/gtm-revenue-loop.js +0 -535
  137. package/scripts/hallucination-detector.js +0 -226
  138. package/scripts/hf-papers.js +0 -317
  139. package/scripts/hook-auto-capture.sh +0 -100
  140. package/scripts/hook-stop-pr-thread-check.sh +0 -68
  141. package/scripts/hook-stop-self-score.sh +0 -51
  142. package/scripts/hook-stop-verify-deploy.sh +0 -31
  143. package/scripts/hook-verify-before-done.sh +0 -20
  144. package/scripts/managed-dpo-export.js +0 -91
  145. package/scripts/markdown-escape.js +0 -12
  146. package/scripts/marketing-experiment.js +0 -657
  147. package/scripts/memalign-recall.js +0 -111
  148. package/scripts/memory-migration.js +0 -296
  149. package/scripts/meta-policy.js +0 -190
  150. package/scripts/metered-billing.js +0 -16
  151. package/scripts/model-tier-router.js +0 -310
  152. package/scripts/money-watcher.js +0 -218
  153. package/scripts/multi-hop-recall.js +0 -240
  154. package/scripts/per-step-scoring.js +0 -163
  155. package/scripts/perplexity-command-center.js +0 -644
  156. package/scripts/perplexity-marketing.js +0 -454
  157. package/scripts/pii-scanner.js +0 -153
  158. package/scripts/plan-gate.js +0 -154
  159. package/scripts/post-everywhere.js +0 -341
  160. package/scripts/post-to-x-retry.sh +0 -22
  161. package/scripts/post-to-x.js +0 -369
  162. package/scripts/pr-manager.js +0 -421
  163. package/scripts/principle-extractor.js +0 -162
  164. package/scripts/pro-features.js +0 -41
  165. package/scripts/prompt-dlp.js +0 -222
  166. package/scripts/prove-adapters.js +0 -860
  167. package/scripts/prove-attribution.js +0 -361
  168. package/scripts/prove-automation.js +0 -651
  169. package/scripts/prove-autoresearch.js +0 -304
  170. package/scripts/prove-claim-verification.js +0 -277
  171. package/scripts/prove-cloudflare-sandbox.js +0 -161
  172. package/scripts/prove-data-pipeline.js +0 -408
  173. package/scripts/prove-data-quality.js +0 -227
  174. package/scripts/prove-evolution.js +0 -352
  175. package/scripts/prove-harnesses.js +0 -287
  176. package/scripts/prove-intelligence.js +0 -257
  177. package/scripts/prove-lancedb.js +0 -425
  178. package/scripts/prove-local-intelligence.js +0 -340
  179. package/scripts/prove-loop-closure.js +0 -263
  180. package/scripts/prove-packaged-runtime.js +0 -327
  181. package/scripts/prove-predictive-insights.js +0 -355
  182. package/scripts/prove-runtime.js +0 -363
  183. package/scripts/prove-seo-gsd.js +0 -234
  184. package/scripts/prove-settings.js +0 -279
  185. package/scripts/prove-subway-upgrades.js +0 -277
  186. package/scripts/prove-tessl.js +0 -229
  187. package/scripts/prove-training-export.js +0 -325
  188. package/scripts/prove-workflow-contract.js +0 -112
  189. package/scripts/prove-xmemory.js +0 -332
  190. package/scripts/publish-decision.js +0 -159
  191. package/scripts/ralph-loop.js +0 -376
  192. package/scripts/ralph-mode-ci.js +0 -434
  193. package/scripts/reddit-dm-outreach.js +0 -192
  194. package/scripts/reddit-monitor-cron.sh +0 -26
  195. package/scripts/reminder-engine.js +0 -132
  196. package/scripts/revenue-status.js +0 -472
  197. package/scripts/rotate-stripe-webhook-secret.js +0 -314
  198. package/scripts/schedule-manager.js +0 -249
  199. package/scripts/self-healing-check.js +0 -193
  200. package/scripts/session-analyzer.js +0 -533
  201. package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
  202. package/scripts/skill-exporter.js +0 -260
  203. package/scripts/skill-materializer.js +0 -134
  204. package/scripts/skill-packs.js +0 -136
  205. package/scripts/skill-proposer.js +0 -99
  206. package/scripts/skill-quality-tracker.js +0 -282
  207. package/scripts/slow-loop.js +0 -72
  208. package/scripts/social-analytics/db/marketing-db.js +0 -179
  209. package/scripts/social-analytics/db/schema.sql +0 -55
  210. package/scripts/social-analytics/digest.js +0 -256
  211. package/scripts/social-analytics/engagement-audit.js +0 -185
  212. package/scripts/social-analytics/generate-instagram-card.js +0 -123
  213. package/scripts/social-analytics/generate-slides.js +0 -268
  214. package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
  215. package/scripts/social-analytics/install-growth-automation.js +0 -114
  216. package/scripts/social-analytics/load-env.js +0 -77
  217. package/scripts/social-analytics/mcp-server.js +0 -289
  218. package/scripts/social-analytics/normalizer.js +0 -580
  219. package/scripts/social-analytics/notify.js +0 -162
  220. package/scripts/social-analytics/poll-all.js +0 -107
  221. package/scripts/social-analytics/pollers/github.js +0 -195
  222. package/scripts/social-analytics/pollers/instagram.js +0 -253
  223. package/scripts/social-analytics/pollers/linkedin.js +0 -340
  224. package/scripts/social-analytics/pollers/plausible.js +0 -245
  225. package/scripts/social-analytics/pollers/reddit.js +0 -306
  226. package/scripts/social-analytics/pollers/threads.js +0 -233
  227. package/scripts/social-analytics/pollers/tiktok.js +0 -203
  228. package/scripts/social-analytics/pollers/x.js +0 -227
  229. package/scripts/social-analytics/pollers/youtube.js +0 -304
  230. package/scripts/social-analytics/pollers/zernio.js +0 -183
  231. package/scripts/social-analytics/post-video.js +0 -316
  232. package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
  233. package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
  234. package/scripts/social-analytics/publishers/devto.js +0 -122
  235. package/scripts/social-analytics/publishers/instagram.js +0 -317
  236. package/scripts/social-analytics/publishers/linkedin.js +0 -294
  237. package/scripts/social-analytics/publishers/reddit.js +0 -385
  238. package/scripts/social-analytics/publishers/threads.js +0 -275
  239. package/scripts/social-analytics/publishers/tiktok.js +0 -217
  240. package/scripts/social-analytics/publishers/x.js +0 -259
  241. package/scripts/social-analytics/publishers/youtube.js +0 -223
  242. package/scripts/social-analytics/publishers/zernio.js +0 -568
  243. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
  244. package/scripts/social-analytics/run-digest.js +0 -34
  245. package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
  246. package/scripts/social-analytics/store.js +0 -455
  247. package/scripts/social-analytics/sync-launch-assets.js +0 -185
  248. package/scripts/social-analytics/utm.js +0 -143
  249. package/scripts/social-pipeline.js +0 -2626
  250. package/scripts/social-post-hourly.js +0 -228
  251. package/scripts/social-quality-gate.js +0 -134
  252. package/scripts/social-reply-monitor.js +0 -592
  253. package/scripts/status-dashboard.js +0 -155
  254. package/scripts/stripe-live-status.js +0 -115
  255. package/scripts/subagent-profiles.js +0 -79
  256. package/scripts/sync-branch-protection.js +0 -340
  257. package/scripts/sync-gh-secrets-from-env.sh +0 -70
  258. package/scripts/sync-github-about.js +0 -55
  259. package/scripts/sync-version.js +0 -479
  260. package/scripts/synthetic-dpo.js +0 -234
  261. package/scripts/tessl-export.js +0 -369
  262. package/scripts/test-coverage.js +0 -128
  263. package/scripts/thumbgate-bench.js +0 -494
  264. package/scripts/thumbgate_session_start.sh +0 -32
  265. package/scripts/train_from_feedback.py +0 -929
  266. package/scripts/validate-feedback.js +0 -581
  267. package/scripts/verify-obsidian-setup.sh +0 -269
  268. package/scripts/verify-run.js +0 -269
  269. package/scripts/weekly-auto-post.js +0 -124
  270. package/scripts/x-autonomous-marketing.js +0 -139
@@ -1,316 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * post-video.js
6
- * Generates a marketing short video and posts it to TikTok, YouTube, and
7
- * Instagram Reels via Zernio. Tracks everything in the marketing DB to
8
- * prevent double-posting.
9
- *
10
- * Usage:
11
- * node scripts/social-analytics/post-video.js
12
- * node scripts/social-analytics/post-video.js --campaign=v1.4.1 --dry-run
13
- * node scripts/social-analytics/post-video.js --video=/path/to/custom.mp4
14
- *
15
- * Required env:
16
- * ZERNIO_API_KEY
17
- * Optional env (overrides hardcoded account IDs):
18
- * ZERNIO_TIKTOK_ACCOUNT_ID
19
- * ZERNIO_YOUTUBE_ACCOUNT_ID
20
- * ZERNIO_INSTAGRAM_ACCOUNT_ID
21
- */
22
-
23
- const { execFileSync, execSync } = require('node:child_process');
24
- const fs = require('node:fs');
25
- const path = require('node:path');
26
- const os = require('node:os');
27
- const { loadLocalEnv } = require('./load-env');
28
-
29
- loadLocalEnv();
30
-
31
- const { hashContent, isDuplicate, record } = require('./db/marketing-db');
32
-
33
- // ---------------------------------------------------------------------------
34
- // Config
35
- // ---------------------------------------------------------------------------
36
-
37
- const ZERNIO_BASE = 'https://zernio.com/api/v1';
38
-
39
- const ACCOUNTS = {
40
- tiktok: process.env.ZERNIO_TIKTOK_ACCOUNT_ID || '69bee0fd6cb7b8cf4c8b2425',
41
- youtube: process.env.ZERNIO_YOUTUBE_ACCOUNT_ID || '69c14dc36cb7b8cf4c91c1e4',
42
- instagram: process.env.ZERNIO_INSTAGRAM_ACCOUNT_ID || '69bed6ad6cb7b8cf4c8b0865',
43
- };
44
-
45
- // Per-platform cooldown in hours — prevents over-posting even when CI fires every 4h
46
- const PLATFORM_COOLDOWN_HOURS = {
47
- tiktok: 4, // up to 6 videos/day — TikTok rewards frequency
48
- instagram: 8, // up to 3 Reels/day
49
- youtube: 12, // 1-2 Shorts/day
50
- };
51
-
52
- const CAPTIONS = {
53
- tiktok: `Your AI agent deleted prod config because it "looked unused" 😬
54
-
55
- ThumbGate v1.4.1 intercepts BEFORE the action runs. Checks it against lessons from past failures. Blocks it permanently.
56
-
57
- 👎 feedback → lesson DB → prevention rule → physical gate
58
-
59
- Not a prompt. A block.
60
-
61
- npx thumbgate serve — free + open source
62
- Try the live GPT first: https://chatgpt.com/g/g-69dcfd1cd5f881918ae31874631d6f08-thumbgate
63
- github.com/IgorGanapolsky/ThumbGate
64
-
65
- #ClaudeCode #AIAgents #DevTools #TechTok #Coding #SoftwareDev #AITools #Programming #DevTok`,
66
-
67
- youtube: `ThumbGate v1.4.1: How to stop AI coding agents from repeating mistakes
68
-
69
- Your agent force-pushed to main. Deleted prod config. Ran the wrong migration. You told it not to. Next session — same mistake.
70
-
71
- ThumbGate solves this with pre-action gates: every 👎 becomes a lesson, every lesson becomes a gate, every gate is enforced via PreToolUse hooks.
72
-
73
- v1.4.1: Thompson Sampling · LanceDB vector search · SQLite+FTS5 lesson DB
74
-
75
- Live GPT demo: https://chatgpt.com/g/g-69dcfd1cd5f881918ae31874631d6f08-thumbgate
76
- Free + open source: https://github.com/IgorGanapolsky/ThumbGate
77
- npx thumbgate serve
78
-
79
- #ClaudeCode #AIAgents #DevTools #Shorts`,
80
-
81
- instagram: `AI agent deleted prod config because it "looked unused" 😬
82
-
83
- ThumbGate v1.4.1: pre-action safety gates that physically block known-bad patterns before they run.
84
-
85
- 👎 → lesson DB → prevention rule → blocked forever
86
-
87
- Live GPT demo: chatgpt.com/g/g-69dcfd1cd5f881918ae31874631d6f08-thumbgate
88
- Free + open source. Link in bio 👆
89
-
90
- #AIAgents #ClaudeCode #DevTools #Coding #TechTok #SoftwareDev #AITools #MachineLearning #BuildInPublic`,
91
- };
92
-
93
- const YT_TITLE = 'ThumbGate v1.4.1: Stop AI Agents From Repeating Mistakes #shorts';
94
-
95
- // ---------------------------------------------------------------------------
96
- // Helpers
97
- // ---------------------------------------------------------------------------
98
-
99
- function parseArgs(argv) {
100
- const opts = { dryRun: false, campaign: 'default', videoPath: null, platforms: null, template: 'auto' };
101
- for (const arg of argv) {
102
- if (arg === '--dry-run') opts.dryRun = true;
103
- else if (arg.startsWith('--campaign=')) opts.campaign = arg.slice(11);
104
- else if (arg.startsWith('--video=')) opts.videoPath = arg.slice(8);
105
- else if (arg.startsWith('--platforms=')) opts.platforms = arg.slice(12).split(',');
106
- else if (arg.startsWith('--template=')) opts.template = arg.slice(11);
107
- }
108
- return opts;
109
- }
110
-
111
- function requireKey() {
112
- const k = process.env.ZERNIO_API_KEY;
113
- if (!k) throw new Error('ZERNIO_API_KEY env var is required');
114
- return k;
115
- }
116
-
117
- async function zernioUpload(apiKey, filePath) {
118
- const out = execFileSync('curl', [
119
- '-s',
120
- '-X', 'POST',
121
- `${ZERNIO_BASE}/media`,
122
- '-H', `Authorization: Bearer ${apiKey}`,
123
- '-F', `files=@${filePath}`,
124
- ], { maxBuffer: 10 * 1024 * 1024 }).toString();
125
-
126
- const data = JSON.parse(out);
127
- const files = data.files || [];
128
- if (!files.length) throw new Error(`Zernio upload failed: ${out}`);
129
- return files[0].url;
130
- }
131
-
132
- async function zernioPost(apiKey, { platform, accountId, title, content, mediaUrl, mediaType = 'video' }) {
133
- const body = {
134
- content,
135
- mediaItems: [{ url: mediaUrl, type: mediaType }],
136
- platforms: [{ platform, accountId }],
137
- publishNow: true,
138
- };
139
- if (title) body.title = title;
140
-
141
- const out = execFileSync('curl', [
142
- '-s',
143
- '-X', 'POST',
144
- `${ZERNIO_BASE}/posts`,
145
- '-H', `Authorization: Bearer ${apiKey}`,
146
- '-H', 'Content-Type: application/json',
147
- '-d', JSON.stringify(body),
148
- ], { maxBuffer: 5 * 1024 * 1024 }).toString();
149
-
150
- return JSON.parse(out);
151
- }
152
-
153
- // ---------------------------------------------------------------------------
154
- // Video generation
155
- // ---------------------------------------------------------------------------
156
-
157
- function generateVideo(outDir, template = 'auto', campaign = 'default') {
158
- const slidesScript = path.join(__dirname, 'generate-slides.js');
159
- const concatFile = path.join(outDir, 'concat.txt');
160
- const videoOut = path.join(outDir, 'thumbgate-short.mp4');
161
-
162
- console.log('[post-video] Generating slides...');
163
- execFileSync(process.execPath, [
164
- slidesScript,
165
- `--out=${outDir}`,
166
- `--template=${template}`,
167
- `--campaign=${campaign}`,
168
- ], { stdio: 'inherit' });
169
-
170
- // Build ffmpeg concat file from manifest
171
- const manifest = JSON.parse(fs.readFileSync(path.join(outDir, 'manifest.json'), 'utf8'));
172
- const concatLines = manifest.slides.map(s => `file '${path.join(outDir, s.file)}'\nduration ${s.holdSeconds}`);
173
- const lastSlide = manifest.slides[manifest.slides.length - 1];
174
- concatLines.push(`file '${path.join(outDir, lastSlide.file)}'`);
175
- fs.writeFileSync(concatFile, concatLines.join('\n'));
176
-
177
- console.log('[post-video] Rendering video...');
178
- execSync(
179
- `ffmpeg -y -f concat -safe 0 -i "${concatFile}" \
180
- -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1" \
181
- -c:v libx264 -r 30 -pix_fmt yuv420p -movflags +faststart \
182
- "${videoOut}"`,
183
- { stdio: 'pipe' }
184
- );
185
-
186
- const size = (fs.statSync(videoOut).size / 1024).toFixed(0);
187
- console.log(`[post-video] Video ready: ${videoOut} (${size} KB, ${manifest.totalDuration}s)`);
188
- return videoOut;
189
- }
190
-
191
- // ---------------------------------------------------------------------------
192
- // Main
193
- // ---------------------------------------------------------------------------
194
-
195
- function prepareVideo(opts) {
196
- let videoPath = opts.videoPath;
197
- let templateId = opts.template;
198
- if (!videoPath) {
199
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'thumbgate-video-'));
200
- videoPath = generateVideo(tmpDir, opts.template, opts.campaign);
201
- // Read back the chosen template id from manifest
202
- try {
203
- const manifest = JSON.parse(fs.readFileSync(path.join(tmpDir, 'manifest.json'), 'utf8'));
204
- templateId = String(manifest.templateId || opts.template);
205
- } catch {}
206
- }
207
- return { videoPath, templateId };
208
- }
209
-
210
- function buildPlatformPlan(platform, baseHash) {
211
- const caption = CAPTIONS[platform];
212
- if (!caption) {
213
- console.warn(`[post-video] No caption for platform: ${platform} — skipping`);
214
- return null;
215
- }
216
-
217
- const contentHash = hashContent(`${baseHash}::${platform}`);
218
- const cooldownHours = PLATFORM_COOLDOWN_HOURS[platform] || 4;
219
- return { platform, caption, contentHash, cooldownDays: cooldownHours / 24 };
220
- }
221
-
222
- function duplicateResult(plan) {
223
- const existing = isDuplicate(plan.platform, plan.contentHash, plan.cooldownDays);
224
- if (!existing) return null;
225
- console.log(`[post-video] SKIP ${plan.platform} — already posted (${existing.published_at}): ${existing.post_url}`);
226
- return { platform: plan.platform, status: 'skipped', reason: 'duplicate', existing };
227
- }
228
-
229
- function recordPostOutcome({ plan, status, postUrl, error, campaign, mediaUrl, templateId, response }) {
230
- if (status === 'published') {
231
- console.log(`[post-video] ✓ ${plan.platform}: ${postUrl}`);
232
- record({ type: 'video', platform: plan.platform, contentHash: plan.contentHash, postUrl, campaign,
233
- tags: ['v1.4.1', 'short', campaign],
234
- extra: { mediaUrl, templateId, zernioPostId: response.post?._id } });
235
- return;
236
- }
237
-
238
- console.error(`[post-video] ✗ ${plan.platform}: ${error}`);
239
- record({ type: 'video', platform: plan.platform, contentHash: plan.contentHash, status: 'failed', campaign,
240
- extra: { error } });
241
- }
242
-
243
- async function processPlatform(plan, context) {
244
- const duplicate = duplicateResult(plan);
245
- if (duplicate) return duplicate;
246
-
247
- if (context.dryRun) {
248
- console.log(`[post-video] DRY-RUN ${plan.platform} — would post video`);
249
- return { platform: plan.platform, status: 'dry-run' };
250
- }
251
-
252
- try {
253
- if (!context.mediaUrl) {
254
- console.log(`[post-video] Uploading video to Zernio...`);
255
- context.mediaUrl = await zernioUpload(context.apiKey, context.videoPath);
256
- console.log(`[post-video] Uploaded: ${context.mediaUrl}`);
257
- }
258
-
259
- console.log(`[post-video] Posting to ${plan.platform}...`);
260
- const response = await zernioPost(context.apiKey, {
261
- platform: plan.platform,
262
- accountId: ACCOUNTS[plan.platform],
263
- title: plan.platform === 'youtube' ? YT_TITLE : undefined,
264
- content: plan.caption,
265
- mediaUrl: context.mediaUrl,
266
- });
267
-
268
- const platformResult = response.post?.platforms?.[0] || {};
269
- const status = platformResult.status || 'unknown';
270
- const postUrl = platformResult.platformPostUrl || '';
271
- const error = platformResult.errorMessage || response.error || '';
272
- recordPostOutcome({ plan, status, postUrl, error, campaign: context.campaign,
273
- mediaUrl: context.mediaUrl, templateId: context.templateId, response });
274
- return { platform: plan.platform, status, postUrl, error };
275
- } catch (err) {
276
- console.error(`[post-video] ✗ ${plan.platform} error: ${err.message}`);
277
- return { platform: plan.platform, status: 'error', error: err.message };
278
- }
279
- }
280
-
281
- function statusIcon(status) {
282
- if (status === 'published') return '✓';
283
- if (['skipped', 'dry-run'].includes(status)) return '→';
284
- return '✗';
285
- }
286
-
287
- function printSummary(results) {
288
- console.log('\n[post-video] Summary:');
289
- for (const r of results) {
290
- const icon = statusIcon(r.status);
291
- console.log(` ${icon} ${r.platform}: ${r.status}${r.postUrl ? ' — ' + r.postUrl : ''}${r.error ? ' — ' + r.error : ''}`);
292
- }
293
- }
294
-
295
- async function main() {
296
- const opts = parseArgs(process.argv.slice(2));
297
- const apiKey = opts.dryRun ? null : requireKey();
298
- const platforms = opts.platforms || ['tiktok', 'youtube', 'instagram'];
299
- console.log(`[post-video] campaign=${opts.campaign} platforms=${platforms.join(',')} dryRun=${opts.dryRun}`);
300
-
301
- const { videoPath, templateId } = prepareVideo(opts);
302
- const baseHash = hashContent(`video::template-${templateId}::${opts.campaign}`);
303
- const context = { apiKey, campaign: opts.campaign, dryRun: opts.dryRun, mediaUrl: null, templateId, videoPath };
304
- const plans = platforms.map(platform => buildPlatformPlan(platform, baseHash)).filter(Boolean);
305
- const results = [];
306
-
307
- for (const plan of plans) {
308
- results.push(await processPlatform(plan, context));
309
- }
310
-
311
- printSummary(results);
312
-
313
- return results;
314
- }
315
-
316
- main().catch(err => { console.error(err.message); process.exit(1); });
@@ -1,104 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * publish-instagram-thumbgate.js
6
- * Complete workflow: generate Instagram card image and post to Instagram via Zernio.
7
- *
8
- * Usage:
9
- * node publish-instagram-thumbgate.js [--image-only] [--post-only]
10
- *
11
- * Options:
12
- * --image-only Generate image only, don't post
13
- * --post-only Post an existing image without regenerating it
14
- */
15
-
16
- const path = require('path');
17
- const fs = require('node:fs');
18
- const { generateInstagramCard } = require('./generate-instagram-card');
19
- const { postThumbGateToInstagram, THUMBGATE_CAPTION } = require('./instagram-thumbgate-post');
20
-
21
- const REPO_ROOT = path.resolve(__dirname, '../..');
22
- const IMAGE_PATH = path.join(REPO_ROOT, '.thumbgate', 'instagram-card.png');
23
-
24
- async function publishInstagramThumbGate(options = {}) {
25
- const {
26
- caption = THUMBGATE_CAPTION,
27
- imageOnly = false,
28
- postOnly = false,
29
- imagePath = IMAGE_PATH,
30
- schedule = '',
31
- timezone = 'America/New_York',
32
- utm,
33
- } = options;
34
-
35
- try {
36
- // Step 1: Generate image (unless --post-only)
37
- if (!postOnly) {
38
- console.log('[workflow] Step 1: Generating Instagram card...');
39
- const generatedPath = await generateInstagramCard(imagePath);
40
- console.log(`[workflow] ✅ Image ready: ${generatedPath}`);
41
-
42
- if (imageOnly) {
43
- console.log('[workflow] Image-only mode. Stopping here.');
44
- return { imagePath: generatedPath };
45
- }
46
- } else if (!fs.existsSync(imagePath)) {
47
- throw new Error(`Image file is required for --post-only mode: ${imagePath}`);
48
- }
49
-
50
- // Step 2: Post to Instagram (unless --image-only)
51
- if (!imageOnly) {
52
- console.log('[workflow] Step 2: Publishing to Instagram via Zernio...');
53
- const postResult = await postThumbGateToInstagram({
54
- caption,
55
- imagePath,
56
- schedule,
57
- timezone,
58
- utm,
59
- });
60
- if (schedule) {
61
- console.log(`[workflow] ✅ Post scheduled: ${postResult.id || postResult.data?.id}`);
62
- } else {
63
- console.log(`[workflow] ✅ Post published: ${postResult.id || postResult.data?.id}`);
64
- }
65
-
66
- return {
67
- success: true,
68
- imagePath: postOnly ? undefined : imagePath,
69
- postId: postResult.id || postResult.data?.id,
70
- scheduled: Boolean(schedule),
71
- scheduledFor: schedule || undefined,
72
- };
73
- }
74
- } catch (err) {
75
- console.error(`[workflow] ❌ Failed: ${err.message}`);
76
- throw err;
77
- }
78
- }
79
-
80
- // CLI execution
81
- if (require.main === module) {
82
- const args = process.argv.slice(2);
83
- const imageOnly = args.includes('--image-only');
84
- const postOnly = args.includes('--post-only');
85
-
86
- if (imageOnly && postOnly) {
87
- console.error('❌ Cannot specify both --image-only and --post-only');
88
- process.exit(1);
89
- }
90
-
91
- (async () => {
92
- try {
93
- const result = await publishInstagramThumbGate({ imageOnly, postOnly });
94
- console.log('\n✅ Workflow complete!');
95
- console.log(JSON.stringify(result, null, 2));
96
- process.exit(0);
97
- } catch (err) {
98
- console.error(`\n❌ Workflow failed: ${err.message}`);
99
- process.exit(1);
100
- }
101
- })();
102
- }
103
-
104
- module.exports = { publishInstagramThumbGate, IMAGE_PATH };