Skip to content

About

Local dashboard for SEO, ASO and app/site monetization — Google Search Console (+ index audit), Umami, Play Console, ASO keyword research (via a local RespectASO instance), App Store Connect (ASO copy + Product Page Optimization tests), RevenueCat, AdMob (including ad mediation by network) and AdSense — with a Claude Code MCP server. SQLite-backed

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

 

History

173 Commits

Folders and files

Repository files navigation

OpenFindability

Self-hosted growth intelligence for indie developers with apps and websites: unifies Google Search Console, Umami, Play Console, App Store Connect, RevenueCat, AdMob and AdSense — plus ASO keyword research via a local RespectASO instance — into one local SQLite-backed dashboard, with read/write store metadata (App Store text + PPO, Play Store listing) and a Claude Code MCP server.

Unlike single-purpose ASO or app-analytics tools, OpenFindability correlates web search demand (GSC), app-store visibility (ASO/Play/App Store) and actual revenue (RevenueCat/AdMob/AdSense) for the same product, and can act on it — not just chart it.

No SaaS, no monorepo, no server process beyond next dev: a local Next.js app backed by a single SQLite file.

What it does

  • Local dashboard — sidebar with every project, a homepage overview and a per-project page (SEO, analytics, Play Store reviews, ASO keywords, ad/subscription revenue, opportunities, sync log)
  • Google Analytics 4 — separate GA4 Data API daily active users, sessions, page views and key events, never conflated with Umami or GSC — see docs/guide/ga4-setup.md
  • Google Sheets + ChatGPT Mobile bridge — daily GSC collector through bound Apps Script and dry-run-first export of non-GSC KPI snapshots; see docs/guide/google-sheets-live-setup.md
  • SEO opportunities — automated detection of low-CTR queries, striking-distance keywords, cannibalization, declining/growing pages, and more
  • GSC index audit — inspects sitemap/Search Analytics URLs across every connected Search Console property, groups indexing problems and keeps history
  • ASO research — keyword popularity/difficulty/opportunity scoring via a local RespectASO instance, with a reusable cross-project keyword cache
  • App Store Connect — read/write live App Store product text (name, subtitle, keywords, description, promotional text) with a full pull/push history, plus Product Page Optimization (icon/screenshot/app preview A/B test) management — see docs/guide/appstoreconnect-workflow.md
  • App & site monetization — RevenueCat (MRR, subscribers, trials), AdMob (in-app ad revenue, impressions, clicks, per-ad-network mediation breakdown, Android + iOS summed per app) and AdSense (site display-ad revenue)
  • Play Console listing writes — pull/push live Play Store title/short/full description (dry-run by default, --apply to publish)
  • Android releases — build a Flutter app and publish its bundle to a Google Play track (internal/alpha/beta/production) from one place, reusing the same service account; preflight-only by default, --apply to publish, signature verified before upload
  • Account-wide app growth stats — discover:apps auto-lists every app visible to your Play Console/App Store Connect credentials and creates matching projects; syncs install/uninstall stats, crash/ANR rate (Play vitals) and App Store downloads/retention (ASC Analytics)
  • MCP server — call live GSC/Umami data and manage projects from any Claude Code session as native tools
  • SQLite storage — data/openfindability.db via Drizzle ORM, a single local file with full history (no server, no manual setup)
  • Demo data — seed and explore without any real credentials

Quick start

pnpm install
pnpm seed:demo
pnpm dev

Open http://localhost:3000.

Guided setup

pnpm install
cp .env.example .env
pnpm run doctor
pnpm dev

Then configure only the connectors you need — every one is optional and independent.

1. Add credentials

Google Search Console / Play Console use a Google service account JSON file. Put it under secrets/google/ and point .env to it:

GOOGLE_SERVICE_ACCOUNT_FILE=./secrets/google/search-console-service-account.json

Umami only needs the API base URL and key:

UMAMI_BASE_URL=https://api.umami.is/v1
UMAMI_API_KEY=...

RespectASO is optional and local:

RESPECT_ASO_BASE_URL=http://localhost

RevenueCat uses a V2 secret key:

REVENUECAT_API_KEY=sk_xxx

AdMob needs interactive OAuth2 (no service account support) — see docs/guide/admob-setup.md for the full walkthrough:

pnpm run admob:auth   # one-time, mints ADMOB_REFRESH_TOKEN (needs a real browser)
pnpm run admob:apps   # lists every AdMob app id on the account

AdSense (site display-ad revenue, distinct from AdMob's in-app ads) also needs interactive OAuth2 — see docs/guide/adsense-setup.md:

pnpm run adsense:auth       # one-time, mints ADSENSE_REFRESH_TOKEN (needs a real browser)
pnpm run adsense:accounts   # lists AdSense account ids

App Store Connect needs an API key (Users and Access → Integrations → App Store Connect API). Put the .p8 file under secrets/appstoreconnect/ and point .env to it:

ASC_ISSUER_ID=...
ASC_KEY_ID=...
ASC_PRIVATE_KEY_PATH=./secrets/appstoreconnect/AuthKey_<KEY_ID>.p8
pnpm run asc:apps   # lists every app visible to the key, to confirm the appStoreTrackId mapping

2. Add a project

pnpm run project:add -- \
  --name "Example Site" \
  --slug example-site \
  --type web \
  --url https://example.com/ \
  --gsc sc-domain:example.com

For an app with ASO research and monetization:

pnpm run project:add -- \
  --name "Example App" \
  --slug example-app \
  --type app \
  --app-store-track-id 1234567890 \
  --respect-aso-app-id 3 \
  --aso-countries it \
  --aso-keywords "main keyword,secondary keyword" \
  --revenuecat-project-id proj_xxx \
  --admob-app-id ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy \
  --admob-app-id-ios ca-app-pub-xxxxxxxxxxxxxxxx~zzzzzzzzzz

For a website with AdSense ads instead of/alongside an app:

pnpm run project:add -- \
  --name "Example Blog" \
  --slug example-blog \
  --type web \
  --url https://example.com/ \
  --adsense-domain example.com

3. Sync or research

pnpm run sync           # sync GSC/Umami/Play Console/RevenueCat/AdMob/AdSense for every project
pnpm run audit:index    # inspect up to 2,000 URLs for every connected GSC property
pnpm run sync:aso       # import configured RespectASO keywords (opt-in, rate-limited)
pnpm run report -- example-app all

For store listing research that combines RespectASO with GSC query language:

pnpm run research:aso -- \
  --slug example-app \
  --gsc-slug example-site \
  --url-contains example.com/app \
  --query-contains "main keyword" \
  --keywords "main keyword,secondary keyword,online,offline"

Research reports are written to project/<slug>/reports/, which is ignored by git. ASO keyword observations are also cached in the database, so later research can reuse recent keyword + country results across projects. Use --refresh to force a new RespectASO lookup.

The index audit is manual and progressive. To make a smaller first pass or target one property/project:

pnpm run audit:index -- 200
pnpm run audit:index -- 2000 example-site

It writes a global report to private-notes/index-audits/ and per-project reports to project/<slug>/reports/. See docs/guide/gsc-index-audit.md for coverage and API limitations.

MCP server — use from any Claude Code project

OpenFindability exposes an MCP server so Claude Code can query your analytics live from any repo.

Setup (once)

Run pnpm install once in OpenFindability, then configure the portable Node launcher in your MCP client. This launcher selects the OpenFindability working directory and TypeScript config, including when called from a different repository.

Windows example (~/.claude/settings.json):

{
  "mcpServers": {
    "openfindability": {
      "command": "node",
      "args": ["C:\\path\\to\\OpenFindability\\scripts\\mcp-launch.mjs"]
    }
  }
}

macOS/Linux: "command": "node" and "args": ["/path/to/OpenFindability/scripts/mcp-launch.mjs"].

Restart the MCP client. This is a local stdio integration, not an always-on ChatGPT Mobile connection.

Available tools

Tool Description
list_projects List configured projects and their slugs
get_project_summary Live GSC + Umami + opportunities in one call
get_growth_snapshot Cached, read-only multi-provider KPI snapshot: SEO, web, revenue, subscriptions, Android/iOS, data freshness
get_gsc_stats Live GSC data: clicks, impressions, top queries, top pages
get_umami_stats Live Umami visitors and pageviews for a date
get_page_keywords All GSC queries ranking for a specific page URL
compare_periods Compare current vs previous period with deltas
get_opportunities SEO opportunities from last sync
list_gsc_properties GSC properties accessible to the service account
create_project Add a new project (also creates its folder structure)
update_project Update project fields
delete_project Remove a project and all its data

Live tools (get_gsc_stats, get_umami_stats, get_project_summary, get_page_keywords, compare_periods) call the APIs in real time — no sync needed.

See docs/guide/mcp-server.md for full setup and usage.

Releasing Android apps

Build a Flutter app and publish its App Bundle to Google Play without Fastlane or per-project store credentials. Run from this repository and point at the app with --path:

npx tsx scripts/release.ts status  --path <flutter project dir>                            # live tracks
npx tsx scripts/release.ts android --path <flutter project dir> --track internal           # preflight only
npx tsx scripts/release.ts android --path <flutter project dir> --track internal --apply   # build + upload

The app is matched to a project by its Gradle applicationId. The preflight checks the package, the signing setup and that the version code is higher than anything already on Play; --build also builds and verifies the bundle signature; only --apply uploads. Signing uses secrets/android/<slug>/key.properties when present, otherwise the Flutter project's own Gradle config. Android only.

See docs/guide/release-workflow.md for setup, options, signing and troubleshooting — it also has a section written for an AI agent asked to publish from the app's own repository.

Useful commands

pnpm run doctor           # check connector config, flag stale syncs
pnpm run growth:summary -- --slug <project-slug>          # offline multi-provider KPI snapshot
pnpm run db:backup        # create + integrity-check a WAL-safe SQLite backup (ignored by git)
pnpm run test:gsc         # daily GSC history/pagination regression tests
pnpm seed:demo            # seed demo data
pnpm dev                  # start the dashboard
pnpm run sync             # sync gsc/umami/play_console/revenuecat/admob/adsense for every project
pnpm run sync:aso         # sync ASO keywords (opt-in)
pnpm run sync:admob:backfill    # one-time full AdMob history backfill (~4 years)
pnpm run sync:adsense:backfill  # one-time full AdSense history backfill (~4 years)
pnpm run admob:auth       # one-time AdMob OAuth2 setup
pnpm run admob:apps       # list AdMob app ids on the account
pnpm run adsense:auth     # one-time AdSense OAuth2 setup
pnpm run adsense:accounts # list AdSense account ids
pnpm run asc:apps         # list App Store Connect apps visible to the key
pnpm run aso:pull-copy -- --slug <project-slug>                  # read live App Store text
pnpm run aso:push-copy -- --slug <project-slug> --locale it [...] [--apply]  # write it (dry-run by default)
pnpm run asc:experiments -- --slug <project-slug>  # sync/create Product Page Optimization tests
pnpm run release:status -- --slug <project-slug>    # live Google Play tracks
pnpm run release:android -- --slug <project-slug> [--track internal] [--apply]  # build + publish (preflight by default)
pnpm run research:aso -- --slug <project-slug>
pnpm run report -- <slug> <gsc|aso|asc|monetization|all>
pnpm run sync:ga4          # Google Analytics 4 (configured projects only)
pnpm run sheet:export -- --days 90                   # dry-run ChatGPT Sheet projection
pnpm run sheet:export -- --days 90 --apply           # update private Sheet (requires GOOGLE_SHEET_ID)
pnpm run test:integrations                            # GA4/Sheets offline tests
pnpm run db:studio        # open Drizzle Studio (local GUI SQL browser)
pnpm run db:generate      # regenerate migrations after editing lib/db/schema.ts
pnpm typecheck
pnpm build

Sync behavior

v0.1 uses manual sync only.

  • GSC imports finalized daily totals and date-qualified page/query rows (last ~30 days by default, up to D-2), plus device/country/searchAppearance breakdowns and sitemaps. Detail is fetched in 7-day windows, paginated up to the Search Analytics row cap; large windows are retried by day. Query data is always potentially incomplete (Google returns top rows only).
  • Umami, AdMob, AdSense and RevenueCat import yesterday's data (with a configurable backfill window for AdMob/AdSense).
  • ASO stays opt-in (pnpm run sync:aso) — rate-limited and cached upstream, so it never runs as part of the default pnpm run sync.
  • Every import keeps rawJson. Connector runs are logged to connectorRuns.
  • Re-running a sync the same day replaces that day's row per project/source instead of duplicating it — different days accumulate, so history is preserved.
  • Page-level GSC data is stored separately from query-level data. Legacy rows from the previous implementation encoded a whole 30-day range as one date: they remain in SQLite but are excluded from daily charts, SEO scoring and reports unless overwritten by a new actual-daily sync. Run pnpm run db:backup before the first new sync. The GSC report and project page rank data from the latest 28 days and weight CTR/position correctly.

Storage

All data lives in data/openfindability.db, a local SQLite database accessed through Drizzle ORM (lib/db/schema.ts). No server process, no extra infra — still a single local file, just relational and queryable (pnpm run db:studio) instead of a hand-rolled JSON blob. pnpm run db:backup makes a consistent SQLite snapshot using the online backup API (including WAL state) under ignored data/backups/. This is manual, not off-site backup or a cloud-sync solution.

Security and public repository checklist

OpenFindability is public, but tokens, store credentials, customer reports and SQLite data must remain private. Scan tracked files and past history locally (no GitHub Actions required):

pnpm run audit:secrets
pnpm run audit:secrets:history
# independent scanner, if installed:
gitleaks git --redact --log-opts="--all"

A clean signature scan is not proof that history is secret-free. Rotate any leaked credential immediately. See SECURITY.md.

For read-only version/rollout checks on an app without deploying it:

pnpm run store:status -- --slug <project-slug>

discover:apps is dry-run by default; review cross-store app matches and rerun with --apply to save safe matches. Fuzzy matches are only suggestions.

Private files

These are intentionally ignored by git:

.env
data/                ← openfindability.db (+ WAL/journal files)
project/             ← per-project reports, context, notes
private-notes/
secrets/

See docs/operations/sensitive-files.md for details.

License

MIT

About

Local dashboard for SEO, ASO and app/site monetization — Google Search Console (+ index audit), Umami, Play Console, ASO keyword research (via a local RespectASO instance), App Store Connect (ASO copy + Product Page Optimization tests), RevenueCat, AdMob (including ad mediation by network) and AdSense — with a Claude Code MCP server. SQLite-backed

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages