# 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 ` | 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](mcp.md#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 ]` | 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 [--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 ]` | 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 ""]` | 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 ` | 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=value] [--dry \| --apply [--yes]] [--from \| --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](studios.md#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 ` | local | AdAttributionKit / SKAdNetwork: ad network ids, postback copy endpoints, attribution windows and cooldown, conversion value updates; exit 1 on a blocker | | `privacy-manifest --ios ` | local | PrivacyInfo.xcprivacy, required-reason APIs, SDKs without manifests | | `api list \| coverage \| apple\|play ` | API | every App Store Connect / Google Play operation (see [Every endpoint](api.md)); GET runs, writes are dry unless `--yes` in a terminal | | `desktop [--yes] [--dry] [--download]` | local + download | the desktop app of this CLI's version, opened on this folder: installs it for this user when missing (size, SHA-256 and signature checked; asks once, `--yes`), updates an older one, notes a newer one (`--force` to downgrade); `--download` only saves the checked installer | | `accounts [list] \| import [--dry] \| use ` | local | the store accounts of this machine (`~/.storebound/credentials`, shared with the desktop app); `import` moves a project's store keys into a profile (same key = same profile), never prints values | | `apps folders \| add [] \| remove ` | local | the app folders of this machine (`~/.storebound/projects.json`), the same list as the desktop app | | `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 | `build.prebuild` first (Capacitor: web build + cap sync; stale copied web assets stop the build), then 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 [] --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 ` | 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] [--resolve-iaps] [--no-submit]` | API | IAP / subscription / group versions into a review submission (the first of a type together with the app version); reuses the open submission (a draft, or one back from review with UNRESOLVED_ISSUES; `--resolve-iaps` marks its REJECTED IAP items resolved), never a second one; asks before adding and before submitting | | `apple submit --build [--version x.y.z]` | API | a version to App Review: build n (by number, VALID) attached, the open review submission reused (a new draft only when none is open), the ready IAPs / subscriptions with it, submitted with the same retry as resubmit; plan table without `--yes` (the owner's step; the desktop's Submit for review) | | `apple resubmit --build [--resolve-iaps]` | API | after a rejection: attaches build n to the rejected version, reuses the UNRESOLVED_ISSUES submission, adds missing items, marks the REJECTED version item resolved (IAP items with `--resolve-iaps`) and resubmits (a REJECTED item left stops it at once; retried while Apple says "not ready yet" only when none is left); plan table without `--yes` | | `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 | `build.prebuild` first (Capacitor: web build + cap sync; stale copied web assets stop the build), then 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 [] [--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 ` | | `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 [sync]` | API | list the groups (read-only); `sync` creates the ones 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 ] [--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 | `/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 --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 ` | 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 [--app a]` | API, read-only | what is left for that version on each store, in fix order, with the owner's next step |
--- [All docs](README.md) ยท [README](../README.md)