---
name: pp-app-store
description: "Apple App Store charts, listings, ratings and reviews for any storefront from one Go binary, with a local history that turns repeated captures into rank, listing-change and traction trends. Trigger phrases: `top free games on the app store`, `app store reviews for`, `track this app's rank across countries`, `what changed in this app store listing`, `app store traction by country`, `use app-store`, `run app-store`."
author: "qazmataz"
license: "Apache-2.0"
argument-hint: "<command> [args] | install cli|mcp"
allowed-tools: "Read Bash"
metadata:
  openclaw:
    requires:
      bins:
        - app-store-pp-cli
    install:
      - kind: go
        bins: [app-store-pp-cli]
        module: github.com/mvanhorn/printing-press-library/library/marketing/app-store/cmd/app-store-pp-cli
---

# App Store — Printing Press CLI

## Prerequisites: Install the CLI

This skill drives the `app-store-pp-cli` binary. **You must verify the CLI is installed before invoking any command from this skill.** If it is missing, install it first:

1. Install via the Printing Press installer. It defaults binaries to `$HOME/.local/bin` on macOS/Linux and `%LOCALAPPDATA%\Programs\PrintingPress\bin` on Windows:
   ```bash
   npx -y @mvanhorn/printing-press-library install app-store --cli-only
   ```
2. Verify: `app-store-pp-cli --version`
3. Ensure the reported install directory is on `$PATH` for the agent/runtime that will invoke this skill.

If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.10 or newer). This installs into `$GOPATH/bin` (default `$HOME/go/bin`), so add that directory to `$PATH` instead:

```bash
go install github.com/mvanhorn/printing-press-library/library/marketing/app-store/cmd/app-store-pp-cli@latest
```

If `--version` reports "command not found" after install, the runtime cannot see the binary directory on `$PATH`. Do not proceed with skill commands until verification succeeds.

Pull 200-rank free and paid charts (top-grossing: up to 100 rows from Apple's legacy feed), listings with privacy labels, version history, in-app purchase prices (the page's top 10) and in-app events, and both review feeds, for any app in any storefront, with no key and no account. Every capture lands in SQLite, so traction, impact, changes, keyword-share and breakouts answer week-over-week questions once you have captures from more than one day.

## When to Use This CLI

Use this CLI for public App Store data on any app: listings, top charts (200 free and paid ranks), reviews across storefronts, privacy labels, version history, in-app purchase prices and in-app events. Its local SQLite history answers week-over-week questions: rank movement, listing changes, keyword share and per-country traction proxies. It needs no Apple account.

## Anti-triggers

Do not use this CLI for:
- Do not use it for real download, sales or revenue numbers: Apple publishes none publicly. For download questions run traction and say its numbers are proxies, never downloads; for apps you own, the asc CLI reads App Store Connect sales.
- Do not use it for App Store Connect work such as builds, TestFlight, submissions or replying to reviews.
- Do not use it for Apple Search Ads campaigns or keyword popularity; use apple-search-ads.
- Do not use it for Google Play apps; use google-play.
- Do not use it to install, buy or download apps.

## Unique Capabilities

### Local history that compounds
- **`traction`** — See per-country demand signals for any app, rolled up daily, monthly, annually or all-time: new ratings, chart rank and review volume, clearly labelled as proxies.

  _Reach for this when asked for downloads, installs or demand by country over time; it returns honest proxies instead of invented download counts._

  ```bash
  app-store-pp-cli traction 324684580 --countries us,gb,pk --period monthly --agent
  ```
- **`impact`** — Compare rank, new ratings, review stars and keyword positions in equal windows before and after a release, listing change or date.

  _Use it to check whether a release or metadata change moved anything, with co-movement numbers and no causal claims._

  ```bash
  app-store-pp-cli impact 324684580 --at latest --window 7d --agent
  ```
- **`changes`** — One dated feed of every change across your tracked apps: new versions, title and subtitle edits, price and in-app purchase price changes, privacy labels, events and featuring.

  _Use it for a weekly competitor review instead of opening every listing._

  ```bash
  app-store-pp-cli changes --since 7d --apps 553834731,850417475 --agent
  ```

### Market and keyword intelligence
- **`keyword-share`** — See which apps hold the top search results across a set of tracked terms and how that share moved since the last capture.

  _Use it for keyword reports across a basket of terms rather than one term at a time._

  ```bash
  app-store-pp-cli keyword-share "music streaming" "podcast player" --country us --top 10 --app 324684580 --agent
  ```
- **`breakouts`** — Find apps climbing or entering the same chart in several countries at once.

  _Use it to separate a multi-market breakout from noise in one storefront._

  ```bash
  app-store-pp-cli breakouts --genre 6014 --chart top-free --since 7d --min-markets 3 --agent
  ```

## History commands need captures

traction, impact, changes, keyword-share and breakouts (and movers, rank-history, keyword-history, watch-listing, review-digest and search) read only the local SQLite store. On a fresh install they exit 0 with empty results and a note naming the capture to run; movement needs captures from two or more days. What feeds each:

- traction: `ratings`, `top` and `reviews`
- impact: `app`, `ratings`, `top`, `reviews` and `keyword-rank` on both sides of the date
- changes: `app`, `iap`, `privacy`, `events` and `featured` (plus the lookup snapshots `prices` and `compare` store)
- keyword-share and keyword-history: `keyword-rank`
- breakouts, movers and rank-history: `top` (breakouts needs several storefronts)
- watch-listing: `app`, `prices` or `compare`; review-digest: `reviews`

If the user needs an answer now, run the capture commands first and say the history starts today.

## Command Reference

**Live App Store reads** (each run also stores what it read)

- `app-store-pp-cli app <app>` — One app's listing in one storefront; `--full` adds description, versions, privacy, in-app purchase prices, events and similar apps
- `app-store-pp-cli top` — One chart: top-free and top-paid have 200 ranks; top-grossing at most 100 rows from Apple's legacy RSS feed (none on Mac); rows carry exact rating counts
- `app-store-pp-cli reviews <app>` — Customer reviews (RSS: 50 per page, up to 10 pages per sort); `--source api` is a robots-disallowed opt-in for explicit requests only
- `app-store-pp-cli ratings <app>` — Exact rating count and average per storefront; `--histogram` adds the star split
- `app-store-pp-cli compare <app> <app> [app...]` — Apps side by side per storefront
- `app-store-pp-cli prices <app> [app...]` — Price and availability per storefront
- `app-store-pp-cli iap <app>` — In-app purchase prices (Apple lists the top 10) per storefront
- `app-store-pp-cli privacy <app>` — App Privacy labels
- `app-store-pp-cli versions <app>` — Version history, newest first (25 newest on the page)
- `app-store-pp-cli events <app>` — In-app events
- `app-store-pp-cli similar <app>` — You Might Also Like apps
- `app-store-pp-cli developer <developer-id-or-app>` — A developer's apps (up to 200) and page shelves
- `app-store-pp-cli featured` — The Today tab's stories and featured apps
- `app-store-pp-cli search-store <term>` — Live store search; `--source web` is a robots-disallowed opt-in
- `app-store-pp-cli suggest <term>` — Search suggestions from Apple's undocumented search-hints endpoint; run it only when asked
- `app-store-pp-cli keyword-rank <term>` — One live search, the `--app` target's rank, and the stored result list

**Local history readers** (no network)

- `app-store-pp-cli movers` — Chart movement between the two latest captures
- `app-store-pp-cli rank-history <app>` — An app's rank over time in one chart
- `app-store-pp-cli keyword-history <term>` — A term's rank history and top results over time
- `app-store-pp-cli watch-listing <app>` — Diff of an app's two latest stored listing snapshots
- `app-store-pp-cli review-digest <app>` — A mechanical digest of stored reviews
- `app-store-pp-cli search <query>` — Offline search of stored reviews and listings

**categories** — App Store chart categories and their genre ids, from the built-in genre table (no request); `--live` reads the public chart page

- `app-store-pp-cli categories games` — List App Store game subcategories (genre ids 7001-7019 such as 7012 Puzzle)
- `app-store-pp-cli categories list` — List App Store app categories (genre ids such as 6014 Games, 6026 Developer Tools, Kids age bands)


**breakouts**


- `app-store-pp-cli breakouts` — Find apps climbing or entering the same chart in several countries at once.


**changes**


- `app-store-pp-cli changes` — One dated feed of every change across your tracked apps: new versions, title and subtitle edits, price and in-app purchase price changes, privacy labels, events and featuring.


**impact**


- `app-store-pp-cli impact` — Compare rank, new ratings, review stars and keyword positions in equal windows before and after a release, listing change or date.


**keyword-share**


- `app-store-pp-cli keyword-share` — See which apps hold the top search results across a set of tracked terms and how that share moved since the last capture.


**traction**


- `app-store-pp-cli traction` — See per-country demand signals for any app, rolled up daily, monthly, annually or all-time: new ratings, chart rank and review volume, clearly labelled as proxies.




### Finding the right command

When you know what you want to do but not which command does it, ask the CLI directly:

```bash
app-store-pp-cli which "<capability in your own words>"
```

`which` resolves a natural-language capability query to the best matching command from this CLI's curated feature index. Exit code `0` means at least one match; exit code `2` means no confident match — fall back to `--help` or use a narrower query. `--json` (and other machine formats) keep that exit-2 contract and write `{"matches":[]}` on stdout so agents can inspect the envelope without treating a miss as success.

## Recipes

### Top 200 free games in Pakistan

```bash
app-store-pp-cli top --chart top-free --genre 6014 --country pk --limit 200 --agent --select rank,title,developerName,ratingCount
```

One chart page plus two Lookup requests (150 + 50 ids) return 200 ranked games with exact rating counts.

### In-app purchase prices for a game

```bash
app-store-pp-cli app 553834731 --full --agent --select trackName,inAppPurchases.name,inAppPurchases.price
```

Narrows the full listing to the in-app purchase names and prices (the page's top 10) with dotted paths.

### Weekly competitor change feed

```bash
app-store-pp-cli changes --since 7d --apps 553834731,850417475 --agent
```

Lists every recorded version, price, IAP, privacy, event and featuring change for the tracked apps.

### Monthly demand proxies by country

```bash
app-store-pp-cli traction 324684580 --countries us,gb,pk --period monthly --agent
```

New ratings, best chart rank and review volume per storefront, labelled as proxies.

## Auth Setup

No API key or Apple account. By default commands read Apple's public iTunes Lookup, Search and RSS feeds and public App Store web pages. suggest calls Apple's undocumented search-hints endpoint, and --source web (search-store, keyword-rank) and --source api (reviews) reach robots-disallowed surfaces: use those only when asked. Data is per storefront: pass --country with a two-letter code; ratings, reviews, iap, prices and compare, and the local readers traction, impact, changes, breakouts and review-digest, also take --countries for a list. Other commands read one storefront per run.

Run `app-store-pp-cli doctor` to verify setup (one paced iTunes Lookup).

Pacing: apps.apple.com pages go about 20 s apart across runs, other surfaces about 1 request per second (Search about 20 per minute). The first 403/429 stops the run (exit 7) and holds that host for at least 5 minutes, across runs; never retry in a loop.

## Agent Mode

Add `--agent` to any command. Expands to: `--json --compact --no-input --no-color`.

Global format flags share one contract on promoted, novel and `--deliver` paths:

- `--json` — one JSON document on stdout (notes and warnings go to stderr)
- `--compact` — keep identity/status/timestamp fields; does not change the document vs stream shape
- `--csv` / `--plain` — tabular rows (collection envelopes unwrap to the row array). `--csv` prefixes `'` on a cell or header key that starts with `=`, `+`, `-`, `@`, tab, CR, or LF (fullwidth `＝＋－＠` too) so a spreadsheet does not run it as a formula; a plain signed number such as `-5` or `+2.5e3` is unchanged.
- `--quiet` — one identity value per row, no envelope

- **Pipeable** — JSON on stdout, errors on stderr
- **Filterable** — `--select` keeps a subset of fields. Dotted paths descend into nested structures; arrays traverse element-wise. Critical for keeping context small on verbose APIs:

  ```bash
  app-store-pp-cli categories list --agent --select genreId,name
  ```
- **Previewable** — `--dry-run` exits before any request or store write and prints the command it would run (no URLs; arguments are not validated)
- **Offline-friendly** — the local history readers (see "History commands need captures") read only the SQLite store; live commands always call Apple
- **Non-interactive** — never prompts, every input is a flag
- **Read-only** — do not use this CLI for create, update, delete, publish, comment, upvote, invite, order, send, or other mutating requests

### Response envelope

With `--agent`, commands wrap output in a provenance envelope:

```json
{
  "meta": {"source": "live" | "local" | "computed" | "dry-run", "synced_at": "...", "reason": "..."},
  "results": <data>
}
```

Parse `.results` for data and `.meta.source` to know where it came from: `live` (fetched from Apple), `local` (read from the store), `computed` (a built-in table such as `categories`) or `dry-run`. The multi-storefront live commands (ratings, iap, prices, compare) put their rows at `.results.items` with `.results.fetch_failures` beside them; without `--agent`, `--json` prints the command's own document (for `app`, the bare listing object). A human-readable `N results (live)` summary is printed to stderr only when stdout is a terminal AND no machine-format flag (`--json`, `--csv`, `--compact`, `--quiet`, `--plain`, `--select`) is set — piped/agent consumers and explicit-format runs get pure JSON on stdout.

## Paths and state

Agents should treat the CLI's path resolver as part of the runtime contract:

- Use `--home <dir>` for one invocation, or set `APP_STORE_HOME=<dir>` to relocate all four path kinds under one root.
- Use per-kind env vars only when a specific kind must diverge: `APP_STORE_CONFIG_DIR`, `APP_STORE_DATA_DIR`, `APP_STORE_STATE_DIR`, `APP_STORE_CACHE_DIR`.
- Resolution order is per-kind env var, `--home`, `APP_STORE_HOME`, XDG (`XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, `XDG_CACHE_HOME`), then platform defaults.
- `config` contains settings like `config.toml` and profiles. `data` contains durable local data such as `data.db`. `state` contains the learn journal, `teach.log` and `appstore-governor.json` (cross-run pacing slots and 403/429 holds; a different `--home` or `APP_STORE_HOME` has its own governor). `cache` contains regenerable HTTP/cache files.
- Run `app-store-pp-cli doctor --fail-on warn` to surface path warnings. `agent-context` exposes a schema v4 `paths` block for agents that need the resolved dirs.
- For MCP, pass relocation through the MCP host config. The MCP binary does not inherit CLI flags:

  ```json
  {
    "mcpServers": {
      "app-store": {
        "command": "app-store-pp-mcp",
        "env": {
          "APP_STORE_HOME": "/srv/app-store"
        }
      }
    }
  }
  ```

Fleet precedence: an inherited per-kind env var overrides an explicit `--home` for that kind. Use `APP_STORE_HOME` or per-kind vars as durable fleet levers, and use `--home` only for a single invocation. Relocation is not reversible by unsetting env vars; move files manually before clearing `APP_STORE_HOME`, or `doctor` will not find files left under the former root.

## Automatic learning

This CLI ships a self-capturing learning loop. The CLI does its own bookkeeping: every invocation is journaled locally, a failed flag followed by a corrected retry auto-derives a `flag_alias` candidate, and a `teach` on a query family without a playbook auto-synthesizes a `playbook_candidate` from the session's journal. Your job is judgment only: `recall` first, act on surfaced candidates, `teach` the final answer, `playbook amend` when you observe a correction. You never record failures by hand.

### Step 1: `recall` before any discovery

Before list/search/drill commands on a new user question, pass the question as an argv or MCP tool argument to `recall --agent`. Do not interpolate user-controlled text into a shell command line.

Quoted `recall "<question>"` breaks on an apostrophe, which is ordinary English. A quoted heredoc breaks when a body line equals the delimiter, and that delimiter is published in these docs. Write the question with a non-shell file-writing tool, then read it back as data:

```bash
# Write the question verbatim with your file-writing tool (no shell involved).
# Command substitution on a file only ever yields data — the shell never
# parses the file's bytes as syntax.
QUERY=$(cat /path/to/question.txt)
app-store-pp-cli recall "$QUERY" --agent
```

Prefer MCP: pass the question as the tool's query argument. `"$QUERY"` after a file read is argv-safe; putting the question itself in the command text is not.

The response envelope:

```json
{
  "query": "...",
  "normalized": "<normalized form>",
  "query_entities": ["..."],
  "found": true | false,
  "match_score": 0.0,
  "results": [
    { "resource_id": "...", "resource_type": "...", "venue": "...",
      "confidence": 2, "entity_match": "exact|partial|unknown",
      "source": "taught|preseed|pattern", "warnings": ["..."] }
  ],
  "mismatches": [ /* only when --debug-mismatches */ ],
  "warnings": [ /* top-level */ ],
  "candidates": [
    { "id": 12, "class": "flag_alias | playbook_candidate",
      "summary": "...", "sightings": 3, "last_seen": "...",
      "rationale": "...",
      "next_action": ["<trial command>", "app-store-pp-cli learnings confirm 12"] }
  ],
  "playbook": {
    "query_family": "...",
    "playbook": {
      "steps": [ { "cmd": "<command with {slot} substitution>", "purpose": "..." } ],
      "entity_slots": ["$ENTITY"],
      "expected_tool_calls": 3
    },
    "slots_resolved": { "$ENTITY": { "token": "<live token>", "canonical": "<canonical>" } },
    "notes": "<workarounds + gotchas for this query family>"
  },
  "notes": "<duplicate surface for non-playbook callers>"
}
```

Empty-store short-circuit: if the store has no learnings, playbooks, or candidates yet (recall finds nothing and `learnings list` and `learnings candidates` are both empty), skip recall for the rest of this session instead of taxing every query; resume recall-first once something has been taught.

### Step 2: decision tree

Read `candidates`, `playbook`, `notes`, `results[0]`, and warnings in that order:

```
if Candidates present (warnings include "candidates_present"):
    -> candidates are try-then-confirm, never facts. Follow each candidate's
       two-step next_action verbatim: run the trial command first, then run
       `learnings confirm <id>` only after the trial verified the behavior.
       Reject a wrong c