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,162 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
7
|
-
const DEFAULT_DIGEST_PATH = path.join(REPO_ROOT, '.artifacts', 'social', 'digests', 'digest.json');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Sends a formatted digest to Slack via an incoming webhook.
|
|
11
|
-
* Skips silently if SLACK_WEBHOOK_URL is not set.
|
|
12
|
-
*
|
|
13
|
-
* @param {object} digest
|
|
14
|
-
* @returns {Promise<void>}
|
|
15
|
-
*/
|
|
16
|
-
async function sendSlackDigest(digest) {
|
|
17
|
-
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
|
|
18
|
-
|
|
19
|
-
if (!webhookUrl) {
|
|
20
|
-
console.log('Slack webhook not configured, skipping notification');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const { period, summary, top_content } = digest;
|
|
25
|
-
|
|
26
|
-
const followerLines = Object.entries(summary.follower_delta)
|
|
27
|
-
.map(([platform, delta]) => {
|
|
28
|
-
const sign = delta >= 0 ? '+' : '';
|
|
29
|
-
return `• ${platform}: ${sign}${delta}`;
|
|
30
|
-
})
|
|
31
|
-
.join('\n');
|
|
32
|
-
|
|
33
|
-
const topThree = (top_content || []).slice(0, 3);
|
|
34
|
-
const topLines = topThree.length
|
|
35
|
-
? topThree
|
|
36
|
-
.map((item, idx) => {
|
|
37
|
-
const urlPart = item.post_url ? ` — <${item.post_url}|link>` : '';
|
|
38
|
-
return `${idx + 1}. *${item.platform}* \`${item.post_id}\` — ${item.total_engagement} eng / ${(item.impressions || 0).toLocaleString()} impressions${urlPart}`;
|
|
39
|
-
})
|
|
40
|
-
.join('\n')
|
|
41
|
-
: '_No content recorded._';
|
|
42
|
-
|
|
43
|
-
const payload = {
|
|
44
|
-
blocks: [
|
|
45
|
-
{
|
|
46
|
-
type: 'header',
|
|
47
|
-
text: {
|
|
48
|
-
type: 'plain_text',
|
|
49
|
-
text: `Weekly Social Digest (${period.start} → ${period.end})`,
|
|
50
|
-
emoji: false,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
type: 'section',
|
|
55
|
-
text: {
|
|
56
|
-
type: 'mrkdwn',
|
|
57
|
-
text: [
|
|
58
|
-
`*Impressions:* ${summary.total_impressions.toLocaleString()}`,
|
|
59
|
-
`*Likes:* ${summary.total_likes.toLocaleString()}`,
|
|
60
|
-
`*Comments:* ${summary.total_comments.toLocaleString()}`,
|
|
61
|
-
`*Shares:* ${summary.total_shares.toLocaleString()}`,
|
|
62
|
-
`*Engagement Rate:* ${summary.engagement_rate}`,
|
|
63
|
-
'',
|
|
64
|
-
'*Follower Delta:*',
|
|
65
|
-
followerLines,
|
|
66
|
-
].join('\n'),
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
type: 'section',
|
|
71
|
-
text: {
|
|
72
|
-
type: 'mrkdwn',
|
|
73
|
-
text: `*Top 3 Content:*\n${topLines}`,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const response = await fetch(webhookUrl, {
|
|
80
|
-
method: 'POST',
|
|
81
|
-
headers: { 'Content-Type': 'application/json' },
|
|
82
|
-
body: JSON.stringify(payload),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
if (!response.ok) {
|
|
86
|
-
const body = await response.text();
|
|
87
|
-
throw new Error(`Slack webhook failed: ${response.status} ${response.statusText} — ${body}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
console.log(`Slack digest sent for period ${period.start} → ${period.end}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Returns a formatted string for terminal output. Always works — no Slack needed.
|
|
95
|
-
*
|
|
96
|
-
* @param {object} digest
|
|
97
|
-
* @returns {string}
|
|
98
|
-
*/
|
|
99
|
-
function formatDigestForConsole(digest) {
|
|
100
|
-
const { period, summary, top_content } = digest;
|
|
101
|
-
|
|
102
|
-
const lines = [];
|
|
103
|
-
|
|
104
|
-
lines.push('');
|
|
105
|
-
lines.push(`Weekly Social Digest: ${period.start} -> ${period.end}`);
|
|
106
|
-
lines.push('='.repeat(60));
|
|
107
|
-
lines.push('');
|
|
108
|
-
lines.push('SUMMARY');
|
|
109
|
-
lines.push(` Impressions : ${summary.total_impressions.toLocaleString()}`);
|
|
110
|
-
lines.push(` Likes : ${summary.total_likes.toLocaleString()}`);
|
|
111
|
-
lines.push(` Comments : ${summary.total_comments.toLocaleString()}`);
|
|
112
|
-
lines.push(` Shares : ${summary.total_shares.toLocaleString()}`);
|
|
113
|
-
lines.push(` Engagement Rate: ${summary.engagement_rate}`);
|
|
114
|
-
lines.push('');
|
|
115
|
-
lines.push('FOLLOWER DELTA');
|
|
116
|
-
for (const [platform, delta] of Object.entries(summary.follower_delta)) {
|
|
117
|
-
const sign = delta >= 0 ? '+' : '';
|
|
118
|
-
lines.push(` ${platform.padEnd(12)}: ${sign}${delta}`);
|
|
119
|
-
}
|
|
120
|
-
lines.push('');
|
|
121
|
-
lines.push('TOP CONTENT');
|
|
122
|
-
|
|
123
|
-
const topThree = (top_content || []).slice(0, 3);
|
|
124
|
-
if (topThree.length === 0) {
|
|
125
|
-
lines.push(' No content recorded in this period.');
|
|
126
|
-
} else {
|
|
127
|
-
topThree.forEach((item, idx) => {
|
|
128
|
-
lines.push(` ${idx + 1}. [${item.platform}] ${item.post_id}`);
|
|
129
|
-
lines.push(` Engagement: ${item.total_engagement} Impressions: ${(item.impressions || 0).toLocaleString()}`);
|
|
130
|
-
if (item.post_url) {
|
|
131
|
-
lines.push(` URL: ${item.post_url}`);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
lines.push('');
|
|
137
|
-
|
|
138
|
-
return lines.join('\n');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
module.exports = {
|
|
142
|
-
sendSlackDigest,
|
|
143
|
-
formatDigestForConsole,
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
// Run as main: load latest digest and send.
|
|
147
|
-
if (require.main === module) {
|
|
148
|
-
if (!fs.existsSync(DEFAULT_DIGEST_PATH)) {
|
|
149
|
-
console.error(`Digest file not found: ${DEFAULT_DIGEST_PATH}`);
|
|
150
|
-
console.error('Run scripts/social-analytics/digest.js first to generate it.');
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const digest = JSON.parse(fs.readFileSync(DEFAULT_DIGEST_PATH, 'utf8'));
|
|
155
|
-
|
|
156
|
-
console.log(formatDigestForConsole(digest));
|
|
157
|
-
|
|
158
|
-
sendSlackDigest(digest).catch((err) => {
|
|
159
|
-
console.error('Failed to send Slack notification:', err.message);
|
|
160
|
-
process.exit(1);
|
|
161
|
-
});
|
|
162
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
const { loadLocalEnv } = require('./load-env');
|
|
5
|
-
|
|
6
|
-
loadLocalEnv({ envPath: path.resolve(__dirname, '..', '..', '.env') });
|
|
7
|
-
|
|
8
|
-
const { initDb } = require('./store');
|
|
9
|
-
|
|
10
|
-
const POLLERS = [
|
|
11
|
-
{ name: 'github', module: './pollers/github', envRequired: ['GITHUB_TOKEN'] },
|
|
12
|
-
// Direct Instagram Graph API poller. Requires INSTAGRAM_ACCESS_TOKEN + INSTAGRAM_USER_ID.
|
|
13
|
-
// When those are absent, Instagram engagement data is still captured via the Zernio poller
|
|
14
|
-
// below (getConnectedAccounts returns Instagram accounts when Zernio is connected to IG).
|
|
15
|
-
{ name: 'instagram', module: './pollers/instagram', envRequired: ['INSTAGRAM_ACCESS_TOKEN', 'INSTAGRAM_USER_ID'] },
|
|
16
|
-
{ name: 'tiktok', module: './pollers/tiktok', envRequired: ['TIKTOK_ACCESS_TOKEN'] },
|
|
17
|
-
{ name: 'linkedin', module: './pollers/linkedin', envRequired: ['LINKEDIN_ACCESS_TOKEN', 'LINKEDIN_PERSON_URN'] },
|
|
18
|
-
{ name: 'x', module: './pollers/x', envRequired: ['X_BEARER_TOKEN', 'X_USER_ID'] },
|
|
19
|
-
{ name: 'reddit', module: './pollers/reddit', envRequired: ['REDDIT_CLIENT_ID', 'REDDIT_CLIENT_SECRET', 'REDDIT_USERNAME', 'REDDIT_PASSWORD'] },
|
|
20
|
-
{ name: 'threads', module: './pollers/threads', envRequired: ['THREADS_ACCESS_TOKEN', 'THREADS_USER_ID'] },
|
|
21
|
-
{ name: 'youtube', module: './pollers/youtube', envRequired: ['YOUTUBE_API_KEY', 'YOUTUBE_CHANNEL_ID'] },
|
|
22
|
-
// PLAUSIBLE_SITE_ID defaults to thumbgate-production.up.railway.app if not set.
|
|
23
|
-
{ name: 'plausible', module: './pollers/plausible', envRequired: ['PLAUSIBLE_API_KEY'] },
|
|
24
|
-
// Zernio covers all connected social accounts (including Instagram) via its unified API.
|
|
25
|
-
// Instagram posts published via Zernio will have their engagement metrics captured here.
|
|
26
|
-
{ name: 'zernio', module: './pollers/zernio', envRequired: ['ZERNIO_API_KEY'] },
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
function hasEnv(keys) {
|
|
30
|
-
return keys.every((k) => process.env[k]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function pollAll(options = {}) {
|
|
34
|
-
const db = initDb(options.dbPath);
|
|
35
|
-
const results = { succeeded: [], skipped: [], failed: [] };
|
|
36
|
-
|
|
37
|
-
for (const poller of POLLERS) {
|
|
38
|
-
if (!hasEnv(poller.envRequired)) {
|
|
39
|
-
console.log(`⏭ ${poller.name}: skipped (missing env: ${poller.envRequired.filter((k) => !process.env[k]).join(', ')})`);
|
|
40
|
-
results.skipped.push(poller.name);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const mod = require(poller.module);
|
|
46
|
-
// Resolve the poll function by trying the simple title-case name first, then
|
|
47
|
-
// known capitalization variants (pollGitHub, pollTikTok, pollLinkedIn, pollYouTube),
|
|
48
|
-
// and finally any exported function whose name starts with "poll" as a last resort.
|
|
49
|
-
const baseName = poller.name.charAt(0).toUpperCase() + poller.name.slice(1);
|
|
50
|
-
const KNOWN_VARIANTS = {
|
|
51
|
-
github: 'pollGitHub',
|
|
52
|
-
tiktok: 'pollTikTok',
|
|
53
|
-
linkedin: 'pollLinkedIn',
|
|
54
|
-
youtube: 'pollYouTube',
|
|
55
|
-
};
|
|
56
|
-
const fn = mod[`poll${baseName}`]
|
|
57
|
-
|| (KNOWN_VARIANTS[poller.name] && mod[KNOWN_VARIANTS[poller.name]])
|
|
58
|
-
|| Object.values(mod).find((v) => typeof v === 'function' && v.name && v.name.startsWith('poll'));
|
|
59
|
-
|
|
60
|
-
if (!fn) {
|
|
61
|
-
console.log(`⚠ ${poller.name}: no poll function found in module`);
|
|
62
|
-
results.skipped.push(poller.name);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
console.log(`🔄 ${poller.name}: polling...`);
|
|
67
|
-
await fn(db);
|
|
68
|
-
console.log(`✅ ${poller.name}: complete`);
|
|
69
|
-
results.succeeded.push(poller.name);
|
|
70
|
-
} catch (err) {
|
|
71
|
-
console.error(`❌ ${poller.name}: ${err.message}`);
|
|
72
|
-
results.failed.push({ name: poller.name, error: err.message });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
db.close();
|
|
77
|
-
return results;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function main() {
|
|
81
|
-
console.log('=== Social Analytics Poll All ===');
|
|
82
|
-
console.log(`Time: ${new Date().toISOString()}`);
|
|
83
|
-
console.log('');
|
|
84
|
-
|
|
85
|
-
const results = await pollAll();
|
|
86
|
-
|
|
87
|
-
console.log('');
|
|
88
|
-
console.log('=== Summary ===');
|
|
89
|
-
console.log(`Succeeded: ${results.succeeded.join(', ') || 'none'}`);
|
|
90
|
-
console.log(`Skipped: ${results.skipped.join(', ') || 'none'}`);
|
|
91
|
-
console.log(`Failed: ${results.failed.map((f) => f.name).join(', ') || 'none'}`);
|
|
92
|
-
|
|
93
|
-
// Exit non-zero only if nothing succeeded AND there were failures.
|
|
94
|
-
// Partial success (some pollers skipped/failed but at least one succeeded) is OK.
|
|
95
|
-
if (results.succeeded.length === 0 && results.failed.length > 0) {
|
|
96
|
-
process.exitCode = 1;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (require.main === module) {
|
|
101
|
-
main().catch((err) => {
|
|
102
|
-
console.error(err);
|
|
103
|
-
process.exit(1);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
module.exports = { pollAll, POLLERS };
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* github.js
|
|
5
|
-
* Polls GitHub REST API for repository traffic data.
|
|
6
|
-
*
|
|
7
|
-
* IMPORTANT: GitHub only retains traffic data for 14 days.
|
|
8
|
-
* This poller must run at least daily to avoid gaps.
|
|
9
|
-
*
|
|
10
|
-
* Required env vars:
|
|
11
|
-
* GITHUB_TOKEN — personal access token with repo scope (required)
|
|
12
|
-
* GITHUB_REPO — owner/repo slug (default: IgorGanapolsky/ThumbGate)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const path = require('path');
|
|
16
|
-
|
|
17
|
-
const GITHUB_API_BASE = 'https://api.github.com';
|
|
18
|
-
const DEFAULT_REPO = 'IgorGanapolsky/ThumbGate';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Build standard GitHub API request headers.
|
|
22
|
-
* @param {string} token
|
|
23
|
-
* @returns {Record<string, string>}
|
|
24
|
-
*/
|
|
25
|
-
function buildHeaders(token) {
|
|
26
|
-
return {
|
|
27
|
-
Authorization: `Bearer ${token}`,
|
|
28
|
-
Accept: 'application/vnd.github+json',
|
|
29
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
30
|
-
'User-Agent': 'social-analytics-poller/1.0',
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Fetch a single GitHub API endpoint and return parsed JSON.
|
|
36
|
-
* Throws on non-2xx responses.
|
|
37
|
-
* @param {string} url
|
|
38
|
-
* @param {string} token
|
|
39
|
-
* @returns {Promise<unknown>}
|
|
40
|
-
*/
|
|
41
|
-
async function ghFetch(url, token) {
|
|
42
|
-
const res = await fetch(url, { headers: buildHeaders(token) });
|
|
43
|
-
if (!res.ok) {
|
|
44
|
-
const body = await res.text().catch(() => '');
|
|
45
|
-
throw new Error(`GitHub API ${res.status} for ${url}: ${body}`);
|
|
46
|
-
}
|
|
47
|
-
return res.json();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Fetch all traffic data for a repository.
|
|
52
|
-
*
|
|
53
|
-
* @param {string} token - GitHub personal access token
|
|
54
|
-
* @param {string} [repo] - owner/repo slug
|
|
55
|
-
* @returns {Promise<{
|
|
56
|
-
* views: object,
|
|
57
|
-
* clones: object,
|
|
58
|
-
* referrers: object[],
|
|
59
|
-
* repoStats: object
|
|
60
|
-
* }>}
|
|
61
|
-
*/
|
|
62
|
-
async function fetchGitHubTraffic(token, repo) {
|
|
63
|
-
if (!token) {
|
|
64
|
-
throw new Error('GITHUB_TOKEN is required');
|
|
65
|
-
}
|
|
66
|
-
const slug = repo || DEFAULT_REPO;
|
|
67
|
-
const base = `${GITHUB_API_BASE}/repos/${slug}`;
|
|
68
|
-
|
|
69
|
-
console.log(`[github] Fetching traffic for ${slug}`);
|
|
70
|
-
|
|
71
|
-
const [views, clones, referrers, repoStats] = await Promise.all([
|
|
72
|
-
ghFetch(`${base}/traffic/views`, token),
|
|
73
|
-
ghFetch(`${base}/traffic/clones`, token),
|
|
74
|
-
ghFetch(`${base}/traffic/popular/referrers`, token),
|
|
75
|
-
ghFetch(base, token),
|
|
76
|
-
]);
|
|
77
|
-
|
|
78
|
-
console.log(
|
|
79
|
-
`[github] views=${views.count} uniques=${views.uniques} ` +
|
|
80
|
-
`clones=${clones.count} stars=${repoStats.stargazers_count} ` +
|
|
81
|
-
`forks=${repoStats.forks_count}`
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
return { views, clones, referrers, repoStats };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Main polling entry point.
|
|
89
|
-
*
|
|
90
|
-
* Fetches GitHub traffic, normalizes each daily view entry, and upserts
|
|
91
|
-
* into the analytics database. Also records a follower_snapshot using
|
|
92
|
-
* stargazers_count as the follower-count equivalent.
|
|
93
|
-
*
|
|
94
|
-
* @param {import('better-sqlite3').Database} db
|
|
95
|
-
* @returns {Promise<void>}
|
|
96
|
-
*/
|
|
97
|
-
async function pollGitHub(db) {
|
|
98
|
-
const token = process.env.GITHUB_TOKEN;
|
|
99
|
-
if (!token) {
|
|
100
|
-
throw new Error('GITHUB_TOKEN environment variable is required');
|
|
101
|
-
}
|
|
102
|
-
const repo = process.env.GITHUB_REPO || DEFAULT_REPO;
|
|
103
|
-
|
|
104
|
-
const { views, clones, referrers, repoStats } = await fetchGitHubTraffic(token, repo);
|
|
105
|
-
|
|
106
|
-
// Lazy-require sibling modules so they can be built/tested independently.
|
|
107
|
-
const { normalizeGitHubMetric: normalizeMetric } = require('../normalizer');
|
|
108
|
-
const { upsertMetric, upsertFollowerSnapshot } = require('../store');
|
|
109
|
-
|
|
110
|
-
const repoName = repo.split('/').pop();
|
|
111
|
-
const fetchedAt = new Date().toISOString();
|
|
112
|
-
|
|
113
|
-
// Build a date-keyed map of clone counts for merge into view records.
|
|
114
|
-
const clonesByDate = {};
|
|
115
|
-
if (Array.isArray(clones.views)) {
|
|
116
|
-
for (const entry of clones.views) {
|
|
117
|
-
const date = entry.timestamp.slice(0, 10);
|
|
118
|
-
clonesByDate[date] = (clonesByDate[date] || 0) + entry.count;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const upsertedDates = [];
|
|
123
|
-
|
|
124
|
-
for (const entry of views.views || []) {
|
|
125
|
-
const metricDate = entry.timestamp.slice(0, 10);
|
|
126
|
-
const cloneCount = clonesByDate[metricDate] || 0;
|
|
127
|
-
|
|
128
|
-
const record = {
|
|
129
|
-
platform: 'github',
|
|
130
|
-
content_type: 'repo',
|
|
131
|
-
post_id: repoName,
|
|
132
|
-
post_url: `https://github.com/${repo}`,
|
|
133
|
-
metric_date: metricDate,
|
|
134
|
-
impressions: entry.count,
|
|
135
|
-
reach: entry.uniques,
|
|
136
|
-
clicks: cloneCount,
|
|
137
|
-
likes: repoStats.stargazers_count,
|
|
138
|
-
shares: repoStats.forks_count,
|
|
139
|
-
comments: 0,
|
|
140
|
-
saves: 0,
|
|
141
|
-
video_views: 0,
|
|
142
|
-
followers_delta: 0,
|
|
143
|
-
extra_json: JSON.stringify({ referrers }),
|
|
144
|
-
fetched_at: fetchedAt,
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
upsertMetric(db, record);
|
|
148
|
-
upsertedDates.push(metricDate);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
console.log(`[github] Upserted ${upsertedDates.length} daily metric records: ${upsertedDates.join(', ')}`);
|
|
152
|
-
|
|
153
|
-
// Record follower snapshot (stars as follower-count equivalent).
|
|
154
|
-
const snapshotDate = fetchedAt.slice(0, 10);
|
|
155
|
-
upsertFollowerSnapshot(db, {
|
|
156
|
-
platform: 'github',
|
|
157
|
-
follower_count: repoStats.stargazers_count,
|
|
158
|
-
snapshot_date: snapshotDate,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
console.log(
|
|
162
|
-
`[github] Follower snapshot upserted: platform=github ` +
|
|
163
|
-
`follower_count=${repoStats.stargazers_count} date=${snapshotDate}`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
module.exports = { fetchGitHubTraffic, pollGitHub };
|
|
168
|
-
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Stand-alone execution
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
if (require.main === module) {
|
|
173
|
-
const Database = require('better-sqlite3');
|
|
174
|
-
const schemaPath = path.join(__dirname, '..', 'db', 'schema.sql');
|
|
175
|
-
const dbPath = path.join(__dirname, '..', 'db', 'social-analytics.db');
|
|
176
|
-
const fs = require('fs');
|
|
177
|
-
|
|
178
|
-
const db = new Database(dbPath);
|
|
179
|
-
db.pragma('busy_timeout = 3000');
|
|
180
|
-
db.pragma('journal_mode = WAL');
|
|
181
|
-
|
|
182
|
-
const schema = fs.readFileSync(schemaPath, 'utf8');
|
|
183
|
-
db.exec(schema);
|
|
184
|
-
|
|
185
|
-
pollGitHub(db)
|
|
186
|
-
.then(() => {
|
|
187
|
-
console.log('[github] Poll complete.');
|
|
188
|
-
db.close();
|
|
189
|
-
})
|
|
190
|
-
.catch((err) => {
|
|
191
|
-
console.error('[github] Poll failed:', err.message);
|
|
192
|
-
db.close();
|
|
193
|
-
process.exit(1);
|
|
194
|
-
});
|
|
195
|
-
}
|