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.
- package/.claude-plugin/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +3 -3
- package/.well-known/llms.txt +5 -5
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +60 -35
- package/adapters/chatgpt/openapi.yaml +118 -2
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +217 -84
- package/adapters/opencode/opencode.json +1 -1
- package/bench/prompt-eval-suite.json +5 -1
- package/bin/cli.js +211 -8
- package/config/enforcement.json +59 -7
- package/config/evals/agent-safety-eval.json +338 -22
- package/config/gates/default.json +33 -0
- package/config/gates/routine.json +43 -0
- package/config/github-about.json +3 -3
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +2 -1
- package/config/model-candidates.json +131 -0
- package/openapi/openapi.yaml +118 -2
- package/package.json +70 -51
- package/public/blog.html +7 -7
- package/public/codex-plugin.html +13 -7
- package/public/compare.html +29 -23
- package/public/dashboard.html +105 -12
- package/public/guide.html +28 -28
- package/public/index.html +233 -97
- package/public/learn.html +87 -20
- package/public/lessons.html +26 -2
- package/public/numbers.html +271 -0
- package/public/pro.html +89 -19
- package/scripts/agent-audit-trace.js +55 -0
- package/scripts/agent-memory-lifecycle.js +96 -0
- package/scripts/agent-readiness-plan.js +118 -0
- package/scripts/agentic-data-pipeline.js +21 -1
- package/scripts/agents-sdk-sandbox-plan.js +57 -0
- package/scripts/ai-org-governance.js +98 -0
- package/scripts/ai-search-distribution.js +43 -0
- package/scripts/artifact-agent-plan.js +81 -0
- package/scripts/billing.js +27 -8
- package/scripts/cli-feedback.js +2 -1
- package/scripts/cli-schema.js +60 -5
- package/scripts/code-mode-mcp-plan.js +71 -0
- package/scripts/commercial-offer.js +1 -1
- package/scripts/context-engine.js +1 -2
- package/scripts/context-manager.js +4 -1
- package/scripts/contextfs.js +214 -32
- package/scripts/dashboard-render-spec.js +1 -1
- package/scripts/dashboard.js +275 -9
- package/scripts/decision-journal.js +13 -3
- package/scripts/document-workflow-governance.js +62 -0
- package/scripts/enterprise-agent-rollout.js +34 -0
- package/scripts/experience-replay-governance.js +69 -0
- package/scripts/export-hf-dataset.js +1 -1
- package/scripts/feedback-loop.js +141 -9
- package/scripts/feedback-to-rules.js +17 -23
- package/scripts/gates-engine.js +4 -6
- package/scripts/growth-campaigns.js +49 -0
- package/scripts/harness-selector.js +145 -1
- package/scripts/hybrid-supervisor-agent.js +64 -0
- package/scripts/inference-cache-policy.js +72 -0
- package/scripts/inference-economics.js +53 -0
- package/scripts/internal-agent-bootstrap.js +12 -2
- package/scripts/knowledge-layer-plan.js +108 -0
- package/scripts/lesson-canonical.js +181 -0
- package/scripts/lesson-db.js +71 -10
- package/scripts/lesson-inference.js +183 -44
- package/scripts/lesson-search.js +4 -1
- package/scripts/lesson-synthesis.js +23 -2
- package/scripts/llm-client.js +157 -26
- package/scripts/mailer/resend-mailer.js +112 -1
- package/scripts/mcp-transport-strategy.js +66 -0
- package/scripts/memory-store-governance.js +60 -0
- package/scripts/meta-agent-loop.js +7 -13
- package/scripts/model-access-eligibility.js +38 -0
- package/scripts/model-migration-readiness.js +55 -0
- package/scripts/native-messaging-audit.js +514 -0
- package/scripts/operational-integrity.js +96 -3
- package/scripts/otel-declarative-config.js +56 -0
- package/scripts/perplexity-client.js +1 -1
- package/scripts/post-training-governance.js +34 -0
- package/scripts/pr-manager.js +47 -7
- package/scripts/private-core-boundary.js +72 -0
- package/scripts/production-agent-readiness.js +40 -0
- package/scripts/profile-router.js +16 -1
- package/scripts/prompt-eval.js +564 -32
- package/scripts/prompt-programs.js +93 -0
- package/scripts/provider-action-normalizer.js +585 -0
- package/scripts/rule-validator.js +285 -0
- package/scripts/scaling-law-claims.js +60 -0
- package/scripts/security-scanner.js +1 -1
- package/scripts/self-distill-agent.js +7 -32
- package/scripts/seo-gsd.js +400 -43
- package/scripts/skill-rag-router.js +53 -0
- package/scripts/spec-gate.js +1 -1
- package/scripts/student-consistent-training.js +73 -0
- package/scripts/synthetic-data-provenance.js +98 -0
- package/scripts/task-context-result.js +81 -0
- package/scripts/telemetry-analytics.js +149 -0
- package/scripts/thompson-sampling.js +2 -2
- package/scripts/token-savings.js +7 -6
- package/scripts/token-tco.js +46 -0
- package/scripts/tool-registry.js +75 -3
- package/scripts/verification-loop.js +10 -1
- package/scripts/verifier-scoring.js +71 -0
- package/scripts/workflow-sentinel.js +284 -28
- package/scripts/workspace-agent-routines.js +118 -0
- package/skills/thumbgate/SKILL.md +1 -1
- package/src/api/server.js +434 -120
- package/.claude-plugin/README.md +0 -170
- package/adapters/README.md +0 -12
- package/scripts/analytics-report.js +0 -328
- package/scripts/autonomous-workflow.js +0 -377
- package/scripts/billing-setup.js +0 -109
- package/scripts/creator-campaigns.js +0 -239
- package/scripts/cross-encoder-reranker.js +0 -235
- package/scripts/daemon-manager.js +0 -108
- package/scripts/decision-trace.js +0 -354
- package/scripts/delegation-runtime.js +0 -896
- package/scripts/dispatch-brief.js +0 -159
- package/scripts/distribution-surfaces.js +0 -110
- package/scripts/feedback-history-distiller.js +0 -382
- package/scripts/funnel-analytics.js +0 -35
- package/scripts/history-distiller.js +0 -200
- package/scripts/hosted-job-launcher.js +0 -256
- package/scripts/intent-router.js +0 -392
- package/scripts/lesson-reranker.js +0 -263
- package/scripts/lesson-retrieval.js +0 -148
- package/scripts/managed-lesson-agent.js +0 -183
- package/scripts/operational-dashboard.js +0 -103
- package/scripts/operational-summary.js +0 -129
- package/scripts/operator-artifacts.js +0 -608
- package/scripts/optimize-context.js +0 -17
- package/scripts/org-dashboard.js +0 -206
- package/scripts/partner-orchestration.js +0 -146
- package/scripts/predictive-insights.js +0 -356
- package/scripts/pulse.js +0 -80
- package/scripts/reflector-agent.js +0 -221
- package/scripts/sales-pipeline.js +0 -681
- package/scripts/session-episode-store.js +0 -329
- package/scripts/session-health-sensor.js +0 -242
- package/scripts/session-report.js +0 -120
- package/scripts/swarm-coordinator.js +0 -81
- package/scripts/tool-kpi-tracker.js +0 -12
- package/scripts/webhook-delivery.js +0 -62
- package/scripts/workflow-sprint-intake.js +0 -475
- package/skills/agent-memory/SKILL.md +0 -97
- package/skills/solve-architecture-autonomy/SKILL.md +0 -17
- package/skills/solve-architecture-autonomy/tool.js +0 -33
- package/skills/thumbgate-feedback/SKILL.md +0 -49
package/.claude-plugin/README.md
DELETED
|
@@ -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`.
|
package/adapters/README.md
DELETED
|
@@ -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
|
-
}
|