Skip to content

crawlproof stats: who arrived and from where, from the CLI - #239

Merged
ralyodio merged 1 commit into
masterfrom
feat/tracker-stats-api
Sep 6, 2026
Merged

crawlproof stats: who arrived and from where, from the CLI#239
ralyodio merged 1 commit into
masterfrom
feat/tracker-stats-api

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Why

The dashboard's stats route is session-authed (requireProjectAccess), so a CLI holding an API token could create ads and slots but could not read a single number about traffic. "Did that post do anything" is exactly the question a terminal is best placed to answer, and it was the one thing the CLI could not.

What

  • GET /api/tracker/v1/stats?site=&range=&who= serves the same panels the dashboard renders (series, sources, referrers, pages), behind the bearer auth /api/ads/v1/* already uses.
  • lib/tracker/apiStats.ts resolves the project by hostname, id or name, always scoped by owner_id, so a token cannot read a site it does not own by guessing an id. With one project the name can be left out.
  • crawlproof stats [site] [--range] [--who] [--json] prints sources, referrers and top pages. Defaults to the last day and humans only, because over a month with bots counted a launch is invisible inside the crawler traffic. An empty window says so and names the likeliest cause rather than printing zeroes.

Verified

  • 9 new tests: id/hostname/name resolution, www and path handling, the owner_id filter, the several-projects and no-projects cases, and the series totals including string-number and wrong-shape inputs.
  • Typecheck clean.
  • Not yet exercised against production, since the endpoint ships with this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_013GX6xEcjGFxcvcMCFLhQng

The dashboard's stats route is session-authed, so a CLI holding an API
token could read ads and slots but nothing about traffic — and "did that
post do anything" is the question a terminal is best placed to answer.

GET /api/tracker/v1/stats?site&range&who serves the same panels the
dashboard renders, with the bearer auth the ads API already uses. The
project is resolved by hostname, id or name and always scoped by owner_id,
so a token cannot read a site it does not own by guessing an id.

`crawlproof stats [site]` prints sources, referrers and top pages.
Defaults are the last day and humans only, because over a month with bots
counted a launch is invisible inside the crawler traffic.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GX6xEcjGFxcvcMCFLhQng
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

39 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 28 | LOW: 9

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit ae54ea9 into master Sep 6, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant