thumbgate 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/.claude-plugin/README.md +134 -0
  2. package/.claude-plugin/bundle/icon.png +0 -0
  3. package/.claude-plugin/bundle/icon.svg +18 -0
  4. package/.claude-plugin/bundle/server/index.js +24 -0
  5. package/.claude-plugin/marketplace.json +36 -0
  6. package/.claude-plugin/plugin.json +21 -0
  7. package/.well-known/mcp/server-card.json +231 -0
  8. package/LICENSE +21 -0
  9. package/README.md +375 -0
  10. package/adapters/README.md +9 -0
  11. package/adapters/amp/skills/thumbgate-feedback/SKILL.md +22 -0
  12. package/adapters/chatgpt/INSTALL.md +83 -0
  13. package/adapters/chatgpt/openapi.yaml +1281 -0
  14. package/adapters/claude/.mcp.json +14 -0
  15. package/adapters/codex/config.toml +9 -0
  16. package/adapters/gemini/function-declarations.json +224 -0
  17. package/adapters/mcp/server-stdio.js +788 -0
  18. package/adapters/opencode/opencode.json +15 -0
  19. package/bin/cli.js +1484 -0
  20. package/bin/memory.sh +64 -0
  21. package/bin/obsidian-sync.sh +20 -0
  22. package/bin/postinstall.js +37 -0
  23. package/config/build-metadata.json +4 -0
  24. package/config/e2e-critical-flows.json +45 -0
  25. package/config/gate-templates.json +77 -0
  26. package/config/gates/claim-verification.json +29 -0
  27. package/config/gates/computer-use.json +39 -0
  28. package/config/gates/default.json +117 -0
  29. package/config/github-about.json +25 -0
  30. package/config/mcp-allowlists.json +135 -0
  31. package/config/model-tiers.json +33 -0
  32. package/config/partner-routing.json +132 -0
  33. package/config/policy-bundles/constrained-v1.json +64 -0
  34. package/config/policy-bundles/default-v1.json +91 -0
  35. package/config/rubrics/default-v1.json +52 -0
  36. package/config/skill-packs/react-testing.json +23 -0
  37. package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
  38. package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
  39. package/config/skill-specs/pr-reviewer.json +9 -0
  40. package/config/skill-specs/release-status.json +9 -0
  41. package/config/skill-specs/ticket-triage.json +9 -0
  42. package/config/subagent-profiles.json +32 -0
  43. package/config/tessl-tiles.json +29 -0
  44. package/config/thumbgate-settings.managed.json +12 -0
  45. package/openapi/openapi.yaml +1281 -0
  46. package/package.json +283 -0
  47. package/plugins/amp-skill/INSTALL.md +52 -0
  48. package/plugins/amp-skill/SKILL.md +64 -0
  49. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
  50. package/plugins/claude-codex-bridge/.mcp.json +12 -0
  51. package/plugins/claude-codex-bridge/INSTALL.md +43 -0
  52. package/plugins/claude-codex-bridge/README.md +46 -0
  53. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
  54. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
  55. package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
  56. package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
  57. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
  58. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
  59. package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
  60. package/plugins/claude-skill/INSTALL.md +55 -0
  61. package/plugins/claude-skill/SKILL.md +46 -0
  62. package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
  63. package/plugins/codex-profile/.mcp.json +12 -0
  64. package/plugins/codex-profile/AGENTS.md +20 -0
  65. package/plugins/codex-profile/INSTALL.md +66 -0
  66. package/plugins/codex-profile/README.md +37 -0
  67. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
  68. package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
  69. package/plugins/cursor-marketplace/LICENSE +21 -0
  70. package/plugins/cursor-marketplace/README.md +124 -0
  71. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
  72. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  73. package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
  74. package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
  75. package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
  76. package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
  77. package/plugins/cursor-marketplace/mcp.json +12 -0
  78. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
  79. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
  80. package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
  81. package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
  82. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
  83. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
  84. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
  85. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
  86. package/plugins/gemini-extension/INSTALL.md +92 -0
  87. package/plugins/gemini-extension/gemini_prompt.txt +14 -0
  88. package/plugins/gemini-extension/tool_contract.json +45 -0
  89. package/plugins/opencode-profile/INSTALL.md +57 -0
  90. package/public/assets/instagram-card.png +0 -0
  91. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  92. package/public/blog.html +400 -0
  93. package/public/dashboard.html +1093 -0
  94. package/public/guide.html +317 -0
  95. package/public/index.html +1014 -0
  96. package/public/learn/agent-harness-pattern.html +180 -0
  97. package/public/learn/ai-agent-persistent-memory.html +202 -0
  98. package/public/learn/learn.css +45 -0
  99. package/public/learn/mcp-pre-action-gates-explained.html +172 -0
  100. package/public/learn/stop-ai-agent-force-push.html +134 -0
  101. package/public/learn/vibe-coding-safety-net.html +142 -0
  102. package/public/learn.html +213 -0
  103. package/public/lessons.html +650 -0
  104. package/public/vercel.json +8 -0
  105. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  106. package/scripts/a2ui-engine.js +73 -0
  107. package/scripts/access-anomaly-detector.js +12 -0
  108. package/scripts/adk-consolidator.js +266 -0
  109. package/scripts/agent-readiness.js +220 -0
  110. package/scripts/agent-security-hardening.js +227 -0
  111. package/scripts/agentic-data-pipeline.js +847 -0
  112. package/scripts/analytics-report.js +328 -0
  113. package/scripts/analytics-window.js +158 -0
  114. package/scripts/async-job-runner.js +1001 -0
  115. package/scripts/audit-trail.js +398 -0
  116. package/scripts/auto-promote-gates.js +299 -0
  117. package/scripts/auto-wire-hooks.js +312 -0
  118. package/scripts/autonomous-sales-agent.js +39 -0
  119. package/scripts/autoresearch-runner.js +216 -0
  120. package/scripts/background-agent-governance.js +237 -0
  121. package/scripts/behavioral-extraction.js +97 -0
  122. package/scripts/belief-update.js +84 -0
  123. package/scripts/billing.js +2438 -0
  124. package/scripts/bot-detector.js +50 -0
  125. package/scripts/budget-guard.js +173 -0
  126. package/scripts/build-claude-mcpb.js +189 -0
  127. package/scripts/build-metadata.js +97 -0
  128. package/scripts/check-congruence.js +322 -0
  129. package/scripts/cli-feedback.js +135 -0
  130. package/scripts/cli-telemetry.js +87 -0
  131. package/scripts/cloudflare-dynamic-sandbox.js +315 -0
  132. package/scripts/code-reasoning.js +350 -0
  133. package/scripts/codegraph-context.js +466 -0
  134. package/scripts/commercial-offer.js +56 -0
  135. package/scripts/computer-use-firewall.js +250 -0
  136. package/scripts/context-engine.js +694 -0
  137. package/scripts/contextfs.js +1287 -0
  138. package/scripts/conversation-context.js +119 -0
  139. package/scripts/creator-campaigns.js +239 -0
  140. package/scripts/daemon-manager.js +108 -0
  141. package/scripts/daily-digest.js +11 -0
  142. package/scripts/dashboard-render-spec.js +395 -0
  143. package/scripts/dashboard.js +1058 -0
  144. package/scripts/data-governance.js +173 -0
  145. package/scripts/delegation-runtime.js +900 -0
  146. package/scripts/deploy-gcp.sh +44 -0
  147. package/scripts/deploy-policy.js +263 -0
  148. package/scripts/disagreement-mining.js +315 -0
  149. package/scripts/dispatch-brief.js +159 -0
  150. package/scripts/distribution-surfaces.js +44 -0
  151. package/scripts/dpo-optimizer.js +209 -0
  152. package/scripts/ephemeral-agent-store.js +219 -0
  153. package/scripts/eval-harness.js +56 -0
  154. package/scripts/evolution-state.js +241 -0
  155. package/scripts/experiment-tracker.js +267 -0
  156. package/scripts/export-databricks-bundle.js +242 -0
  157. package/scripts/export-dpo-pairs.js +345 -0
  158. package/scripts/export-kto-pairs.js +310 -0
  159. package/scripts/export-training.js +448 -0
  160. package/scripts/failure-diagnostics.js +558 -0
  161. package/scripts/feedback-attribution.js +313 -0
  162. package/scripts/feedback-fallback.js +111 -0
  163. package/scripts/feedback-history-distiller.js +391 -0
  164. package/scripts/feedback-inbox-read.js +162 -0
  165. package/scripts/feedback-loop.js +1887 -0
  166. package/scripts/feedback-paths.js +145 -0
  167. package/scripts/feedback-quality.js +139 -0
  168. package/scripts/feedback-root-consolidator.js +238 -0
  169. package/scripts/feedback-schema.js +426 -0
  170. package/scripts/feedback-session.js +286 -0
  171. package/scripts/feedback-to-memory.js +185 -0
  172. package/scripts/feedback-to-rules.js +163 -0
  173. package/scripts/filesystem-search.js +404 -0
  174. package/scripts/funnel-analytics.js +35 -0
  175. package/scripts/gate-satisfy.js +42 -0
  176. package/scripts/gate-stats.js +116 -0
  177. package/scripts/gate-templates.js +70 -0
  178. package/scripts/gates-engine.js +816 -0
  179. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  180. package/scripts/generate-pretool-hook.sh +40 -0
  181. package/scripts/github-about.js +350 -0
  182. package/scripts/github-outreach.js +65 -0
  183. package/scripts/gtm-revenue-loop.js +520 -0
  184. package/scripts/hallucination-detector.js +226 -0
  185. package/scripts/hf-papers.js +317 -0
  186. package/scripts/history-distiller.js +200 -0
  187. package/scripts/hook-auto-capture.sh +95 -0
  188. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  189. package/scripts/hook-stop-self-score.sh +51 -0
  190. package/scripts/hook-stop-verify-deploy.sh +31 -0
  191. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  192. package/scripts/hook-verify-before-done.sh +20 -0
  193. package/scripts/hosted-config.js +170 -0
  194. package/scripts/hybrid-feedback-context.js +676 -0
  195. package/scripts/install-mcp.js +159 -0
  196. package/scripts/intent-router.js +392 -0
  197. package/scripts/internal-agent-bootstrap.js +490 -0
  198. package/scripts/jsonl-watcher.js +155 -0
  199. package/scripts/lesson-db.js +613 -0
  200. package/scripts/lesson-inference.js +315 -0
  201. package/scripts/lesson-retrieval.js +95 -0
  202. package/scripts/lesson-rotation.js +137 -0
  203. package/scripts/lesson-search.js +644 -0
  204. package/scripts/lesson-synthesis.js +196 -0
  205. package/scripts/license.js +50 -0
  206. package/scripts/local-model-profile.js +383 -0
  207. package/scripts/markdown-escape.js +12 -0
  208. package/scripts/marketing-experiment.js +671 -0
  209. package/scripts/mcp-config.js +149 -0
  210. package/scripts/mcp-policy.js +99 -0
  211. package/scripts/memalign-recall.js +111 -0
  212. package/scripts/memory-firewall.js +222 -0
  213. package/scripts/memory-migration.js +296 -0
  214. package/scripts/meta-policy.js +194 -0
  215. package/scripts/metered-billing.js +16 -0
  216. package/scripts/model-tier-router.js +301 -0
  217. package/scripts/money-watcher.js +71 -0
  218. package/scripts/multi-hop-recall.js +240 -0
  219. package/scripts/natural-language-harness.js +330 -0
  220. package/scripts/obsidian-export.js +712 -0
  221. package/scripts/operational-dashboard.js +103 -0
  222. package/scripts/operational-summary.js +93 -0
  223. package/scripts/optimize-context.js +17 -0
  224. package/scripts/org-dashboard.js +201 -0
  225. package/scripts/partner-orchestration.js +146 -0
  226. package/scripts/per-step-scoring.js +165 -0
  227. package/scripts/perplexity-marketing.js +466 -0
  228. package/scripts/pii-scanner.js +153 -0
  229. package/scripts/plan-gate.js +154 -0
  230. package/scripts/post-everywhere.js +308 -0
  231. package/scripts/post-to-x-retry.sh +22 -0
  232. package/scripts/post-to-x.js +369 -0
  233. package/scripts/pr-manager.js +236 -0
  234. package/scripts/predictive-insights.js +356 -0
  235. package/scripts/principle-extractor.js +162 -0
  236. package/scripts/pro-features.js +40 -0
  237. package/scripts/pro-local-dashboard.js +174 -0
  238. package/scripts/problem-detail.js +53 -0
  239. package/scripts/product-feedback.js +134 -0
  240. package/scripts/profile-router.js +245 -0
  241. package/scripts/prompt-dlp.js +221 -0
  242. package/scripts/prompt-guard.js +83 -0
  243. package/scripts/prove-adapters.js +863 -0
  244. package/scripts/prove-attribution.js +365 -0
  245. package/scripts/prove-automation.js +653 -0
  246. package/scripts/prove-autoresearch.js +304 -0
  247. package/scripts/prove-claim-verification.js +277 -0
  248. package/scripts/prove-cloudflare-sandbox.js +163 -0
  249. package/scripts/prove-data-pipeline.js +410 -0
  250. package/scripts/prove-data-quality.js +227 -0
  251. package/scripts/prove-evolution.js +352 -0
  252. package/scripts/prove-harnesses.js +287 -0
  253. package/scripts/prove-intelligence.js +259 -0
  254. package/scripts/prove-lancedb.js +371 -0
  255. package/scripts/prove-local-intelligence.js +342 -0
  256. package/scripts/prove-loop-closure.js +263 -0
  257. package/scripts/prove-predictive-insights.js +357 -0
  258. package/scripts/prove-runtime.js +350 -0
  259. package/scripts/prove-seo-gsd.js +234 -0
  260. package/scripts/prove-settings.js +279 -0
  261. package/scripts/prove-subway-upgrades.js +277 -0
  262. package/scripts/prove-tessl.js +229 -0
  263. package/scripts/prove-training-export.js +327 -0
  264. package/scripts/prove-workflow-contract.js +116 -0
  265. package/scripts/prove-xmemory.js +332 -0
  266. package/scripts/publish-decision.js +133 -0
  267. package/scripts/pulse.js +80 -0
  268. package/scripts/rate-limiter.js +125 -0
  269. package/scripts/reddit-dm-outreach.js +182 -0
  270. package/scripts/reddit-monitor-cron.sh +26 -0
  271. package/scripts/reflector-agent.js +221 -0
  272. package/scripts/reminder-engine.js +132 -0
  273. package/scripts/revenue-status.js +472 -0
  274. package/scripts/risk-scorer.js +458 -0
  275. package/scripts/rlaif-self-audit.js +129 -0
  276. package/scripts/rubric-engine.js +230 -0
  277. package/scripts/schedule-manager.js +251 -0
  278. package/scripts/secret-scanner.js +414 -0
  279. package/scripts/self-heal.js +147 -0
  280. package/scripts/self-healing-check.js +188 -0
  281. package/scripts/semantic-layer.js +98 -0
  282. package/scripts/seo-gsd.js +1153 -0
  283. package/scripts/settings-hierarchy.js +214 -0
  284. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  285. package/scripts/skill-exporter.js +262 -0
  286. package/scripts/skill-generator.js +446 -0
  287. package/scripts/skill-materializer.js +134 -0
  288. package/scripts/skill-packs.js +136 -0
  289. package/scripts/skill-proposer.js +99 -0
  290. package/scripts/skill-quality-tracker.js +284 -0
  291. package/scripts/slo-alert-engine.js +14 -0
  292. package/scripts/slow-loop.js +72 -0
  293. package/scripts/social-analytics/db/schema.sql +32 -0
  294. package/scripts/social-analytics/digest.js +256 -0
  295. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  296. package/scripts/social-analytics/instagram-thumbgate-post.js +73 -0
  297. package/scripts/social-analytics/mcp-server.js +289 -0
  298. package/scripts/social-analytics/normalizer.js +580 -0
  299. package/scripts/social-analytics/notify.js +162 -0
  300. package/scripts/social-analytics/poll-all.js +107 -0
  301. package/scripts/social-analytics/pollers/github.js +195 -0
  302. package/scripts/social-analytics/pollers/instagram.js +253 -0
  303. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  304. package/scripts/social-analytics/pollers/plausible.js +247 -0
  305. package/scripts/social-analytics/pollers/reddit.js +306 -0
  306. package/scripts/social-analytics/pollers/threads.js +233 -0
  307. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  308. package/scripts/social-analytics/pollers/x.js +227 -0
  309. package/scripts/social-analytics/pollers/youtube.js +304 -0
  310. package/scripts/social-analytics/pollers/zernio.js +180 -0
  311. package/scripts/social-analytics/publish-instagram-thumbgate.js +85 -0
  312. package/scripts/social-analytics/publishers/devto.js +122 -0
  313. package/scripts/social-analytics/publishers/instagram.js +317 -0
  314. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  315. package/scripts/social-analytics/publishers/reddit.js +390 -0
  316. package/scripts/social-analytics/publishers/threads.js +275 -0
  317. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  318. package/scripts/social-analytics/publishers/x.js +259 -0
  319. package/scripts/social-analytics/publishers/youtube.js +223 -0
  320. package/scripts/social-analytics/publishers/zernio.js +209 -0
  321. package/scripts/social-analytics/run-digest.js +34 -0
  322. package/scripts/social-analytics/store.js +257 -0
  323. package/scripts/social-analytics/utm.js +143 -0
  324. package/scripts/social-pipeline.js +2628 -0
  325. package/scripts/social-quality-gate.js +18 -0
  326. package/scripts/social-reply-monitor.js +445 -0
  327. package/scripts/status-dashboard.js +155 -0
  328. package/scripts/statusline-lesson.js +16 -0
  329. package/scripts/statusline-tower.js +8 -0
  330. package/scripts/statusline.sh +116 -0
  331. package/scripts/stripe-live-status.js +115 -0
  332. package/scripts/subagent-profiles.js +79 -0
  333. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  334. package/scripts/sync-github-about.js +52 -0
  335. package/scripts/sync-version.js +451 -0
  336. package/scripts/synthetic-dpo.js +234 -0
  337. package/scripts/telemetry-analytics.js +821 -0
  338. package/scripts/tessl-export.js +371 -0
  339. package/scripts/test-coverage.js +120 -0
  340. package/scripts/thompson-sampling.js +417 -0
  341. package/scripts/thumbgate-search.js +189 -0
  342. package/scripts/tool-kpi-tracker.js +12 -0
  343. package/scripts/tool-registry.js +811 -0
  344. package/scripts/train_from_feedback.py +910 -0
  345. package/scripts/user-profile.js +78 -0
  346. package/scripts/validate-feedback.js +580 -0
  347. package/scripts/validate-workflow-contract.js +287 -0
  348. package/scripts/vector-store.js +198 -0
  349. package/scripts/verification-loop.js +291 -0
  350. package/scripts/verify-obsidian-setup.sh +269 -0
  351. package/scripts/verify-run.js +269 -0
  352. package/scripts/webhook-delivery.js +62 -0
  353. package/scripts/weekly-auto-post.js +124 -0
  354. package/scripts/workflow-runs.js +154 -0
  355. package/scripts/workflow-sprint-intake.js +475 -0
  356. package/scripts/workspace-evolver.js +374 -0
  357. package/scripts/x-autonomous-marketing.js +139 -0
  358. package/scripts/xmemory-lite.js +405 -0
  359. package/skills/agent-memory/SKILL.md +97 -0
  360. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  361. package/skills/solve-architecture-autonomy/tool.js +33 -0
  362. package/skills/thumbgate/SKILL.md +114 -0
  363. package/skills/thumbgate-feedback/SKILL.md +49 -0
  364. package/src/api/server.js +4208 -0
@@ -0,0 +1,328 @@
1
+ 'use strict';
2
+
3
+ const https = require('https');
4
+ const { PRODUCTHUNT_URL } = require('./distribution-surfaces');
5
+ const { getOperationalBillingSummary } = require('./operational-summary');
6
+ const { summarizeCreatorPerformance } = require('./creator-campaigns');
7
+ const { getFeedbackPaths } = require('./feedback-loop');
8
+ const { buildPredictiveInsights } = require('./predictive-insights');
9
+ const { getTelemetryAnalytics } = require('./telemetry-analytics');
10
+
11
+ const NPM_PACKAGE = 'thumbgate';
12
+ const GITHUB_REPO = 'IgorGanapolsky/ThumbGate';
13
+ const PLAUSIBLE_URL = 'https://plausible.io/thumbgate-production.up.railway.app';
14
+ const LANDING_PAGE = 'https://thumbgate-production.up.railway.app';
15
+
16
+ function httpsGet(url) {
17
+ return new Promise((resolve, reject) => {
18
+ https.get(url, { headers: { 'User-Agent': 'thumbgate-analytics' } }, (res) => {
19
+ let data = '';
20
+ res.on('data', (chunk) => { data += chunk; });
21
+ res.on('end', () => {
22
+ if (res.statusCode >= 400) {
23
+ reject(new Error(`HTTP ${res.statusCode} for ${url}: ${data}`));
24
+ return;
25
+ }
26
+ try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
27
+ });
28
+ }).on('error', reject);
29
+ });
30
+ }
31
+
32
+ function sparkline(values) {
33
+ const chars = '▁▂▃▄▅▆▇█';
34
+ const max = Math.max(...values, 1);
35
+ return values.map((v) => chars[Math.min(Math.floor((v / max) * (chars.length - 1)), chars.length - 1)]).join('');
36
+ }
37
+
38
+ async function fetchNpmMonthly() {
39
+ return httpsGet(`https://api.npmjs.org/downloads/range/last-month/${NPM_PACKAGE}`);
40
+ }
41
+
42
+ async function fetchNpmWeekly() {
43
+ return httpsGet(`https://api.npmjs.org/downloads/point/last-week/${NPM_PACKAGE}`);
44
+ }
45
+
46
+ async function fetchGitHub() {
47
+ return httpsGet(`https://api.github.com/repos/${GITHUB_REPO}`);
48
+ }
49
+
50
+ async function fetchNpmVersions() {
51
+ return httpsGet(`https://registry.npmjs.org/${NPM_PACKAGE}`);
52
+ }
53
+
54
+ function loadTelemetrySnapshot() {
55
+ const { FEEDBACK_DIR } = getFeedbackPaths();
56
+ return getTelemetryAnalytics(FEEDBACK_DIR);
57
+ }
58
+
59
+ function getCounterValue(counter = {}, key) {
60
+ return Number(counter && counter[key]) || 0;
61
+ }
62
+
63
+ function mergeDimensionCounters(metricCounters = {}) {
64
+ const metrics = Object.keys(metricCounters);
65
+ const rows = new Map();
66
+
67
+ for (const metric of metrics) {
68
+ const counter = metricCounters[metric] || {};
69
+ for (const [rawKey, rawValue] of Object.entries(counter)) {
70
+ const key = String(rawKey || '').trim() || 'unknown';
71
+ const row = rows.get(key) || { key };
72
+ row[metric] = Number(rawValue || 0);
73
+ rows.set(key, row);
74
+ }
75
+ }
76
+
77
+ return Array.from(rows.values());
78
+ }
79
+
80
+ function buildPredictiveStagingModel(telemetry = {}, billingSummary = {}) {
81
+ return {
82
+ dims: {
83
+ sources: mergeDimensionCounters({
84
+ pageViews: telemetry.visitors && telemetry.visitors.bySource,
85
+ checkoutStarts: telemetry.ctas && telemetry.ctas.checkoutStartsBySource,
86
+ acquisitionLeads: billingSummary.attribution && billingSummary.attribution.acquisitionBySource,
87
+ paidCustomers: billingSummary.attribution && billingSummary.attribution.paidBySource,
88
+ bookedRevenueCents: billingSummary.attribution && billingSummary.attribution.bookedRevenueBySourceCents,
89
+ }),
90
+ creators: mergeDimensionCounters({
91
+ pageViews: telemetry.visitors && telemetry.visitors.byCreator,
92
+ checkoutStarts: telemetry.ctas && telemetry.ctas.checkoutStartsByCreator,
93
+ acquisitionLeads: billingSummary.attribution && billingSummary.attribution.acquisitionByCreator,
94
+ paidCustomers: billingSummary.attribution && billingSummary.attribution.paidByCreator,
95
+ bookedRevenueCents: billingSummary.attribution && billingSummary.attribution.bookedRevenueByCreatorCents,
96
+ workflowSprintLeads: billingSummary.pipeline && billingSummary.pipeline.workflowSprintLeads && billingSummary.pipeline.workflowSprintLeads.byCreator,
97
+ qualifiedWorkflowSprintLeads: billingSummary.pipeline && billingSummary.pipeline.qualifiedWorkflowSprintLeads && billingSummary.pipeline.qualifiedWorkflowSprintLeads.byCreator,
98
+ }),
99
+ },
100
+ };
101
+ }
102
+
103
+ function resolveProductHuntCount(primaryCounter = {}, secondaryCounter = {}) {
104
+ const primary = getCounterValue(primaryCounter, 'producthunt');
105
+ if (primary > 0) return primary;
106
+ return getCounterValue(secondaryCounter, 'producthunt');
107
+ }
108
+
109
+ async function collectAnalytics(fetchers = {}) {
110
+ const fetchMonthly = fetchers.fetchNpmMonthly || fetchNpmMonthly;
111
+ const fetchWeekly = fetchers.fetchNpmWeekly || fetchNpmWeekly;
112
+ const fetchRepo = fetchers.fetchGitHub || fetchGitHub;
113
+ const fetchVersions = fetchers.fetchNpmVersions || fetchNpmVersions;
114
+ const fetchTelemetry = fetchers.fetchTelemetry || loadTelemetrySnapshot;
115
+ const fetchBillingSummary = fetchers.fetchBillingSummary || (async () => {
116
+ const result = await getOperationalBillingSummary();
117
+ return result.summary;
118
+ });
119
+
120
+ const [monthly, weekly, github, npmMeta, telemetry, billingSummary] = await Promise.all([
121
+ fetchMonthly(),
122
+ fetchWeekly(),
123
+ fetchRepo(),
124
+ fetchVersions().catch(() => null),
125
+ Promise.resolve().then(() => fetchTelemetry()).catch(() => null),
126
+ Promise.resolve().then(() => fetchBillingSummary()).catch(() => null),
127
+ ]);
128
+
129
+ return { monthly, weekly, github, npmMeta, telemetry, billingSummary };
130
+ }
131
+
132
+ /**
133
+ * Estimate organic downloads by filtering out publish-day inflation.
134
+ * npm registry mirrors, bot crawlers (socket.dev, snyk, bundlephobia),
135
+ * and npm-stat bots all re-download on publish events.
136
+ * Weekend days with no publishes give the organic baseline.
137
+ */
138
+ function estimateOrganicDownloads(dailyDownloads, publishDates) {
139
+ const publishSet = new Set(publishDates);
140
+
141
+ let weekendNoPublishTotal = 0;
142
+ let weekendNoPublishCount = 0;
143
+ let publishDayTotal = 0;
144
+ let publishDayCount = 0;
145
+ let noPublishDayTotal = 0;
146
+ let noPublishDayCount = 0;
147
+
148
+ for (const day of dailyDownloads) {
149
+ const dt = new Date(day.day + 'T00:00:00Z');
150
+ const isWeekend = dt.getUTCDay() === 0 || dt.getUTCDay() === 6;
151
+ const isPublishDay = publishSet.has(day.day);
152
+
153
+ if (isPublishDay) {
154
+ publishDayTotal += day.downloads;
155
+ publishDayCount++;
156
+ } else {
157
+ noPublishDayTotal += day.downloads;
158
+ noPublishDayCount++;
159
+ if (isWeekend) {
160
+ weekendNoPublishTotal += day.downloads;
161
+ weekendNoPublishCount++;
162
+ }
163
+ }
164
+ }
165
+
166
+ const organicDailyBaseline = weekendNoPublishCount > 0
167
+ ? Math.round(weekendNoPublishTotal / weekendNoPublishCount)
168
+ : (noPublishDayCount > 0 ? Math.round(noPublishDayTotal / noPublishDayCount) : 0);
169
+
170
+ const totalDownloads = dailyDownloads.reduce((s, d) => s + d.downloads, 0);
171
+ const estimatedOrganic30d = organicDailyBaseline * 30;
172
+ const estimatedInflated = totalDownloads - estimatedOrganic30d;
173
+ const organicRate = totalDownloads > 0 ? (estimatedOrganic30d / totalDownloads * 100) : 0;
174
+
175
+ return {
176
+ organicDailyBaseline,
177
+ estimatedOrganic30d,
178
+ estimatedInflated: Math.max(0, estimatedInflated),
179
+ organicRate: Math.min(100, organicRate),
180
+ organicWeekly: organicDailyBaseline * 7,
181
+ publishDayAvg: publishDayCount > 0 ? Math.round(publishDayTotal / publishDayCount) : 0,
182
+ noPublishDayAvg: noPublishDayCount > 0 ? Math.round(noPublishDayTotal / noPublishDayCount) : 0,
183
+ publishDayCount,
184
+ totalDownloads,
185
+ };
186
+ }
187
+
188
+ function formatCreatorRows(telemetry = null, billingSummary = null) {
189
+ return summarizeCreatorPerformance(telemetry, billingSummary).map((entry, index) => {
190
+ const revenueDollars = (entry.bookedRevenueCents / 100).toFixed(2);
191
+ return ` ${index + 1}. ${entry.creator} — rev $${revenueDollars}, paid ${entry.paidOrders}, sprint ${entry.qualifiedSprintLeads}/${entry.sprintLeads}, checkouts ${entry.checkoutStarts}, visitors ${entry.visitors}`;
192
+ });
193
+ }
194
+
195
+ function formatReport(monthly, weekly, github, npmMeta, telemetry = null, billingSummary = null) {
196
+ const weeklyDownloads = weekly.downloads || 0;
197
+ const allDays = monthly.downloads || [];
198
+ const monthlyDownloads = allDays.reduce((sum, d) => sum + d.downloads, 0);
199
+ const dailyValues = allDays.slice(-7).map((d) => d.downloads);
200
+ const trend = sparkline(dailyValues);
201
+
202
+ // Extract publish dates from npm registry metadata
203
+ const publishDates = [];
204
+ if (npmMeta && npmMeta.time) {
205
+ for (const [version, timestamp] of Object.entries(npmMeta.time)) {
206
+ if (version !== 'created' && version !== 'modified') {
207
+ publishDates.push(timestamp.slice(0, 10));
208
+ }
209
+ }
210
+ }
211
+
212
+ const organic = estimateOrganicDownloads(allDays, publishDates);
213
+ const productHuntVisitors = telemetry
214
+ ? resolveProductHuntCount(telemetry.visitors.byTrafficChannel, telemetry.visitors.bySource)
215
+ : 0;
216
+ const productHuntCtas = telemetry
217
+ ? resolveProductHuntCount(telemetry.ctas.byTrafficChannel, telemetry.ctas.bySource)
218
+ : 0;
219
+ const productHuntCheckouts = telemetry
220
+ ? resolveProductHuntCount(telemetry.ctas.checkoutStartsByTrafficChannel, telemetry.ctas.checkoutStartsBySource)
221
+ : 0;
222
+ const telemetryWindow = telemetry && telemetry.window ? telemetry.window : 'all';
223
+ const telemetryLastSeen = telemetry && telemetry.latestSeenAt ? telemetry.latestSeenAt : 'none';
224
+ const creatorRows = formatCreatorRows(telemetry, billingSummary);
225
+ const predictive = buildPredictiveInsights({
226
+ telemetryAnalytics: telemetry || {},
227
+ billingSummary: billingSummary || {},
228
+ stagingModel: buildPredictiveStagingModel(telemetry || {}, billingSummary || {}),
229
+ });
230
+
231
+ const lines = [
232
+ '',
233
+ '╔══════════════════════════════════════════════════════════════════╗',
234
+ '║ ThumbGate — Unified Analytics Snapshot ║',
235
+ '╚══════════════════════════════════════════════════════════════════╝',
236
+ '',
237
+ '📦 npm — thumbgate (REPORTED)',
238
+ ` Weekly downloads: ${weeklyDownloads.toLocaleString()}`,
239
+ ` Monthly downloads: ${monthlyDownloads.toLocaleString()}`,
240
+ ` Daily trend (7d): ${trend} [${dailyValues.join(', ')}]`,
241
+ '',
242
+ '🔬 npm — ORGANIC ESTIMATE (excluding publish-day + bot inflation)',
243
+ ` Organic baseline: ~${organic.organicDailyBaseline}/day → ~${organic.organicWeekly}/week → ~${organic.estimatedOrganic30d.toLocaleString()}/month`,
244
+ ` Publish-day avg: ${organic.publishDayAvg}/day (${organic.publishDayCount} publish days this period)`,
245
+ ` No-publish avg: ${organic.noPublishDayAvg}/day`,
246
+ ` Organic rate: ${organic.organicRate.toFixed(1)}% of reported downloads`,
247
+ ` Inflated by: ~${organic.estimatedInflated.toLocaleString()} downloads (registry mirrors, bots, self-installs)`,
248
+ '',
249
+ '⭐ GitHub — IgorGanapolsky/ThumbGate',
250
+ ` Stars: ${(github.stargazers_count || 0).toLocaleString()}`,
251
+ ` Forks: ${(github.forks_count || 0).toLocaleString()}`,
252
+ ` Open issues: ${(github.open_issues_count || 0).toLocaleString()}`,
253
+ ` Watchers: ${(github.subscribers_count || 0).toLocaleString()}`,
254
+ '',
255
+ '🌐 Landing Page',
256
+ ` Plausible: ${PLAUSIBLE_URL}`,
257
+ ` ⚠️ Check Plausible to exclude your own IP (Settings → filter)`,
258
+ '',
259
+ '🚀 ProductHunt',
260
+ ` Listing: ${PRODUCTHUNT_URL}`,
261
+ ` Tracked: utm_source=producthunt → traffic_channel=producthunt`,
262
+ ` Visitors: ${productHuntVisitors}`,
263
+ ` CTA clicks: ${productHuntCtas}`,
264
+ ` Checkouts: ${productHuntCheckouts}`,
265
+ ` Window: ${telemetryWindow}`,
266
+ ` Last seen: ${telemetryLastSeen}`,
267
+ '',
268
+ '🎥 Creator Partnerships',
269
+ ' Ranked: booked revenue → paid orders → qualified sprint leads → checkouts',
270
+ ...(creatorRows.length > 0 ? creatorRows : [' No attributed creator campaigns yet.']),
271
+ '',
272
+ '🔮 Predictive Insights',
273
+ ` Pro propensity: ${predictive.upgradePropensity.pro.band} (${predictive.upgradePropensity.pro.score})`,
274
+ ` Team propensity: ${predictive.upgradePropensity.team.band} (${predictive.upgradePropensity.team.score})`,
275
+ ` Revenue forecast: $${(predictive.revenueForecast.predictedBookedRevenueCents / 100).toFixed(2)} (+$${(predictive.revenueForecast.incrementalOpportunityCents / 100).toFixed(2)} opportunity)`,
276
+ ` Predictive alerts:${predictive.anomalySummary.count} (${predictive.anomalySummary.severity})`,
277
+ ...(predictive.topCreators[0]
278
+ ? [` Top creator opp: ${predictive.topCreators[0].key} → +$${(predictive.topCreators[0].opportunityRevenueCents / 100).toFixed(2)}`]
279
+ : [' Top creator opp: none yet']),
280
+ ...(predictive.topSources[0]
281
+ ? [` Top channel opp: ${predictive.topSources[0].key} → +$${(predictive.topSources[0].opportunityRevenueCents / 100).toFixed(2)}`]
282
+ : [' Top channel opp: none yet']),
283
+ '',
284
+ '🔗 UTM links for sharing (tracks referral source in Plausible)',
285
+ ` Twitter: ${LANDING_PAGE}?utm_source=twitter&utm_medium=social&utm_campaign=launch`,
286
+ ` LinkedIn: ${LANDING_PAGE}?utm_source=linkedin&utm_medium=social&utm_campaign=launch`,
287
+ ` Reddit: ${LANDING_PAGE}?utm_source=reddit&utm_medium=social&utm_campaign=launch`,
288
+ ` HackerNews: ${LANDING_PAGE}?utm_source=hackernews&utm_medium=social&utm_campaign=launch`,
289
+ '',
290
+ '📏 HONEST METRICS (use these, not the inflated ones)',
291
+ ` Real npm traction: ~${organic.organicWeekly} downloads/week`,
292
+ ` GitHub stars: ${(github.stargazers_count || 0)}`,
293
+ ` ProductHunt: check listing for upvotes/followers`,
294
+ ` Landing page: check Plausible (exclude your IP!)`,
295
+ '',
296
+ ];
297
+
298
+ return lines.join('\n');
299
+ }
300
+
301
+ async function run(options = {}) {
302
+ const log = options.log || console.log;
303
+ const error = options.error || console.error;
304
+ const exit = options.exit || process.exit;
305
+
306
+ try {
307
+ const { monthly, weekly, github, npmMeta, telemetry, billingSummary } = await collectAnalytics(options.fetchers);
308
+ log(formatReport(monthly, weekly, github, npmMeta, telemetry, billingSummary));
309
+ } catch (err) {
310
+ error('Analytics fetch failed:', err.message);
311
+ exit(1);
312
+ }
313
+ }
314
+
315
+ module.exports = {
316
+ run,
317
+ collectAnalytics,
318
+ formatReport,
319
+ fetchNpmMonthly,
320
+ fetchNpmWeekly,
321
+ fetchGitHub,
322
+ fetchNpmVersions,
323
+ estimateOrganicDownloads,
324
+ };
325
+
326
+ if (require.main === module) {
327
+ run();
328
+ }
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ function normalizeText(value) {
4
+ if (value === undefined || value === null) return null;
5
+ const text = String(value).trim();
6
+ return text || null;
7
+ }
8
+
9
+ function validateTimeZone(value) {
10
+ const resolved = normalizeText(value) ||
11
+ Intl.DateTimeFormat().resolvedOptions().timeZone ||
12
+ 'UTC';
13
+ try {
14
+ new Intl.DateTimeFormat('en-US', {
15
+ timeZone: resolved,
16
+ year: 'numeric',
17
+ month: '2-digit',
18
+ day: '2-digit',
19
+ }).format(new Date());
20
+ return resolved;
21
+ } catch {
22
+ const err = new Error(`Invalid timezone: ${resolved}`);
23
+ err.code = 'invalid_timezone';
24
+ throw err;
25
+ }
26
+ }
27
+
28
+ function parseNow(value) {
29
+ if (value === undefined || value === null || value === '') {
30
+ return new Date();
31
+ }
32
+
33
+ const parsed = value instanceof Date ? new Date(value.getTime()) : new Date(value);
34
+ if (Number.isNaN(parsed.getTime())) {
35
+ const err = new Error('Invalid now timestamp.');
36
+ err.code = 'invalid_now';
37
+ throw err;
38
+ }
39
+ return parsed;
40
+ }
41
+
42
+ function formatLocalDate(date, timeZone) {
43
+ const formatter = new Intl.DateTimeFormat('en-US', {
44
+ timeZone,
45
+ year: 'numeric',
46
+ month: '2-digit',
47
+ day: '2-digit',
48
+ });
49
+ const parts = formatter.formatToParts(date);
50
+ const year = parts.find((part) => part.type === 'year')?.value;
51
+ const month = parts.find((part) => part.type === 'month')?.value;
52
+ const day = parts.find((part) => part.type === 'day')?.value;
53
+ return `${year}-${month}-${day}`;
54
+ }
55
+
56
+ function shiftLocalDate(localDate, offsetDays) {
57
+ const [year, month, day] = String(localDate || '').split('-').map(Number);
58
+ const shifted = new Date(Date.UTC(year, month - 1, day + offsetDays));
59
+ return shifted.toISOString().slice(0, 10);
60
+ }
61
+
62
+ function normalizeWindow(value) {
63
+ const normalized = (normalizeText(value) || 'lifetime').toLowerCase();
64
+ switch (normalized) {
65
+ case 'day':
66
+ case 'daily':
67
+ return 'today';
68
+ case '7':
69
+ case '7days':
70
+ case '7-day':
71
+ return '7d';
72
+ case '30':
73
+ case '30days':
74
+ case '30-day':
75
+ return '30d';
76
+ default:
77
+ return normalized;
78
+ }
79
+ }
80
+
81
+ function serializeAnalyticsWindow(window) {
82
+ return {
83
+ window: window.window,
84
+ timeZone: window.timeZone,
85
+ bounded: window.bounded,
86
+ startLocalDate: window.startLocalDate,
87
+ endLocalDate: window.endLocalDate,
88
+ now: window.now,
89
+ };
90
+ }
91
+
92
+ function resolveAnalyticsWindow(options = {}) {
93
+ if (options && options.__kind === 'analytics_window') {
94
+ return options;
95
+ }
96
+
97
+ const window = normalizeWindow(options.window);
98
+ if (!['lifetime', 'today', '7d', '30d'].includes(window)) {
99
+ const err = new Error(`Invalid analytics window: ${window}`);
100
+ err.code = 'invalid_window';
101
+ throw err;
102
+ }
103
+
104
+ const timeZone = validateTimeZone(options.timeZone || options.timezone);
105
+ const nowDate = parseNow(options.now);
106
+ const endLocalDate = formatLocalDate(nowDate, timeZone);
107
+ let startLocalDate = null;
108
+
109
+ if (window === 'today') {
110
+ startLocalDate = endLocalDate;
111
+ } else if (window === '7d') {
112
+ startLocalDate = shiftLocalDate(endLocalDate, -6);
113
+ } else if (window === '30d') {
114
+ startLocalDate = shiftLocalDate(endLocalDate, -29);
115
+ }
116
+
117
+ return {
118
+ __kind: 'analytics_window',
119
+ window,
120
+ timeZone,
121
+ bounded: Boolean(startLocalDate),
122
+ startLocalDate,
123
+ endLocalDate,
124
+ now: nowDate.toISOString(),
125
+ };
126
+ }
127
+
128
+ function eventOccursInWindow(timestamp, options = {}) {
129
+ const window = resolveAnalyticsWindow(options);
130
+ if (!window.bounded) return true;
131
+
132
+ const normalized = normalizeText(timestamp);
133
+ if (!normalized) return false;
134
+
135
+ const parsed = new Date(normalized);
136
+ if (Number.isNaN(parsed.getTime())) {
137
+ return false;
138
+ }
139
+
140
+ const localDate = formatLocalDate(parsed, window.timeZone);
141
+ return localDate >= window.startLocalDate && localDate <= window.endLocalDate;
142
+ }
143
+
144
+ function filterEntriesForWindow(entries = [], options = {}, resolveTimestamp = (entry) => entry && entry.timestamp) {
145
+ const window = resolveAnalyticsWindow(options);
146
+ return entries.filter((entry) => {
147
+ if (!entry) return false;
148
+ return eventOccursInWindow(resolveTimestamp(entry), window);
149
+ });
150
+ }
151
+
152
+ module.exports = {
153
+ eventOccursInWindow,
154
+ filterEntriesForWindow,
155
+ formatLocalDate,
156
+ resolveAnalyticsWindow,
157
+ serializeAnalyticsWindow,
158
+ };