thumbgate 1.14.1 → 1.16.0

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 (150) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.well-known/llms.txt +5 -5
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +60 -35
  6. package/adapters/chatgpt/openapi.yaml +118 -2
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/mcp/server-stdio.js +217 -84
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bench/prompt-eval-suite.json +5 -1
  11. package/bin/cli.js +211 -8
  12. package/config/enforcement.json +59 -7
  13. package/config/evals/agent-safety-eval.json +338 -22
  14. package/config/gates/default.json +33 -0
  15. package/config/gates/routine.json +43 -0
  16. package/config/github-about.json +3 -3
  17. package/config/mcp-allowlists.json +4 -0
  18. package/config/merge-quality-checks.json +2 -1
  19. package/config/model-candidates.json +131 -0
  20. package/openapi/openapi.yaml +118 -2
  21. package/package.json +70 -51
  22. package/public/blog.html +7 -7
  23. package/public/codex-plugin.html +13 -7
  24. package/public/compare.html +29 -23
  25. package/public/dashboard.html +105 -12
  26. package/public/guide.html +28 -28
  27. package/public/index.html +233 -97
  28. package/public/learn.html +87 -20
  29. package/public/lessons.html +26 -2
  30. package/public/numbers.html +271 -0
  31. package/public/pro.html +89 -19
  32. package/scripts/agent-audit-trace.js +55 -0
  33. package/scripts/agent-memory-lifecycle.js +96 -0
  34. package/scripts/agent-readiness-plan.js +118 -0
  35. package/scripts/agentic-data-pipeline.js +21 -1
  36. package/scripts/agents-sdk-sandbox-plan.js +57 -0
  37. package/scripts/ai-org-governance.js +98 -0
  38. package/scripts/ai-search-distribution.js +43 -0
  39. package/scripts/artifact-agent-plan.js +81 -0
  40. package/scripts/billing.js +27 -8
  41. package/scripts/cli-feedback.js +2 -1
  42. package/scripts/cli-schema.js +60 -5
  43. package/scripts/code-mode-mcp-plan.js +71 -0
  44. package/scripts/commercial-offer.js +1 -1
  45. package/scripts/context-engine.js +1 -2
  46. package/scripts/context-manager.js +4 -1
  47. package/scripts/contextfs.js +214 -32
  48. package/scripts/dashboard-render-spec.js +1 -1
  49. package/scripts/dashboard.js +275 -9
  50. package/scripts/decision-journal.js +13 -3
  51. package/scripts/document-workflow-governance.js +62 -0
  52. package/scripts/enterprise-agent-rollout.js +34 -0
  53. package/scripts/experience-replay-governance.js +69 -0
  54. package/scripts/export-hf-dataset.js +1 -1
  55. package/scripts/feedback-loop.js +141 -9
  56. package/scripts/feedback-to-rules.js +17 -23
  57. package/scripts/gates-engine.js +4 -6
  58. package/scripts/growth-campaigns.js +49 -0
  59. package/scripts/harness-selector.js +145 -1
  60. package/scripts/hybrid-supervisor-agent.js +64 -0
  61. package/scripts/inference-cache-policy.js +72 -0
  62. package/scripts/inference-economics.js +53 -0
  63. package/scripts/internal-agent-bootstrap.js +12 -2
  64. package/scripts/knowledge-layer-plan.js +108 -0
  65. package/scripts/lesson-canonical.js +181 -0
  66. package/scripts/lesson-db.js +71 -10
  67. package/scripts/lesson-inference.js +183 -44
  68. package/scripts/lesson-search.js +4 -1
  69. package/scripts/lesson-synthesis.js +23 -2
  70. package/scripts/llm-client.js +157 -26
  71. package/scripts/mailer/resend-mailer.js +112 -1
  72. package/scripts/mcp-transport-strategy.js +66 -0
  73. package/scripts/memory-store-governance.js +60 -0
  74. package/scripts/meta-agent-loop.js +7 -13
  75. package/scripts/model-access-eligibility.js +38 -0
  76. package/scripts/model-migration-readiness.js +55 -0
  77. package/scripts/native-messaging-audit.js +514 -0
  78. package/scripts/operational-integrity.js +96 -3
  79. package/scripts/otel-declarative-config.js +56 -0
  80. package/scripts/perplexity-client.js +1 -1
  81. package/scripts/post-training-governance.js +34 -0
  82. package/scripts/pr-manager.js +47 -7
  83. package/scripts/private-core-boundary.js +72 -0
  84. package/scripts/production-agent-readiness.js +40 -0
  85. package/scripts/profile-router.js +16 -1
  86. package/scripts/prompt-eval.js +564 -32
  87. package/scripts/prompt-programs.js +93 -0
  88. package/scripts/provider-action-normalizer.js +585 -0
  89. package/scripts/rule-validator.js +285 -0
  90. package/scripts/scaling-law-claims.js +60 -0
  91. package/scripts/security-scanner.js +1 -1
  92. package/scripts/self-distill-agent.js +7 -32
  93. package/scripts/seo-gsd.js +400 -43
  94. package/scripts/skill-rag-router.js +53 -0
  95. package/scripts/spec-gate.js +1 -1
  96. package/scripts/student-consistent-training.js +73 -0
  97. package/scripts/synthetic-data-provenance.js +98 -0
  98. package/scripts/task-context-result.js +81 -0
  99. package/scripts/telemetry-analytics.js +149 -0
  100. package/scripts/thompson-sampling.js +2 -2
  101. package/scripts/token-savings.js +7 -6
  102. package/scripts/token-tco.js +46 -0
  103. package/scripts/tool-registry.js +75 -3
  104. package/scripts/verification-loop.js +10 -1
  105. package/scripts/verifier-scoring.js +71 -0
  106. package/scripts/workflow-sentinel.js +284 -28
  107. package/scripts/workspace-agent-routines.js +118 -0
  108. package/skills/thumbgate/SKILL.md +1 -1
  109. package/src/api/server.js +434 -120
  110. package/.claude-plugin/README.md +0 -170
  111. package/adapters/README.md +0 -12
  112. package/scripts/analytics-report.js +0 -328
  113. package/scripts/autonomous-workflow.js +0 -377
  114. package/scripts/billing-setup.js +0 -109
  115. package/scripts/creator-campaigns.js +0 -239
  116. package/scripts/cross-encoder-reranker.js +0 -235
  117. package/scripts/daemon-manager.js +0 -108
  118. package/scripts/decision-trace.js +0 -354
  119. package/scripts/delegation-runtime.js +0 -896
  120. package/scripts/dispatch-brief.js +0 -159
  121. package/scripts/distribution-surfaces.js +0 -110
  122. package/scripts/feedback-history-distiller.js +0 -382
  123. package/scripts/funnel-analytics.js +0 -35
  124. package/scripts/history-distiller.js +0 -200
  125. package/scripts/hosted-job-launcher.js +0 -256
  126. package/scripts/intent-router.js +0 -392
  127. package/scripts/lesson-reranker.js +0 -263
  128. package/scripts/lesson-retrieval.js +0 -148
  129. package/scripts/managed-lesson-agent.js +0 -183
  130. package/scripts/operational-dashboard.js +0 -103
  131. package/scripts/operational-summary.js +0 -129
  132. package/scripts/operator-artifacts.js +0 -608
  133. package/scripts/optimize-context.js +0 -17
  134. package/scripts/org-dashboard.js +0 -206
  135. package/scripts/partner-orchestration.js +0 -146
  136. package/scripts/predictive-insights.js +0 -356
  137. package/scripts/pulse.js +0 -80
  138. package/scripts/reflector-agent.js +0 -221
  139. package/scripts/sales-pipeline.js +0 -681
  140. package/scripts/session-episode-store.js +0 -329
  141. package/scripts/session-health-sensor.js +0 -242
  142. package/scripts/session-report.js +0 -120
  143. package/scripts/swarm-coordinator.js +0 -81
  144. package/scripts/tool-kpi-tracker.js +0 -12
  145. package/scripts/webhook-delivery.js +0 -62
  146. package/scripts/workflow-sprint-intake.js +0 -475
  147. package/skills/agent-memory/SKILL.md +0 -97
  148. package/skills/solve-architecture-autonomy/SKILL.md +0 -17
  149. package/skills/solve-architecture-autonomy/tool.js +0 -33
  150. package/skills/thumbgate-feedback/SKILL.md +0 -49
@@ -1,170 +0,0 @@
1
- # ThumbGate for Claude Desktop
2
-
3
- **Give thumbs up 👍 or thumbs down 👎 on any agent action. ThumbGate captures it, runs History-aware lesson distillation across up to 8 prior recorded entries, and blocks the pattern from repeating. Just type "thumbs up" or "thumbs down" in the chat.**
4
-
5
- ## Try it now
6
-
7
- 1. Install ThumbGate
8
- 2. Start a Claude Desktop session
9
- 3. When the agent does something wrong, type: **thumbs down**
10
- 4. ThumbGate captures the mistake, distills a lesson, and creates a prevention rule
11
- 5. Next session: the agent physically cannot repeat that mistake
12
-
13
- That's it. One thumbs-down, never again.
14
-
15
- ## What it does
16
-
17
- - **👎 Thumbs down** → captures the mistake → distills a lesson → auto-promotes to a prevention rule → PreToolUse hook blocks the pattern before execution
18
- - **👍 Thumbs up** → reinforces good patterns → agent starts preferring your approved flows without re-explaining them each session
19
- - **33 pre-action gates** → block destructive actions (force-push, mass delete, destructive SQL) before they execute
20
- - **Budget enforcement** → action count + time limits prevent runaway sessions
21
- - **Self-protection** → agent cannot disable its own governance
22
- - **Compliance tags** → NIST, SOC2, OWASP, CWE on gate rules for enterprise teams
23
- - **Shared team enforcement** → one engineer's thumbs-down protects the whole team
24
- - **60-second follow-up** → feedback can link to a prior mistake with `relatedFeedbackId` so delayed corrections still become useful gates
25
-
26
- ## Installation
27
-
28
- ### Local install today
29
-
30
- Use the portable npm launcher:
31
-
32
- ```bash
33
- claude mcp add thumbgate -- npx --yes --package thumbgate thumbgate serve
34
- ```
35
-
36
- Or use the project bootstrap:
37
-
38
- ```bash
39
- npx thumbgate init
40
- ```
41
-
42
- ### Direct bundle download
43
-
44
- Download the latest packaged Claude Desktop bundle from GitHub Releases:
45
-
46
- https://github.com/IgorGanapolsky/ThumbGate/releases/latest/download/thumbgate-claude-desktop.mcpb
47
-
48
- That bundle is built from the same `.claude-plugin` metadata in this repo and is meant for people who want a ready-to-install artifact instead of building locally.
49
-
50
- ### Review packet zip
51
-
52
- Anthropic's submission flow may ask for a GitHub link or a zip that preserves the plugin folder structure. The review-ready source zip lives on GitHub Releases:
53
-
54
- https://github.com/IgorGanapolsky/ThumbGate/releases/latest/download/thumbgate-claude-plugin-review.zip
55
-
56
- ### Anthropic directory path
57
-
58
- If Anthropic approves the listing, install from Claude Desktop via `Settings -> Extensions`.
59
-
60
- Directory inclusion is an external review process. Do not claim listing or approval before it is real.
61
-
62
- Submission forms:
63
-
64
- - https://claude.ai/settings/plugins/submit
65
- - https://platform.claude.com/plugins/submit
66
-
67
- ### Repo marketplace while review is pending
68
-
69
- Claude Code users do not need to wait for the official directory. Anthropic's plugin docs allow adding a repository marketplace directly when the repo contains `.claude-plugin/marketplace.json`.
70
-
71
- Inside Claude Code, run:
72
-
73
- ```text
74
- /plugin marketplace add IgorGanapolsky/ThumbGate
75
- /plugin install thumbgate@thumbgate-marketplace
76
- ```
77
-
78
- That uses the marketplace metadata already published in this repository while Anthropic reviews the official directory submission.
79
-
80
- ### MCPB bundle build
81
-
82
- Maintainers can build the local Claude Desktop bundle directly from this repo:
83
-
84
- ```bash
85
- npm run build:claude-mcpb
86
- ```
87
-
88
- That command stages a clean bundle, installs production dependencies, packs a `.mcpb`, and validates it with Anthropic's official MCPB CLI.
89
-
90
- ## Configuration
91
-
92
- The local OSS path needs no API key.
93
-
94
- Optional hosted path:
95
-
96
- ```json
97
- {
98
- "mcpServers": {
99
- "thumbgate": {
100
- "command": "npx",
101
- "args": ["--yes", "--package", "thumbgate", "thumbgate", "serve"],
102
- "env": {
103
- "THUMBGATE_BASE_URL": "https://thumbgate-production.up.railway.app",
104
- "THUMBGATE_API_KEY": "tg_YOUR_KEY_HERE"
105
- }
106
- }
107
- }
108
- }
109
- ```
110
-
111
- ## Examples
112
-
113
- ### Example 1: Block force-push
114
-
115
- ```
116
- You: "Push my changes to main"
117
- Claude: [tries git push --force]
118
- ThumbGate: ⛔ Blocked — "no-force-push" (confidence: 0.94)
119
- You: Never had to correct it again.
120
- ```
121
-
122
- ### Example 2: Thumbs-down on bad action
123
-
124
- ```
125
- You: "thumbs down"
126
- ThumbGate: 👎 Captured. History-aware lesson distillation from up to 8 prior recorded entries...
127
- Lesson: "Agent edited production config without approval"
128
- Follow-up window: 60-second follow-up can attach relatedFeedbackId
129
- Rule auto-promoted. Will block matching actions in future sessions.
130
- ```
131
-
132
- ### Example 3: Thumbs-up reinforces good patterns
133
-
134
- ```
135
- You: "thumbs up"
136
- ThumbGate: 👍 Recorded. Reinforcing: "Agent used feature branch + PR workflow"
137
- Agent will prefer this pattern in future sessions.
138
- ```
139
-
140
- ### Example 4: Budget enforcement
141
-
142
- ```
143
- [Agent hits 500 actions in strict mode]
144
- ThumbGate: ⛔ Budget exceeded: 501/500 actions used. Session budget exhausted.
145
- ```
146
-
147
- ## Privacy Policy
148
-
149
- For complete privacy information, see: https://thumbgate-production.up.railway.app/privacy
150
-
151
- ### Data Collection
152
-
153
- - Local installs store workflow memory, feedback entries, and proof artifacts in local project files.
154
- - Optional hosted mode sends feedback and memory data to the configured `THUMBGATE_BASE_URL`.
155
- - Optional CLI telemetry is best-effort and can be disabled with `THUMBGATE_NO_TELEMETRY=1`.
156
- - We do not sell customer data; retention and deletion details live in the public privacy policy.
157
-
158
- ## Support
159
-
160
- - GitHub Issues: https://github.com/IgorGanapolsky/ThumbGate/issues
161
- - Security Advisories: https://github.com/IgorGanapolsky/ThumbGate/security
162
- - Verification evidence: https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/VERIFICATION_EVIDENCE.md
163
- - Product Hunt: https://www.producthunt.com/products/thumbgate
164
-
165
- ## Notes For Submission
166
-
167
- - Local Claude metadata lives in `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`.
168
- - The MCPB bundle is built with `npm run build:claude-mcpb`.
169
- - The review packet zip is built with `npm run build:claude-review-zip`.
170
- - Anthropic directory requirements and the internal publish checklist live in `docs/CLAUDE_DESKTOP_EXTENSION.md`.
@@ -1,12 +0,0 @@
1
- # Adapter Bundles
2
-
3
- - `chatgpt/openapi.yaml`: import into GPT Actions.
4
- - `gemini/function-declarations.json`: Gemini function-calling definitions.
5
- - `mcp/server-stdio.js`: underlying local MCP stdio server implementation.
6
- - `claude/.mcp.json`: example Claude Code MCP config using `npx --yes --package thumbgate@1.14.1 thumbgate serve`.
7
- - `codex/config.toml`: example Codex MCP profile section using the same version-pinned portable launcher.
8
- - `amp/skills/thumbgate-feedback/SKILL.md`: Amp skill template.
9
- - `opencode/opencode.json`: portable OpenCode MCP profile using the same version-pinned portable launcher.
10
- - `perplexity/.mcp.json`: Claude Code config with ThumbGate + Perplexity MCP servers side-by-side.
11
- - `perplexity/config.toml`: Codex config with ThumbGate + Perplexity MCP servers.
12
- - `perplexity/opencode.json`: OpenCode config with ThumbGate + Perplexity MCP servers.
@@ -1,328 +0,0 @@
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
- }