Skip to main content
Glama
Aero25x

ASO-Copilot

by Aero25x
README.md
# ASO Copilot

A self-hosted, free alternative to tools like rankd.dev/AppTweak/ASOMobile for developers managing
their own apps. Single-user, no accounts, no billing — just your apps.

Also exposes an MCP server so an agent (Claude Code, Claude Desktop, etc.) can manage your ASO
directly: add apps, track keywords, pull health reports, spy on competitors, find keyword
opportunities, and write AI copy suggestions itself with no separate API key.



https://github.com/user-attachments/assets/970ce32e-a980-42ce-a0eb-529c425439af




## Features

- **Daily keyword rank tracking** — real ranks from live App Store / Google Play search results
- **Unlimited apps, keywords, competitors** — it's your database, no plan limits
- **Competitor spy** — track competitor apps and see their rank for every keyword you track
- **Health score analysis** — title/subtitle/screenshots/description/ratings/freshness breakdown
  with suggestions, same shape as the paid tools
- **Keyword research** — "find winning keywords" derives candidates from your + competitors'
  metadata and scores them by estimated demand/difficulty using live store search data
- **Global Reach** — scans your rank for a keyword across ~40 major App Store / Google Play
  storefronts, plotted on a world map
- **Review analysis** — rating distribution, positive/negative theme extraction, and a keyword-gap
  finder that mines your own reviews for untracked search terms
- **Product health overlay** — pairs your app's real daily active users (via a per-app PostHog
  project) against its ASO health score over the same window
- **AI Copy Suggestions** — rewrites title/subtitle/description for 8 locales (US/UK/Canada
  English, Spanish, German, French, Portuguese, Japanese), adapting keywords into real local
  search terms rather than translating literally. Two ways to generate it, no lock-in to either:
  - via [OpenRouter](https://openrouter.ai) (any backing model) through the web UI, or
  - **key-free**, by pointing any MCP-connected model (e.g. Claude Code) at the
    `prepare_copy_localization_brief` / `save_copy_suggestions` tools — the model composes the
    copy itself, no API spend beyond what you're already paying for the agent
- **MCP server** at `/api/mcp` for agent-driven ASO management

## Stack

Next.js (App Router) + Prisma + PostgreSQL. Store data comes from the public iTunes Search/Lookup
API (App Store) and `google-play-scraper` (Google Play). There is no free official API for search
volume or keyword rank position, so:

- **Rank position** is real: it's your app's actual index in a live search for that term.
- **Volume/difficulty** are heuristics (0-100) derived from result-count and competitor-authority
  proxies, not real search volume — there's no free source for that.
- iOS **subtitle** is scraped from the public App Store product page (not exposed by the iTunes
  API). If Apple changes their page markup this can silently start returning `null` — see
  `fetchSubtitle` in `src/lib/stores/appstore.ts`.
- Every store call (both platforms) retries transient failures (429s, 5xx, network errors) with
  backoff via `src/lib/withRetry.ts`, so one rate-limited request doesn't fail an entire daily
  tracking pass.

## Setup

```bash
npm install
docker compose up -d          # local Postgres on port 5433
npx prisma migrate dev        # create tables
npm run dev
```

Open http://localhost:3000, click **Add App**, search for your app, and add it.

Copy `.env.example` to `.env` and adjust `DATABASE_URL` if you're pointing at a different Postgres
instance (e.g. Supabase/Neon/RDS instead of the bundled docker-compose one).

## Daily tracking

Rank checks don't run automatically — trigger `POST /api/track` (protected by `CRON_SECRET` from
your `.env`) once a day:

```bash
curl -X POST http://localhost:3000/api/track -H "Authorization: Bearer $CRON_SECRET"
```

`scripts/daily-track.sh` wraps this for cron:

```cron
0 6 * * * APP_URL=http://localhost:3000 CRON_SECRET=... /path/to/scripts/daily-track.sh >> /var/log/aso-track.log 2>&1
```

Any external scheduler works the same way (GitHub Actions `schedule:` trigger, a hosting
provider's cron jobs, etc.) — it just needs to hit that one endpoint once a day.

### Or run the bundled cron container

`docker compose up -d cron` starts a small Alpine container (`docker/cron/`) that runs
`scripts/daily-track.sh` on a schedule via `crond`, hitting the app over `host.docker.internal`
since the app itself runs on the host, not in Docker. Configure it via `.env`:

```bash
CRON_SECRET="..."                        # required, shared with the app's own .env
APP_URL="http://host.docker.internal:3000"  # optional, this is the default
CRON_SCHEDULE="0 6 * * *"                # optional, standard 5-field cron syntax
```

## Connecting an agent (MCP)

The app exposes an MCP server at `/api/mcp` (Streamable HTTP transport) with tools for listing
apps, searching stores, adding apps/keywords/competitors, pulling health reports, finding winning
keywords, generating AI copy suggestions, and running a tracking pass on demand.

Point any MCP-compatible client at it, e.g. in Claude Code (`.mcp.json`, already checked in at the
repo root pointing at localhost):

```json
{
  "mcpServers": {
    "aso-copilot": {
      "url": "http://localhost:3000/api/mcp"
    }
  }
}
```

There's no auth on this endpoint by default — it has full read/write access to your ASO data. Set
`MCP_AUTH_TOKEN` in `.env` before exposing it beyond localhost, then have your client send it:

```json
{
  "mcpServers": {
    "aso-copilot": {
      "url": "http://localhost:3000/api/mcp",
      "headers": { "Authorization": "Bearer <MCP_AUTH_TOKEN>" }
    }
  }
}
```

## AI Copy Suggestions

Two independent ways to generate rewritten title/subtitle/description copy, in
`src/components/AICopySuggestions.tsx` on each app's Overview tab - pick either, both write to the
same `AiCopySuggestion` table so the panel shows whichever ran most recently:

- **OpenRouter** (works from the web UI): set `OPENROUTER_API_KEY` in `.env` (get one at
  [openrouter.ai/keys](https://openrouter.ai/keys)); optionally `OPENROUTER_MODEL` to pick a
  different model than the default. Click **Get AI suggestions**.
- **MCP, no key needed**: have an MCP-connected model call `prepare_copy_localization_brief` (returns
  current copy, character limits, and a localization instruction per locale - no network call, no
  key) and write the copy itself, then `save_copy_suggestions` to persist it. Useful if you're
  already paying for an agent and don't want a second LLM bill for this.

## Analytics (optional)

Two separate PostHog integrations - don't mix them up:

<img width="1558" height="521" alt="image" src="https://github.com/user-attachments/assets/04985484-c04c-4e2d-95c4-65c56c2f9e1c" />


- **This tool's own usage analytics** (are you clicking around this app, not any tracked app's
  users) — off by default. Set `NEXT_PUBLIC_POSTHOG_KEY` (and optionally
  `NEXT_PUBLIC_POSTHOG_HOST` if self-hosting PostHog) in `.env` to enable pageview tracking via
  `src/components/PostHogProvider.tsx` — no code changes needed, it's a no-op until the key is set.
- **Per-app product health** — each tracked app can link its *own* PostHog project (Settings tab,
  `src/components/PostHogSettings.tsx`) so its real daily active users show up next to its ASO
  health score on the Overview tab. Unrelated to the key above; configured per-app in the UI, not
  via env vars.

## Project layout

- `src/lib/stores/` — App Store (iTunes) and Google Play data fetching
- `src/lib/health.ts` — health score engine
- `src/lib/research.ts` — keyword candidate generation + volume/difficulty scoring
- `src/lib/ai.ts` / `src/lib/aiLocales.ts` — AI Copy Suggestions: OpenRouter prompt + call, the
  key-free localization-brief builder, and the 8-locale list
- `src/lib/reviewAnalysis.ts` — rating distribution + positive/negative theme extraction
- `src/lib/posthogIntegration.ts` — per-app PostHog client (daily active users query)
- `src/lib/storeLinks.ts` / `src/lib/metricColor.ts` — small shared UI helpers (outbound store
  links, volume/difficulty coloring) kept out of `research.ts` so Client Components importing them
  don't drag in server-only store-scraping dependencies
- `src/lib/appService.ts` — shared logic used by both the REST API and the MCP tools
- `src/app/api/` — REST routes
- `src/app/api/[transport]/route.ts` — MCP server
- `docker/cron/` — optional bundled cron container for daily tracking (see "Daily tracking" above)