storebound/README.md
CRT c9a7f095c4 console: agent-first mode (console plan / console verify), verified App Privacy and Play Create app facts
- `storebound console plan <form> [--json] [--lookup]`: read-only plan for an AI agent that drives the browser
  itself: start URL (deep links), ordered steps, answer sheet with the console's exact labels (certain / ask owner,
  curly quotes noted), never-press list, verified UI facts, driving guidance (in-page async loop + window.__log),
  verify instructions. All six forms.
- `storebound console verify <form> [--from-text <file|->]`: App Privacy per-type summaries from page text, Play
  Create app from input states, content rating / target audience summaries, App Store record and age rating from
  the API; exit 1 on a mismatch.
- MCP: console_plan, console_verify (read-only); console fill stays off MCP.
- App Privacy (verified 2026-10-12): two information steps, unlabeled purpose checkboxes (nearest parent text),
  "Definitions and Examples" never pressed; fixture and engine follow.
- Play Create app (verified 2026-10-12): /developers/<id>/create-new-app, App package name + read-only Check
  availability, material-radio inner [role=radio] click, app dashboard URL and play.appId, service account
  "Add app" as an owner step.
- console-autofill skill rewritten agent-first; agents-core rule 10; README; pitfalls 12, 18 (confirmed sign-out),
  19; CHANGELOG; play-first-release.
2026-10-12 08:39:12 +08:00

92 KiB

storebound logo

storebound

Website: storebound.dev · npm: storebound · bunx storebound init

npm license: MIT version 0.6.0 runtime: Bun runtime dependencies: 0 Agent Skills: 25 MCP: 2026-07-28

Take a mobile app or game from nothing to "Submit for review" on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire, account deletion and review access, privacy, icons, screenshots and previews, with one config and your AI coding agent.

Hey, I'm CRTkafa. I built this while getting my own game through both stores and hit every wall on the way: draft apps, edits that die when you touch the Console, the "first IAP must ship with a version" dance, Data safety CSVs that silently need one more answer, an IARC questionnaire, target audience, App Privacy labels that are saved but not published, screenshots in 7 sizes x 14 languages... I wrote each fix down as code or as a checklist. None of those walls is game-only: a to-do app with accounts and a subscription hits the same ones, plus a few of its own (a demo account for the reviewer, account deletion, subscription disclosures), so storebound covers apps and games alike. It works for me, so I open-sourced it. Use it too.

No account, no sign-up, no telemetry. A small Bun CLI that talks to the official store APIs, Agent Skills that walk your AI coding tool (or you) through the parts that are still console clicks, and an MCP server so agents can call the CLI safely. storebound never presses Submit, Release or Publish for you.

Pick a profile and start from honest templates: init --profile app (utility / productivity app, optional account, analytics, no ads; the default), --profile subscription-app (accounts + auto-renewable subscriptions) or --profile game (free-to-play with ads and IAP).

It keeps going after launch: TestFlight groups and testers, crash and ANR vitals against the store thresholds, review replies you approve before they go out, prices for many products and regions from one CSV, store analytics next to your listing changes, and App Store product page tests from your screenshot variants.

The journey: config, app records, products, listings, questionnaires, privacy and icons, store art, validate, then you press Submit

The journey

Everything an app or game needs before review, in the order I do it. For each step: what storebound does through the store APIs (or on your machine), what the skill walks your agent through where the stores have no public API, and what stays your own click.

  • storebound: a CLI command (also an MCP tool). Every write has --dry; MCP write tools default to a dry run.
  • skill + agent (UI): no public API exists for it. The named skill has the exact navigation and the prepared answers. An agent with browser tools (a Chrome extension, computer use, a Playwright MCP ...) can click through it while you watch; without one, you click with the skill as your checklist.
  • you: public, legal or money steps. The skills tell every agent to stop and hand these to you.
# step storebound (API / local) skill + agent (UI) you
1 Set up init --profile app|game|subscription-app (config, store/*.md, declarations, App Privacy and privacy templates per profile), install --agent <tool>, check Play: service account + API access invite; App Store: API team key (play-first-release, appstore-first-release) developer accounts; Paid Applications agreement, banking and tax; Play payments profile
2 App records and bundle id apple bundle registers the bundle id, apple status finds the app record App Store Connect > New App (no API); Play Console > Create app; the very first AAB upload when Play needs it from the Console the declarations you accept when creating the Play app
3 Builds and testers play next-version-code, play build, play upload --track internal, apple signing ensure, apple build, apple upload (waits for processing), apple encryption, apple testflight groups / what-to-test / info / distribute (internal groups) Play internal testers list, Apple sandbox testers (play-first-release, testflight) apple testflight testers (Apple emails the invitations), external groups and Beta App Review (distribute --submit-review)
4 In-app products and subscriptions play products (regional prices from USD + your overrides, activated), apple iap (consumable, non-consumable, non-renewing; localizations, price schedule, availability), apple iap-review, apple subscriptions (groups, levels, equalized prices, intro and promotional offers, offer codes), play subscriptions (base plans, regional prices, offers), catalog diff (both stores against one catalog) Play license testers, Apple sandbox testers, "Add for Review" of the first IAPs and subscriptions into the version's submission, Play promo codes (store-iap-setup, store-subscriptions) custom offer codes (apple subscriptions --codes asks first)
5 Store listings, every language play listing (title, short / full description, contact email + website, promo video URL), apple listing --version x --build N (name, subtitle, description, keywords, promo text, URLs, privacy URL, manual release), listing translate (drafts per language), whats-new, check (limits), check --aso Play store settings: category and tags (play-first-release)
6 Play questionnaires play data-safety: the whole Data safety form from dataSafety in the config (CSV import through the API) Policy and programs > App content, answers prepared in store/declarations.md (store-declarations): privacy policy, app access with the demo account (review-access), delete-account URL (account-deletion), ads, content rating (IARC), target audience and content, advertising ID, news, government, financial and health features; Data safety summary check the answers themselves are your statements: the agent asks when the code does not prove them
7 App Store questionnaires apple appinfo (categories, age rating declaration, content rights, Free price, availability), apple encryption (export compliance from apple.usesNonExemptEncryption, never guessed), apple review-details (contact, demo account from .storebound/, notes) App Privacy nutrition labels from store/app-privacy.md, then Publish (store-declarations); encryption documentation when the answer is yes (export-compliance); the first availability record if the API refuses it the export compliance answer (a legal statement, set once in the config)
8 Privacy and accounts privacy policy page template (init), privacy-manifest --ios <path> (PrivacyInfo.xcprivacy, required-reason APIs, SDK manifests), validate checks the policy, delete-account and terms URLs answer host the pages, link them in both consoles and inside the app; in-app account deletion (privacy-policy, privacy-manifest, account-deletion)
9 Icons icons: App Store 1024 without alpha, Play 512, adaptive + legacy Android launcher icons, web icons, Capacitor sources
10 Screenshots and previews shots capture / render / check / gallery, play images, apple screenshots, shots record / video, apple previews, apple ppo create / upload (App Store product page tests from shots variants; see Store art) Play store listing experiments (optional, store-screenshots) uploading the promo video to YouTube; apple ppo start / stop
11 Validate scan (offline: rejection risks in the project files, see Catch rejections before you submit), check (offline) and validate (read-only on both stores: every blocker in fix order, plus the local renders) the UI-only items validate cannot see (store-preflight lists them)
12 Submit play release --version-code N --production draft, apple status Play: countries / regions, open the production draft and save it (play-first-release) Play: Send changes for review. App Store: Submit for Review.
13 After review apple status --wait-review, apple release --phased on / pause, play rollout --halt, reply drafts (review-rejections) releasing the approved version, resuming or completing a phased release or rollout (terminal commands that ask first, release-management)
14 Live app health [--weekly] (crash / ANR vitals vs Play's thresholds, App Store diagnostics; also in validate), reviews list / draft, analytics snapshot / compare, prices export, prices import --dry, apple ppo status store-health, review-replies, store-analytics; Play statistics come from the reports bucket (no API) reviews send (approved drafts only), prices import (asks first)

New personal Play developer accounts also need a closed test with 12+ testers for 14 days before production; the play-first-release skill covers it.

API or console, honestly

  • Through the API (storebound does it): bundle id, builds on tracks, versions and build selection, listings in every language, graphics, screenshots, app previews, in-app products and prices (also from one CSV), Play Data safety, Apple age rating, categories, content rights, price and availability, export compliance answer, App Review details with the demo account, subscriptions (Apple groups, levels, prices, introductory and promotional offers, offer codes; Play base plans, regional prices, offers), phased release and staged rollout, TestFlight groups / testers / What to Test / Test Information / build processing / expiring builds, crash and ANR vitals and App Store diagnostics, TestFlight feedback, review replies, App Store analytics reports, sales and finance reports, App Store Product Page Optimization tests, IAP and subscription review submissions, offer codes for one-time purchases, win-back offers, promoted IAPs, pre-orders, custom product pages and in-app events, and pulling live listing texts back.
  • Console only (no public API; the skill + your agent's browser, or you): creating the App Store Connect app record and the Play app, Play App content (IARC content rating, target audience, ads, advertising ID, app access, government / financial / health / news, app access with the demo account), Play category and tags, Play countries, App Privacy labels and their Publish, Play promo codes, Play pre-registration, Play store listing experiments, test results of both stores' experiments, promotional images of promoted IAPs. Play store statistics have no API either: storebound reads the monthly exports from your Play reports bucket.
  • Only you: agreements, banking and tax, and the final Send for review / Submit for Review (apps, IAPs, custom product pages, in-app events). Releases and rollouts after approval, Beta App Review, tester invitations, sending review replies, applying price changes, starting / stopping a product page test, creating offer codes, win-back offers, switching on promoted IAPs, pre-orders and expiring builds too. None of these is an MCP tool; the terminal commands ask first.

Catch rejections before you submit

App Store rejections cost days: you wait for review, read one sentence in the Resolution Center, fix it, and wait again. On Google Play a policy problem can mean a blocked update, a removal or a strike on the account. Most of the usual ones are visible in the project long before that. storebound scan reads the project itself, offline and read-only (Xcode project, Info.plist and build settings, entitlements, PrivacyInfo.xcprivacy, Podfile.lock, Package.resolved, Cartfile, Gradle files and version catalogs, AndroidManifest.xml (and the merged manifest of a local build), res/xml and strings.xml, CMake / ndk-build files, Unity ProjectVersion.txt, package.json, Expo app.json, capacitor.config, Cordova config.xml, pubspec, Unity Packages/manifest.json, ProjectSettings and External Dependency Manager files, Godot export presets), plus your config and listing files. It works out the stack and the SDKs (ads, analytics, attribution, crash, sign-in, payments, push, with versions and the file:line they come from) and reports every rejection risk it can prove, each with the guideline, the evidence, the fix, a severity and the skill to open. On the teaser fixture (tests/fixtures/scan/teaser):

$ storebound scan
Rejection risks: 2 blocker, 3 high, 0 medium, 0 low
  1. BLOCKER 2.1(a) · App Store · Sign-in is required but there is no demo account for App Review
       at storebound.config.ts:12  reviewAccess: { signInRequired: true, instructions: 'Sign in with the demo account.' },
       at storebound.config.ts:11  accounts: { enabled: true },
       at Podfile.lock:3  Google Sign-In: GoogleSignIn 7.1.0
  2. BLOCKER 5.1.1(v) · App Store · Accounts can be created but no in-app account deletion was found
       at storebound.config.ts:11  accounts: { enabled: true },
       at Planner/LoginView.swift:11  Button("Create account") { Task { try await AccountApi.signUp() } }
       at Planner/LoginView.swift:17  static func signUp() async throws {
  3. HIGH    4.8 · App Store · Third-party login (Google / Facebook / ...) without Sign in with Apple or another equivalent login option
       at Podfile.lock:3  Google Sign-In: GoogleSignIn 7.1.0
       at Planner/LoginView.swift:9  GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController()) { _, _ in }
  4. HIGH    3.1.1 · App Store · Restorable purchases (non-consumables or subscriptions) but no Restore Purchases path was found
       at storebound.config.ts:16  com.example.planner.pro (non_consumable)
  5. HIGH    5.1.2(i) · App Store · Tracking-capable code without App Tracking Transparency (Google AdMob, IDFA)
       at Podfile.lock:2  Google AdMob: Google-Mobile-Ads-SDK 11.10.0
       at Planner/Ads.swift:7  print(ASIdentifierManager.shared().advertisingIdentifier)

Every finding also has a fix: line and the skill. Below the risks comes the declarations proposal: the Play Data safety and App Store App Privacy answers, App Tracking Transparency and privacy manifest entries the detected SDKs and code imply, each marked detected (file:line) or ask owner, as a diff against dataSafety / appPrivacy in the config. Nothing is written unless you run storebound scan --write (only detected answers; questions stay questions). The MCP tool scan is read-only and cannot write; validate includes the open scan findings; the desktop app shows them on the Checks tab. A finding you decided is fine goes into the config with its reason: scan: { ignore: [{ rule: 'demo-account', reason: '...' }] }; a rule with several checks (e.g. play-metadata) takes match: '<text in the finding title>' to accept just one of them.

rule App Store Google Play what scan proves
demo-account, play-app-access 2.1(a) App access a sign-in (config, auth SDKs, sign-in code) without reviewAccess or without the demo account in .storebound/
account-deletion, play-deletion-url 5.1.1(v) User Data: account deletion sign-up or social login without a deletion path in code or accounts.inApp; no accounts.deletionUrl
login-services 4.8 Google / Facebook / other third-party login without Sign in with Apple or another equivalent option
restore-purchases, external-purchase, play-payments 3.1.1 Payments non-consumables or subscriptions without a restore path; card / PayPal SDKs next to in-app products; "buy on our website" (Play: unless play.billingPrograms lists user choice billing or external offers)
subscription-info 3.1.2(c), 3.1.2 no Terms of Use URL, no Terms link or renewal wording in the description, no legal links or "Manage subscription" in the app
att 5.1.2(i) tracking-capable SDKs or IDFA without ATT; the prompt without its purpose string; the string without a prompt
purpose-strings, unused-permission 5.1.1(ii), 5.1.1(iv), ITMS-90683 a used permission without its NS*UsageDescription; empty or vague strings; declared but unused
privacy-manifest ITMS-91053 / 91061 no PrivacyInfo.xcprivacy, required-reason APIs not declared, SDKs older than their first manifest version
privacy-policy-link, play-privacy-policy 5.1.1(i) User Data: privacy policy no privacy link in the app's sources (Play: every app; medium when it collects data), an http:// link
privacy-labels 5.1.2 User Data: Data safety SDK data types missing from dataSafety; Play also: an answer narrower than the SDK's disclosure (purposes, sharing), SDKs with no dataSafety at all
play-prominent-disclosure User Data: prominent disclosure, Location permissions background location without a disclosure that says "location" and "background"; location, contacts, calendar or phone number access without any disclosure or rationale text in sources or strings.xml
play-background-location, play-photo-video, play-all-files-access, play-accessibility, play-install-packages, play-health-connect, play-full-screen-intent, play-vpn Location permissions, Photo and Video Permissions, All files access, AccessibilityService API, REQUEST_INSTALL_PACKAGES, Health Connect, Full-screen intent, VpnService the restricted permission or service in the manifest (or the merged manifest of a local build), each with the core-use test and the Play Console declaration it needs; isAccessibilityTool lowers the accessibility finding to medium
play-fgs, play-exact-alarm, play-query-all-packages, play-sms-call-log Foreground service types, exact alarms, package visibility, SMS and Call Log foreground services without a type or its permission (and the Console declaration); USE_EXACT_ALARM; QUERY_ALL_PACKAGES; SMS and call log permissions
kids-ads, play-families-ads, play-families-ad-id, play-families-location 1.3 Families a Kids Category age band with third-party ads or analytics SDKs; play.targetAges under 13 with an ad SDK that is not Families self-certified (or older than its certified version), the AD_ID permission in a child-only app (mixed audience: ad / attribution SDKs need child-directed treatment), location in a child-only app
play-ad-id Advertising ID targetSdk 33+ with ad / attribution SDKs and no AD_ID permission (medium when the merged manifest proves it is missing; Google Mobile Ads adds it itself)
play-16kb 16 KB page sizes (updates blocked from 2027-02-01) Unity before 6000.0.38f1 / 2022.3.56f1 / 2021.3.48f1, React Native before 0.77, NDK before r28 for the app's own C/C++ without the 16 KB flags, AGP before 8.5.1 with uncompressed native libraries; else prebuilt .so files to check
play-ads-launch Ads, Better Ads Experiences an interstitial shown from onCreate of the launcher activity (Android native; other engines: review-only)
play-testing-requirement Testing for new personal accounts play.accountType: 'personal' created after 2023-11-13 (12 testers, 14 days of closed testing before production); low when the account type is not set
other-platforms 2.3.10 "Android", "Google Play" and other stores in the App Store text or screenshot captions
metadata-keywords, kids-terms, play-metadata 2.3.7, 2.3.8 Metadata other apps' names, prices and "best / #1" in name, subtitle or keywords; keyword lists; "For Kids" outside the Kids Category. Play title over 30 characters, emoji, ALL CAPS, rank / price / promotion terms; other apps in the title or short description; repeated keywords or keyword lists in the full description
play-impersonation Impersonation a well-known brand in the package name or app name (low: a check, not a verdict)
placeholder-text 2.1(a) Metadata lorem ipsum, TODO, template lines in the listings
beta-wording 2.2 "beta", "demo", "trial version", "test build", "early access" in the app name (config, CFBundleDisplayName) or the App Store name, subtitle, keywords, promotional text and description ("demo account" is fine)
originality (with --originality) 4.3 Repetitive content a near-duplicate of one of your own apps (storebound originality scores: name, listing text, icon and screenshot hashes, project structure)
min-functionality 4.2 Capacitor server.url / Cordova <content src> on a remote site; a few native files around a remote WKWebView
deprecated-api, private-api 2.5.1 (ITMS-90809) UIWebView; App-Prefs: / prefs:root URL schemes
ipv6, ats 2.5.5, App Transport Security IPv4 literals in server URLs; NSAllowsArbitraryLoads
export-compliance export compliance no ITSAppUsesNonExemptEncryption and no apple.usesNonExemptEncryption, or the two disagree (never guessed)
review-prompt 5.6.1, 3.2.2(x) / 5.6.3 a custom "rate us" prompt without the system API; rewards for ratings
play-target-sdk Target API level below API 36 (Wear OS / Automotive 35, TV / XR 34) for new apps and updates since 2026-08-31
play-package-name Play Console upload the reserved com.example prefix

storebound scan --originality [--against <folders>] adds the Guideline 4.3 / Play repetitive content check against your other apps to the same report (folders default to originality.against in the config; storebound originality stays the command with every score and suggestion).

Rules cite Apple's App Review Guidelines and the Play Policy Center / Play Console Help pages they come from (checked 2026-10-11, in the code comments of cli/lib/scan/rules.ts and cli/lib/scan/play-rules.ts); the SDK table with each vendor's data disclosure page and Google's Families self-certified ads SDK list is cli/lib/scan/sdks.ts. Play config the rules read: play.targetAges (Target audience age groups), play.accountType / play.accountCreated, and play.billingPrograms. When the project was built locally, scan also reads the merged manifest under build/intermediates (the permissions libraries add); otherwise it says that it could not. Not a rule because no Play policy page covers it: device admin apps (BIND_DEVICE_ADMIN); not a policy at all: Play Integrity / anti-tamper.

Checked at review, not by scan (no file shows them): crashes and hangs on the reviewer's device (2.1, store-health), links that are dead at review time (validate checks the configured URLs), screenshots and descriptions that do not match the app (2.3, store-screenshots), unfinished UI (4.0), "too simple" and spam / template apps (4.2, 4.3, portfolio-originality), IAPs that fail in the sandbox (2.1(b), store-iap-setup), the content of the privacy policy (privacy-policy), local law (5.2). On Google Play: whether a restricted permission really serves the app's core purpose (the Console declaration and its video decide), the wording and timing of a prominent disclosure, ad placements in Unity, Flutter or React Native code, the Health apps declaration answers, and the alignment inside a prebuilt .so (scan lists them; zipalign -c -P 16 and check_elf_alignment.sh check the built app). The review-rejections skill covers what to do when one of these comes back.

5-minute quick start

You need Bun 1.1+ (and ffmpeg for icons, screenshots and video).

cd my-app
bun add -d storebound playwright   # or: bun add -d git+https://git.crt.fyi/CRT/storebound.git playwright
bunx storebound init               # --profile app (default) | subscription-app | game: config, store/*.md, declarations, privacy policy, .storebound/ (gitignored)
# fill in storebound.config.ts and the store/ files
bunx storebound check              # offline: config, copy limits, Data safety answers, products, files

Add credentials (below), then walk the journey with dry runs first:

bunx storebound apple bundle --dry
bunx storebound play listing --dry
bunx storebound play products --dry && bunx storebound apple iap --dry
bunx storebound apple subscriptions --dry && bunx storebound play subscriptions --dry
bunx storebound apple review-details --dry   # contact + demo account from .storebound/ (masked) + notes
bunx storebound play data-safety --dry --csv store/data-safety.csv
bunx storebound apple appinfo --dry
bunx storebound icons --dry
bunx storebound shots capture && bunx storebound shots render && bunx storebound shots gallery
bunx storebound validate           # read-only: what still blocks a submission, per store, in fix order
bunx storebound catalog diff       # read-only: products + subscriptions on both stores vs the config, prices per region

After launch:

bunx storebound apple testflight what-to-test --build 42 --dry   # then distribute --build 42 --group Team
bunx storebound health --weekly                                  # vitals + diagnostics, .storebound/health/<date>.md
bunx storebound reviews draft                                    # reply drafts in .storebound/replies/, then reviews send
bunx storebound prices export && bunx storebound prices import --dry
bunx storebound analytics snapshot && bunx storebound analytics compare

Or ask your agent: "take my app to Google Play and the App Store", "fill in the store questionnaires", "set up the in-app purchases and a yearly subscription with a free trial on both stores", "do both stores sell the same catalog", "the reviewer could not sign in", "make store screenshots in 5 languages", "is my app ready to submit", "why was my update rejected", "put build 42 on TestFlight", "how are our crash rates", "answer the new reviews", "did the new screenshots help". The matching skill takes over.

Coming from fastlane

storebound import fastlane turns an existing fastlane setup into storebound files. It only reads (no Ruby is run), never overwrites anything and prints what it could not map:

bunx storebound import fastlane --dry      # the plan + report; default --dir: fastlane/, ios/fastlane/, android/fastlane/
bunx storebound import fastlane            # writes the files below; an existing file gets a <name>.imported.<ext> copy
bunx storebound init                       # adds the remaining templates, keeps the imported files
bunx storebound check
fastlane storebound
Appfile app_identifier, package_name apple.bundleId, play.packageName
deliver metadata/<locale>/*.txt (+ default/) store/appstore.md per App Store locale; URLs, copyright, categories and review notes in the config
supply metadata/android/<lang>/*.txt, newest changelogs/<versionCode>.txt store/listing.md per Play language
deliver and supply language folders languages[] with both store codes (tr + tr-TR, zh-Hans + zh-CN, he + iw-IL)
screenshots/<locale>/, images/phoneScreenshots/, tenInchScreenshots/ store/shots/frames/<lang>/<phone|tablet>/NN.png with a folder capture source
supply icon, featureGraphic play.icon, store/renders/<lang>/feature/1024x500.*

Keys and accounts are never copied: json_key_file, the API key, team ids, the App Review contact and demo account are named in the report with where they go (.storebound/credentials.json). Submission and release flags, price_tier, the age rating config, lanes and signing (match) are listed with their storebound counterpart. The migrate-from-fastlane skill walks an agent through it.

Credentials (never in the repo)

Read from environment variables, or from .storebound/credentials.json in your project (gitignored by init):

name what
PLAY_SERVICE_ACCOUNT path to the Google Cloud service account JSON key (or the JSON itself) with access to your app in Play Console
ASC_KEY_ID, ASC_ISSUER_ID App Store Connect API team key (Users and Access > Integrations)
ASC_P8 path to AuthKey_<id>.p8 (default ~/.appstoreconnect/private_keys/AuthKey_<id>.p8)
ASC_VENDOR_NUMBER vendor number for reports apple-sales / apple-finance (Payments and Financial Reports)
ANDROID_KEYSTORE, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD upload key for play build (path to the .jks / .p12 and its passwords; handed to Gradle through the environment)
reviewContact (file only) App Review contact name / phone / email
reviewDemoAccount / REVIEW_DEMO_USER, REVIEW_DEMO_PASSWORD demo account for App Review (reviewAccess.signInRequired); never in the config or the repo, masked in logs

Several store accounts on one machine: credential profiles (~/.storebound/credentials/<profile>.json, the same names except the per-app upload keystore) picked with --account <profile> or by a workspace (Many apps and studios).

Install for your AI tool

Every route gives the agent the same skills and the same MCP tools. Nothing here requires Claude: the skills are plain Markdown and the CLI is a normal command.

tool install
Claude Code /plugin marketplace add https://git.crt.fyi/CRT/storebound.git then /plugin install storebound@crtkafa (skills, MCP server, storebound on PATH). Or per project: bunx storebound install --agent claude
OpenAI Codex bunx storebound install --agent codex (skills in .agents/skills, MCP in .codex/config.toml; Codex reads that file for trusted projects). The repo is also a Codex plugin (.codex-plugin/plugin.json, skills only)
Gemini CLI gemini extensions install https://git.crt.fyi/CRT/storebound.git (skills, MCP server, GEMINI.md). Or bunx storebound install --agent gemini
Cursor bunx storebound install --agent cursor (skills in .agents/skills, .cursor/mcp.json); the repo's .cursor/rules/storebound.mdc is an agent-requested rule you can copy
VS Code / GitHub Copilot bunx storebound install --agent copilot (skills in .github/skills, .vscode/mcp.json)
Windsurf (Devin Desktop) bunx storebound install --agent windsurf (skills in .agents/skills, MCP in your global mcp_config.json)
OpenCode bunx storebound install --agent opencode (skills in .opencode/skills, opencode.json)
anything that reads AGENTS.md copy or link AGENTS.md (rules + skill index) and use the CLI

install takes several agents at once (--agent codex,cursor), --dry to preview, --no-skills / --no-mcp, and --command "<cmd>" to choose how the MCP server starts. It only adds or updates the storebound entry and its own skill folders; everything else in those files stays as it is, and re-running it is safe.

Commands

Every write command takes --dry; re-runs are idempotent; output is one timestamped line per step, so long jobs can run in the background with a log (storebound shots render > .storebound/render.log 2>&1 &).

Setup and checks
command kind what
init [--profile app|game|subscription-app] local copy the templates of a profile (default app), gitignore .storebound/
import fastlane [--dir fastlane] [--profile ...] local config, store/appstore.md, store/listing.md and screenshot frames from a fastlane setup; never overwrites, reports what is not mapped
install --agent <tool> local skills + MCP entry for an AI tool
mcp local stdio MCP server wrapping the commands
serve --stdio local JSON-RPC session for the storebound desktop app (see Desktop machine interface)
inspect [--json] local offline summary of the config: name, store ids, languages, products
check local config, copy limits, data safety, products, subscriptions, profile (delete-account URL, demo account, terms URL), files; warns on untranslated release notes
catalog diff [--store] [--threshold 10] API, read-only one catalog vs both stores: missing / extra / mismatched products and subscriptions, US and override prices, locales, offers, per-region price differences between the stores; exit 1 on mismatches
check --aso local keyword audit: App Store keyword field rules, Play stuffing / claims
scan [--store apple|play] [--json] [--write] [--originality --against <folders>] local, read-only rejection risks in the project itself (stack, SDKs, Info.plist, manifest, listings) with guideline, evidence file:line, fix and skill; Data safety / App Privacy / ATT / privacy manifest proposal as a diff (--write adds the detected answers to the config); exit 1 on blockers
validate [--apple] [--play] API, read-only readiness report with ordered blockers plus the local renders checks (--no-local skips); exit 1 while any blocker is left
originality --against <folders> [--json] local, read-only Guideline 4.3 check vs your other apps: name, listing text, icon and screenshot perceptual hashes, project structure; scores, near-duplicate flags, suggestions; exit 1 when flagged
whats-new [--since <tag>] local draft player-facing release notes from git log into both listing files
whats-new --languages en,de,tr / check / apply local one file per language (store/whats-new/) for your agent (or a --translate hook of yours) to translate, per-store limits (Play 500, App Store 4000), then into both listing files
listing translate --to de,ja [--from en] [--translate "<cmd>"] local per-language listing sections in both listing files (App Store name, subtitle, promotional text, keywords, description; Play title, short, full) for your agent (or a hook) to translate, marked as drafts until reviewed; keywords cleaned up
listing check / listing reviewed --lang <ids> local every language section against the store limits and keyword rules, untranslated and unreviewed drafts (exit 1 on a problem) / remove the draft marker after a human read it
flow list | validate | run <name> [NAME=value] [--dry | --apply] [--from <step> | --resume] local + whatever the steps call repo-local workflows (store/workflows/*.yml): steps in order with progress; writes run as dry runs unless --apply, owner steps are listed as your click and never run (see Repo-local workflows)
catalog pull [--write] API, read-only + local IAP and subscription texts on the stores vs the config: a proposal diff (.storebound/pull/catalog.json); the config is never rewritten
ads scan --ios <path> local AdAttributionKit / SKAdNetwork: ad network ids, postback copy endpoints, attribution windows and cooldown, conversion value updates; exit 1 on a blocker
privacy-manifest --ios <path> local PrivacyInfo.xcprivacy, required-reason APIs, SDKs without manifests
api list | coverage | apple|play <operation> API every App Store Connect / Google Play operation (see Every endpoint); GET runs, writes are dry unless --yes in a terminal
desktop [--download] local open the storebound desktop app (in an app folder: add or select it there); not installed: the download for this system, --download saves it to ~/Downloads after checking its signature (never installs)
doctor [--json] local which local tools are here (ffmpeg, Chrome, playwright, Xcode, adb, git) and how to install the missing ones
icons local App Store 1024 (no alpha), Play 512, web, Capacitor sources, Android legacy launcher icons
Google Play
command kind what
play listing [--include-drafts] API app details, title / short / full description per language, promo video URL (play.video); prints the plan first (old -> new per field, draft languages skipped), --dry stops there, the real run asks and sends only changes
play listing pull [--write] API, read-only + local live listings back into store/listing.md: diff first, writes only with --write
play images [--variant b] API icon, feature graphic, phone / 7" / 10" screenshots, one edit per language
play products API one-time products: regional prices from convertRegionPrices + your overrides, activate
play subscriptions [--csv] API subscriptions: listings + benefits, one auto-renewing base plan, regional prices, intro and extra offers, activate (plan first, asks)
play data-safety API Data safety form (CSV import); --csv writes the file
play build [--version-code N] [--flavor f] local + API read Gradle bundleRelease (Flutter flutter build appbundle; Capacitor, React Native, Unity export found) with the next free versionCode and the upload key from env / .storebound (passed through the environment, never argv); AAB checked and copied to build/android/
play upload [<aab>] --track internal|alpha|beta API AAB to a testing track with release notes; versionCode checked first; --dry is read-only, a real run asks
play release <aab> API upload + tracks (--internal completed --production draft, --production inProgress --fraction 0.05), release notes
play rollout API --fraction x, --halt, --resume, --complete (asks first, except halt)
play next-version-code API, read-only next free versionCode
play status API, read-only tracks, listings, products
App Store
command kind what
apple bundle API register the bundle id, find the app record
apple listing --version x.y.z [--build N] [--include-drafts] API prints the plan first (version: keep / update / create / refuse with the reason; per locale create / update, old -> new per field; draft locales skipped), --dry stops there, the real run asks and applies exactly it: version (manual release), the build with exactly that number, texts per locale (name: the locale's ### name, else apple.name, else app.name)
apple listing pull [--version x.y.z] [--write] API, read-only + local live texts back into store/appstore.md: diff first, writes only with --write
apple screenshots [--variant b] API 6.9" + 6.5" (+ 13" iPad with apple.ipad, + 6.3" with apple.iphoneMedium) per locale, MD5-idempotent
apple previews [--poster 5] API app preview videos per locale, checked first, MD5-idempotent, poster frame
apple iap / apple iap-review API IAPs (consumable, non-consumable, non-renewing), localizations, price schedules, availability / review screenshots
apple iap-submit [--with-version] [--no-submit] API IAP / subscription / group versions into a review submission (the first of a type together with the app version); asks before adding and before submitting
apple offer-codes [--codes] API offer code campaigns for one-time purchases; custom and one-time-use codes only with --codes (asks; codes saved to .storebound/offer-codes/)
apple win-back API win-back offers for lapsed subscribers (asks: they go live on their start date)
apple promoted [--enable] API promoted in-app purchases: create (disabled), order, switch on (asks)
apple preorder status | enable --release-date | end API pre-orders per territory (enable / end ask first)
apple cpp list | create | upload | submit | visible API custom product pages from the config and a shots variant; submit / visible ask first
apple events list | create | upload | submit API in-app events with schedule, localizations, card and details images; submit asks first
apple subscriptions [--csv] [--codes] API groups, subscriptions, levels, localizations, availability, equalized prices + overrides + CSV (scheduled changes), introductory and promotional offers, offer code campaigns, review screenshots (plan first, asks; custom codes only with --codes)
apple appinfo API categories, age rating, content rights, Free, territories; plan first (old -> new), only changes are sent
apple review-details API App Review contact, demo account (from .storebound/, masked), notes (+ sign-in steps, where account deletion is)
apple encryption API export compliance answer from apple.usesNonExemptEncryption (never guessed)
apple release --phased on|pause|resume|complete, --manual-release API phased release, release an approved version (resume / complete / release ask first)
apple signing status API, read-only bundle id capabilities, distribution certificates (with / without a private key on this Mac), App Store profiles per bundle id, installed or not
apple signing ensure [--keychain] [--no-install] API missing capabilities, a distribution certificate from a key made here (kept 0600 in .storebound/signing/, never printed), App Store profiles installed for Xcode; dry first, asks; Developer ID needs the Account Holder (403 explained)
apple build [--version x.y.z] [--build-number N] local + API read xcodebuild archive + exportArchive with a generated ExportOptions.plist (app-store-connect, team, signing style, profiles) and the API key; next build number; Capacitor, React Native, Flutter, Unity projects found
apple upload [<ipa>] [--via api|altool] API IPA through the App Store Connect API build upload (or altool), build number checked, waits until VALID (exit 0 / 1 / 7); dry first, asks
apple next-build API, read-only next safe build number
apple status API versions, builds, IAPs, submissions; --wait-review (exit 0 / 1 / 7); --cancel-submission <id>
apple ppo status | create --variant b | upload --variant b API Product Page Optimization test from shots variants: treatments, localizations, screenshot sets
apple ppo start | stop API start / stop the test (asks first; a stopped test cannot be restarted)
TestFlight
command kind what
apple testflight status API, read-only groups, testers and builds per group, recent builds with internal / external beta states
apple testflight groups API create the groups of apple.testflight.groups that are missing
apple testflight testers [--csv f] [--remove] [--invite] API testers from a CSV in .storebound/ into their groups (asks first; emails masked)
apple testflight what-to-test --build N API "What to Test" per locale from store/appstore.md (whatToTest, else whatsNew)
apple testflight info API Beta App Description, feedback email, URLs, Beta App Review contact and notes
apple testflight distribute --build N --group a,b [--submit-review] API the build to groups; external groups and Beta App Review ask first
apple testflight wait --build N [--timeout 60m] API, read-only until processing ends, with progress; exit 0 VALID / 1 FAILED / 7 timeout
apple testflight expire --keep N | --older-than D API expire old builds (never attached or processing ones); dry run, then asks
After launch
command kind what
health [--weekly] [--days 7] API, read-only Play vitals (user-perceived crash / ANR vs 1.09 % / 0.47 %, devices, top issues), App Store metrics, diagnostics, TestFlight crashes; --weekly writes a Markdown report
reviews list [--unanswered] API, read-only recent reviews of both stores
reviews draft API + local one reply draft per unanswered review in .storebound/replies/
reviews send API sends drafts with approved: true (checks length and incentives, asks first)
prices export [--no-subscriptions] API + local current prices of every product and subscription (Apple current + scheduled, Play base plan regions), both stores, as store/prices.csv; subscription rows feed apple subscriptions --csv / play subscriptions --csv
prices import [--reconvert] API diff table, then (asks first) Apple price schedules incl. future-dated changes and Play regional prices
analytics setup API App Store analytics report request (once, Admin key)
analytics snapshot [--days 28] API + local impressions, page views, downloads (App Store), visitors, acquisitions, installs (Play reports bucket) -> .storebound/analytics/
analytics compare local snapshots vs the git history of the store files: before / after each listing change
reports apple-sales | apple-finance | play-earnings | play-sales API, read-only + local sales / finance reports into .storebound/reports/, summary per product and currency, CSV export
triage [--days 14] API, read-only TestFlight crash and screenshot feedback, Play error reports: grouped by signature with devices, OS versions, builds
aso rank [--storefronts us,de] network + local App Store rank per keyword and storefront plus competitors from the public iTunes Search API (rate limited) -> .storebound/aso/
aso competitors [--term <kw>] [--storefront us,tr] [--top 10] network + local top apps per keyword (name, developer, rating count, first release, last update, localized genre; no subtitles: not in any public API) and the words in their names you do not cover, same rate limit -> .storebound/aso/competitors/
aso suggest [--locale en-US] [--write] local (+ Apple Ads API with credentials) keyword candidates (own text, competitor names, localized category names, Apple Ads keyword suggestions with their relative 0-100 popularity) scored by relevance and competition, and a 100-byte keyword field per locale as a diff; --write puts it into store/appstore.md as a draft for review. Popularity is never estimated
aso audit local one report: check --aso, free keyword room, rank trend arrows, competitor gaps, Play short description and density, App Store conversion from the analytics snapshots, PPO / Play experiment ideas and the next 3 actions
Screenshots and video
command kind what
shots capture [--source web|ios-sim|android-emu|folder] [--concurrency N] [--only-missing | --force] local raw frames from your running app or game; web: parallel contexts, optional one boot per language (capture.session), skips unchanged frames, timing per step
shots fonts network font subsets for your copy (Noto for CJK / Arabic / Cyrillic / Devanagari ...), your own local font files, and the glyph coverage check per language
shots render [--variant b | --variants] [--concurrency N] local every store size and language in the game or app style, feature graphic, contact sheet, overflow report
shots jpeg local JPEG q92 copies of the Play renders (about 4x faster uploads)
shots check [--variants] [--json] local, read-only store rules: sizes, alpha, format, bytes, counts, overflow, fallback / missing glyphs, videos, folder frames
shots gallery local <renders>/index.html review page
shots record [--source ...] local app or gameplay clips: web (Chromium screencast), simulator, emulator, folder
shots video [--target iphone,ipad,youtube] local app previews + promo video with captions, poster, checks
Many apps and release trains
command kind what
ws init local find the app folders below and write storebound.workspace.json (globs like games/*)
ws list [--all] local apps (folders + store-only apps), studios, accounts and whether each credential profile has its keys
ws status [--offline] [--apps a,b] API, read-only portfolio table: every app x both stores (live / rollout % / next version), catalog parity, check; parallel
ws check, ws validate [--apps a,b] local / API, read-only the offline check / the readiness report of every app in one table
ws run <command> --apps a,b | --all [--apply] per command any read command in every app; writes as dry runs unless --apply; never submit / release / rollout / send
ws apps import --account <profile> API, read-only + local cache the apps a store account sees, paired with the folders (the desktop's rules)
ws desktop export [--out f | --merge [--apply]], ws desktop import local the workspace to and from the desktop app's state (labels, never keys)
release status [--app a,b] [--versions] API, read-only both stores on one train per version: build > testing > review > live > rollout %
release plan <version> [--app a] API, read-only what is left for that version on each store, in fix order, with the owner's next step

Growth extras

Optional commands for after (or around) the first release. Each has --dry and --json; store and API writes ask in a terminal first and are never offered over MCP.

  • Purchasing power prices: storebound prices ppp [--base 4.99] [--source worldbank|bigmac|file] proposes a price per region from the bundled World Bank ICP PPP conversion factors (cli/data/ppp-worldbank.json, CC BY 4.0, dated and cited; refresh with bun tools/update-ppp.ts), clamped to prices.ppp.floor..ceiling of the US price and snapped to each store's price points. It writes a sheet in the prices import format (default store/prices-ppp.csv) and shows the diff; storebound prices import --csv store/prices-ppp.csv --dry applies it. Skill: ppp-pricing.
  • Apple Ads: storebound ads apple status | campaigns | report [--by campaign|keyword] [--csv out.csv]: spend, taps, installs, CPA, read-only (OAuth client credentials from env / .storebound/, never printed). bid, budget and pause print old -> new and ask first. Skill: apple-ads.
  • Web presence: storebound web init [--landing] writes apple-app-site-association, .well-known/assetlinks.json (Play app signing SHA-256), the Smart App Banner tag and an optional minimal landing page (store marks from simple-icons, privacy / support links from the config). storebound web check <domain> verifies the live files: 200 without redirects, JSON content type, valid JSON, ids match the config. Skill: web-presence.
  • Ad-hoc and internal sharing: storebound apple adhoc devices | register | profile | options | export (UDIDs masked; registering asks first) and storebound play share --aab app.aab (Play internal app sharing; asks first, prints the install link). Skill: adhoc-distribution.
  • RevenueCat (optional): storebound revenuecat diff | sync mirrors products, subscriptions, entitlements and a default offering into a RevenueCat project (REST API v2, secret key from env / .storebound/). sync only creates and attaches, never deletes. Runtime entitlements, receipts and paywalls stay RevenueCat's job. Skill: revenuecat-sync.
  • Console forms without an API, agent-first: storebound console plan <form> [--json] gives your AI agent the start URL (deep links when the ids are known), the steps, the answer sheet from the config (each answer certain or "ask owner", with the console's exact labels), the buttons it must never press and the UI facts verified on the real consoles; the agent drives the page with its own browser tool (Claude in Chrome, a built-in browser, Playwright), stops before the final button and tells you what to press. storebound console verify <form> [--from-text -] then checks the page against the config (App Privacy per-type summaries, Play Create app input states, summaries, or the App Store Connect API) and exits 1 on a mismatch. Both are read-only MCP tools (console_plan, console_verify).
  • Scripted fast path: storebound console fill <form> (apple-app-record, apple-age-rating, apple-app-privacy, play-app-record, play-content-rating, play-target-audience) opens a visible browser, goes to the form by itself (Apps > + > New App, the app's App Privacy, Play All apps > Create app, Play app list > App content > Start / Manage; navigation clicks only, never Save / Submit / Publish / Create / Next), fills what the config proves, highlights the rest and stops before the final button: you click. Sign-in: storebound's own profile (one per store, ~/.storebound/console-profile/<store>): you sign in there once by hand, it is kept between runs (session restore on launch, graceful close; tick "Keep me signed in" / "Stay signed in"). storebound starts that Chrome itself as a normal window (no --enable-automation, navigator.webdriver false) so Google accepts the sign-in; if Google still answers /signin/rejected it says so. A Chrome running with remote debugging is used in place. It never copies your Google cookies by default: Google binds them to your device (Chrome's Device Bound Session Credentials) and signs out sessions whose cookies look copied, which can sign you out of your real Chrome. --use-chrome-session google|apple is an explicit opt-in (a temporary copy of only that store's cookies, deleted at the end) that prints this warning; see the console-autofill skill. It detects sign-in pages by URL, frame and text (Apple's starts with no password field), waits while you sign in and then opens the form from step 1; after printing manual steps it keeps watching and picks up where you are. One login, many studios: it finds the Play developer account (by play.packageName, or play.developerId) and the App Store Connect team (account menu; apple.teamId, or by bundle id) that has the app and remembers it in ~/.storebound/console-accounts.json (ids only). Play Create app (play-app-record, the Play Developer API cannot create apps): the developer account comes from play.developerId, the workspace account, or play.developerAccount (its name on "Choose a developer account"); with several accounts and none set it stops and lists them. It fills App name (play.name or app.name, 30 characters), Default language, App or game (the profile) and Free or paid (play.free, else Free for IAP / ads: an owner check, a free app can never become paid). It never ticks the Developer Program Policies or US export laws declarations and never presses the form's Create app: you do. With play.developerId it opens the form's own URL (/console/u/0/developers/<id>/create-new-app); it fills App package name from play.packageName and presses the read-only "Check availability", and clicks the inner [role=radio] of Play's material radios (the native input does not update the form). After Create app, add the new app to your service account yourself (Users and permissions > the service account > Add app): app-level permissions do not cover apps created later. When a page has changed it falls back to step-by-step guidance with copyable answers. Console pages change; this is AI-assisted: review every field and save it yourself. Desktop: "Fill in console" on the App info tab. Never over MCP. Skill: console-autofill.

MCP gets only the safe parts: prices_ppp, web_init, apple_adhoc_options (local files, dry run by default) and the reads ads_apple_status, ads_apple_campaigns, ads_apple_report, web_check, apple_adhoc_devices, revenuecat_diff.

Many apps and studios

A studio with twenty games, or a developer with apps under two store accounts, works from one folder above the app folders. storebound ws init writes storebound.workspace.json:

{
  "apps": ["games/*", { "path": "apps/notes", "apple": "solo-apple" }],
  "studios": [
    { "name": "Acme Games", "color": "indigo", "apps": ["games/*"],
      "accounts": [{ "store": "apple", "profile": "acme-apple" }, { "store": "play", "profile": "acme-play" }] }
  ],
  "accounts": [{ "store": "apple", "profile": "solo-apple", "label": "Solo" }],
  "ignore": ["games/old-*"],
  "overrides": { "pairs": [], "unpairs": [], "links": [], "appStudio": [] },
  "concurrency": 4
}
  • apps: folders or globs (only folders with a storebound.config.* count; node_modules and dot folders are skipped). An object entry names the folder's own credential profiles.
  • studios: name, colour (the desktop's set), the store accounts and the app folders (globs) that belong to it.
  • accounts: store accounts without a studio. An account is a store and a credential profile name, never a key.
  • ignore: folder globs, app:<store app key> and account:<profile>, as in the desktop's ignore list.
  • overrides: the desktop's pairing corrections (pairs, unpairs, links of a store app to a folder, appStudio), with folders named by their path. A storebound.workspace.ts with export default { ... } works too.

Which keys an app uses: its own entry's profiles, else the single account of that store in its studio, else the single account of that store in the workspace, else the folder's own credentials (env or .storebound/).

Credential profiles keep many accounts apart on one machine: ~/.storebound/credentials/<profile>.json with the account names of .storebound/credentials.json (PLAY_SERVICE_ACCOUNT, ASC_KEY_ID, ASC_ISSUER_ID, ASC_P8, ASC_VENDOR_NUMBER; the Android upload keystore belongs to the app and stays in the project; relative key paths are relative to that folder, which STOREBOUND_CREDENTIALS_DIR can move), or env STOREBOUND_PROFILE_<NAME>_<KEY> (for CI). Any command takes --account <profile>. ws list shows per account whether its profile has keys; no command prints a key.

ws status, ws check, ws validate and ws run read the apps a few at a time (concurrency, 1-8, or --concurrency), each app with its own keys handed to its child process only. ws run takes any read command and the MCP tool set; write commands get --dry unless --apply, and submit, release, rollout, send, start / stop, testers and --yes are refused: those stay with the owner in the app folder.

ws apps import --account <profile> lists the apps a store account sees (as apps list) into .storebound/workspace/ and pairs them with the folders by the desktop's rules (cli/lib/identity.ts, the same code): a folder takes the store apps its config names, the remaining iOS and Android apps pair by id, close id or name, unpairs keep two apart, and the rest are store-only apps that ws status reads too.

The desktop app keeps the same model (studios, accounts, folders, overrides). ws desktop import turns its state.json (or --state <file>) into a workspace file and lists the credential profiles to create: the desktop's keychain is never read. ws desktop export prints the workspace as a desktop state (--out <file>), and --merge --apply adds it to the desktop's state.json while the app is closed (a backup is kept; nothing is removed; new accounts get their keys in the desktop's Accounts screen).

MCP: workspace_list (offline) and workspace_status (read-only).

Release trains

One version, both stores, one track: build > testing > review > live > rollout. The CLI and the desktop's Releases tab share the code (cli/lib/train.ts, cli/lib/portfolio.ts), so both show the same stage for the same store state.

$ storebound release status
app     version  train    App Store               Google Play
------  -------  -------  ----------------------  -----------------------------
Puzzle  2.3.0    current  rollout 20% (build 45)  rollout 10% (build 230)
Puzzle  2.4.0    next     build                   testing (build 240, internal)

release plan <version> lists what is left for that version on each store in fix order: config problems (check), the version on the store (how to create it when it is missing), store blockers (validate, while nothing is submitted), the next move and the warnings. Owner steps (submit for review, promote to production, release an approved version, raise or complete a rollout) are named with where the owner does them; storebound does not do them. A store it cannot read is "unknown", never "not there". Both commands work in an app folder (that app) and in a workspace (every app, or --app a,b); --json gives the structured result. MCP: release_status, release_plan (read-only).

Repo-local workflows

Your release routine as a file in the repo, run step by step. storebound init writes two to start from (store/workflows/release.yml, beta.yml; app and game versions):

# store/workflows/release.yml (app profile, shortened)
params:
  VERSION: "1.0.0"
steps:
  - id: next-build
    if: apple
    run: apple next-build
    capture: BUILD
  - id: build
    if: apple
    run: apple build --version ${VERSION} --build-number ${BUILD}
  - id: upload
    if: apple
    run: apple upload --no-wait
  # wait, play-build, play-upload, whats-new, listing, validate, scan, plan ...
  - id: submit
    owner: "App Store Connect: Submit for Review (version ${VERSION}, build ${BUILD}) ..."

The plan of that file (--dry: nothing runs; without it the reads run and ${BUILD} is filled in):

$ storebound flow run release VERSION=1.2.0 --dry
[flow]  1/12 next-build: storebound apple next-build  (plan only)
[flow]  2/12 build: storebound apple build --version 1.2.0 --build-number ${BUILD} --dry  (plan only)
[flow]  3/12 upload: storebound apple upload --no-wait --dry  (plan only)
[flow]  4/12 wait: storebound apple testflight wait --build ${BUILD} --version 1.2.0 --dry  (plan only)
[flow]  5/12 play-build: storebound play build --version-name 1.2.0 --dry  (plan only)
[flow]  6/12 play-upload: storebound play upload --track internal --dry  (plan only)
[flow]  7/12 whats-new: storebound whats-new apply  (if exists store/whats-new: false)
[flow]  8/12 listing: storebound apple listing --version 1.2.0 --build ${BUILD} --dry  (plan only)
[flow]  9/12 validate: storebound validate  (plan only)
[flow] 10/12 scan: storebound scan  (plan only)
[flow] 11/12 plan: storebound release plan 1.2.0  (plan only)
[flow] 12/12 submit: YOUR CLICK  App Store Connect: Submit for Review (version 1.2.0, build ${BUILD}). Play Console: promote the internal release and Send changes for review
  • Read steps (check, validate, scan, release plan, next-build, status) run; write steps run with --dry unless --apply, and even then each command asks its own confirmation; wait steps check once; sh: steps (your own build commands) run only with --apply. --dry prints the plan without running anything.
  • Owner steps (submit, release, phased release, rollout, review replies, price imports, tester invitations, Beta App Review) are never executed, also when written as run:; the flow ends with the list of your clicks.
  • capture: turns a step's last output line into a variable (${BUILD}), if: skips a step (apple, play, exists <path>, ${VAR} == x), flow: reuses another flow (private: true sub-flows), before / after / onError are hooks, continueOnError keeps going.
  • A failed step stops the flow; --resume starts again at that step with the captured variables (.storebound/flows/<name>.json), --from <step> anywhere. flow validate checks commands, variables, duplicate ids and sub-flow cycles. Several flows can also live in storebound.workflows.yml|json|ts.
  • MCP: flow_list and flow_run (the dry mode only; --apply stays a terminal command). Skill: release-flows.

Listing in more languages

storebound listing translate --to de,ja adds a section per language to store/appstore.md (name, subtitle, promotional text, keywords, description) and store/listing.md (title, short and full description) with the source text and a marker, <!-- storebound-translation from=en-US status=translate -->. Your agent translates in place (listing-localization skill: native wording, researched keywords, the store limits) and sets status=draft; or --translate "<command>" runs a command of yours once per field (source text on stdin, STOREBOUND_TARGET_LANG, STOREBOUND_FIELD, STOREBOUND_LIMIT in the environment). storebound itself calls no translation service. listing check enforces each store's limits (App Store subtitle 30, promotional text 170, keywords 100 without duplicates or name words, description 4000; Play title 30, short 80, full 4000) and lists drafts; storebound check warns while one is left. After a human read a draft, listing reviewed --lang de removes the marker. Reviewed sections are never overwritten without --force. MCP: listing_translate, listing_check.

Every endpoint

The commands above cover the journey. For anything else, storebound api calls every operation of the App Store Connect API (1303 operations, OpenAPI 4.5.1) and of the Google Play Developer APIs (Android Publisher v3: 145, Play Developer Reporting v1beta1: 25), generated from the official specs pinned in specs/ (source URLs, dates and SHA-256 in specs/SOURCES.json). Same credentials, same retries on 429 / 5xx (Retry-After honoured), JSON:API documents on the Apple side.

storebound api list --store apple --search "customerReviews"          # find an operation
storebound api apple apps_getCollection --param 'filter[bundleId]=com.example.app'
storebound api apple GET /v1/apps/123/appStoreVersions --all-pages      # METHOD path works too
storebound api play reviews.list --param packageName=com.example.app --all-pages --json
storebound api play POST /v1beta1/apps/com.example.app/crashRateMetricSet:query --body query.json
storebound api coverage                                                 # counts per API and resource, what commands wrap
  • GET runs right away (so do the Reporting API :query calls and other POSTs that only read).
  • Every write is a dry run by default: it prints the exact request (method, URL, body; the credential is never shown) and is only sent with --yes from an interactive terminal.
  • Owner-only operations (submit, release, delete, cancel, refunds, prices, testers and users, review replies, product page tests, Play edits.commit, app signing; the list is in cli/api/gate.ts) also ask you to type the operation id before anything is sent.
  • Agents read, never write: MCP has one tool for this, api_get (GET operations only). Under MCP or the desktop app no write goes out, whatever the flags.
  • Small on purpose: one table row per operation plus generic helpers (cli/api/), typed from the rows (path and required parameters, JSON:API request and response shapes). bun tools/gen-api.ts regenerates the tables and docs/api-coverage.md (every operation, its gate and the commands that already call it); CI fails when they are stale.

Store art

Screenshots and previews are one step of the journey, but the one that takes the longest by hand, so storebound does a lot here. Two looks: shots.style: 'game' (sunburst, sparkles, badges) and shots.style: 'app' (calm gradient, a clean device frame, headline + subline). Everything below was made by storebound itself from two tiny fictional demos: a game ("Prism Puddle", procedural art) and an app ("Tidy Lists", one static page with sample data): raw frames captured from the web build, captions in several languages, every store size, a review gallery and an app preview video. No store account needed to try it.

App style: four calm screenshots and the feature graphic of the demo list app, rendered by storebound

Store screenshots rendered by storebound: four scenes in English plus the hero scene in Turkish, Japanese and Arabic

Play feature graphic App preview (App Store, 886 x 1920)
Play feature graphic, 1024 x 500 A few seconds of the app preview video

The review gallery: every language, size, scene and variant on one local page, store check issues highlighted

Contact sheet of one language (every size, packed into a grid)

Contact sheet: iPhone 6.9" and 6.5", 13" iPad, Play phone, 7" and 10" tablets, feature graphic

How it works

  1. Frames. Give your app or game a few switches (language, demo data or a fixed seed, no ads, jump to a screen, freeze, autoplay) and let shots capture take the frames: Playwright for a web build, simctl for anything that runs in the iOS Simulator (Unity, Godot, Flutter, React Native, native, Capacitor), adb for an Android emulator or device, or bring a folder of your own frames. The status bar is cropped off and redrawn clean (9:41, full battery).

  2. Copy. Per language and scene: eyebrow, two-line title, subtitle, badges. Fonts are subset from Google Fonts to exactly your characters, so Japanese, Chinese, Korean, Arabic (right to left) and Hindi just work. shots fonts checks every character of every language against the font's cmap (TTF / OTF / WOFF / WOFF2, read in plain TypeScript): a glyph the font lacks gets an automatic Noto fallback (logged), no fallback is a clear error, and a render that drew any glyph with a fallback you did not choose, or with a system font, fails shots check.

    script title / body font (default stack)
    Latin incl. Latin Extended (tr, pl, cs, ro, hu ...) and Vietnamese your shots.fonts; default game Paytone One + Montserrat, app Inter (all cover them)
    Cyrillic, Greek Noto Sans (whole line)
    Arabic, Hebrew, Devanagari, Bengali, Thai Noto Sans Arabic / Hebrew / Devanagari / Bengali / Thai (whole line)
    Japanese, Korean, Chinese Noto Sans JP / KR / SC (TC for zh-Hant, TW, HK) (whole line)
    a character the stack lacks Noto Sans Display (titles) or Noto Sans (body), Noto Sans <Script>, then Noto Sans Symbols 2 / Symbols / Math: an ERROR in shots check unless listed in shots.fonts.fallback

    Some display fonts lack Latin Extended: Lilita One has no ş ğ İ ą ę ń ś ź ż ć ă ș ț č ř ě ů ő ű or Vietnamese, so Turkish, Polish, Czech, Romanian, Hungarian or Vietnamese titles need another title font (Paytone One, Baloo 2, Rowdies) or fonts: { ..., fallback: ['Noto Sans Display'] } to accept the mixed glyphs.

    Your own brand font. shots.fonts.title / body / fallback[] also take a local file (relative to the config; TTF, OTF, WOFF or WOFF2, checked by extension and signature) or { family, file, weight?, style?, license? }:

    fonts: {
      title: { family: 'ER Display', file: 'apps/game/public/assets/fonts/er-display.woff2', license: 'OFL-1.1' },
      body: 'Montserrat',
      fallback: ['Noto Sans Display'],   // tried in order, before the automatic Noto fallbacks
    },
    

    shots fonts runs the same cmap check on the file (pl title: ę ł ą Ł not in ER Display -> Noto Sans Display), prints its size, glyph count, sha256 and license, and records it in fonts.json. The file is never copied: render (and shots video captions) serve it from where it is, and a changed file changes fonts.css (the capture cache misses) and makes shots render ask for shots fonts again. shots check counts the local font and listed fallbacks as configured, not as fallbacks, and warns when a local font has no license note.

  3. Render. Every store size at exact pixels: iPhone 6.9" and 6.5", 13" iPad (if your app runs on iPad), Play phone, 7" and 10" tablets, the 1024 x 500 feature graphic. 24-bit RGB, no alpha (both stores reject it).

  4. Check and review. shots check applies the store rules locally (sources cited in cli/shots/spec.ts); shots gallery puts everything on one page with problems outlined. validate repeats the checks next to the store state.

  5. Variants. Other first screenshot, order or copy as shots.variants, rendered side by side, for a Play store listing experiment or an App Store Product Page Optimization test (set up in the consoles; storebound makes and checks the images).

  6. Video. shots record films the autoplay mode, shots video cuts 15-30 s App Store previews (886 x 1920, 1200 x 1600) and a 9:16 / 16:9 promo for YouTube with captions in every language, apple previews uploads them. Play takes a YouTube URL in play.video.

Details: the store-screenshots and app-previews skills.

Skills

In journey order. Each one says which steps are API (storebound) and which are console clicks, with the exact navigation.

skill for
migrate-from-fastlane storebound import fastlane: ids, listings, languages and screenshots from fastlane; keys and accounts stay out
play-first-release Google Play from zero to "In review"
appstore-first-release App Store from API key to "Waiting for review", incl. xcodebuild with the API key
build-and-sign API signing (capabilities, certificate, profiles), apple build / apple upload, play build / play upload, for native, Capacitor, React Native, Flutter and Unity projects
testflight beta groups, testers from a CSV that never enters git, What to Test, Test Information, Beta App Review, waiting for processing, expiring old builds
store-iap-setup in-app products on both stores (consumable, non-consumable, non-renewing), prices and the price sheet CSV, catalog diff, testers, review submissions through the API and the first-IAP-with-version rule
store-subscriptions subscription groups, levels, base plans, prices, intro and promotional offers, offer codes, disclosure and Terms (3.1.2), first submission
review-access demo account for reviewers (App Review fields, Play App access), never committed
account-deletion Apple 5.1.1(v) in-app deletion, Play delete-account web page and Data safety
aso-basics keyword field, subtitle, cross-localization, Play placement without stuffing, keyword ranks, competitors, keyword field proposals and the ASO audit (iTunes Search API, Apple Ads popularity with credentials)
app-store-promotion IAP offer codes, win-back offers, promoted IAPs, pre-orders (Play pre-registration: console guide), custom product pages, in-app events
store-declarations every questionnaire: Play Data safety (API) and App content (IARC, target audience, ads, advertising ID, app access, government / financial / health), Apple App Privacy labels and age rating
export-compliance encryption answer, Info.plist key, declarations; never exempt by default
privacy-policy one policy page for all your apps with a per-app services table (templates per profile)
privacy-manifest PrivacyInfo.xcprivacy, required-reason APIs, SDK manifests (ITMS-91053 / 91061)
app-icons one artwork to every icon, adaptive / legacy launcher pitfalls
store-screenshots capture from any engine, copy, fonts, render, checks, gallery, variants, App Store product page tests, Play experiments, upload
app-previews app or gameplay clips, App Store previews, Play promo video, checks, upload
store-preflight run and read check, check --aso, validate, privacy-manifest and release plan before submitting; ws check / ws validate for many apps
release-management updates: release trains (release status, release plan), Apple phased + manual release, Play staged rollout, halt / pause (not a rollback), review wait
store-preflight run and read check, check --aso, validate, privacy-manifest before submitting
store-api any App Store Connect / Google Play operation without a command: find it, read freely, writes dry until the owner sends them
release-management updates: Apple phased + manual release, Play staged rollout, halt / pause (not a rollback), review wait, pulling console edits back
store-health crash / ANR vitals vs Play's bad behavior thresholds, App Store diagnostics, weekly report, crash and feedback triage
review-replies reply drafts, tone, no incentives, store limits, the owner approves and sends
store-analytics App Store analytics reports, Play reports bucket, before / after listing changes, sales / finance / earnings reports
whats-new player-facing release notes from git, one file per language for your agent to translate, store limits checked
listing-localization the listing in more languages: drafts per language, native wording and keyword research, store limits, review before push
release-flows repo-local workflows (store/workflows/*.yml): release and beta routines with dry runs, resume and the owner's clicks
review-rejections triage a rejection (fix / clarify / push back / appeal), reply template, the guidelines apps and games hit
ads-mediation-checks AdMob, AppLovin MAX and LevelPlay: ids, debug tools, adapters, app-ads.txt, SKAdNetwork, AdAttributionKit (ads scan), no-fill (ads-levelplay-checks stays as a pointer)
portfolio-originality Guideline 4.3 gate for developers shipping many similar apps or games, with storebound originality scores

MCP server

storebound mcp speaks MCP over stdio, tools only: spec revision 2026-07-28 (stateless: server/discover, per-request _meta, resultType, cacheable tools/list) and, on the same process, clients that still open with initialize (2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05). It runs each tool as a CLI call in the project directory (the server's working directory, or STOREBOUND_PROJECT) and returns the output.

  • One tool per command. Read-only: scan, check, check_aso, aso_competitors (no snapshot saved), aso_suggest (never writes), aso_audit, validate, catalog_diff, health, reviews_list, prices_diff, analytics_compare, apple_status, apple_next_build, apple_testflight_status, apple_ppo_status, play_status, play_next_version_code, privacy_manifest, shots_check, workspace_list, workspace_status, release_status, release_plan, flow_list, flow_run (dry mode only), listing_check. Local writes: import_fastlane, whats_new, listing_translate, icons, play_status, play_next_version_code, privacy_manifest, shots_check, api_get (any App Store Connect or Google Play GET operation by id). Local writes: import_fastlane, whats_new, icons, health_report, reviews_draft, prices_export, analytics_snapshot, shots_capture, shots_render, shots_gallery, shots_record, shots_video. Store writes: play_listing, play_images, play_products, play_data_safety, play_subscriptions, play_release_testing, apple_bundle, apple_iap, apple_iap_review, apple_subscriptions, apple_listing, apple_screenshots, apple_previews, apple_appinfo, apple_review_details, apple_encryption, apple_phased_release, apple_testflight_groups, apple_testflight_what_to_test, apple_testflight_info, apple_testflight_distribute (internal groups only), apple_ppo_create, apple_ppo_upload, analytics_setup.
  • Annotations say what is read-only, destructive (replaces data), idempotent and network-bound.
  • No MCP SDK: a small newline-delimited JSON-RPC server (server/discover, initialize, ping, tools/list, tools/call, cancellation), so storebound keeps zero runtime dependencies.

Manual config for a tool that install does not know: command bunx with args ["storebound", "mcp"] (or the absolute path of bin/storebound plus mcp).

Desktop machine interface

storebound serve --stdio is what the desktop app (apps/desktop, in progress) runs as its sidecar: newline-delimited JSON-RPC on the same core as the MCP server (tool list, validation, forbidden flags, child-process runner), with typed methods (check, validate, apple.status, play.listing, ... plus project.inspect and portfolio.status over many project folders), structured results (the commands' --json output), progress notifications and cancellation. The first request is session.hello with a per-session secret only the host knows. Write methods are dry runs unless the call carries a single-use confirm token (HMAC over method, arguments and expiry) that the host mints after the owner confirmed a native dialog showing that dry run. There is no method that submits, releases or publishes; Play testing releases and phased release changes stay out of the desktop profile too. Types: cli/lib/desktop-protocol.ts. bun tools/build-sidecars.ts compiles the CLI for macOS, Linux and Windows (bun build --compile). Design, safety model and the Dependencies screen (tool sources and licenses): docs/desktop.md.

Safety model

  • Credentials live in env vars or the gitignored .storebound/credentials.json, never in the config or the repo. storebound only talks to the official store APIs (plus Google Fonts, your own Play reports bucket and your own URLs); see SECURITY.md.
  • Dry run everywhere. Every write command takes --dry; every MCP write tool has dry_run, default true. Raw API writes (storebound api) are dry unless --yes is given in an interactive terminal; owner-only operations also need the operation id typed, and MCP only gets the read-only api_get.
  • Nothing public or irreversible through an agent. Submitting for review (App Review and Beta App Review), releasing a version, production releases, resuming or completing a rollout or phased release, canceling a submission, deleting, inviting or removing testers, sending review replies, applying prices and starting or stopping a product page test and creating custom offer codes are not MCP tools. Subscription syncs through MCP only create what is new; price changes of live subscriptions wait for a terminal run. They stay terminal commands that ask for confirmation, and the skills tell every agent to ask the owner first (docs/agents-core.md).
  • Personal data stays local. Tester lists, the App Review demo account, reply drafts, health reports and analytics snapshots live in the gitignored .storebound/ (the demo account is masked in every log line); tester emails are masked in every log line, and the config refuses a tester CSV path outside .storebound/.
  • Idempotent writes. Re-running a command converges (MD5 checks for screenshots and previews, one Play edit per language), so a failed upload is fixed by running it again.
  • Simulators and emulators: storebound shuts down only a simulator it booted itself and never boots, wipes or reboots an Android emulator or device.

FAQ

Is it only for games? No. It started with my game, but every command is about store records, not engines or genres. init --profile app and --profile subscription-app give you templates for a utility app with accounts and for a subscription app (Data safety, App Privacy, declarations, listing and screenshot copy), the app screenshot style, account deletion and demo account checks, and subscriptions on both stores. Game-only advice (IARC game questions, gameplay footage, ad mediation) is marked as such in the skills.

Do I need Claude (or any AI tool)? No. The CLI is a normal command and the skills are Markdown checklists you can read yourself. The AI integrations are optional adapters generated from the same files.

Does it fill the questionnaires? The ones with an API, yes: Play Data safety (the whole form from dataSafety in the config; the import replaces the form, so the config is the single truth), Apple age rating, content rights, categories, price and availability, the export compliance answer and the App Review details. The stores have no public API for Play's App content cards (IARC content rating, target audience, ads, advertising ID, app access, government / financial / health / news) or for Apple's App Privacy labels. For those, init gives you prepared answers (store/declarations.md, store/app-privacy.md, per profile: a utility app with an account, a subscription app, or a free-to-play game with ads and IAP), and the store-declarations skill has the exact clicks: an agent with browser tools can fill them in while you watch, or you copy them in yourself. Either way the answers are your statements to the stores: change what does not match your app, and the agent asks you whenever the code does not prove an answer. validate cannot see these forms, so store-preflight lists them as a manual check.

My app or game is Unity / Godot / Flutter / React Native / native, not web. Screenshots and clips come from the iOS Simulator (ios-sim), an Android emulator or device (android-emu) or a folder you fill with any tool; only the rendering of the store images uses a headless browser. The store commands do not care about the engine at all: they work on your AAB / build numbers and listing files.

Is my store account safe? storebound uses the official APIs with keys you create and can revoke, never your password. Store writes are dry runs until you say otherwise, and the public steps (submit, release) stay with you. Use an App Store Connect key with the App Manager role and a Play service account limited to your app.

Why Bun? One fast runtime with TypeScript, fetch, a test runner and a static server built in: no build step and zero runtime dependencies. It needs Bun installed (it does not run on plain Node).

Does it upload my video to YouTube? No. Play only takes a YouTube URL, and uploading to YouTube needs your Google account; shots video makes the file, you upload it and paste the URL into play.video.

Can it run store experiments for me? On the App Store, mostly: apple ppo create / upload builds a Product Page Optimization test from your shots variants (treatments, locales, screenshots) and you start it (apple ppo start asks first). Results per treatment are only shown in App Store Connect. Play store listing experiments have no API: storebound renders and checks the variant images, you set the experiment up in Play Console.

Does it answer reviews by itself? No. It drafts one file per review, your agent helps write the replies, and only the drafts you mark approved: true are sent, by you, after a confirmation. Replies that offer something for a rating are refused.

Roadmap

Wave 2 (TestFlight, vitals, review replies, price sheets, analytics, MCP 2026-07-28, Product Page Optimization) shipped in 0.4.0. 0.5.0 made storebound app-first (profiles, app screenshot style, account deletion, review access) and brought the full in-app catalog: subscriptions on both stores with offers and offer codes, non-renewing products and the cross-store catalog diff. Next, roughly in this order:

  • Play prepaid base plans for non_renewing.
  • The App Asset Library (API 4.5.1) for screenshots, custom product pages and in-app events once the screenshot sets it replaces stop working.
  • Play store listing experiments and their results, as soon as Google offers an API.
  • App preview treatments and alternate icons in Product Page Optimization tests.
  • The iPhone Duo screenshot classes Apple lists for apps built with the iOS 27.1 SDK (required from April 2027).

Contributing

Issues and pull requests are welcome: store rule updates (with a source), new capture sources, adapters for more AI tools. See CONTRIBUTING.md; bun run ci must be green.

Agent support: formats and what is not verified

Checked against each tool's own docs in October 2026:

  • Agent Skills: frontmatter is only name (lowercase-hyphen, equals the folder) and description; the generator fails if a skill adds anything else.
  • Claude Code: plugin manifest + root .mcp.json with ${CLAUDE_PLUGIN_ROOT}; claude plugin validate . passes.
  • Codex: .codex-plugin/plugin.json with "skills": "./skills/". Not verified: whether Codex starts the root .mcp.json from a plugin and expands ${CLAUDE_PLUGIN_ROOT} there, so use install --agent codex for the MCP server. OpenAI also documents a newer portable root plugin.json; not shipped yet.
  • Gemini CLI: gemini-extension.json with ${extensionPath}${/}, GEMINI.md as context, skills/ inside the extension. Google now points unpaid users to Antigravity CLI; not tested there. The .gemini/skills folder used by install --agent gemini is not verified (the extension route is).
  • Windsurf: the current docs name ~/.config/devin/mcp_config.json; install keeps using ~/.codeium/windsurf/mcp_config.json when that file already exists (older installs).
  • MCP: built for 2026-07-28 (final, modelcontextprotocol.io) and dual-era: initialize clients get 2025-11-25 (or 2025-06-18 / 2025-03-26 / 2024-11-05). Tested with both handshakes on one stdio process, not yet against every client listed above.
  • Not verified against a live account yet (no credentials during development; request builders are tested with fixtures from the official docs): the scale of the Play Reporting API rates (read as fractions, 0.0109 = 1.09 %), the App Store review reply limit (5970 characters is commonly reported; Apple documents none, storebound only warns), the TestFlight "What to Test" limit (4000 assumed), whether an analytics segment checksum covers the gzip file or its content (both are accepted), that a new App Store price schedule replaces the old one (Apple's example implies it), stopping a Product Page Optimization test with started: false, and APP_IPHONE_61 for the 6.3" iPhone set. From 0.5.0 (request bodies follow Apple's and Google's published schemas, tested with fixtures, not yet run against a live account): free promotional offers and offer codes sent with territories but no price point, subscription prices and introductory offers created inline through PATCH /v1/subscriptions/{id}, Play pay-as-you-go offers as a relativeDiscount, Play "developer determined" offers as offers without targeting plus an offer tag, the ISO 8601 strings for Play billing periods (Google lists them only in words), the Play subscription benefit length (40, from Play Console), the Data safety sign-up method ids (PSL_ACM_*, Google publishes none: compare with a Console export) and the delete-account URL question id (use dataSafety.extra). From the store operations (built from the App Store Connect API 4.5.1 OpenAPI spec and the cited Help pages, tested with recorded answers, not yet run live): the first IAP of a type sent through reviewSubmissionItems next to the app version (--no-submit leaves the draft for the UI if Apple refuses it), free IAP offer code and win-back prices without a price point, the order request for promoted purchases, the Play earnings / sales zip names beyond the documented prefix (storebound lists the prefix), and where Xcode stores AttributionCopyEndpoint (top level or inside an AdAttributionKit dictionary: both are read).
  • Apple IAP description: 45 characters (App Store Connect Help, "In-app purchase information"); an older App Store marketing page says 55. storebound checks 45.
Develop
bun install
bun run ci                  # adapters fresh (gen-adapters --check), tests, typecheck, secrets / personal data scrub
bun tools/gen-adapters.ts   # after editing skills/*/SKILL.md or docs/agents-core.md

skills/*/SKILL.md and docs/agents-core.md are the single source; AGENTS.md, GEMINI.md, the plugin manifests, .github/copilot-instructions.md and .cursor/rules/storebound.mdc are generated and committed, and CI fails when they are stale.

Thanks and idea sources

Some skills were sparked by other people's open work. I wrote my own text; the ideas deserve the credit:

Built with Bun and plain fetch against the Google Play Developer API v3, the Play Developer Reporting API and the App Store Connect API. Screenshot fonts come from Google Fonts at render time (Noto, Paytone One, Montserrat, Inter; SIL Open Font License 1.1). Not affiliated with Google or Apple.

License

MIT, see LICENSE. Copyright (c) 2026 CRTkafa.