thumbgate 1.4.3 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/adapters/README.md +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/package.json +157 -9
- package/scripts/statusline.sh +1 -0
- package/src/api/server.js +113 -16
- package/src/index.js +3 -0
- package/.claude-plugin/bundle/icon.png +0 -0
- package/.claude-plugin/bundle/icon.svg +0 -18
- package/.claude-plugin/bundle/server/index.js +0 -24
- package/adapters/chatgpt/INSTALL.md +0 -158
- package/adapters/perplexity/.mcp.json +0 -36
- package/adapters/perplexity/config.toml +0 -16
- package/adapters/perplexity/opencode.json +0 -29
- package/bin/memory.sh +0 -64
- package/bin/obsidian-sync.sh +0 -20
- package/plugins/amp-skill/INSTALL.md +0 -52
- package/plugins/amp-skill/SKILL.md +0 -64
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
- package/plugins/claude-codex-bridge/.mcp.json +0 -14
- package/plugins/claude-codex-bridge/INSTALL.md +0 -43
- package/plugins/claude-codex-bridge/README.md +0 -46
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
- package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
- package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
- package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
- package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
- package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
- package/plugins/claude-skill/INSTALL.md +0 -55
- package/plugins/claude-skill/SKILL.md +0 -46
- package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
- package/plugins/codex-profile/.mcp.json +0 -14
- package/plugins/codex-profile/AGENTS.md +0 -20
- package/plugins/codex-profile/INSTALL.md +0 -89
- package/plugins/codex-profile/README.md +0 -61
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
- package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
- package/plugins/cursor-marketplace/LICENSE +0 -21
- package/plugins/cursor-marketplace/README.md +0 -124
- package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
- package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
- package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
- package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
- package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
- package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
- package/plugins/cursor-marketplace/mcp.json +0 -14
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
- package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
- package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
- package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
- package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
- package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
- package/plugins/gemini-extension/INSTALL.md +0 -92
- package/plugins/gemini-extension/gemini_prompt.txt +0 -14
- package/plugins/gemini-extension/tool_contract.json +0 -45
- package/plugins/opencode-profile/INSTALL.md +0 -57
- package/public/assets/instagram-card.png +0 -0
- package/public/assets/tiktok-agent-memory.mp4 +0 -0
- package/public/blog.html +0 -474
- package/public/compare/mem0.html +0 -189
- package/public/compare/speclock.html +0 -180
- package/public/compare.html +0 -310
- package/public/dashboard.html +0 -1100
- package/public/guide.html +0 -317
- package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
- package/public/guides/codex-cli-guardrails.html +0 -158
- package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
- package/public/guides/pre-action-gates.html +0 -162
- package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
- package/public/index.html +0 -1225
- package/public/js/buyer-intent.js +0 -252
- package/public/learn/agent-harness-pattern.html +0 -180
- package/public/learn/ai-agent-persistent-memory.html +0 -203
- package/public/learn/learn.css +0 -45
- package/public/learn/mcp-pre-action-gates-explained.html +0 -172
- package/public/learn/stop-ai-agent-force-push.html +0 -134
- package/public/learn/vibe-coding-safety-net.html +0 -142
- package/public/learn.html +0 -274
- package/public/lessons.html +0 -967
- package/public/llm-context.md +0 -156
- package/public/pro.html +0 -1087
- package/public/vercel.json +0 -8
- package/scripts/a2ui-engine.js +0 -73
- package/scripts/adk-consolidator.js +0 -274
- package/scripts/agent-security-hardening.js +0 -225
- package/scripts/ai-search-visibility.js +0 -116
- package/scripts/autonomous-sales-agent.js +0 -39
- package/scripts/autoresearch-runner.js +0 -216
- package/scripts/background-agent-governance.js +0 -229
- package/scripts/behavioral-extraction.js +0 -93
- package/scripts/budget-enforcer.js +0 -173
- package/scripts/budget-guard.js +0 -173
- package/scripts/build-claude-mcpb.js +0 -255
- package/scripts/build-codex-plugin.js +0 -152
- package/scripts/capture-railway-diagnostics.sh +0 -97
- package/scripts/changeset-check.js +0 -372
- package/scripts/check-congruence.js +0 -443
- package/scripts/computer-use-firewall.js +0 -280
- package/scripts/content-engine/linkedin-content-generator.js +0 -154
- package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
- package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
- package/scripts/content-engine/reddit-thread-finder.js +0 -154
- package/scripts/context-engine.js +0 -710
- package/scripts/daily-digest.js +0 -11
- package/scripts/data-governance.js +0 -173
- package/scripts/deploy-gcp.sh +0 -44
- package/scripts/deploy-policy.js +0 -249
- package/scripts/disagreement-mining.js +0 -315
- package/scripts/dpo-optimizer.js +0 -206
- package/scripts/ensure-repo-bootstrap.js +0 -130
- package/scripts/ephemeral-agent-store.js +0 -212
- package/scripts/eval-harness.js +0 -56
- package/scripts/export-kto-pairs.js +0 -309
- package/scripts/export-training.js +0 -446
- package/scripts/feedback-fallback.js +0 -111
- package/scripts/feedback-inbox-read.js +0 -162
- package/scripts/feedback-root-consolidator.js +0 -233
- package/scripts/feedback-to-memory.js +0 -185
- package/scripts/gate-satisfy.js +0 -42
- package/scripts/generate-paperbanana-diagrams.sh +0 -99
- package/scripts/generate-pretool-hook.sh +0 -40
- package/scripts/github-about.js +0 -430
- package/scripts/github-outreach.js +0 -65
- package/scripts/gtm-revenue-loop.js +0 -535
- package/scripts/hallucination-detector.js +0 -226
- package/scripts/hf-papers.js +0 -317
- package/scripts/hook-auto-capture.sh +0 -100
- package/scripts/hook-stop-pr-thread-check.sh +0 -68
- package/scripts/hook-stop-self-score.sh +0 -51
- package/scripts/hook-stop-verify-deploy.sh +0 -31
- package/scripts/hook-verify-before-done.sh +0 -20
- package/scripts/managed-dpo-export.js +0 -91
- package/scripts/markdown-escape.js +0 -12
- package/scripts/marketing-experiment.js +0 -657
- package/scripts/memalign-recall.js +0 -111
- package/scripts/memory-migration.js +0 -296
- package/scripts/meta-policy.js +0 -190
- package/scripts/metered-billing.js +0 -16
- package/scripts/model-tier-router.js +0 -310
- package/scripts/money-watcher.js +0 -218
- package/scripts/multi-hop-recall.js +0 -240
- package/scripts/per-step-scoring.js +0 -163
- package/scripts/perplexity-command-center.js +0 -644
- package/scripts/perplexity-marketing.js +0 -454
- package/scripts/pii-scanner.js +0 -153
- package/scripts/plan-gate.js +0 -154
- package/scripts/post-everywhere.js +0 -341
- package/scripts/post-to-x-retry.sh +0 -22
- package/scripts/post-to-x.js +0 -369
- package/scripts/pr-manager.js +0 -421
- package/scripts/principle-extractor.js +0 -162
- package/scripts/pro-features.js +0 -41
- package/scripts/prompt-dlp.js +0 -222
- package/scripts/prove-adapters.js +0 -860
- package/scripts/prove-attribution.js +0 -361
- package/scripts/prove-automation.js +0 -651
- package/scripts/prove-autoresearch.js +0 -304
- package/scripts/prove-claim-verification.js +0 -277
- package/scripts/prove-cloudflare-sandbox.js +0 -161
- package/scripts/prove-data-pipeline.js +0 -408
- package/scripts/prove-data-quality.js +0 -227
- package/scripts/prove-evolution.js +0 -352
- package/scripts/prove-harnesses.js +0 -287
- package/scripts/prove-intelligence.js +0 -257
- package/scripts/prove-lancedb.js +0 -425
- package/scripts/prove-local-intelligence.js +0 -340
- package/scripts/prove-loop-closure.js +0 -263
- package/scripts/prove-packaged-runtime.js +0 -327
- package/scripts/prove-predictive-insights.js +0 -355
- package/scripts/prove-runtime.js +0 -363
- package/scripts/prove-seo-gsd.js +0 -234
- package/scripts/prove-settings.js +0 -279
- package/scripts/prove-subway-upgrades.js +0 -277
- package/scripts/prove-tessl.js +0 -229
- package/scripts/prove-training-export.js +0 -325
- package/scripts/prove-workflow-contract.js +0 -112
- package/scripts/prove-xmemory.js +0 -332
- package/scripts/publish-decision.js +0 -159
- package/scripts/ralph-loop.js +0 -376
- package/scripts/ralph-mode-ci.js +0 -434
- package/scripts/reddit-dm-outreach.js +0 -192
- package/scripts/reddit-monitor-cron.sh +0 -26
- package/scripts/reminder-engine.js +0 -132
- package/scripts/revenue-status.js +0 -472
- package/scripts/rotate-stripe-webhook-secret.js +0 -314
- package/scripts/schedule-manager.js +0 -249
- package/scripts/self-healing-check.js +0 -193
- package/scripts/session-analyzer.js +0 -533
- package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
- package/scripts/skill-exporter.js +0 -260
- package/scripts/skill-materializer.js +0 -134
- package/scripts/skill-packs.js +0 -136
- package/scripts/skill-proposer.js +0 -99
- package/scripts/skill-quality-tracker.js +0 -282
- package/scripts/slow-loop.js +0 -72
- package/scripts/social-analytics/db/marketing-db.js +0 -179
- package/scripts/social-analytics/db/schema.sql +0 -55
- package/scripts/social-analytics/digest.js +0 -256
- package/scripts/social-analytics/engagement-audit.js +0 -185
- package/scripts/social-analytics/generate-instagram-card.js +0 -123
- package/scripts/social-analytics/generate-slides.js +0 -268
- package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
- package/scripts/social-analytics/install-growth-automation.js +0 -114
- package/scripts/social-analytics/load-env.js +0 -77
- package/scripts/social-analytics/mcp-server.js +0 -289
- package/scripts/social-analytics/normalizer.js +0 -580
- package/scripts/social-analytics/notify.js +0 -162
- package/scripts/social-analytics/poll-all.js +0 -107
- package/scripts/social-analytics/pollers/github.js +0 -195
- package/scripts/social-analytics/pollers/instagram.js +0 -253
- package/scripts/social-analytics/pollers/linkedin.js +0 -340
- package/scripts/social-analytics/pollers/plausible.js +0 -245
- package/scripts/social-analytics/pollers/reddit.js +0 -306
- package/scripts/social-analytics/pollers/threads.js +0 -233
- package/scripts/social-analytics/pollers/tiktok.js +0 -203
- package/scripts/social-analytics/pollers/x.js +0 -227
- package/scripts/social-analytics/pollers/youtube.js +0 -304
- package/scripts/social-analytics/pollers/zernio.js +0 -183
- package/scripts/social-analytics/post-video.js +0 -316
- package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
- package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
- package/scripts/social-analytics/publishers/devto.js +0 -122
- package/scripts/social-analytics/publishers/instagram.js +0 -317
- package/scripts/social-analytics/publishers/linkedin.js +0 -294
- package/scripts/social-analytics/publishers/reddit.js +0 -385
- package/scripts/social-analytics/publishers/threads.js +0 -275
- package/scripts/social-analytics/publishers/tiktok.js +0 -217
- package/scripts/social-analytics/publishers/x.js +0 -259
- package/scripts/social-analytics/publishers/youtube.js +0 -223
- package/scripts/social-analytics/publishers/zernio.js +0 -568
- package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
- package/scripts/social-analytics/run-digest.js +0 -34
- package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
- package/scripts/social-analytics/store.js +0 -455
- package/scripts/social-analytics/sync-launch-assets.js +0 -185
- package/scripts/social-analytics/utm.js +0 -143
- package/scripts/social-pipeline.js +0 -2626
- package/scripts/social-post-hourly.js +0 -228
- package/scripts/social-quality-gate.js +0 -134
- package/scripts/social-reply-monitor.js +0 -592
- package/scripts/status-dashboard.js +0 -155
- package/scripts/stripe-live-status.js +0 -115
- package/scripts/subagent-profiles.js +0 -79
- package/scripts/sync-branch-protection.js +0 -340
- package/scripts/sync-gh-secrets-from-env.sh +0 -70
- package/scripts/sync-github-about.js +0 -55
- package/scripts/sync-version.js +0 -479
- package/scripts/synthetic-dpo.js +0 -234
- package/scripts/tessl-export.js +0 -369
- package/scripts/test-coverage.js +0 -128
- package/scripts/thumbgate-bench.js +0 -494
- package/scripts/thumbgate_session_start.sh +0 -32
- package/scripts/train_from_feedback.py +0 -929
- package/scripts/validate-feedback.js +0 -581
- package/scripts/verify-obsidian-setup.sh +0 -269
- package/scripts/verify-run.js +0 -269
- package/scripts/weekly-auto-post.js +0 -124
- 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 };
|