---
name: super-aso
description: Full-funnel App Store Optimization for an iOS app — keyword research (popularity/difficulty driven), a keyword-led name/subtitle/keyword field, App Store metadata localized into up to 50 locales, localized screenshot headings burned over your mockups, worldwide territory pricing, in-app string localization, custom product pages + App Store A/B experiments (PPO), an AI-discoverability audit for how legible the listing is to semantic search and assistants like ChatGPT/Gemini, and continuous rank/review monitoring after launch. Use when the user wants to "do the ASO", "find keywords", "localize my app / my App Store page / my screenshots", "pick a name and subtitle", "set worldwide prices", "set up a custom product page", "A/B test my screenshots / icon", "is my app discoverable through AI search", "check my rankings/reviews", or is preparing an App Store submission.
user-invocable: true
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash
  - AskUserQuestion
---

# Super ASO — the whole App Store funnel, from Claude Code

You built an app. This skill takes it the rest of the way: what people search,
what your listing says in every language, what your screenshots say, what the
app costs in every country, and what the app itself says once installed.

It is organized as ten phases. Phases 0–6 are the core pipeline — run them
in order for a new app, or jump to the one the user asked for. Phases 7–10 are
optional add-ons for an app that already has a live listing: run them
whenever the user asks, not just on a first pass. Each phase has a reference
file with the full rules — **read the reference file before starting a
phase**; this page is the map, not the territory.

| Phase | Does | Reference |
|---|---|---|
| 0 SETUP | one-time wizard: ASC API key, translation engine, data source | below |
| 1 RESEARCH | keyword research → main keyword, subtitle keyword, keyword field | `reference/keyword-research.md` |
| 2 METADATA | name/subtitle/keywords/description, en-US first, then all chosen locales | `reference/metadata.md` |
| 3 SCREENSHOTS | localized headings burned over the user's mockups, all locales; optionally locate + spec-check + upload the user's own App Preview video(s) — never generated or edited | `reference/screenshots.md` |
| 4 PRICING | pricing model chosen by the user, applied per territory | `reference/pricing.md` |
| 5 IN-APP | localize the app's own UI strings | `reference/app-localization.md` |
| 6 SUBMIT | upload, verify, and the manual-steps list Apple's API can't do | `reference/submission-checklist.md` |
| 7 CUSTOM PAGES | custom product pages (organic-search keyword targeting) + App Store A/B experiments (PPO) — needs a live app | `reference/custom-product-pages.md` |
| 8 AI AUDIT | audit + improve how legible the listing is to semantic search and AI assistants (ChatGPT, Gemini, Apple Intelligence) | `reference/ai-discoverability.md` |
| 9 MONITOR | repeatable rank/review/rating check-in — snapshot, diff against the last run, recommend when a refresh is warranted | `reference/monitoring.md` |
| 10 IN-APP EVENTS | live/limited-time event cards (tournaments, premieres, new seasons, content drops) surfaced in search results, Today/Games tabs, and the product page — needs a live app; submitted to App Review independently of app versions | `reference/in-app-events.md` |

A worked example runs through every reference file: a calorie-tracking app
whose brand name is **Glow Up**. Every rule is illustrated on it; none of the
rules are specific to it.

## Phase 0 — Setup wizard (first run, or whenever a check fails)

**The `<app>` slug.** Every phase from here on persists state under
`~/.super-aso/<app>/...`. `<app>` is **the app's numeric App Store Connect ID**
(the `id` in `GET /v1/apps`, the same digits in the App Store URL's
`id1234567890` segment) — never the app's display name. A name-derived slug
drifts ("Glow Up" one session, "glow-up" the next) and silently breaks every
downstream phase's ability to find its own prior output; the numeric ID is
stable and, once the ASC key works, always one `GET /v1/apps` away. Resolve it
once per app, at the start of Phase 0, and reuse it for the rest of the
conversation — don't re-derive it per phase.

**Recall prior progress before doing anything else.** Once `<app>` is known
and the ASC key round-trip passes, run `scripts/check_setup.sh <app>` (the
app-id argument makes it also report per-app state, not just environment
readiness — see the script's own per-app section). If it reports prior phase
output, summarize what's already done and ask the user how to proceed instead
of silently starting from Phase 1 — mirror the "here's where we left off"
status summary the sibling `aso-appstore-screenshots` skill already opens
with, e.g.:

```
Here's where we left off for this app:

✅ Phase 1 (keyword research) — completed 2026-08-12
✅ Phase 2 (metadata) — en-US + 12 locales written, last edit 2026-08-15
⏳ Phase 4 (pricing) — no plan found yet
⏳ Phase 9 (monitoring) — no baseline snapshot yet

Continue with pricing, or revisit something first?
```

If `check_setup.sh <app>` reports nothing under `~/.super-aso/<app>/`, this is
a first run for this app — say so plainly and proceed to whichever phase the
user asked for (or Phase 1 by default for a brand-new app).

Then run `scripts/check_setup.sh` (no argument needed again if already run
above) to confirm the environment itself. If everything passes, skip to the
phase the user wants. Otherwise walk ONLY the missing pieces, in this order:

**1. App Store Connect API key** (needed by phases 2, 3, 4, 6, 7, 8).
Ask the user to create one — App Store Connect → Users and Access →
Integrations → App Store Connect API → Team Keys → **Generate API Key**, role
**App Manager**, then download the `.p8` file (downloadable **once**). Then:

- Store the `.p8` at `~/.super-aso/AuthKey.p8`, `chmod 600`.
- Write `~/.super-aso/config.json` (create the dir `chmod 700`, file `chmod 600`):

```json
{
  "asc": {
    "key_id": "<10-char Key ID shown next to the key>",
    "issuer_id": "<UUID at the top of the Team Keys page>",
    "p8_path": "~/.super-aso/AuthKey.p8"
  },
  "translation": { "engine": "subagents" }
}
```

- Verify with `ruby scripts/asc.rb GET '/v1/apps?limit=1'` → must print `HTTP 200`.

**Security is non-negotiable:** the key lives in `~/.super-aso/`, never inside
any project directory, never in a repo, never in a commit, never echoed to the
terminal, never pasted into a file the user might share. If the user pastes the
key contents into chat, save it and tell them to revoke-and-regenerate if this
conversation ever leaves their machine.

**2 & 3. Translation engine + keyword data source — ask together, one
`AskUserQuestion` call, two questions.** Both are simple closed-set choices
gathered up front, with no dependency on each other — there's no reason to
make the user sit through two separate round trips for two independent
intake questions at the very start of their first conversation with this
skill. Combine the two questions below into a single call.

**2. Translation engine** (phases 2, 3, 5):

- **Claude subagents** (default; recommended) — translations are generated by
  spawned subagents, batched per locale. No extra account, no extra key. Cost
  is the user's existing Claude usage.
- **DeepSeek API** — cheapest for very large volumes (a whole in-app catalog ×
  40 locales runs on cents). Key goes to `~/.super-aso/deepseek-key`
  (`chmod 600`), config: `{"engine": "deepseek", "api_key_path":
  "~/.super-aso/deepseek-key", "base_url": "https://api.deepseek.com", "model":
  "deepseek-chat"}`.
- **Any OpenAI-compatible API** — same shape: `{"engine": "openai",
  "api_key_path": "...", "base_url": "...", "model": "..."}`. The user brings
  whatever provider they like.

For API engines, verify the key works with one tiny request before any long
run. For DeepSeek specifically, **check the account balance, not just the
key** — a valid key on an empty account fails every request with `HTTP 402`,
and it will do so 30 minutes into a cascade rather than up front
(`check_setup.sh` does this).

**3. Keyword data source** (phase 1). The research method needs, per keyword:
**popularity** (search volume proxy), **difficulty**, and ideally "how many
apps use this in their name/subtitle". Ask what the user has:

- **Astro** (Mac ASO app) with its MCP server connected — best supported; the
  reference file maps each step to Astro's tools.
- **Another ASO tool** (AppFigures, Sensor Tower, Mobile Action, AppTweak…) —
  the method is identical; the user runs the lookups in their tool and pastes
  numbers when asked.
- **No tool** — phase 1 still works but degrades honestly: Apple's own search
  suggestions + competitor listings give the keyword *candidates*, and the
  skill says plainly that popularity/difficulty calls are guesses. Recommend
  getting a data tool before betting the app name on a keyword.

Store the answer in config as `"keyword_source": "astro" | "manual" | "none"`.

**Optional extra data sources** (no setup step here — each phase that uses
one checks for it and degrades honestly if it's missing): the free iTunes
Search API (no key), the App Store Connect Analytics Reports API (same key,
but check the role — it may need Admin/Sales and Reports, not just App
Manager), and, only if the user already has them, Apple Search Ads Search
Popularity or Google Trends. Full mechanics and reliability notes for all
four in `reference/data-sources.md`.

## Cross-phase laws

These hold in every phase; the reference files repeat them where they bite.

1. **Fan out to all locales in one pass, review by automated checks.** Never
   pause a localization run for a per-language human review unless the user
   explicitly asks for one. A human cannot review 40+ languages, and reviewing
   one language proves nothing about the other 39. The review that works:
   char-limit checks, format-specifier parity, verbatim-atom checks,
   same-as-source detection, plus spot-checks of the hard scripts (CJK, RTL,
   Indic) — all defined in the reference files.
2. **Ask which locales, once, in phase 2.** Default: all supported locales.
   The user may cut the set (e.g. top-15 markets); whatever is chosen there is
   the set for screenshots and in-app strings too. Do not re-ask per phase.
3. **App Store Connect is the source of truth** for anything Apple owns —
   live metadata, prices, review state, IAP existence. Pull from it before
   editing; never trust a possibly stale local copy over the live listing.
4. **Sweep once, slice locally.** The ASC API is slow and rate-limited.
   Anything that walks many locales or many products gets dumped to a local
   file once; every follow-up question is answered from the file, never by
   re-sweeping because the analysis changed.
5. **A 2xx is not verification.** After any write, GET the field back and
   check the value. Report only what a read-back confirmed.
6. **Character limits are hard**: name 30, subtitle 30, keyword field 100,
   promotional text 170, description 4000. Validate after writing, per locale.
7. **Some strings never localize**: Apple product names (iPhone, Apple Watch,
   iPad), URLs, email addresses, the brand name as the user styles it, product
   codes. A translated support email is a dead mailto; a "translated" URL is a
   404.
8. **Chunk long-running work.** Translation cascades and bulk uploads run as
   many small foreground commands (per locale, per batch), never one giant
   call that outlives the shell's patience. Report only what is verified on
   disk or read back from the API — "the cascade is running" is not a result.
9. **Preview before a bulk write, always.** Anything that writes many
   locales, many territories, or many pages in one pass shows the full plan
   first — a diff table (field → current value → new value), one row per
   item — and gets the user's explicit go-ahead before the first real write.
   `scripts/asc.rb` supports `--dry-run` on every call: it prints the exact
   method/path/body that would be sent and sends nothing, so the preview step
   can literally show the request, not just a description of it. Phase 2
   (`reference/metadata.md`) and phase 4 (`reference/pricing.md`) spell out
   the diff-table format for their own bulk writes; reuse it rather than
   inventing a new preview shape per phase.
10. **Sweeps don't fail silently.** `scripts/asc.rb` retries transient
    failures (429/500/502/503/504, and network-level timeouts/resets) with
    capped exponential backoff before giving up — a rate limit or a blip
    mid-sweep is not a reason to abandon 30 already-done locales. Use
    `--paginate` on any GET against a list endpoint that could plausibly
    exceed one page (reviews, many custom product pages, many app
    localizations) so a sweep never silently reads only page one.
11. **Log every storefront write, in one place.** Any confirmed write to App
    Store Connect — metadata (phase 2), a screenshot heading (phase 3), a
    price tier (phase 4), a custom product page or PPO variant (phase 7), an
    in-app event (phase 10) — gets one entry appended via:

    ```bash
    ruby scripts/log_change.rb <app> <phase> <locale> <field> "<old>" "<new>" [version] ["why"]
    ```

    Call this **only after** law 5's read-back has confirmed the write
    stuck — never before, and never for a write that failed verification.
    It writes to both `~/.super-aso/<app>/change_log.json` (the
    machine-readable log phase 9 reads to name specific causes instead of
    vague timing — see `reference/monitoring.md`) and
    `~/.super-aso/<app>/change_log.csv` (the same entries, flattened, so the
    user can open it in Excel/Numbers/Sheets and line it up by date against
    any external export — the App Store Connect Analytics Reports CSV, a
    third-party ASO tool's ranking export, a Firebase/GA4 pull — without
    needing another tool). One log file per app, not one per phase: a
    phase-3 screenshot swap and a phase-2 subtitle change three days apart
    both belong in the same log so phase 9 can see both when explaining a
    rank move.

## Scripts and assets in this skill

```
scripts/asc.rb          # minimal ASC API client — ruby asc.rb GET '/v1/apps'
                         # flags: --dry-run (preview, sends nothing),
                         # --paginate (GET, follows links.next, merges pages),
                         # --retries=N (override the retry budget)
scripts/check_setup.sh  # PASS/WARN/FAIL per prerequisite, with fix commands;
                         # pass an app id (check_setup.sh <app>) to also report
                         # per-app progress under ~/.super-aso/<app>/
scripts/log_change.rb   # cross-phase law 11 — appends one confirmed ASC write
                         # to ~/.super-aso/<app>/change_log.json + .csv
renderer/               # screenshot heading renderer (playwright + per-script fonts)
renderer/fetch_fonts.sh # one-time ~55MB font download (DM Sans + Noto per script)
reference/*.md          # the ten phase guides — read before the phase
reference/data-sources.md # shared reference for optional external data (iTunes
                         # Search, ASC Analytics Reports, ASA popularity, Trends)
reference/monitoring.md # phase 9 — rank/review snapshot + diff, run any time
reference/in-app-events.md # phase 10 — live/limited-time event cards, run any time
```

The renderer needs a one-time `npm install && npx playwright install chromium`
and `./fetch_fonts.sh` inside `renderer/` (check_setup.sh tells you if they're
missing).

**Free Figma mockup template** (iPhone / iPad / Apple Watch frames with
"Your screenshot here" placeholders) — offer it whenever the user has no
screenshot designs or asks how to provide them:
https://www.figma.com/design/ftvaN3ZMfgkGrn7SAuqCvt/vibe-ASO-mockups

## Reporting

End every phase with four short sections: **Done** (what happened, verified),
**Problems** (what surprised you and how it was handled), **Needs you** (manual
steps only the user can do — or "nothing"), and **Suggested next** (the next
logical phase from the table above, one line — e.g. "Phase 2 (metadata) is
next; it consumes the keyword table this phase just produced"). Keep it
outcome-level; skip file paths and internals unless asked. The "Suggested
next" line exists so a user finishing a phase doesn't have to re-read the
phase table themselves to know what comes after — say "nothing, you're done
with the pipeline" if the phase just run was the last one the user wanted.
