- `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. |
||
|---|---|---|
| .claude-plugin | ||
| .codex-plugin | ||
| .cursor/rules | ||
| .forgejo/workflows | ||
| .github | ||
| apps/desktop | ||
| bin | ||
| brand | ||
| cli | ||
| docs | ||
| examples | ||
| media/teaser | ||
| site | ||
| skills | ||
| specs | ||
| templates | ||
| tests | ||
| tools | ||
| .gitignore | ||
| .mcp.json | ||
| AGENTS.md | ||
| bunfig.toml | ||
| CHANGELOG.md | ||
| CONTRIBUTING.md | ||
| gemini-extension.json | ||
| GEMINI.md | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| SECURITY.md | ||
| tsconfig.json | ||

storebound
Website: storebound.dev · npm: storebound · bunx storebound init
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
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 withbun tools/update-ppp.ts), clamped toprices.ppp.floor..ceilingof the US price and snapped to each store's price points. It writes a sheet in theprices importformat (defaultstore/prices-ppp.csv) and shows the diff;storebound prices import --csv store/prices-ppp.csv --dryapplies 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,budgetandpauseprint old -> new and ask first. Skill:apple-ads. - Web presence:
storebound web init [--landing]writesapple-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) andstorebound play share --aab app.aab(Play internal app sharing; asks first, prints the install link). Skill:adhoc-distribution. - RevenueCat (optional):
storebound revenuecat diff | syncmirrors 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.webdriverfalse) so Google accepts the sign-in; if Google still answers/signin/rejectedit 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|appleis 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 (byplay.packageName, orplay.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 fromplay.developerId, the workspace account, orplay.developerAccount(its name on "Choose a developer account"); with several accounts and none set it stops and lists them. It fills App name (play.nameorapp.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. Withplay.developerIdit opens the form's own URL (/console/u/0/developers/<id>/create-new-app); it fills App package name fromplay.packageNameand 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_modulesand 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>andaccount:<profile>, as in the desktop's ignore list. - overrides: the desktop's pairing corrections (
pairs,unpairs,linksof a store app to a folder,appStudio), with folders named by their path. Astorebound.workspace.tswithexport 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
--dryunless--apply, and even then each command asks its own confirmation; wait steps check once;sh:steps (your own build commands) run only with--apply.--dryprints 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: truesub-flows),before/after/onErrorare hooks,continueOnErrorkeeps going.- A failed step stops the flow;
--resumestarts again at that step with the captured variables (.storebound/flows/<name>.json),--from <step>anywhere.flow validatechecks commands, variables, duplicate ids and sub-flow cycles. Several flows can also live instorebound.workflows.yml|json|ts. - MCP:
flow_listandflow_run(the dry mode only;--applystays 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
:querycalls 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
--yesfrom 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 incli/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.tsregenerates 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.
| Play feature graphic | App preview (App Store, 886 x 1920) |
|---|---|
![]() |
![]() |
How it works
-
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 capturetake the frames: Playwright for a web build,simctlfor anything that runs in the iOS Simulator (Unity, Godot, Flutter, React Native, native, Capacitor),adbfor 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). -
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 fontschecks 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, failsshots 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 checkunless listed inshots.fonts.fallbackSome 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 fontsruns 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 infonts.json. The file is never copied: render (andshots videocaptions) serve it from where it is, and a changed file changesfonts.css(the capture cache misses) and makesshots renderask forshots fontsagain.shots checkcounts the local font and listed fallbacks as configured, not as fallbacks, and warns when a local font has nolicensenote. -
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).
-
Check and review.
shots checkapplies the store rules locally (sources cited incli/shots/spec.ts);shots galleryputs everything on one page with problems outlined.validaterepeats the checks next to the store state. -
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). -
Video.
shots recordfilms the autoplay mode,shots videocuts 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 previewsuploads them. Play takes a YouTube URL inplay.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 hasdry_run, default true. Raw API writes (storebound api) are dry unless--yesis given in an interactive terminal; owner-only operations also need the operation id typed, and MCP only gets the read-onlyapi_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) anddescription; the generator fails if a skill adds anything else. - Claude Code: plugin manifest + root
.mcp.jsonwith${CLAUDE_PLUGIN_ROOT};claude plugin validate .passes. - Codex:
.codex-plugin/plugin.jsonwith"skills": "./skills/". Not verified: whether Codex starts the root.mcp.jsonfrom a plugin and expands${CLAUDE_PLUGIN_ROOT}there, so useinstall --agent codexfor the MCP server. OpenAI also documents a newer portable rootplugin.json; not shipped yet. - Gemini CLI:
gemini-extension.jsonwith${extensionPath}${/},GEMINI.mdas context,skills/inside the extension. Google now points unpaid users to Antigravity CLI; not tested there. The.gemini/skillsfolder used byinstall --agent geminiis not verified (the extension route is). - Windsurf: the current docs name
~/.config/devin/mcp_config.json;installkeeps using~/.codeium/windsurf/mcp_config.jsonwhen that file already exists (older installs). - MCP: built for 2026-07-28 (final, modelcontextprotocol.io) and dual-era:
initializeclients 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, andAPP_IPHONE_61for 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 throughPATCH /v1/subscriptions/{id}, Play pay-as-you-go offers as arelativeDiscount, 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 (usedataSafety.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 throughreviewSubmissionItemsnext to the app version (--no-submitleaves 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 storesAttributionCopyEndpoint(top level or inside anAdAttributionKitdictionary: 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:
- rorkai/app-store-connect-cli-skills (MIT): App Store Connect workflow ideas.
- rshankras/claude-code-apple-skills (MIT): Apple platform and App Store topic ideas.
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.





