- one version: package.json's version is the desktop app's too (desktopVersion removed); tools/desktop-version.ts keeps the plugin manifests and the app's copies equal, desktop-release publish refuses another version, release order app first then npm publish; the unpublished desktop 0.1.0 candidate is noted in the docs and CHANGELOG - storebound desktop installs (per user, no admin: macOS DMG -> /Applications or ~/Applications, Windows NSIS /S, Linux AppImage + .desktop with the storebound:// handler), updates an older app, notes a newer one (downgrade only with --force), asks once ([Y/n], --yes, exit 3 without a terminal), opens on the folder; --dry / --json / --download; the install steps are pure plans (cli/lib/desktop-install.ts); the version's own manifest dl/<version>/latest.json - credentials: one store, the CLI's credential profiles in ~/.storebound/credentials; the desktop writes and reads them (profiles.rs, files 0600), moves old keychain accounts into profiles on first start, shows profiles the CLI made; precedence env > named profile > project file > default profile; config `account`, "account" in the project's credentials.json; storebound accounts list | import (dedup by key id / service account e-mail, key files copied 0600, project file keeps keystore and review contact, backup, --dry, never prints values) | use - app folders: one list ~/.storebound/projects.json shared by the desktop (registry.rs), storebound desktop, apps folders | add | remove, ws init and ws without a workspace file - desktop: notice when a folder pins another storebound version; storebound-desktop --version - tests: version drift, desktop decisions and per-OS plans, credential precedence and import, registry (TS + Rust), keychain migration and profile round trip (Rust), boot check with old keychain state and the new shared state
170 lines
20 KiB
Markdown
170 lines
20 KiB
Markdown
# 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 &`).
|
|
|
|
<details>
|
|
<summary><b>Setup and checks</b></summary>
|
|
|
|
| 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](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 <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 [--yes]] [--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](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 <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](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 <profile>` | 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 [<folder>] \| remove <folder>` | 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 |
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>Google Play</b></summary>
|
|
|
|
| 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 [<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 |
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>App Store</b></summary>
|
|
|
|
| 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 <n> [--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 <n> [--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 [<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) |
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>TestFlight</b></summary>
|
|
|
|
| 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 |
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>After launch</b></summary>
|
|
|
|
| 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 |
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>Screenshots and video</b></summary>
|
|
|
|
| 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 |
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>Many apps and release trains</b></summary>
|
|
|
|
| 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 |
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
[All docs](README.md) · [README](../README.md)
|