# Changelog ## Unreleased - Fixes from a guarded read-only run of the Play paths that need POST / DELETE (edit insert and delete, Reporting API `:query`, nothing else let through) against a live account: - `health` / `validate`: Play's top issues search (`errorIssues:search`) answered 400 "Unsupported timezone": it takes only hour-aligned UTC intervals, unlike the metric timelines (America/Los_Angeles days). It now asks in UTC. A failing issues search no longer throws away the crash and ANR rates already read (a `vitals-issues` warning and "not read" in the table instead of "vitals not readable"), and Reporting API errors leave out the query string so the reason is not cut off in tables. - `play listing --dry` and `play images --dry` are read-only now: one throwaway edit, the current details, listings and images (sha256 of each uploaded file) compared with the local copy, the edit deleted. Before, the dry runs wrote into the edit (PUT details and listings; for images every image set cleared and uploaded) and called `:validate` before discarding it. The output says per language what would change. - `play products --dry` names what a run would change per product (listings added / changed / removed, regional prices old -> new, dropped regions, activation) instead of "update · 174 regions" for every existing product. - `play products` asks before changing what an existing product costs or where it is sold (regional prices, the other-regions price, dropped regions, activating a purchase option), the same way `play subscriptions` does: the diff, then a terminal confirmation (or `--yes`). Without one (MCP, CI, serve without a desktop owner-action token) those changes are skipped with a warning; new products and listing text still go through. `apple iap` and `apple appinfo` only create a price schedule or availability where none exists, so they need no gate; price changes on both stores stay with `prices import` (confirmation, the desktop Prices "Apply" owner action). - `play status`, `play next-version-code`: the read-only edit goes through one helper that always deletes it and warns when the delete fails; `next-version-code` counts distinct version codes (one code on two tracks is one). - Fixes from a second read-only run (GET only) of the desktop read paths against live accounts: `analytics snapshot` no longer writes a snapshot when every store failed (no ONGOING report request yet, no `play.reportsBucket`); it exits with both reasons, so the desktop dry run shows them. `analytics compare --json` adds `errors` (why a store has no days), and the Growth tab shows that reason instead of zero totals and "one day of data". Listing completeness no longer counts a contact sheet in the language folder as a screenshot. Price sheet round trip (export, then compare) checked on recorded App Store and Play price answers (`tests/desktop-real-reads.test.ts`). - Growth tab chart: impressions, downloads and conversion are drawn as an index (100 = each line's own average over the days shown) on one axis, so downloads no longer lie flat under impressions; hover still shows the real numbers. - **`storebound import fastlane`** (`--dir`, `--out`, `--profile`, `--dry`; MCP `import_fastlane`, dry run by default): reads Appfile / Deliverfile (plain `key("value")` lines only; Ruby logic is reported, never run), deliver and supply metadata and screenshots, and writes `storebound.config.ts`, `store/appstore.md`, `store/listing.md`, the Play icon and feature graphics, and screenshot frames with a `folder` capture source. App Store and Play language codes are paired into one `languages[]` entry (`cli/lib/store-locales.ts`). Existing files are never overwritten (a `.imported.` copy plus a diff hint). Keys, team ids, the App Review contact and demo account are only named in the report; submission flags, `price_tier`, the age rating config, lanes and signing are listed with their storebound counterpart. New skill `migrate-from-fastlane`. - Store language codes checked against the official lists (2026-10-11): the 50 App Store Connect locale shortcodes and the 86 Google Play listing codes all match; Zulu (`zu`) is no longer a Play listing language and was dropped; `import fastlane` also accepts region-less folders of the 2025 App Store languages (`ta`, `sl`, `ur`, ...) and App Store spellings in Play folders (`pa-IN`, `ur-PK`, `sl-SI`, ...). Sources are cited in `cli/lib/store-locales.ts`. - Fixes from a read-only run against live store accounts: `apps check` says `api-disabled` (not `permission`) when the Play Developer Reporting API is off, and `apps list` / serve `apps.list` fail with that reason and the fix instead of a JSON fragment (API error bodies are kept on one line everywhere); the newest App Store version is the newest by date (the API groups versions by platform), per-app reads run four at a time; the account check counts every app of the team; Play prices under 1.00 (no `units` in the answer) no longer read as NaN in `catalog diff` and subscription price diffs; `health` treats App Store Connect's 404 for builds without field data as no diagnostics; `validate` no longer blocks on age rating fields accepted submissions leave empty (`gracRatingClassificationNumber`, `socialMediaAgeRestricted` unless social media is yes; `socialMedia` is a warning). - **Desktop D5: release pipeline.** The desktop app is a separate signed download from storebound.dev/desktop/ (DMG, Windows installer, AppImage / deb) that updates itself; the npm package stays the CLI, MCP server and skills. `storebound desktop` opens the app (in an app folder it adds or selects that folder through `storebound://open?path=`, after the app's own dialog) or prints the download for this system; `--download` saves it to ~/Downloads after checking its SHA-256 and minisign signature, and never installs. One desktop version (`desktopVersion` in package.json, checked in CI), a privacy scan of every artifact (`tools/privacy-scan.ts`), path-free builds, `tools/desktop-release.ts` (universal macOS app + DMG, Developer ID + notarization when the certificate exists, otherwise an ad-hoc `-unsigned` preview; self-test of the exact artifacts; signed latest.json), a Forgejo workflow for Linux and Windows, the root-owned publish step on the server and a downloads page (`site/desktop/`). Settings > Updates checks and installs signed updates on request. See `docs/desktop-release.md`. - **Desktop machine interface** (`serve --stdio`): JSON-RPC session for the coming desktop app, built on the MCP core. Typed methods with argument schemas, structured results, progress notifications, cancellation, `project.inspect` and `portfolio.status`: apps across iOS and Android with identity, config check, release trains (current and next version on both stores) and catalog parity (`live` reads App Store and Play status). `system.tools` reports ffmpeg, Chrome, Xcode tools, simctl, adb and git with versions; `STOREBOUND_TOOL_PATH` / `STOREBOUND_CHROME` point the CLI at app-managed copies. Writes are dry runs unless the host sends a single-use confirm token bound to the method, arguments and a short expiry (HMAC with a per-session secret handed over on stdin). No submit / release / publish method exists. Under `serve`, terminal confirmations always answer no. - `--json` on `check` (and `check --aso`), `apple testflight status`, `apple ppo status`, `apple next-build`, `play next-version-code`, `analytics compare` and `prices import` (read-only diff); new `inspect` command. - **Desktop app shell** (`apps/desktop`, not released yet): Tauri 2 + React 19, app-centric (Portfolio, App with Releases / Catalog / Listings / Health / Reviews / TestFlight, App folders, Accounts, Dependencies, Settings), store keys in the OS keychain, native confirm dialogs with the dry run before any write. See `docs/desktop.md`. - `--json` on `catalog diff` goes through the same machine-readable result line. - **`storebound doctor`** (`--json`): ffmpeg, ffprobe, Chrome, playwright, Xcode tools, simctl, adb and git with versions (the same detection as the desktop Dependencies screen) and the install commands for this OS. It runs none. - `playwright` is an optional peer dependency (it was only documented before). The compiled desktop sidecar bundles `playwright-core` and renders with the app's Chrome for Testing (`STOREBOUND_CHROME`), so desktop users need neither. - `inspect --icon` / `inspect --listings` (listing completeness per language on both stores, rendered screenshots), `reviews drafts [--json]` (local reply drafts with the send checks), `reviews approve [--undo] [--dry]`, `init --dry`; templates are embedded so `init` works from a single-file binary. - `apple status --json` includes the phased release of the newest live versions; release trains now count a staged rollout or iOS phased release (also halted / paused) as the current train. - **Desktop D2** (`apps/desktop`): redesigned shell (command palette, toasts with progress and cancel, skeletons, illustrated empty states, first-run guide), release timeline per app, catalog parity matrix, listing completeness, health sparklines against the Play thresholds, reviews inbox with star distribution and draft approval, TestFlight groups and builds, store readiness with fix hints; offline cache of the last reads (SQLite, "last read" stamps); one-click ffmpeg (pinned SHA-256) and Chrome for Testing (Google's code signature) downloads into app data; English and Turkish with an i18n check, 7:1 text contrast checked in both themes. - `tools/build-sidecars.ts`: single-file executables for macOS (arm64, x64), Linux (x64, arm64) and Windows (x64), re-signed ad hoc on macOS and checked for build-machine paths. The MCP server now also runs from such a binary. ## 0.5.0 · 2026-10-11 Apps and games: storebound is for every mobile app, not only games, and the in-app catalog is complete on both stores. - **Positioning**: "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store" across the README, the site (new "Apps and games" section, app screenshot style, og image), package, plugin and adapter descriptions, CLI help, MCP server instructions and the skills. Game-specific advice stays, marked for games. - **`init --profile app|game|subscription-app`** (default `app`): per profile a config, `declarations.md`, `app-privacy.md`, the Data safety section, listing and screenshot copy and a privacy policy. app = utility / productivity app with an optional account and analytics, no ads; subscription-app = accounts + auto-renewable subscriptions with the disclosure text; game = the 0.4 templates. Every answer is flagged "edit to match your app". Configs without `app.profile` keep working as games. - **App screenshot style** (`shots.style: 'app'`, the default for the app profiles): calm gradient, a clean device frame, headline + subline, no sparkles or badges (badges only as quiet chips), status bar text that follows the frame, Inter by default, app palettes (mist, paper, mint, sand, rose, sky, ocean, slate), its own feature graphic. Game style unchanged. New demo `examples/demo-app` ("Tidy Lists"). - **App identity**: `app.platforms` (default from the `apple` / `play` sections) and `platforms` on a product or subscription to sell it on one store only; one shared catalog and shared listings, per-platform settings in `apple` / `play`. - **Subscriptions on both stores**: `apple subscriptions` (groups and display names, subscriptions with period, level, family sharing, localizations, availability, prices from the USA price point and its equalizations plus overrides and `--csv` rows of the prices sheet incl. scheduled changes, introductory offers per territory, promotional offers, offer code campaigns, custom codes only with `--codes` after a terminal confirmation, review screenshots) and `play subscriptions` (listings with benefits, one auto-renewing base plan with grace period and regional prices from `convertRegionPrices`, overrides and CSV, activation, the intro offer for new subscribers and extra offers chosen by your app through offer tags). Both print a plan, `--dry` stops there, a terminal run asks first; without a terminal (MCP) only new records are created and live price changes wait for the owner. Skill `store-subscriptions`. - **Full IAP catalog**: product type `non_renewing` (Apple NON_RENEWING_SUBSCRIPTION; a consumed one-time product on Play), and **`catalog diff`**: the config's products and subscriptions against both stores (missing, extra, type, billing period, level, US and override prices, localizations, offers) plus regions where the two stores price the same item differently (same currency, `--threshold`). Read-only, exit 1 on mismatches. - **Review access**: `apple review-details` sends `demoAccountRequired` / `demoAccountName` / `demoAccountPassword` from `reviewDemoAccount` in `.storebound/credentials.json` (or `REVIEW_DEMO_USER` / `REVIEW_DEMO_PASSWORD`), masked in every log line, and adds the sign-in steps (`reviewAccess.instructions`) and where account deletion is (`accounts.inApp`) to the notes (4000-byte check). Skill `review-access`. - **Account deletion**: `accounts { enabled, deletionUrl, inApp }`; skill `account-deletion` (Apple 5.1.1(v), Play's in-app + web requirement and Data safety). `dataSafety.extra` adds CSV rows for questions Google does not document (e.g. the delete-account URL id from a Console export). - **Checks**: `check` reports the profile (delete-account URL, Data safety consistent with accounts, demo account present, terms URL with subscriptions, subscription review screenshots); `validate` adds subscription states, localizations, group names and review screenshots (App Store), active base plans (Play), the delete-account and terms URLs answering, the demo account on App Review and a Terms of Use link in the descriptions. - **Ad mediation**: `ads-levelplay-checks` became `ads-mediation-checks` (AdMob Ad Inspector, AppLovin MAX Mediation Debugger, LevelPlay as a section, app-ads.txt, SKAdNetwork); the old name stays as a pointer skill. - **MCP tools**: `apple_subscriptions`, `play_subscriptions` (dry run by default), `catalog_diff` (read-only). Custom offer codes and live price changes are not reachable through MCP. - Unverified until a live account run (see the README): free promotional offers / offer codes without a price point, inline subscription prices and intro offers via `PATCH /v1/subscriptions/{id}`, Play relative-discount and untargeted offers, the Play billing period strings and the undocumented Data safety ids. ## 0.4.0 · 2026-10-10 Wave 2: TestFlight and life after launch (stability, reviews, prices, analytics, experiments), plus MCP 2026-07-28. - **TestFlight through the API** (`apple testflight status | groups | testers | what-to-test | info | distribute`): beta groups from `apple.testflight.groups` (internal / external, public links; created when missing, never changed or deleted), testers from a CSV in `.storebound/` (the config refuses a path outside it; emails masked in every log line; asks before Apple emails invitations; `--remove`, `--invite`), "What to Test" per locale from the App Store listing (`### whatToTest`, else the `whats-new` notes), Test Information and Beta App Review contact / notes, a build by its exact number to groups, Beta App Review submission behind `--submit-review` and a confirmation. Skill `testflight`. - **Crash and vitals health** (`health [--weekly]`): Play Developer Reporting API (user-perceived crash and ANR rates, 28-day user-weighted, worst device models, top error issues) and App Store power and performance metrics, diagnostic signatures and TestFlight crash reports. Play's bad behavior thresholds (1.09 % crash, 0.47 % ANR, 8 % per device, developer.android.com/topic/performance/vitals): over = blocker, from 80 % = warn, also in `validate` (`--no-health`). Weekly Markdown report with week-over-week change in `.storebound/health/`. Skill `store-health`. - **Review replies** (`reviews list | draft | send`): App Store customer reviews / responses and Play reviews; drafts as Markdown in `.storebound/replies/` (never overwritten), `send` only sends drafts with `approved: true` after a confirmation and refuses incentives, rating requests and Play replies over 350 characters. Skill `review-replies`. - **Price sheet** (`prices export | import`): one CSV of product x store x region; diff table first, then (after a confirmation) one App Store price schedule per changed product (base territory, every manual price, future-dated changes with `start_date`) and the Play purchase option's regional prices (`convertRegionPrices` for regions without a price, `--reconvert`). `store-iap-setup` skill: price sheet section. - **Analytics snapshots** (`analytics setup | snapshot | compare`): App Store Analytics Reports API (Discovery and Engagement + Downloads, md5-checked segments, newest processing per day) and the Play statistics exports in the `pubsite_prod_rev_` bucket (`play.reportsBucket`); snapshots in `.storebound/analytics/`, `compare` lines them up with the git history of the store files. Skill `store-analytics`. - **MCP 2026-07-28**: stateless requests with per-request `_meta` and `server/discover` (supported versions, capabilities, instructions; `resultType`, server info in every result's `_meta`, cacheable `tools/list`, -32022 for unsupported versions); `initialize` clients (2025-11-25 and older) keep working on the same process. - **Product Page Optimization through the API** (`apple ppo status | create | upload | start | stop`): v2 tests with one treatment per `shots` variant, a localization per locale and the variant renders in the treatment screenshot sets; start / stop ask first. Play store listing experiments stay console-only (documented). - **`iphone-medium` size key** (1206 x 2622, `APP_IPHONE_61`), off by default; `apple.iphoneMedium: true` renders, checks and uploads it. Apple scales the 6.5" set down when it is missing. - **MCP tools**: `apple_testflight_status`, `apple_testflight_groups`, `apple_testflight_what_to_test`, `apple_testflight_info`, `apple_testflight_distribute` (internal groups only), `health`, `health_report`, `reviews_list`, `reviews_draft`, `prices_diff`, `prices_export`, `analytics_snapshot`, `analytics_compare`, `analytics_setup`, `apple_ppo_status`, `apple_ppo_create`, `apple_ppo_upload`. Not exposed: tester changes and invitations, external distribution and Beta App Review, sending replies, applying prices, starting / stopping tests. - Agent rules: owner-only steps now include review replies, tester invitations, price changes and experiments; personal data (tester lists, reply drafts, health and analytics files) stays in `.storebound/`. - `apple screenshots --dry` no longer creates empty screenshot sets. ## 0.3.1 · 2026-10-10 Positioning: storebound is the whole road from zero to "Submit for review", screenshots are one step of it. - **README** rebuilt around the end-to-end journey: a new diagram (`docs/img/journey.svg`) and a step table for both stores showing what storebound does through the APIs, what the skills walk your agent through in the consoles (IARC, target audience, ads, advertising ID, App Privacy, app records, the first Play production send) and what stays your own click (Send for review / Submit for Review, releases). "API or console, honestly" summary, a new FAQ ("Does it fill the questionnaires?"), quick start along the journey with the npm package, skills in journey order; the screenshot and video showcase moved to a "Store art" section. - Package, plugin, Codex, Gemini and marketplace descriptions follow the new positioning. - `package.json` `bin` path normalised (`cli/index.ts`), so npm keeps the `storebound` command. ## 0.3.0 · 2026-10-10 Screenshots and videos from any engine, reviewed before they leave your machine. - **Capture from anything** (`shots capture --source`): `web` (Playwright, unchanged), `ios-sim` (simctl boot, 9:41 status bar, launch arguments / deep links, `-AppleLanguages`, warm-up after a cold boot, shuts down only what it booted), `android-emu` (demo-mode status bar, per-app language, `am start` with deep links / extras, screencap; never boots, wipes or reboots), `folder` (your own frames, checked for aspect and size). Status bar cropped per device class. `--dry` prints every simctl / adb command. - **13" iPad** end to end with `apple.ipad: true`: `ipad13` size (2064 x 2752, 3:4 layout), iPad frame defaults, the `APP_IPAD_PRO_3GEN_129` set in `apple screenshots`, required by `validate`. - **Store rules as data** (`cli/shots/spec.ts`, sources cited): pixel sizes per store type, alpha, Play JPEG / 24-bit PNG and 8 MB, counts per set, feature graphic, app preview rules. Used by the new `shots check` (local, exit 1 on errors, `--variants`, `--json`) and by `validate` (local renders become blockers next to the store state). - **`shots gallery`**: one self-contained `index.html` next to the renders (no network): every language x size x scene, app previews with posters, variant tabs, filters, light / dark, issues outlined with their message. - **A/B variants**: `shots.variants` (scene order / subset, copy overrides, palettes) rendered with `shots render --variant b | --variants`, the feature graphic included; `--variant` on `play images`, `apple screenshots` and `shots jpeg`. The skill explains Play store listing experiments and App Store Product Page Optimization and what stays manual there. - **App previews and promo video**: `shots record` (web via Chromium screencast, `simctl io recordVideo`, `adb screenrecord`, or a folder of clips), `shots video` (886 x 1920 iPhone, 1200 x 1600 iPad, YouTube 9:16 / 16:9, captions from the screenshot copy in every script, silent or music track, poster, ffprobe checks), `apple previews` (preview sets per locale, MD5-idempotent, poster frame time code, files checked before upload), `play listing` sends `play.video` (Play takes a YouTube URL, no upload). - Contact sheet packed into a grid; Play 7" / 10" tablets drawn in an Android tablet body. - **MCP**: `shots_check` (read-only), `shots_capture`, `shots_render`, `shots_gallery`, `shots_record`, `shots_video` (local files, dry run by default), `apple_previews` (dry run by default); `variant` on the upload tools. - **Skills**: new `app-previews`; `store-screenshots` covers sources, iPad, checks, gallery, variants, experiments and the iPhone display class Apple now marks as required (6.9" stays the default, scaled down by Apple). - **Demo game** `examples/demo-game/` ("Prism Puddle", fictional, procedural art) that storebound renders for the showcase in this README; `--dry` on `shots render`, `shots video`, `shots gallery`. - README rewritten; `CONTRIBUTING.md`, `SECURITY.md`. Playwright stays a dev dependency; runtime dependencies stay 0. ## 0.2.0 · 2026-10-10 Works with any AI coding tool now, plus the release and review side of shipping. - **One source, many tools**: skills stay plain Agent Skills (`name` + `description` only); `tools/gen-adapters.ts` writes the Claude Code plugin + `.mcp.json`, Codex plugin, Gemini CLI extension + `GEMINI.md`, `AGENTS.md`, `.github/copilot-instructions.md` and a Cursor rule. CI fails when they are stale. Shared agent rules in `docs/agents-core.md`. - **`storebound mcp`**: stdio MCP server (2025-11-25), one tool per command, write tools dry run by default, nothing public or irreversible exposed. No SDK, still zero runtime dependencies. - **`storebound install --agent claude|codex|cursor|copilot|windsurf|gemini|opencode`**: skills + MCP entry into a game project, idempotent, never touches other entries, `--dry`. - New commands: `validate` (read-only readiness report for both stores, ordered blockers, exit 1), `apple release --phased on|pause|resume|complete` and `--manual-release`, `apple status --wait-review` (exit 0 / 1 / 7), `apple next-build`, `apple encryption`, `play rollout --fraction|--halt|--resume|--complete`, `play next-version-code`, `check --aso`, `whats-new --since `, `privacy-manifest --ios `, `version`. - New skills: review-rejections, portfolio-originality, release-management, store-preflight, privacy-manifest, export-compliance, aso-basics, whats-new. - Updated skills: select builds by explicit number, first availability record UI fallback, phased + manual release by default, IAP versions in review submissions (API 4.4.1, reported, verify), privacy manifest cross-links, tool-neutral CLI instructions. - Fix: Apple IAP description limit is 45 characters (App Store Connect Help), not 55. - Config: `apple.usesNonExemptEncryption`, `apple.screenshotTypes`, `ads.appAdsTxt`. - `bun run ci`: adapters check, tests, typecheck, secrets / personal data scrub. ## 0.1.0 · 2026-10-10 First public cut, extracted from the scripts I used to ship my own game. - `storebound` CLI: `init`, `check`; Play `products`, `listing`, `images`, `data-safety`, `release`, `status`; App Store `bundle`, `iap`, `iap-review`, `listing`, `screenshots`, `appinfo`, `review-details`, `status` (incl. canceling a review submission); `icons`; `shots capture | fonts | render | jpeg`. - Per-project `storebound.config.ts`; credentials only from env or `.storebound/credentials.json`. - Claude Code plugin + marketplace with 8 skills: play-first-release, appstore-first-release, store-iap-setup, store-screenshots, app-icons, store-declarations, privacy-policy, ads-levelplay-checks. - Templates: config, Play + App Store listing copy, screenshot copy (5 languages incl. Arabic + Japanese), Data safety CSV, App Privacy answers, Play declarations, studio-wide privacy policy page. - Tests for config validation, copy parsing, Data safety CSV, price mapping, script fonts, args.