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
package/scripts/plan-gate.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Gate validators
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
function countTableRows(content, sectionHeading) {
|
|
12
|
-
const sectionRegex = new RegExp(
|
|
13
|
-
`#+\\s*${sectionHeading}[^\\n]*\\n([\\s\\S]*?)(?=\\n#+\\s|$)`,
|
|
14
|
-
);
|
|
15
|
-
const match = content.match(sectionRegex);
|
|
16
|
-
if (!match) return 0;
|
|
17
|
-
|
|
18
|
-
const lines = match[1].split('\n').filter((l) => l.trim().startsWith('|'));
|
|
19
|
-
// Subtract header row and separator row
|
|
20
|
-
const dataRows = lines.filter(
|
|
21
|
-
(l) => !/^\|\s*-+/.test(l.trim()) && !/^\|\s*:?-+/.test(l.trim()),
|
|
22
|
-
);
|
|
23
|
-
// First row is the header
|
|
24
|
-
return Math.max(0, dataRows.length - 1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function countContracts(content) {
|
|
28
|
-
const sectionRegex = /#+\s*Contracts[^\n]*\n([\s\S]*?)(?=\n#+\s|$)/;
|
|
29
|
-
const match = content.match(sectionRegex);
|
|
30
|
-
if (!match) return 0;
|
|
31
|
-
|
|
32
|
-
const section = match[1];
|
|
33
|
-
// Find code blocks and look for interface/type keywords inside them
|
|
34
|
-
const codeBlockRegex = /```[\s\S]*?```/g;
|
|
35
|
-
let count = 0;
|
|
36
|
-
let blockMatch;
|
|
37
|
-
while ((blockMatch = codeBlockRegex.exec(section)) !== null) {
|
|
38
|
-
const block = blockMatch[0];
|
|
39
|
-
const interfaceMatches = block.match(/\b(interface|type)\s+\w+/g);
|
|
40
|
-
if (interfaceMatches) count += interfaceMatches.length;
|
|
41
|
-
}
|
|
42
|
-
return count;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function countValidationScenarios(content) {
|
|
46
|
-
const sectionRegex =
|
|
47
|
-
/#+\s*Validation\s+Checklist[^\n]*\n([\s\S]*?)(?=\n#+\s|$)/;
|
|
48
|
-
const match = content.match(sectionRegex);
|
|
49
|
-
if (!match) return 0;
|
|
50
|
-
|
|
51
|
-
const lines = match[1].split('\n');
|
|
52
|
-
return lines.filter((l) => /^\s*-\s*\[\s*\]/.test(l)).length;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getStatus(content) {
|
|
56
|
-
const match = content.match(/#+\s*Status[^\n]*\n\s*(\S+)/);
|
|
57
|
-
return match ? match[1].trim() : null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Main
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
function validatePlan(content) {
|
|
65
|
-
const questionCount = countTableRows(content, 'Clarifying Questions Resolved');
|
|
66
|
-
const contractCount = countContracts(content);
|
|
67
|
-
const scenarioCount = countValidationScenarios(content);
|
|
68
|
-
const status = getStatus(content);
|
|
69
|
-
|
|
70
|
-
const gates = [
|
|
71
|
-
{
|
|
72
|
-
name: 'Clarifying Questions',
|
|
73
|
-
pass: questionCount >= 3,
|
|
74
|
-
detail: `${questionCount} questions resolved`,
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'Contracts Defined',
|
|
78
|
-
pass: contractCount >= 1,
|
|
79
|
-
detail: `${contractCount} interface${contractCount !== 1 ? 's' : ''} found`,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'Validation Checklist',
|
|
83
|
-
pass: scenarioCount >= 2,
|
|
84
|
-
detail: `${scenarioCount} scenarios defined`,
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
name: 'Status',
|
|
88
|
-
pass: status !== 'COMPLETE',
|
|
89
|
-
detail:
|
|
90
|
-
status === 'COMPLETE'
|
|
91
|
-
? 'COMPLETE (already finished — cannot re-approve)'
|
|
92
|
-
: `${status || 'UNKNOWN'} (not COMPLETE)`,
|
|
93
|
-
},
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
const allPass = gates.every((g) => g.pass);
|
|
97
|
-
return { gates, allPass };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function formatReport(result) {
|
|
101
|
-
const lines = result.gates.map(
|
|
102
|
-
(g) => `${g.pass ? '✅' : '❌'} ${g.name}: ${g.detail}`,
|
|
103
|
-
);
|
|
104
|
-
lines.push('');
|
|
105
|
-
lines.push(
|
|
106
|
-
result.allPass
|
|
107
|
-
? 'RESULT: PASS — all gates satisfied'
|
|
108
|
-
: 'RESULT: BLOCKED — resolve issues above before spawning agents',
|
|
109
|
-
);
|
|
110
|
-
return lines.join('\n');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function run() {
|
|
114
|
-
const args = process.argv.slice(2);
|
|
115
|
-
const jsonFlag = args.includes('--json');
|
|
116
|
-
const filePath = args.find((a) => a !== '--json');
|
|
117
|
-
|
|
118
|
-
if (!filePath) {
|
|
119
|
-
console.error('Usage: node scripts/plan-gate.js <plan-file.md> [--json]');
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const resolved = path.resolve(filePath);
|
|
124
|
-
if (!fs.existsSync(resolved)) {
|
|
125
|
-
console.error(`File not found: ${resolved}`);
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const content = fs.readFileSync(resolved, 'utf-8');
|
|
130
|
-
const result = validatePlan(content);
|
|
131
|
-
|
|
132
|
-
if (jsonFlag) {
|
|
133
|
-
console.log(JSON.stringify(result, null, 2));
|
|
134
|
-
} else {
|
|
135
|
-
console.log(formatReport(result));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
process.exit(result.allPass ? 0 : 1);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Export for testing
|
|
142
|
-
module.exports = {
|
|
143
|
-
validatePlan,
|
|
144
|
-
formatReport,
|
|
145
|
-
countTableRows,
|
|
146
|
-
countContracts,
|
|
147
|
-
countValidationScenarios,
|
|
148
|
-
getStatus,
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// Run only when executed directly
|
|
152
|
-
if (require.main === module) {
|
|
153
|
-
run();
|
|
154
|
-
}
|
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* post-everywhere.js
|
|
5
|
-
* Unified CLI to post content to all social platforms from a single markdown post file.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* node scripts/post-everywhere.js docs/marketing/reddit-cursor-post.md
|
|
9
|
-
* node scripts/post-everywhere.js docs/marketing/reddit-cursor-post.md --dry-run
|
|
10
|
-
* node scripts/post-everywhere.js docs/marketing/reddit-cursor-post.md --platforms=reddit,x,devto
|
|
11
|
-
*
|
|
12
|
-
* Post file format (markdown with metadata):
|
|
13
|
-
* # Reddit Post: r/cursor
|
|
14
|
-
* **Subreddit:** r/cursor
|
|
15
|
-
* **Title:** ...
|
|
16
|
-
* **Body:** ...
|
|
17
|
-
* **Comment (post immediately after):** ...
|
|
18
|
-
*
|
|
19
|
-
* The script parses the markdown, extracts platform-specific fields, and dispatches to
|
|
20
|
-
* the appropriate publisher module.
|
|
21
|
-
*
|
|
22
|
-
* Env vars: see individual publisher modules for required credentials per platform.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
const fs = require('fs');
|
|
26
|
-
const path = require('path');
|
|
27
|
-
const { tagUrlsInText } = require('./social-analytics/utm');
|
|
28
|
-
const { isDuplicate, recordPost } = require('./social-analytics/publishers/zernio');
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Publisher imports (lazy — only loaded when needed)
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
function getPublisher(platform) {
|
|
35
|
-
const publishers = {
|
|
36
|
-
reddit: () => require('./social-analytics/publishers/reddit.js'),
|
|
37
|
-
x: () => require('./post-to-x.js'),
|
|
38
|
-
linkedin: () => require('./social-analytics/publishers/linkedin.js'),
|
|
39
|
-
devto: () => require('./social-analytics/publishers/devto.js'),
|
|
40
|
-
threads: () => require('./social-analytics/publishers/threads.js'),
|
|
41
|
-
instagram: () => require('./social-analytics/publishers/instagram.js'),
|
|
42
|
-
tiktok: () => require('./social-analytics/publishers/tiktok.js'),
|
|
43
|
-
youtube: () => require('./social-analytics/publishers/youtube.js'),
|
|
44
|
-
};
|
|
45
|
-
const loader = publishers[platform];
|
|
46
|
-
if (!loader) throw new Error(`Unknown platform: ${platform}`);
|
|
47
|
-
return loader();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Markdown parser
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Parse a marketing post markdown file into structured fields.
|
|
56
|
-
* Extracts: subreddit, title, body, comment, platform hints.
|
|
57
|
-
*/
|
|
58
|
-
function parsePostFile(filePath) {
|
|
59
|
-
const raw = fs.readFileSync(filePath, 'utf8');
|
|
60
|
-
const lines = raw.split('\n');
|
|
61
|
-
|
|
62
|
-
const result = {
|
|
63
|
-
platform: null,
|
|
64
|
-
subreddit: null,
|
|
65
|
-
title: null,
|
|
66
|
-
body: null,
|
|
67
|
-
comment: null,
|
|
68
|
-
tags: [],
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Detect platform from header
|
|
72
|
-
const header = lines[0] || '';
|
|
73
|
-
if (/reddit/i.test(header)) result.platform = 'reddit';
|
|
74
|
-
else if (/obsidian/i.test(header)) result.platform = 'reddit'; // Obsidian posts go to Reddit
|
|
75
|
-
else if (/locallama/i.test(header)) result.platform = 'reddit';
|
|
76
|
-
else if (/programming/i.test(header)) result.platform = 'reddit';
|
|
77
|
-
else if (/twitter|x\.com/i.test(header)) result.platform = 'x';
|
|
78
|
-
else if (/linkedin/i.test(header)) result.platform = 'linkedin';
|
|
79
|
-
else if (/dev\.to/i.test(header)) result.platform = 'devto';
|
|
80
|
-
|
|
81
|
-
// Extract subreddit
|
|
82
|
-
const subLine = lines.find((l) => /^\*\*Subreddit:\*\*/i.test(l.trim()));
|
|
83
|
-
if (subLine) {
|
|
84
|
-
const match = subLine.match(/r\/(\w+)/);
|
|
85
|
-
if (match) result.subreddit = match[1];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Extract title
|
|
89
|
-
const titleLine = lines.find((l) => /^\*\*Title:\*\*/i.test(l.trim()));
|
|
90
|
-
if (titleLine) {
|
|
91
|
-
result.title = titleLine.replace(/^\*\*Title:\*\*\s*/i, '').trim();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Extract body — content between **Body:** and the next **Comment or --- separator
|
|
95
|
-
const bodyStartIdx = lines.findIndex((l) => /^\*\*Body:\*\*/i.test(l.trim()));
|
|
96
|
-
if (bodyStartIdx !== -1) {
|
|
97
|
-
const bodyLines = [];
|
|
98
|
-
for (let i = bodyStartIdx + 1; i < lines.length; i++) {
|
|
99
|
-
const line = lines[i];
|
|
100
|
-
// Stop at comment section or horizontal rule before comment
|
|
101
|
-
if (/^\*\*Comment/i.test(line.trim())) break;
|
|
102
|
-
if (line.trim() === '---' && i + 1 < lines.length && /^\*\*Comment/i.test(lines[i + 1].trim())) break;
|
|
103
|
-
bodyLines.push(line);
|
|
104
|
-
}
|
|
105
|
-
result.body = bodyLines.join('\n').trim();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Extract comment
|
|
109
|
-
const commentStartIdx = lines.findIndex((l) => /^\*\*Comment/i.test(l.trim()));
|
|
110
|
-
if (commentStartIdx !== -1) {
|
|
111
|
-
const commentLines = [];
|
|
112
|
-
for (let i = commentStartIdx + 1; i < lines.length; i++) {
|
|
113
|
-
commentLines.push(lines[i]);
|
|
114
|
-
}
|
|
115
|
-
result.comment = commentLines.join('\n').trim();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
// Platform dispatchers
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
|
|
125
|
-
async function postToReddit(parsed, dryRun) {
|
|
126
|
-
const { subreddit, title, body, comment } = parsed;
|
|
127
|
-
if (!subreddit || !title || !body) {
|
|
128
|
-
throw new Error('Reddit post requires subreddit, title, and body');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (dryRun) {
|
|
132
|
-
console.log(`[dry-run] Reddit r/${subreddit}: "${title}" (${body.length} chars)`);
|
|
133
|
-
if (comment) console.log(`[dry-run] Reddit follow-up comment: (${comment.length} chars)`);
|
|
134
|
-
return { dryRun: true };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const reddit = getPublisher('reddit');
|
|
138
|
-
const postData = await reddit.publishToReddit({ subreddit, title, text: body });
|
|
139
|
-
|
|
140
|
-
// Reddit follow-up comments are manual-review only.
|
|
141
|
-
if (comment && postData.name) {
|
|
142
|
-
console.log('[post-everywhere] Reddit follow-up comment skipped; manual review required');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return postData;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function postToX(parsed, dryRun) {
|
|
149
|
-
const text = parsed.title ? `${parsed.title}\n\n${(parsed.body || '').slice(0, 240)}` : parsed.body;
|
|
150
|
-
if (!text) throw new Error('X post requires title or body');
|
|
151
|
-
|
|
152
|
-
if (dryRun) {
|
|
153
|
-
console.log(`[dry-run] X/Twitter: "${text.slice(0, 100)}..." (${text.length} chars)`);
|
|
154
|
-
return { dryRun: true };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const x = getPublisher('x');
|
|
158
|
-
return x.postTweet(text);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function postToLinkedIn(parsed, dryRun) {
|
|
162
|
-
const text = parsed.body || '';
|
|
163
|
-
if (!text) throw new Error('LinkedIn post requires body');
|
|
164
|
-
|
|
165
|
-
if (dryRun) {
|
|
166
|
-
console.log(`[dry-run] LinkedIn: "${text.slice(0, 100)}..." (${text.length} chars)`);
|
|
167
|
-
return { dryRun: true };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const linkedin = getPublisher('linkedin');
|
|
171
|
-
return linkedin.publishPost({ text });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async function postToDevTo(parsed, dryRun) {
|
|
175
|
-
const { title, body } = parsed;
|
|
176
|
-
if (!title || !body) throw new Error('Dev.to post requires title and body');
|
|
177
|
-
|
|
178
|
-
if (dryRun) {
|
|
179
|
-
console.log(`[dry-run] Dev.to: "${title}" (${body.length} chars)`);
|
|
180
|
-
return { dryRun: true };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const devto = getPublisher('devto');
|
|
184
|
-
return devto.publishArticle({ title, body_markdown: body, tags: parsed.tags });
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function postToTikTok(parsed, dryRun) {
|
|
188
|
-
const text = parsed.body || '';
|
|
189
|
-
if (!text) throw new Error('TikTok post requires body');
|
|
190
|
-
|
|
191
|
-
if (dryRun) {
|
|
192
|
-
console.log(`[dry-run] TikTok: "${text.slice(0, 100)}..." (${text.length} chars)`);
|
|
193
|
-
return { dryRun: true };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const tiktok = getPublisher('tiktok');
|
|
197
|
-
return tiktok.publishPost({ text });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async function postToYouTube(parsed, dryRun) {
|
|
201
|
-
const { title, body } = parsed;
|
|
202
|
-
if (!title || !body) throw new Error('YouTube post requires title and body');
|
|
203
|
-
|
|
204
|
-
if (dryRun) {
|
|
205
|
-
console.log(`[dry-run] YouTube: "${title}" (${body.length} chars)`);
|
|
206
|
-
return { dryRun: true };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const youtube = getPublisher('youtube');
|
|
210
|
-
return youtube.publishPost({ title, description: body });
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
// Main orchestrator
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
const DISPATCHERS = {
|
|
218
|
-
reddit: postToReddit,
|
|
219
|
-
x: postToX,
|
|
220
|
-
linkedin: postToLinkedIn,
|
|
221
|
-
devto: postToDevTo,
|
|
222
|
-
tiktok: postToTikTok,
|
|
223
|
-
youtube: postToYouTube,
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
async function postEverywhere(filePath, { platforms, dryRun } = {}) {
|
|
227
|
-
const parsed = parsePostFile(filePath);
|
|
228
|
-
console.log(`[post-everywhere] Parsed: platform=${parsed.platform}, subreddit=${parsed.subreddit}, title="${parsed.title}"`);
|
|
229
|
-
|
|
230
|
-
const qualityGate = require('./social-quality-gate');
|
|
231
|
-
const postText = [parsed.title, parsed.body, parsed.comment].filter(Boolean).join('\n');
|
|
232
|
-
const gateResult = qualityGate.gatePost(postText);
|
|
233
|
-
if (!gateResult.allowed) {
|
|
234
|
-
const reasons = gateResult.findings.map(f => f.reason).join(', ');
|
|
235
|
-
console.error(`[post-everywhere] BLOCKED by quality gate: ${reasons}`);
|
|
236
|
-
return { blocked: true, reasons: gateResult.findings };
|
|
237
|
-
}
|
|
238
|
-
console.log('[post-everywhere] Quality gate: PASSED');
|
|
239
|
-
|
|
240
|
-
// Determine which platforms to post to.
|
|
241
|
-
// Default excludes devto — high-volume Dev.to posting is counterproductive (0 engagement on 427 posts).
|
|
242
|
-
// Use --platforms=devto explicitly for monthly cross-posts only.
|
|
243
|
-
const DEFAULT_PLATFORMS = ['reddit', 'x', 'linkedin', 'tiktok', 'youtube'];
|
|
244
|
-
const targetPlatforms = platforms || (parsed.platform ? [parsed.platform] : DEFAULT_PLATFORMS);
|
|
245
|
-
|
|
246
|
-
// Preserve original body/comment so each platform gets a fresh UTM tag
|
|
247
|
-
const originalBody = parsed.body;
|
|
248
|
-
const originalComment = parsed.comment;
|
|
249
|
-
|
|
250
|
-
// Tag trackable URLs with per-platform UTM parameters before dispatching
|
|
251
|
-
const results = {};
|
|
252
|
-
for (const platform of targetPlatforms) {
|
|
253
|
-
const utmOpts = { source: platform, medium: 'social', campaign: 'organic' };
|
|
254
|
-
parsed.body = originalBody ? tagUrlsInText(originalBody, utmOpts) : originalBody;
|
|
255
|
-
parsed.comment = originalComment ? tagUrlsInText(originalComment, utmOpts) : originalComment;
|
|
256
|
-
|
|
257
|
-
const dispatcher = DISPATCHERS[platform];
|
|
258
|
-
if (!dispatcher) {
|
|
259
|
-
console.warn(`[post-everywhere] No dispatcher for platform: ${platform}, skipping`);
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Dedup guard: skip platforms where identical content was posted in last 24h
|
|
264
|
-
const dedupContent = [parsed.title, parsed.body].filter(Boolean).join('\n');
|
|
265
|
-
if (!dryRun && isDuplicate(dedupContent, platform)) {
|
|
266
|
-
console.log(`[post-everywhere] ${platform}: SKIPPED — duplicate content within 24h`);
|
|
267
|
-
results[platform] = { skipped: true, reason: 'duplicate_content_24h' };
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
console.log(`\n[post-everywhere] Posting to ${platform}...`);
|
|
273
|
-
results[platform] = await dispatcher(parsed, dryRun);
|
|
274
|
-
if (!dryRun) recordPost(dedupContent, platform);
|
|
275
|
-
console.log(`[post-everywhere] ${platform}: OK`);
|
|
276
|
-
} catch (err) {
|
|
277
|
-
console.error(`[post-everywhere] ${platform}: FAILED — ${err.message}`);
|
|
278
|
-
results[platform] = { error: err.message };
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return results;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
module.exports = { postEverywhere, parsePostFile };
|
|
286
|
-
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
// CLI
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
|
-
if (require.main === module) {
|
|
291
|
-
const args = process.argv.slice(2);
|
|
292
|
-
const filePath = args.find((a) => !a.startsWith('--'));
|
|
293
|
-
const dryRun = args.includes('--dry-run');
|
|
294
|
-
|
|
295
|
-
function getArg(flag) {
|
|
296
|
-
const prefix = `${flag}=`;
|
|
297
|
-
const entry = args.find((a) => a.startsWith(prefix));
|
|
298
|
-
return entry ? entry.slice(prefix.length) : null;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const platformsArg = getArg('--platforms');
|
|
302
|
-
const platforms = platformsArg ? platformsArg.split(',').map((p) => p.trim()) : null;
|
|
303
|
-
|
|
304
|
-
if (!filePath) {
|
|
305
|
-
console.error('Usage: node scripts/post-everywhere.js <post-file.md> [--dry-run] [--platforms=reddit,x,devto]');
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const resolved = path.resolve(filePath);
|
|
310
|
-
if (!fs.existsSync(resolved)) {
|
|
311
|
-
console.error(`File not found: ${resolved}`);
|
|
312
|
-
process.exit(1);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Load .env if available
|
|
316
|
-
const envPath = path.resolve(__dirname, '..', '.env');
|
|
317
|
-
if (fs.existsSync(envPath)) {
|
|
318
|
-
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
319
|
-
for (const line of envContent.split('\n')) {
|
|
320
|
-
const trimmed = line.trim();
|
|
321
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
322
|
-
const eqIdx = trimmed.indexOf('=');
|
|
323
|
-
if (eqIdx > 0) {
|
|
324
|
-
const key = trimmed.slice(0, eqIdx);
|
|
325
|
-
const value = trimmed.slice(eqIdx + 1);
|
|
326
|
-
if (!process.env[key]) process.env[key] = value;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
postEverywhere(resolved, { platforms, dryRun })
|
|
332
|
-
.then((results) => {
|
|
333
|
-
console.log('\n[post-everywhere] Results:', JSON.stringify(results, null, 2));
|
|
334
|
-
const failed = Object.values(results).filter((r) => r.error);
|
|
335
|
-
if (failed.length > 0) process.exit(1);
|
|
336
|
-
})
|
|
337
|
-
.catch((err) => {
|
|
338
|
-
console.error('[post-everywhere] Fatal:', err.message);
|
|
339
|
-
process.exit(1);
|
|
340
|
-
});
|
|
341
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Retry posting to X.com until it succeeds (X API v2 has frequent 503s)
|
|
3
|
-
# Usage: source .env && bash scripts/post-to-x-retry.sh
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
MAX_RETRIES=10
|
|
8
|
-
RETRY_DELAY=30
|
|
9
|
-
|
|
10
|
-
for i in $(seq 1 $MAX_RETRIES); do
|
|
11
|
-
echo "Attempt $i/$MAX_RETRIES..."
|
|
12
|
-
if node scripts/post-to-x.js "$@" 2>&1 | grep -q "Posted tweet"; then
|
|
13
|
-
echo "✅ Tweet posted successfully!"
|
|
14
|
-
exit 0
|
|
15
|
-
fi
|
|
16
|
-
echo " Retrying in ${RETRY_DELAY}s..."
|
|
17
|
-
sleep $RETRY_DELAY
|
|
18
|
-
RETRY_DELAY=$((RETRY_DELAY * 2))
|
|
19
|
-
done
|
|
20
|
-
|
|
21
|
-
echo "❌ Failed after $MAX_RETRIES attempts. X API may be down."
|
|
22
|
-
exit 1
|