994 lines
81 KiB
HTML
994 lines
81 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>storebound · from zero to Submit for review on Google Play and the App Store</title>
|
|
<meta name="description" content="Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, in-app products and subscriptions, listings in every language, every questionnaire, account deletion and review access, privacy, icons, screenshots and previews. One config and your AI coding agent. storebound never presses Submit.">
|
|
<link rel="canonical" href="https://storebound.dev/">
|
|
<meta name="color-scheme" content="light dark">
|
|
<meta name="theme-color" content="#f8fafc" media="(prefers-color-scheme: light)">
|
|
<meta name="theme-color" content="#0b1120" media="(prefers-color-scheme: dark)">
|
|
<link rel="icon" href="favicon.ico?v=2" sizes="16x16 32x32 48x48">
|
|
<link rel="manifest" href="manifest.webmanifest?v=2">
|
|
<link rel="apple-touch-icon" href="apple-touch-icon.png?v=2">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:site_name" content="storebound">
|
|
<meta property="og:url" content="https://storebound.dev/">
|
|
<meta property="og:title" content="storebound · take your app or game from zero to Submit for review">
|
|
<meta property="og:description" content="For mobile apps and games: app records, in-app products and subscriptions, listings in every language, every questionnaire, privacy, icons, screenshots. One config and your AI coding agent. Open source, MIT.">
|
|
<meta property="og:image" content="https://storebound.dev/og.png?v=2">
|
|
<meta property="og:image:width" content="1200">
|
|
<meta property="og:image:height" content="630">
|
|
<meta property="og:image:alt" content="storebound: take a mobile app or game from zero to Submit for review on Google Play and the App Store">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="storebound · take your app or game from zero to Submit for review">
|
|
<meta name="twitter:description" content="For mobile apps and games: app records, in-app products and subscriptions, listings in every language, every questionnaire, privacy, icons, screenshots. One config and your AI coding agent.">
|
|
<meta name="twitter:image" content="https://storebound.dev/og.png?v=2">
|
|
<link rel="stylesheet" href="style.css?v=20261012b">
|
|
<script>document.documentElement.classList.add('js')</script>
|
|
<script src="main.js?v=0ac6422" defer></script>
|
|
</head>
|
|
<body>
|
|
<a class="skip" href="#main">Skip to content</a>
|
|
|
|
<header class="top">
|
|
<div class="wrap top-in">
|
|
<a class="brand" href="#top" aria-label="storebound home">
|
|
<img class="mark" src="img/logo-mark.png?v=2" srcset="img/logo-mark.png?v=2 1x, img/logo-mark@2x.png?v=2 2x" width="28" height="28" alt="storebound logo">
|
|
<span>storebound</span>
|
|
</a>
|
|
<nav aria-label="Primary">
|
|
<a href="#how">How it works</a>
|
|
<a href="#journey">Journey</a>
|
|
<a href="#start">Quick start</a>
|
|
<a href="#faq">FAQ</a>
|
|
<a href="desktop/">Desktop</a>
|
|
<a class="nav-git" href="https://git.crt.fyi/CRT/storebound" target="_blank" rel="noopener">git<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="main">
|
|
|
|
<section class="hero" id="top">
|
|
<div class="hero-bg" aria-hidden="true"></div>
|
|
<div class="wrap hero-in">
|
|
<div class="hero-copy">
|
|
<p class="eyebrow"><span class="dot" aria-hidden="true"></span>Open source · for apps and games</p>
|
|
<h1>From zero to <span class="hl">Submit for review</span> on both app stores.</h1>
|
|
<p class="lede">Your AI coding agent sets up Google Play and the App Store for your app or game: records, products, listings, forms and store art. Dry runs first. <strong>The Submit click stays yours.</strong></p>
|
|
<div class="ctas">
|
|
<a class="btn btn-primary" href="#start">Get started<span class="ic ic-caret-right" aria-hidden="true"></span></a>
|
|
<a class="btn btn-ghost" href="#how">How it works</a>
|
|
</div>
|
|
<div class="install" role="group" aria-label="Install command">
|
|
<span class="prompt" aria-hidden="true">$</span>
|
|
<code id="install-cmd">bunx storebound init</code>
|
|
<button class="copy" type="button" data-copy="bunx storebound init" aria-label="Copy install command">Copy</button>
|
|
</div>
|
|
<p class="install-note">Needs <a href="https://bun.sh" target="_blank" rel="noopener">Bun<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a> 1.1+. No account, no sign-up.</p>
|
|
</div>
|
|
|
|
<figure class="demo" aria-labelledby="demo-cap" data-demo>
|
|
<div class="demo-win">
|
|
<div class="demo-bar" aria-hidden="true"><span></span><span></span><span></span><em data-cwd>examples/demo-game</em></div>
|
|
<div class="sc-tabs" role="tablist" aria-label="What storebound does, real output">
|
|
<button type="button" role="tab" id="sc-t1" aria-controls="sc-p1" aria-selected="true">Screenshots</button>
|
|
<button type="button" role="tab" id="sc-t2" aria-controls="sc-p2" aria-selected="false" tabindex="-1">Pre-flight</button>
|
|
<button type="button" role="tab" id="sc-t3" aria-controls="sc-p3" aria-selected="false" tabindex="-1">Store forms</button>
|
|
<button type="button" role="tab" id="sc-t4" aria-controls="sc-p4" aria-selected="false" tabindex="-1">Icons</button>
|
|
</div>
|
|
<div class="sc-stage">
|
|
<!-- Terminal lines, counts and check results are real CLI output from the bundled demos (timestamps and absolute paths trimmed, long lines shortened with …). -->
|
|
<section class="sc sc-shots" id="sc-p1" role="tabpanel" aria-labelledby="sc-t1" data-cwd="examples/demo-game" tabindex="0">
|
|
<div class="sc-head">
|
|
<p class="sc-h">Store screenshots, every size, every language</p>
|
|
<p class="sc-out">One command rendered <b>200 store-ready images</b>: 6 device sizes plus the Play feature graphic, 4 languages, an A/B variant. Then checked them against both stores' specs: <b>0 errors</b>.</p>
|
|
</div>
|
|
<p class="t-cmd" data-s="type"><i>$</i> <span>storebound shots render --variants</span></p>
|
|
<div class="tick" aria-hidden="true">
|
|
<p data-s="22"><b>[shots]</b> store/renders/en/ios69/01.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/en/ios65/02.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/en/ipad13/03.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/tr/play/01.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/tr/tab7/04.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/ja/ios69/02.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/ja/tab10/03.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/ar/ios69/01.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/ar/ipad13/04.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/ar/feature/1024x500.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/b/en/ios69/01.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/b/ja/play/02.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/b/ar/tab10/04.png</p>
|
|
<p data-s="22"><b>[shots]</b> store/renders/b/tr/feature/1024x500.png</p>
|
|
<p class="t-ok" data-s="22"><b>[shots]</b> checked 200 images, 8 videos, 56 sets: 0 errors, 0 warnings</p>
|
|
</div>
|
|
<ul class="shot-grid" aria-label="Rendered App Store screenshots in English, Japanese, Arabic and Turkish">
|
|
<li data-s="45"><img src="img/hero/s-en-1.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>EN</span></li>
|
|
<li data-s="45"><img src="img/hero/s-ja-1.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>JA</span></li>
|
|
<li data-s="45"><img src="img/hero/s-ar-1.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>AR</span></li>
|
|
<li data-s="45"><img src="img/hero/s-tr-1.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>TR</span></li>
|
|
<li data-s="45"><img src="img/hero/s-en-2.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>EN</span></li>
|
|
<li data-s="45"><img src="img/hero/s-ja-2.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>JA</span></li>
|
|
<li data-s="45"><img src="img/hero/s-ar-2.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>AR</span></li>
|
|
<li data-s="45"><img src="img/hero/s-tr-2.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>TR</span></li>
|
|
<li data-s="45"><img src="img/hero/s-en-3.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>EN</span></li>
|
|
<li data-s="45"><img src="img/hero/s-ja-3.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>JA</span></li>
|
|
<li data-s="45"><img src="img/hero/s-ar-3.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>AR</span></li>
|
|
<li data-s="45"><img src="img/hero/s-tr-3.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>TR</span></li>
|
|
<li data-s="45"><img src="img/hero/s-en-4.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>EN</span></li>
|
|
<li data-s="45"><img src="img/hero/s-ja-4.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>JA</span></li>
|
|
<li data-s="45"><img src="img/hero/s-ar-4.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>AR</span></li>
|
|
<li data-s="45"><img src="img/hero/s-tr-4.webp" width="120" height="261" alt="" loading="lazy" decoding="async"><span>TR</span></li>
|
|
</ul>
|
|
<p class="sc-foot" data-s="300"><b class="big"><span data-count="200">200</span> images</b> <span class="sep">·</span> iPhone 6.9" and 6.5", iPad 13", Play phone, 7" and 10" tablet, feature graphic</p>
|
|
<p class="sc-hand"><span>By hand</span> 200 exports from a design tool, each dropped into the right slot in two consoles.</p>
|
|
</section>
|
|
|
|
<section class="sc sc-pf" id="sc-p2" role="tabpanel" aria-labelledby="sc-t2" data-cwd="examples/demo-app" tabindex="0" hidden>
|
|
<div class="sc-head">
|
|
<p class="sc-h">Catches what gets you rejected, before you upload</p>
|
|
<p class="sc-out">Checked a sign-in app against 7 store requirements, offline. <b>3 would have stopped it</b>, each named with its fix. Fixed, re-checked: <b>all 7 pass</b>.</p>
|
|
</div>
|
|
<div class="pf-strip">
|
|
<div class="tick tick-pf" aria-hidden="true">
|
|
<p class="t-cmd" data-s="type" data-o="1"><i>$</i> <span>storebound check</span></p>
|
|
<p class="t-err" data-s="2000" data-o="10" data-pill="bad">error: 3 problems</p>
|
|
<p class="t-cmd" data-s="type" data-o="11"><i>$</i> <span>storebound init</span></p>
|
|
<p class="t-out" data-s="200" data-o="12"><b>[init]</b> create store/listing.md</p>
|
|
<p class="t-out" data-s="500" data-o="13"><b>[init]</b> create store/appstore.md</p>
|
|
<p class="t-note" data-s="1100" data-o="14"># you add the App Review demo account to .storebound/ (never chat, never git)</p>
|
|
<p class="t-cmd" data-s="type" data-o="15"><i>$</i> <span>storebound check</span></p>
|
|
<p class="t-ok" data-s="260" data-o="16" data-flip="pf-1"><b>[check]</b> play listing: 2 languages</p>
|
|
<p class="t-ok" data-s="260" data-o="17" data-flip="pf-2"><b>[check]</b> appstore listing: 2 locales</p>
|
|
<p class="t-ok t-strong" data-s="200" data-o="18" data-flip="pf-6" data-pill="good"><b>[check]</b> all good</p>
|
|
</div>
|
|
<p class="sc-pill" aria-hidden="true"><span class="pill-bad">3 to fix</span><span class="pill-good">7 of 7 pass</span></p>
|
|
</div>
|
|
<ol class="pf-list" aria-label="What storebound check verified">
|
|
<li class="pf-row" id="pf-1" data-bad data-s="260" data-o="2"><span class="pf-ic" aria-hidden="true"></span><span class="pf-what">Play listing<small>title 30 · short 80 · full 4000</small></span><span class="pf-det"><span class="d-bad">store/listing.md is missing · fix: <code>storebound init</code></span><span class="d-ok">2 languages, every field within limits <em>fixed by init</em></span></span></li>
|
|
<li class="pf-row" id="pf-2" data-bad data-s="260" data-o="3"><span class="pf-ic" aria-hidden="true"></span><span class="pf-what">App Store listing<small>name · subtitle · keywords 100</small></span><span class="pf-det"><span class="d-bad">store/appstore.md is missing · fix: <code>storebound init</code></span><span class="d-ok">2 locales, every field within limits <em>fixed by init</em></span></span></li>
|
|
<li class="pf-row" data-s="140" data-o="4"><span class="pf-ic" aria-hidden="true"></span><span class="pf-what">Screenshot captions<small>every scene, every language</small></span><span class="pf-det"><span class="d-ok">2 languages × 4 scenes, none missing</span></span></li>
|
|
<li class="pf-row" data-s="140" data-o="5"><span class="pf-ic" aria-hidden="true"></span><span class="pf-what">Play Data safety<small>the form Play requires</small></span><span class="pf-det"><span class="d-ok">764 answers built from the config</span></span></li>
|
|
<li class="pf-row" data-s="140" data-o="6"><span class="pf-ic" aria-hidden="true"></span><span class="pf-what">Account deletion<small>Play web page · Apple 5.1.1(v)</small></span><span class="pf-det"><span class="d-ok">web page and in-app path both set</span></span></li>
|
|
<li class="pf-row" id="pf-6" data-bad data-s="260" data-o="7"><span class="pf-ic" aria-hidden="true"></span><span class="pf-what">App Review sign-in<small>Apple 2.1 · Play App access</small></span><span class="pf-det"><span class="d-bad">sign-in required, no demo account · fix: add it to <code>.storebound/</code></span><span class="d-ok">demo account set, kept only in .storebound/ <em>fixed by you</em></span></span></li>
|
|
<li class="pf-row" data-s="140" data-o="8"><span class="pf-ic" aria-hidden="true"></span><span class="pf-what">Secrets stay out of git<small>store keys, demo account</small></span><span class="pf-det"><span class="d-ok">.storebound/ is in .gitignore</span></span></li>
|
|
<li class="pf-row pf-na" data-s="140" data-o="9"><span class="pf-ic" aria-hidden="true"></span><span class="pf-what">In-app products, subscriptions<small>price texts, terms URL</small></span><span class="pf-det"><span class="d-ok">none in this app, skipped</span></span></li>
|
|
</ol>
|
|
<p class="sc-hand"><span>Without it</span> a missing demo account comes back as a Guideline 2.1 rejection.</p>
|
|
</section>
|
|
|
|
<section class="sc sc-forms" id="sc-p3" role="tabpanel" aria-labelledby="sc-t3" data-cwd="examples/demo-app" tabindex="0" hidden>
|
|
<div class="sc-head">
|
|
<p class="sc-h">Store forms answered from your config</p>
|
|
<p class="sc-out">Play's Data safety form: <b>764 answers</b> derived from 12 lines of config, as the CSV Play imports. What only you can know, like encryption, <b>it asks you</b>. It never guesses.</p>
|
|
</div>
|
|
<p class="t-cmd" data-s="type"><i>$</i> <span>storebound play data-safety --dry</span></p>
|
|
<p class="t-out t-wrap" data-s="200"><b>[play]</b> data safety: 4 data types declared (PSL_EMAIL, PSL_USER_ACCOUNT, PSL_USER_GENERATED_CONTENT, PSL_CRASH_LOGS)</p>
|
|
<!-- The CSV streams in file order, then the final frame keeps only its key rows (data-key): the same real rows, in file order (the data types are in the [play] line above). -->
|
|
<div class="tick tick-csv" id="csv" aria-hidden="true">
|
|
<p data-s="24" data-key><em>TRUE</em> <span class="lbl">Does your app collect or share any of the required user data types?</span></p>
|
|
<p data-s="24" data-key class="hot"><em>TRUE</em> <span class="lbl">Is all of the user data collected by your app encrypted in transit?</span></p>
|
|
<p data-s="24" data-key class="hot"><em>TRUE</em> <span class="lbl">Do you provide a way for users to request that their data is deleted?</span></p>
|
|
<p data-s="24" data-key><em>TRUE</em> <span class="lbl">Account creation / PSL_ACM_USER_ID_PASSWORD</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">PSL_DATA_TYPES_PERSONAL / PSL_EMAIL</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">PSL_DATA_TYPES_PERSONAL / PSL_USER_ACCOUNT</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">PSL_DATA_TYPES_CALENDAR / PSL_CALENDAR</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">PSL_DATA_TYPES_APP_PERFORMANCE / PSL_CRASH_LOGS</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">PSL_DATA_TYPES_APP_ACTIVITY / PSL_USER_GENERATED_CONTENT</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_EMAIL) / Collected</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_EMAIL) / Shared</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_EMAIL) / Required</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_EMAIL) / Collection purpose</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_EMAIL) / Collection purpose</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_USER_ACCOUNT) / Collected</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_USER_ACCOUNT) / Required</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_USER_ACCOUNT) / Collection purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_USER_ACCOUNT) / Sharing purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_PHONE) / Collection purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_RACE_ETHNICITY) / Sharing purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_SEXUAL_ORIENTATION_GENDER_IDENTITY) / Collection purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_PURCHASE_HISTORY) / Required</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_CREDIT_SCORE) / Sharing purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_OTHER) / Collection purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_PRECISE_LOCATION) / Processed ephemerally</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_WEB_BROWSING_HISTORY) / Sharing purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_SMS_CALL_LOG) / Collection purpose</span></p>
|
|
<p data-s="24"><i>-</i> <span class="lbl">Data usage and handling (PSL_PHOTOS) / Collected</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_CRASH_LOGS) / Collected</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_CRASH_LOGS) / Required</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_CRASH_LOGS) / Collection purpose</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_USER_GENERATED_CONTENT) / Collected</span></p>
|
|
<p data-s="24"><em>TRUE</em> <span class="lbl">Data usage and handling (PSL_USER_GENERATED_CONTENT) / Required</span></p>
|
|
</div>
|
|
<p class="sc-foot" data-s="500" data-flip="csv"><b class="big"><span data-count="764">764</span> answers</b> <span class="sep">·</span> from 12 lines of config</p>
|
|
<p class="t-cmd" data-s="type"><i>$</i> <span>storebound apple encryption --dry</span></p>
|
|
<p class="t-err t-wrap" data-s="300">error: apple.usesNonExemptEncryption is not set in storebound.config.ts. Decide it with the export-compliance skill (…); storebound never guesses it.</p>
|
|
<p class="sc-hand"><span>By hand</span> Play's form, question by question, for every data type you collect.</p>
|
|
</section>
|
|
|
|
<section class="sc sc-icons" id="sc-p4" role="tabpanel" aria-labelledby="sc-t4" data-cwd="examples/demo-game" tabindex="0" hidden>
|
|
<div class="sc-head">
|
|
<p class="sc-h">Every icon both stores and the web need</p>
|
|
<p class="sc-out">One 1024 px artwork became <b>21 icons</b>: App Store, Google Play, the Android launcher in 5 densities (round and legacy), Capacitor layers, web and favicons.</p>
|
|
</div>
|
|
<p class="t-cmd" data-s="type"><i>$</i> <span>storebound icons</span></p>
|
|
<div class="ic-row">
|
|
<figure class="ic-src" data-s="300"><img src="img/hero/i-src.webp" width="76" height="76" alt="The one artwork: a drop over a rainbow puddle" decoding="async"><figcaption>icon.png</figcaption></figure>
|
|
<span class="ic-arrow" aria-hidden="true" data-s="150">→</span>
|
|
<ul class="ic-out" aria-label="Icons storebound wrote">
|
|
<li data-s="70"><img src="img/hero/i-1024.webp" width="60" height="60" class="ic-sq" alt="" loading="lazy" decoding="async"><small>1024 iOS</small></li>
|
|
<li data-s="70"><img src="img/hero/i-play.webp" width="52" height="52" class="ic-sq" alt="" loading="lazy" decoding="async"><small>512 Play</small></li>
|
|
<li data-s="70"><img src="img/hero/i-mask.webp" width="44" height="44" class="ic-ro" alt="" loading="lazy" decoding="async"><small>maskable</small></li>
|
|
<li data-s="70"><img src="img/hero/i-192.webp" width="40" height="40" class="ic-sq" alt="" loading="lazy" decoding="async"><small>192 web</small></li>
|
|
<li data-s="70"><img src="img/hero/i-round.webp" width="40" height="40" alt="" loading="lazy" decoding="async"><small>round</small></li>
|
|
<li data-s="70"><img src="img/hero/i-legacy.webp" width="40" height="40" alt="" loading="lazy" decoding="async"><small>legacy</small></li>
|
|
<li data-s="70"><img src="img/hero/i-64.webp" width="28" height="28" alt="" loading="lazy" decoding="async"><small>64 fav</small></li>
|
|
<li data-s="70"><img src="img/hero/i-32.webp" width="16" height="16" alt="" loading="lazy" decoding="async"><small>32 fav</small></li>
|
|
</ul>
|
|
</div>
|
|
<div class="tick tick-ic" aria-hidden="true">
|
|
<p data-s="20"><b>[icons]</b> store/icons/app-store-1024.png <span class="kb">105.2 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/play-512.png <span class="kb">40.7 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/web/icon-512.png <span class="kb">17.5 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/web/icon-192.png <span class="kb">5.6 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/web/icon-180.png <span class="kb">5.2 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/web/favicon-64.png <span class="kb">2.2 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/web/favicon-32.png <span class="kb">1.6 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/web/maskable-512.webp <span class="kb">7.2 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/capacitor/icon-only.png <span class="kb">105.2 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/capacitor/icon-background.png <span class="kb">16.5 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/capacitor/icon-foreground.png <span class="kb">104.8 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-mdpi/ic_launcher_round.png <span class="kb">1.9 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-mdpi/ic_launcher.png <span class="kb">1.6 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-hdpi/ic_launcher_round.png <span class="kb">3.0 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-hdpi/ic_launcher.png <span class="kb">2.6 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-xhdpi/ic_launcher_round.png <span class="kb">4.1 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-xhdpi/ic_launcher.png <span class="kb">3.6 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-xxhdpi/ic_launcher_round.png <span class="kb">7.1 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-xxhdpi/ic_launcher.png <span class="kb">6.1 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-xxxhdpi/ic_launcher_round.png <span class="kb">10.0 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> store/icons/android/res/mipmap-xxxhdpi/ic_launcher.png <span class="kb">8.8 KB</span></p>
|
|
<p data-s="20"><b>[icons]</b> icons done (4s)</p>
|
|
</div>
|
|
<p class="sc-foot" data-s="300"><b class="big"><span data-count="21">21</span> icons from one artwork</b> <span class="sep">·</span> in 4 s</p>
|
|
<p class="sc-hand"><span>By hand</span> 21 exports at exact sizes, each into the right folder.</p>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
<figcaption id="demo-cap" class="demo-cap">Real output · <code>examples/demo-game</code> and <code>demo-app</code> · offline commands and dry runs, nothing sent to a store</figcaption>
|
|
</figure>
|
|
</div>
|
|
|
|
<div class="wrap">
|
|
<ul class="trust" aria-label="At a glance">
|
|
<li>MIT licensed</li>
|
|
<li>Zero runtime dependencies</li>
|
|
<li>No telemetry</li>
|
|
<li><code>--dry</code> on every write</li>
|
|
<li>25 Agent Skills + MCP server</li>
|
|
</ul>
|
|
<p class="works"><span>Works with</span> Claude Code · Codex · Cursor · Gemini CLI · Copilot · Windsurf · OpenCode · or just the CLI</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec" id="how" aria-labelledby="how-h">
|
|
<div class="wrap">
|
|
<div class="sec-head">
|
|
<p class="kicker">How it works</p>
|
|
<h2 id="how-h">Five steps, from an empty repo to your Submit click.</h2>
|
|
<p class="sub">storebound lives in your app's repo. Describe the app once, add your keys, then ask your agent or run the commands yourself.</p>
|
|
</div>
|
|
|
|
<ol class="steps" data-steps>
|
|
<li class="st">
|
|
<div class="st-head" id="st-1">
|
|
<span class="st-num" aria-hidden="true">1</span>
|
|
<div class="st-t"><h3>Start in your app repo</h3><p>Pick a profile. Adds config and store files, never overwrites.</p></div>
|
|
</div>
|
|
<div class="st-panel" id="sp-1">
|
|
<div class="pv pv-term">
|
|
<div class="pv-bar"><span class="pv-k">Terminal</span><button class="copy" type="button" data-copy="bunx storebound init --profile app" aria-label="Copy step 1 command">Copy</button></div>
|
|
<pre><code><span class="p">$</span> bunx storebound init --profile app</code></pre>
|
|
</div>
|
|
<div class="pv-row">
|
|
<span class="pv-label">Profiles</span>
|
|
<span class="pill"><code>app</code></span><span class="pill"><code>subscription-app</code></span><span class="pill"><code>game</code></span>
|
|
</div>
|
|
<ul class="tree" aria-label="Files init adds">
|
|
<li><code>storebound.config.ts</code><span>ids, languages, products, Data safety</span></li>
|
|
<li><code>store/</code><span>listing text, questionnaire answers, screenshot captions</span></li>
|
|
<li><code>.storebound/</code><span>your keys, gitignored</span></li>
|
|
</ul>
|
|
<p class="pv-note">Needs <a href="https://bun.sh" target="_blank" rel="noopener">Bun<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a> 1.1+, plus <code>ffmpeg</code> for icons, screenshots and video.</p>
|
|
<div class="st-foot" hidden><span>Step 1 of 5</span><button class="st-next" type="button" data-next="1">Next: Describe the app once<span class="ic ic-caret-right" aria-hidden="true"></span></button></div>
|
|
</div>
|
|
</li>
|
|
|
|
<li class="st">
|
|
<div class="st-head" id="st-2">
|
|
<span class="st-num" aria-hidden="true">2</span>
|
|
<div class="st-t"><h3>Describe the app once</h3><p>One config for both stores, checked offline.</p></div>
|
|
</div>
|
|
<div class="st-panel" id="sp-2">
|
|
<div class="pv pv-file">
|
|
<div class="pv-bar"><span class="pv-k">storebound.config.ts</span></div>
|
|
<pre><code><span class="k">const</span> config: ShipConfig = {
|
|
app: { name: <span class="s">'My App'</span>, profile: <span class="s">'app'</span> },
|
|
languages: [
|
|
{ id: <span class="s">'en'</span>, play: <span class="s">'en-US'</span>, apple: <span class="s">'en-US'</span> },
|
|
{ id: <span class="s">'tr'</span>, play: <span class="s">'tr-TR'</span>, apple: <span class="s">'tr'</span> },
|
|
],
|
|
play: { packageName: <span class="s">'com.example.app'</span> },
|
|
apple: { bundleId: <span class="s">'com.example.app'</span> },
|
|
};</code></pre>
|
|
</div>
|
|
<div class="pv pv-term">
|
|
<div class="pv-bar"><span class="pv-k">Terminal</span><button class="copy" type="button" data-copy="bunx storebound check" aria-label="Copy step 2 command">Copy</button></div>
|
|
<pre><code><span class="p">$</span> bunx storebound check</code></pre>
|
|
</div>
|
|
<p class="pv-note">Store text lives next to it in <code>store/listing.md</code> and <code>store/appstore.md</code>, one section per language.</p>
|
|
<div class="st-foot" hidden><span>Step 2 of 5</span><button class="st-next" type="button" data-next="2">Next: Add your store keys<span class="ic ic-caret-right" aria-hidden="true"></span></button></div>
|
|
</div>
|
|
</li>
|
|
|
|
<li class="st">
|
|
<div class="st-head" id="st-3">
|
|
<span class="st-num" aria-hidden="true">3</span>
|
|
<div class="st-t"><h3>Add your store keys</h3><p>Official API keys you create and can revoke.</p></div>
|
|
</div>
|
|
<div class="st-panel" id="sp-3">
|
|
<div class="pv pv-file">
|
|
<div class="pv-bar"><span class="pv-k">.storebound/credentials.json</span><span class="pv-tag">gitignored</span><button class="copy" type="button" data-copy-target="how-creds" aria-label="Copy credentials template">Copy</button></div>
|
|
<pre id="how-creds"><code>{
|
|
<span class="s">"PLAY_SERVICE_ACCOUNT"</span>: <span class="s">"/keys/play.json"</span>,
|
|
<span class="s">"ASC_KEY_ID"</span>: <span class="s">"KEY_ID"</span>,
|
|
<span class="s">"ASC_ISSUER_ID"</span>: <span class="s">"ISSUER_ID"</span>,
|
|
<span class="s">"ASC_P8"</span>: <span class="s">"/keys/AuthKey_KEY_ID.p8"</span>
|
|
}</code></pre>
|
|
</div>
|
|
<p class="pv-note">A Play service account and an App Store Connect API key. Env vars work too. Never your password, never committed.</p>
|
|
<div class="st-foot" hidden><span>Step 3 of 5</span><button class="st-next" type="button" data-next="3">Next: Connect your agent, ask in plain words<span class="ic ic-caret-right" aria-hidden="true"></span></button></div>
|
|
</div>
|
|
</li>
|
|
|
|
<li class="st">
|
|
<div class="st-head" id="st-4">
|
|
<span class="st-num" aria-hidden="true">4</span>
|
|
<div class="st-t"><h3>Connect your agent, ask in plain words</h3><p>It runs dry runs first and walks you through console-only forms.</p></div>
|
|
</div>
|
|
<div class="st-panel" id="sp-4">
|
|
<div class="pv pv-term">
|
|
<div class="pv-bar"><span class="pv-k">Terminal</span><button class="copy" type="button" data-copy="bunx storebound install --agent claude" aria-label="Copy step 4 command">Copy</button></div>
|
|
<pre><code><span class="p">$</span> bunx storebound install --agent claude</code></pre>
|
|
</div>
|
|
<p class="pv-note">Or <code>codex</code>, <code>cursor</code>, <code>copilot</code>, <code>gemini</code>, <code>windsurf</code>, <code>opencode</code> (<a href="#start">per tool</a>). No agent? Every step is a CLI command too.</p>
|
|
<ul class="asks2" aria-label="Things to ask">
|
|
<li><span>Set up my in-app products on both stores</span><button class="copy" type="button" data-copy="Set up my in-app products on both stores" aria-label="Copy prompt: Set up my in-app products on both stores">Copy</button></li>
|
|
<li><span>Make store screenshots in 14 languages</span><button class="copy" type="button" data-copy="Make store screenshots in 14 languages" aria-label="Copy prompt: Make store screenshots in 14 languages">Copy</button></li>
|
|
<li><span>What still blocks my submission?</span><button class="copy" type="button" data-copy="What still blocks my submission?" aria-label="Copy prompt: What still blocks my submission?">Copy</button></li>
|
|
</ul>
|
|
<div class="st-foot" hidden><span>Step 4 of 5</span><button class="st-next" type="button" data-next="4">Next: Zero blockers, then you submit<span class="ic ic-caret-right" aria-hidden="true"></span></button></div>
|
|
</div>
|
|
</li>
|
|
|
|
<li class="st">
|
|
<div class="st-head" id="st-5">
|
|
<span class="st-num" aria-hidden="true">5</span>
|
|
<div class="st-t"><h3>Zero blockers, then you submit</h3><p>A read-only check of both stores, in fix order.</p></div>
|
|
</div>
|
|
<div class="st-panel" id="sp-5">
|
|
<div class="pv pv-term">
|
|
<div class="pv-bar"><span class="pv-k">Terminal</span><button class="copy" type="button" data-copy="bunx storebound validate" aria-label="Copy step 5 command">Copy</button></div>
|
|
<pre><code><span class="p">$</span> bunx storebound validate</code></pre>
|
|
</div>
|
|
<ol class="gate" aria-label="The last step">
|
|
<li><b>Blockers, per store, in fix order</b><span>build, encryption, metadata, screenshots, review details, prices, products</span></li>
|
|
<li><b>Fix, run again, until zero</b><span>read-only, so it is safe to run as often as you like</span></li>
|
|
<li class="gate-you"><b>You press Submit for review</b><span>and Send changes for review on Play. Never the agent.</span></li>
|
|
</ol>
|
|
<div class="st-foot" hidden><span>Step 5 of 5</span><a class="st-next" href="#start">Set it up in five minutes<span class="ic ic-caret-right" aria-hidden="true"></span></a></div>
|
|
</div>
|
|
</li>
|
|
</ol>
|
|
|
|
<div class="for">
|
|
<h3 class="for-h" id="who-h">Built for</h3>
|
|
<ul class="for-list" aria-labelledby="who-h">
|
|
<li>
|
|
<span class="for-ic" aria-hidden="true"><span class="ic ic-user"></span></span>
|
|
<div><b>Indie developers</b><p>One app or a handful: the store chores without a week of console tabs.</p></div>
|
|
</li>
|
|
<li>
|
|
<span class="for-ic" aria-hidden="true"><span class="ic ic-squares-four"></span></span>
|
|
<div><b>Studios shipping many titles</b><p>One config per title, the same checks and store art pipeline every time.</p></div>
|
|
</li>
|
|
<li>
|
|
<span class="for-ic" aria-hidden="true"><span class="ic ic-briefcase"></span></span>
|
|
<div><b>Agencies</b><p>Reviewable config in the client's repo. Their keys, their Submit.</p></div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec alt" id="session" aria-labelledby="session-h">
|
|
<div class="wrap">
|
|
<p class="kicker">A first session</p>
|
|
<h2 id="session-h">What talking to your agent looks like.</h2>
|
|
<p class="sub">One request, dry runs, your yes, then what is left for you. An example with the demo list app; output lines follow the real CLI format, ids and numbers are illustrative.</p>
|
|
<div class="session">
|
|
<figure class="term chat" aria-labelledby="chat-cap">
|
|
<div class="term-bar"><span></span><span></span><span></span><em>example · tidy-lists</em></div>
|
|
<pre><code><span class="say"><span class="who-you">you<span class="sr-only">:</span></span> Add a yearly Pro plan with a 1-week free trial and a monthly plan, on both stores.</span><span class="say"><span class="who-ai">agent<span class="sr-only">:</span></span> Adding both to subscriptions in storebound.config.ts (USD 29.99 / 3.99, Turkey set by hand). Dry runs first.</span><span class="p">$</span> bunx storebound check
|
|
<span class="o">09:41:02 [check] config ok: storebound.config.ts
|
|
09:41:02 [check] subscriptions: 2 in 1 group(s)
|
|
09:41:02 [check] all good</span>
|
|
<span class="p">$</span> bunx storebound apple subscriptions --dry
|
|
<span class="o">09:41:05 [apple] reading subscription groups, subscriptions, ...
|
|
09:41:07 [apple] 1/9 create subscription group "Tidy Pro"
|
|
09:41:07 [apple] 3/9 create pro_yearly (ONE_YEAR, level 1)
|
|
09:41:07 [apple] 6/9 pro_yearly: introductory offer FREE_TRIAL P1W
|
|
09:41:07 [apple] subscriptions planned (dry run, nothing written; ...) (5s)</span>
|
|
<span class="p">$</span> bunx storebound play subscriptions --dry
|
|
<span class="o">09:41:10 [play] 1/2 pro_yearly: create, base plan yearly (P1Y) in 173 regions · intro offer intro (free)
|
|
09:41:10 [play] 2/2 pro_monthly: create, base plan monthly (P1M) in 173 regions
|
|
09:41:10 [play] subscriptions planned (dry run, nothing written) (3s)</span>
|
|
<span class="say"><span class="who-ai">agent<span class="sr-only">:</span></span> Plan matches the config. Create them on both stores?</span><span class="say"><span class="who-you">you<span class="sr-only">:</span></span> yes</span><span class="p">$</span> bunx storebound apple subscriptions && bunx storebound play subscriptions
|
|
<span class="o">09:42:31 [apple] group "Tidy Pro" created
|
|
09:42:33 [apple] 1/2 pro_yearly: created
|
|
09:42:52 [apple] subscriptions done (first ones are submitted with an app version: ...) (21s)
|
|
09:43:04 [play] pro_yearly: base plan yearly activated
|
|
09:43:05 [play] pro_yearly: offer intro created and activated
|
|
09:43:07 [play] subscriptions done (25s)</span>
|
|
<span class="p">$</span> bunx storebound validate
|
|
<span class="o"> 1. <span class="bl">BLOCKER</span> [apple] pro_yearly: no review screenshot
|
|
fix: subscriptions[].reviewScreenshot, then storebound apple subscriptions
|
|
2. warn [apple] description without a Terms of Use link: tr (3.1.2; ...)
|
|
09:43:20 [validate] 1 blocker(s)</span>
|
|
<span class="say"><span class="who-ai">agent<span class="sr-only">:</span></span> Left for you: a paywall screenshot for the reviewer (I'll upload it), the Terms link in the Turkish description, and in App Store Connect, adding the first subscriptions to the version's submission (no API; I'll walk you through it).</span><span class="say say-end"><span class="you">Submit for review · your click</span></span></code></pre>
|
|
<figcaption id="chat-cap" class="sr-only">Example session: the user asks for two subscriptions, the agent runs storebound dry runs, the user confirms, storebound creates them on both stores, and validate lists what is left.</figcaption>
|
|
</figure>
|
|
|
|
<div class="see">
|
|
<figure class="see-fig" aria-labelledby="see-cap">
|
|
<p class="see-k">See it</p>
|
|
<div class="player" data-player>
|
|
<video controls preload="none" playsinline width="1280" height="720" poster="img/teaser-poster.webp?v=3" aria-label="storebound teaser video, 49 seconds, with music">
|
|
<source src="img/teaser.mp4?v=3" type="video/mp4">
|
|
</video>
|
|
<button class="play" type="button" hidden aria-label="Play the 49-second teaser (with sound)"><span class="ic ic-play" aria-hidden="true"></span><span>Play · 0:49</span></button>
|
|
</div>
|
|
<figcaption id="see-cap">49-second teaser: a scripted terminal run with the demo game, then the desktop app, from config to “Submit for review · your click”. Has music.</figcaption>
|
|
</figure>
|
|
<ul class="facts session-facts">
|
|
<li><b>Dry runs first.</b> Every write shows its plan before anything reaches a store.</li>
|
|
<li><b>Your yes.</b> The agent asks before writing, and never submits or releases.</li>
|
|
<li><b>Console-only forms</b> come with exact clicks and prepared answers, not guesses.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec" id="journey" aria-labelledby="journey-h">
|
|
<div class="wrap">
|
|
<p class="kicker">The journey</p>
|
|
<h2 id="journey-h">Everything an app or game needs before review, in order.</h2>
|
|
<p class="sub">For each step: what storebound does through the store APIs or on your machine, what a skill walks your agent through where the stores have no public API, and what stays your own click.</p>
|
|
<figure class="journey">
|
|
<picture><source media="(max-width: 640px)" srcset="img/journey-tall.svg" width="422" height="1520"><img src="img/journey.svg" width="1122" height="596" alt="The journey: config, app records, products, listings, questionnaires, privacy and icons, store art, validate, then you press Submit. Each step is tagged API, UI, local or you." loading="lazy" decoding="async"></picture>
|
|
</figure>
|
|
<ul class="legend" aria-label="Legend">
|
|
<li><span class="tag t-api">API</span> a storebound command (also an MCP tool)</li>
|
|
<li><span class="tag t-local">LOCAL</span> runs on your machine</li>
|
|
<li><span class="tag t-ui">UI</span> a skill guides your agent, or you, through the console</li>
|
|
<li><span class="tag t-you">YOU</span> your own click</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec alt" id="scan" aria-labelledby="scan-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Rejection scan</p>
|
|
<h2 id="scan-h">Catch rejections before you submit.</h2>
|
|
<p class="sub"><code>storebound scan</code> reads your project offline and read-only: Xcode and Gradle files, Info.plist, manifests, entitlements, lockfiles and engine settings, plus your config and listings. It works out the stack and the SDKs, then reports every rejection risk it can prove, each with the guideline, the evidence as <code>file:line</code>, the fix, a severity and the skill to open.</p>
|
|
<div class="term scan-term">
|
|
<div class="term-bar" aria-hidden="true"><span></span><span></span><span></span><em>tests/fixtures/scan/teaser</em></div>
|
|
<pre><code><span class="p">$</span> 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 }
|
|
<span class="c"> ... 2 more findings; every finding also has a fix: line and the skill</span></code></pre>
|
|
</div>
|
|
<h3 class="rules-h">What it proves, on both stores</h3>
|
|
<ul class="rules" aria-label="Scan rules, summarized">
|
|
<li><span class="rg"><b>2.1(a)</b><i>App access</i></span><p>A sign-in with no demo account for the reviewer.</p></li>
|
|
<li><span class="rg"><b>5.1.1(v)</b><i>Account deletion</i></span><p>Sign-up or social login without an in-app deletion path or a deletion URL.</p></li>
|
|
<li><span class="rg"><b>4.8</b></span><p>Google or Facebook login without Sign in with Apple or another equivalent option.</p></li>
|
|
<li><span class="rg"><b>3.1.1, 3.1.2</b><i>Payments</i></span><p>No Restore Purchases path, card SDKs next to in-app products, missing subscription terms.</p></li>
|
|
<li><span class="rg"><b>5.1.2</b><i>Data safety</i></span><p>Tracking SDKs without ATT; SDK data types missing from App Privacy or Data safety.</p></li>
|
|
<li><span class="rg"><b>5.1.1, ITMS-91053</b></span><p>Permissions without purpose strings; a missing or incomplete privacy manifest.</p></li>
|
|
<li><span class="rg"><i>Restricted permissions</i></span><p>Background location, photos, all files, accessibility, exact alarms, foreground services, and the Console declaration each one needs.</p></li>
|
|
<li><span class="rg"><b>1.3</b><i>Families</i></span><p>Ad or analytics SDKs in a Kids Category or child-directed app.</p></li>
|
|
<li><span class="rg"><i>Target API, 16 KB pages</i></span><p>targetSdk below 36; Unity, React Native, NDK or AGP versions without 16 KB page support.</p></li>
|
|
<li><span class="rg"><b>2.3.7, 2.3.10</b><i>Metadata</i></span><p>Other apps' names, "best / #1", keyword lists, other stores in App Store text, placeholder text.</p></li>
|
|
<li><span class="rg"><b>2.2</b></span><p>"Beta", "demo" or "test" wording in the app name and the listing.</p></li>
|
|
<li><span class="rg"><b>4.3</b><i>Repetitive content</i></span><p><code>scan --originality --against <folders></code> adds the near-duplicate check against your other apps.</p></li>
|
|
</ul>
|
|
<ul class="legend" aria-label="Legend">
|
|
<li><span class="rg"><b>4.8</b></span> App Store guideline</li>
|
|
<li><span class="rg"><i>Families</i></span> Google Play policy</li>
|
|
</ul>
|
|
<p class="note">Below the risks comes a proposal for Data safety, App Privacy, ATT and the privacy manifest from the detected SDKs, each answer marked detected or ask owner. Nothing is written unless you run <code>scan --write</code>. <code>validate</code> includes the open findings, and the desktop app shows them on its Checks tab.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec" id="split" aria-labelledby="split-h">
|
|
<div class="wrap">
|
|
<p class="kicker">API or console, honestly</p>
|
|
<h2 id="split-h">What it automates, what your agent walks through, what stays your click.</h2>
|
|
<div class="cols3">
|
|
<article class="col">
|
|
<span class="tag t-api">API</span>
|
|
<h3>storebound does it</h3>
|
|
<p class="col-sub">A CLI command, also an MCP tool. Every write has <code>--dry</code>.</p>
|
|
<ul>
|
|
<li>Bundle id, builds on tracks, versions and build selection</li>
|
|
<li>Listings in every language, graphics, screenshots, app previews</li>
|
|
<li>In-app products with regional prices</li>
|
|
<li>Play Data safety (the whole form, from the config)</li>
|
|
<li>Apple age rating, categories, content rights, price and availability</li>
|
|
<li>Export compliance answer, App Review details</li>
|
|
<li>Phased release and staged rollout</li>
|
|
<li>TestFlight groups, testers, What to Test</li>
|
|
<li>Crash / ANR vitals, review replies, analytics, price CSV, product page tests</li>
|
|
</ul>
|
|
</article>
|
|
<article class="col">
|
|
<span class="tag t-ui">UI</span>
|
|
<h3>Skill + your agent walk through</h3>
|
|
<p class="col-sub">No public API. The skill has the exact navigation and prepared answers; an agent with browser tools clicks while you watch, or you use it as a checklist.</p>
|
|
<ul>
|
|
<li>Creating the App Store Connect app record and the Play app</li>
|
|
<li>Play App content: IARC content rating, target audience, ads, advertising ID, app access, government / financial / health / news</li>
|
|
<li>Play category, tags and countries</li>
|
|
<li>App Privacy labels and their Publish</li>
|
|
<li>Adding the first IAPs to the submission</li>
|
|
<li>Play store listing experiments</li>
|
|
</ul>
|
|
</article>
|
|
<article class="col col-you">
|
|
<span class="tag t-you">YOU</span>
|
|
<h3>Only you</h3>
|
|
<p class="col-sub">Public, legal or money steps. The skills tell every agent to stop and hand these to you. None of them is an MCP tool.</p>
|
|
<ul>
|
|
<li>Developer accounts, agreements, banking and tax</li>
|
|
<li>The answers to the questionnaires: they are your statements</li>
|
|
<li><strong>Play: Send changes for review</strong></li>
|
|
<li><strong>App Store: Submit for Review</strong></li>
|
|
<li>Releasing an approved version, resuming or completing a rollout or phased release</li>
|
|
<li>Beta App Review, tester invitations, sending review replies, applying price changes, starting a product page test</li>
|
|
</ul>
|
|
</article>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec alt" id="apps" aria-labelledby="apps-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Apps and games</p>
|
|
<h2 id="apps-h">Not just for games. Pick a profile, start from honest templates.</h2>
|
|
<p class="sub"><code>init --profile app</code> (the default: utility or productivity app, optional account, analytics, no ads), <code>--profile subscription-app</code> or <code>--profile game</code>. Each profile brings its own config, Data safety and App Privacy answers, declarations, listing and screenshot copy, all marked “edit to match your app”.</p>
|
|
<div class="qgrid">
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span><span class="ic ic-googleplay" aria-hidden="true"></span>Both stores</span><span class="tag t-api">API</span></div>
|
|
<h3>Subscriptions</h3>
|
|
<p>Apple groups and levels, Play base plans, regional prices from one USD base, free trials, promotional offers and offer codes. Plan first, then your yes.</p>
|
|
<code class="cmd">apple subscriptions · play subscriptions</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span><span class="ic ic-googleplay" aria-hidden="true"></span>Both stores</span><span class="tag t-api">API</span></div>
|
|
<h3>One catalog, both stores</h3>
|
|
<p>Consumables, non-consumables, non-renewing passes and subscriptions from one list. A parity report shows what is missing, mismatched or priced differently per region.</p>
|
|
<code class="cmd">catalog diff</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span><span class="ic ic-googleplay" aria-hidden="true"></span>Both stores</span><span class="tag t-api">API</span></div>
|
|
<h3>Review access</h3>
|
|
<p>A demo account for the reviewer, kept only in <code>.storebound/</code>, sent to App Review masked; the Play App access answer prepared.</p>
|
|
<code class="cmd">apple review-details</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span><span class="ic ic-googleplay" aria-hidden="true"></span>Both stores</span><span class="tag t-local">local</span></div>
|
|
<h3>Account deletion</h3>
|
|
<p>Apple 5.1.1(v) in-app deletion and Play's delete-account page: in the config, the review notes and the checks.</p>
|
|
<code class="cmd">check · validate</code>
|
|
</article>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec" id="questionnaires" aria-labelledby="q-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Questionnaires</p>
|
|
<h2 id="q-h">Every store form, prepared. The ones with an API, filled.</h2>
|
|
<p class="sub"><code>init</code> gives you prepared answers (<code>store/declarations.md</code>, <code>store/app-privacy.md</code>), per profile: a utility app with an account, a subscription app, or a free-to-play game with ads and IAP. Change what does not match your app; the agent asks you whenever the code does not prove an answer.</p>
|
|
<div class="qgrid">
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-googleplay" aria-hidden="true"></span>Google Play</span><span class="tag t-api">API</span></div>
|
|
<h3>Data safety</h3>
|
|
<p>The whole form from <code>dataSafety</code> in the config, imported as CSV through the API. The import replaces the form, so the config is the single truth.</p>
|
|
<code class="cmd">play data-safety</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-googleplay" aria-hidden="true"></span>Google Play</span><span class="tag t-ui">UI</span></div>
|
|
<h3>IARC content rating</h3>
|
|
<p>No public API. The <code>store-declarations</code> skill has the exact clicks and the prepared answers; your agent fills it in while you watch, or you copy them in.</p>
|
|
<code class="cmd">store-declarations</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-googleplay" aria-hidden="true"></span>Google Play</span><span class="tag t-ui">UI</span></div>
|
|
<h3>Target audience and content</h3>
|
|
<p>Walked through by the skill, together with ads, advertising ID, app access and the government, financial, health and news cards.</p>
|
|
<code class="cmd">store-declarations</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span>App Store</span><span class="tag t-ui">UI</span></div>
|
|
<h3>App Privacy labels</h3>
|
|
<p>From <code>store/app-privacy.md</code>, entered in App Store Connect, then <strong>Publish</strong>: saved labels that are not published are a classic blocker.</p>
|
|
<code class="cmd">store-declarations</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span>App Store</span><span class="tag t-api">API</span></div>
|
|
<h3>Age rating</h3>
|
|
<p>The age rating declaration through the API, together with categories, content rights, Free price and availability.</p>
|
|
<code class="cmd">apple appinfo</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span>App Store</span><span class="tag t-api">API</span></div>
|
|
<h3>Export compliance</h3>
|
|
<p>The answer comes from <code>apple.usesNonExemptEncryption</code> in your config, never guessed. When it is yes, the <code>export-compliance</code> skill covers the documentation.</p>
|
|
<code class="cmd">apple encryption</code>
|
|
</article>
|
|
</div>
|
|
<p class="note"><code>validate</code> cannot see the console-only forms, so the <code>store-preflight</code> skill lists them as a manual check before you submit.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec alt" id="works-with" aria-labelledby="w-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Works with</p>
|
|
<h2 id="w-h">Your AI coding agent. Or none.</h2>
|
|
<p class="sub">Every route gives the agent the same skills and the same MCP tools. The skills are plain Markdown and the CLI is a normal command, so nothing here requires a particular tool.</p>
|
|
<ul class="badges" aria-label="Supported AI tools">
|
|
<li>Claude Code</li>
|
|
<li>OpenAI Codex</li>
|
|
<li>Cursor</li>
|
|
<li>GitHub Copilot</li>
|
|
<li>Gemini CLI</li>
|
|
<li>Windsurf</li>
|
|
<li>OpenCode</li>
|
|
<li>anything that reads AGENTS.md</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec" id="store-art" aria-labelledby="art-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Store art</p>
|
|
<h2 id="art-h">Screenshots and previews, every size, every language.</h2>
|
|
<p class="sub">The step that takes longest by hand, in two looks: <code>app</code> (calm gradient, device frame, headline and subline) and <code>game</code> (sunburst, sparkles, badges). Everything below was made by storebound itself from two tiny fictional demos, a list app (“Tidy Lists”) and a game (“Prism Puddle”): raw frames from the web build, captions in 4 languages, every store size, a review gallery and an app preview video.</p>
|
|
<figure class="shot shot-wide">
|
|
<img src="img/app-style.jpg" width="1600" height="962" alt="App style: four calm screenshots of a demo list app in two sizes and its feature graphic" loading="lazy" decoding="async">
|
|
<figcaption>App style: the demo list app, iPhone 6.9" and Play phone sizes, feature graphic.</figcaption>
|
|
</figure>
|
|
<figure class="shot shot-wide">
|
|
<img src="img/showcase.webp" width="1600" height="505" alt="Store screenshots rendered by storebound: four scenes in English plus the hero scene in Turkish, Japanese and Arabic" loading="lazy" decoding="async">
|
|
<figcaption>Game style: four scenes in English, the hero scene in Turkish, Japanese and Arabic (right to left).</figcaption>
|
|
</figure>
|
|
<div class="art2">
|
|
<figure class="shot">
|
|
<img src="img/gallery.webp" width="1400" height="860" alt="The review gallery: every language, size, scene and variant on one local page, store check issues highlighted" loading="lazy" decoding="async">
|
|
<figcaption><code>shots gallery</code>: every language, size, scene and variant on one local page, store check issues outlined.</figcaption>
|
|
</figure>
|
|
<figure class="shot shot-video">
|
|
<video src="img/preview.mp4" poster="img/preview-poster.webp" width="240" height="520" autoplay muted loop playsinline preload="metadata" aria-label="A few seconds of the app preview video"></video>
|
|
<figcaption>App preview, cut by <code>shots video</code>.</figcaption>
|
|
</figure>
|
|
</div>
|
|
<ul class="facts">
|
|
<li><b>Capture</b> from a web build (Playwright), the iOS Simulator, an Android emulator or a folder of your own frames.</li>
|
|
<li><b>Render</b> iPhone 6.9" and 6.5", 13" iPad, Play phone, 7" and 10" tablets, the 1024 x 500 feature graphic. 24-bit RGB, no alpha.</li>
|
|
<li><b>Check</b> the store rules locally with <code>shots check</code>; <code>validate</code> repeats it next to the store state.</li>
|
|
<li><b>Fonts</b> subset to exactly your characters, so Japanese, Chinese, Korean, Arabic and Hindi just work.</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec alt" id="after-launch" aria-labelledby="after-h">
|
|
<div class="wrap">
|
|
<p class="kicker">After launch</p>
|
|
<h2 id="after-h">The app is live. storebound keeps going.</h2>
|
|
<p class="sub">Drafts and dry runs by default; anything that reaches users or money waits for your yes.</p>
|
|
<div class="qgrid">
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span>App Store</span><span class="tag t-api">API</span></div>
|
|
<h3>TestFlight</h3>
|
|
<p>Beta groups, testers from a local CSV, builds to groups, What to Test in every language. Beta App Review stays your click.</p>
|
|
<code class="cmd">apple testflight</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span><span class="ic ic-googleplay" aria-hidden="true"></span>Both stores</span><span class="tag t-api">API</span></div>
|
|
<h3>Crash and ANR health</h3>
|
|
<p>Play vitals against the bad-behaviour thresholds and App Store diagnostics, in <code>validate</code> and a weekly report.</p>
|
|
<code class="cmd">health --weekly</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span><span class="ic ic-googleplay" aria-hidden="true"></span>Both stores</span><span class="tag t-api">API</span></div>
|
|
<h3>Review replies</h3>
|
|
<p>Your agent drafts replies to new reviews into local files. Only drafts you mark approved are sent, after a confirmation.</p>
|
|
<code class="cmd">reviews draft</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span><span class="ic ic-googleplay" aria-hidden="true"></span>Both stores</span><span class="tag t-api">API</span></div>
|
|
<h3>Prices from one CSV</h3>
|
|
<p>Every product in every region at once. A diff table first, then your yes; App Store price changes can be dated.</p>
|
|
<code class="cmd">prices import</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span><span class="ic ic-googleplay" aria-hidden="true"></span>Both stores</span><span class="tag t-api">API</span></div>
|
|
<h3>Analytics snapshots</h3>
|
|
<p>Installs, page views and conversion next to your listing changes and experiments. App Store through the API, Play from your monthly reports bucket.</p>
|
|
<code class="cmd">analytics compare</code>
|
|
</article>
|
|
<article class="q">
|
|
<div class="q-top"><span class="store"><span class="ic ic-appstore" aria-hidden="true"></span>App Store</span><span class="tag t-api">API</span></div>
|
|
<h3>Product page tests</h3>
|
|
<p>A/B screenshot sets from your variants into App Store Product Page Optimization. Starting a test stays your click.</p>
|
|
<code class="cmd">apple ppo</code>
|
|
</article>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec" id="more" aria-labelledby="more-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Everything else</p>
|
|
<h2 id="more-h">The rest of the toolbox, one line each.</h2>
|
|
<p class="sub">Same rules as everywhere else: reads run, writes show a dry run first, and public steps stay your click. <code>storebound help</code> lists every command.</p>
|
|
<ul class="more">
|
|
<li><span class="more-cmd"><code>ws</code><code>release status|plan</code></span><p>Many apps and studios at once; one release train per version across both stores, and what is left on each.</p></li>
|
|
<li><span class="more-cmd"><code>api</code></span><p>Every App Store Connect and Google Play API operation. GET runs; writes stay dry.</p></li>
|
|
<li><span class="more-cmd"><code>apple build</code><code>apple upload</code><code>play build</code><code>play upload</code><code>apple signing</code></span><p>Build, sign and upload with the next free build number, from the terminal.</p></li>
|
|
<li><span class="more-cmd"><code>console fill</code></span><p>AI-assisted autofill of console-only forms in a visible browser you sign in to. Stops before Save or Submit.</p></li>
|
|
<li><span class="more-cmd"><code>apple testflight</code><code>apple cpp</code><code>apple events</code><code>apple offer-codes</code><code>apple win-back</code><code>apple preorder</code><code>reports</code><code>triage</code></span><p>Store operations: betas, custom product pages, in-app events, offers, pre-orders, sales reports and crash feedback.</p></li>
|
|
<li><span class="more-cmd"><code>prices ppp</code></span><p>A purchasing-power price sheet, snapped to each store's price points.</p></li>
|
|
<li><span class="more-cmd"><code>ads apple</code></span><p>Apple Ads campaigns and reports: spend, taps, installs and CPA per campaign or keyword.</p></li>
|
|
<li><span class="more-cmd"><code>web init|check</code></span><p>Smart App Banner, apple-app-site-association, assetlinks.json and a landing page, then a live check.</p></li>
|
|
<li><span class="more-cmd"><code>apple adhoc</code><code>play share</code></span><p>Ad-hoc builds for registered devices and Play internal app sharing links.</p></li>
|
|
<li><span class="more-cmd"><code>revenuecat diff|sync</code></span><p>Keep a RevenueCat catalog, entitlements and offering in step with your config.</p></li>
|
|
<li><span class="more-cmd"><code>flow list|run</code><span class="new">new</span></span><p>Repo-local workflows in <code>store/workflows/*.yml</code>, e.g. next build, build, upload, wait, What's new, validate, scan, plan. Writes stay dry unless <code>--apply</code>; submit and release steps show as your click; <code>--from</code> resumes.</p></li>
|
|
<li><span class="more-cmd"><code>listing translate --to de-DE,ja</code><span class="new">new</span></span><p>Listing drafts per language with each store's limits and keyword rules, translated by your agent or a <code>--translate</code> hook, marked as drafts until reviewed.</p></li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec alt" id="desktop" aria-labelledby="desktop-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Desktop app · preview</p>
|
|
<h2 id="desktop-h">Every app on both stores, in one window.</h2>
|
|
<p class="sub">storebound desktop shows every app and game you ship: releases on both stores, catalog parity, listings, crash health, reviews and TestFlight. It drives the same open source CLI, keeps store keys in the system keychain, shows a dry run before every change, and never presses Submit.</p>
|
|
<figure class="shot shot-wide">
|
|
<img src="img/desktop/portfolio.webp?v=1" width="1280" height="800" alt="storebound desktop, Portfolio: every app of every studio grouped by studio, with its state on Google Play and the App Store" loading="lazy" decoding="async">
|
|
<figcaption>Portfolio: every app of every studio, both stores side by side.</figcaption>
|
|
</figure>
|
|
<div class="desk2">
|
|
<figure class="shot">
|
|
<img src="img/desktop/releases.webp?v=1" width="1280" height="800" alt="storebound desktop, Releases: the release timeline of the demo game Prism Puddle, both stores rolling out at 20 percent" loading="lazy" decoding="async">
|
|
<figcaption>Releases: one timeline per version, both stores rolling out.</figcaption>
|
|
</figure>
|
|
<figure class="shot">
|
|
<img src="img/desktop/checks.webp?v=1" width="1280" height="800" alt="storebound desktop, Checks: the Rejection risks card with the scan findings of the demo game" loading="lazy" decoding="async">
|
|
<figcaption>Checks: the rejection scan findings, with evidence and the fix.</figcaption>
|
|
</figure>
|
|
</div>
|
|
<div class="ctas">
|
|
<a class="btn btn-primary" href="desktop/">Download the desktop app<span class="ic ic-caret-right" aria-hidden="true"></span></a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec" id="start" aria-labelledby="start-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Quick start</p>
|
|
<h2 id="start-h">Five minutes to a checked config.</h2>
|
|
<div class="start">
|
|
<div class="step">
|
|
<h3><span class="num">1</span>Add it to your app or game</h3>
|
|
<div class="code">
|
|
<button class="copy" type="button" data-copy-target="qs1" aria-label="Copy commands">Copy</button>
|
|
<pre id="qs1"><code>cd my-app
|
|
bun add -d storebound playwright
|
|
bunx storebound init <span class="c"># --profile app (default) | subscription-app | game</span>
|
|
bunx storebound check <span class="c"># offline: config, copy limits, Data safety, products, files</span></code></pre>
|
|
</div>
|
|
<p class="fine">Credentials come from environment variables or the gitignored <code>.storebound/credentials.json</code>: a Play service account key and an App Store Connect API team key.</p>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<h3><span class="num">2</span>Connect your AI tool</h3>
|
|
<div class="tabs" data-tabs>
|
|
<div class="tablist" role="tablist" aria-label="AI tool">
|
|
<button role="tab" id="tab-claude" aria-controls="p-claude" aria-selected="true">Claude Code</button>
|
|
<button role="tab" id="tab-codex" aria-controls="p-codex" aria-selected="false" tabindex="-1">Codex</button>
|
|
<button role="tab" id="tab-gemini" aria-controls="p-gemini" aria-selected="false" tabindex="-1">Gemini CLI</button>
|
|
<button role="tab" id="tab-cursor" aria-controls="p-cursor" aria-selected="false" tabindex="-1">Cursor</button>
|
|
<button role="tab" id="tab-copilot" aria-controls="p-copilot" aria-selected="false" tabindex="-1">Copilot</button>
|
|
<button role="tab" id="tab-windsurf" aria-controls="p-windsurf" aria-selected="false" tabindex="-1">Windsurf</button>
|
|
<button role="tab" id="tab-opencode" aria-controls="p-opencode" aria-selected="false" tabindex="-1">OpenCode</button>
|
|
<button role="tab" id="tab-agents" aria-controls="p-agents" aria-selected="false" tabindex="-1">AGENTS.md</button>
|
|
</div>
|
|
|
|
<div class="panel" role="tabpanel" id="p-claude" aria-labelledby="tab-claude" tabindex="0">
|
|
<h4 class="panel-h">Claude Code</h4>
|
|
<div class="code"><button class="copy" type="button" data-copy-target="c-claude" aria-label="Copy Claude Code commands">Copy</button>
|
|
<pre id="c-claude"><code>/plugin marketplace add https://git.crt.fyi/CRT/storebound.git
|
|
/plugin install storebound@crtkafa</code></pre></div>
|
|
<p>Skills, MCP server and <code>storebound</code> on PATH. Or per project: <code>bunx storebound install <span class="nw">--agent claude</span></code></p>
|
|
</div>
|
|
<div class="panel" role="tabpanel" id="p-codex" aria-labelledby="tab-codex" tabindex="0">
|
|
<h4 class="panel-h">OpenAI Codex</h4>
|
|
<div class="code"><button class="copy" type="button" data-copy-target="c-codex" aria-label="Copy Codex command">Copy</button>
|
|
<pre id="c-codex"><code>bunx storebound install --agent codex</code></pre></div>
|
|
<p>Skills in <code>.agents/skills</code>, MCP in <code>.codex/config.toml</code> (Codex reads it for trusted projects). The repo is also a Codex plugin, skills only.</p>
|
|
</div>
|
|
<div class="panel" role="tabpanel" id="p-gemini" aria-labelledby="tab-gemini" tabindex="0">
|
|
<h4 class="panel-h">Gemini CLI</h4>
|
|
<div class="code"><button class="copy" type="button" data-copy-target="c-gemini" aria-label="Copy Gemini CLI command">Copy</button>
|
|
<pre id="c-gemini"><code>gemini extensions install https://git.crt.fyi/CRT/storebound.git</code></pre></div>
|
|
<p>Skills, MCP server and <code>GEMINI.md</code>. Or <code>bunx storebound install --agent gemini</code></p>
|
|
</div>
|
|
<div class="panel" role="tabpanel" id="p-cursor" aria-labelledby="tab-cursor" tabindex="0">
|
|
<h4 class="panel-h">Cursor</h4>
|
|
<div class="code"><button class="copy" type="button" data-copy-target="c-cursor" aria-label="Copy Cursor command">Copy</button>
|
|
<pre id="c-cursor"><code>bunx storebound install --agent cursor</code></pre></div>
|
|
<p>Skills in <code>.agents/skills</code> and <code>.cursor/mcp.json</code>. The repo's <code>.cursor/rules/storebound.mdc</code> is an agent-requested rule you can copy.</p>
|
|
</div>
|
|
<div class="panel" role="tabpanel" id="p-copilot" aria-labelledby="tab-copilot" tabindex="0">
|
|
<h4 class="panel-h">VS Code / GitHub Copilot</h4>
|
|
<div class="code"><button class="copy" type="button" data-copy-target="c-copilot" aria-label="Copy Copilot command">Copy</button>
|
|
<pre id="c-copilot"><code>bunx storebound install --agent copilot</code></pre></div>
|
|
<p>Skills in <code>.github/skills</code> and <code>.vscode/mcp.json</code>.</p>
|
|
</div>
|
|
<div class="panel" role="tabpanel" id="p-windsurf" aria-labelledby="tab-windsurf" tabindex="0">
|
|
<h4 class="panel-h">Windsurf (Devin Desktop)</h4>
|
|
<div class="code"><button class="copy" type="button" data-copy-target="c-windsurf" aria-label="Copy Windsurf command">Copy</button>
|
|
<pre id="c-windsurf"><code>bunx storebound install --agent windsurf</code></pre></div>
|
|
<p>Skills in <code>.agents/skills</code>, MCP in your global <code>mcp_config.json</code>.</p>
|
|
</div>
|
|
<div class="panel" role="tabpanel" id="p-opencode" aria-labelledby="tab-opencode" tabindex="0">
|
|
<h4 class="panel-h">OpenCode</h4>
|
|
<div class="code"><button class="copy" type="button" data-copy-target="c-opencode" aria-label="Copy OpenCode command">Copy</button>
|
|
<pre id="c-opencode"><code>bunx storebound install --agent opencode</code></pre></div>
|
|
<p>Skills in <code>.opencode/skills</code> and <code>opencode.json</code>.</p>
|
|
</div>
|
|
<div class="panel" role="tabpanel" id="p-agents" aria-labelledby="tab-agents" tabindex="0">
|
|
<h4 class="panel-h">Anything that reads AGENTS.md</h4>
|
|
<p>Copy or link <code>AGENTS.md</code> (rules and skill index) into your project and use the CLI. For an MCP client <code>install</code> does not know: command <code>bunx</code> with args <code>["storebound", "mcp"]</code>.</p>
|
|
</div>
|
|
</div>
|
|
<p class="fine"><code>install</code> takes several agents at once (<code>--agent codex,cursor</code>), <code>--dry</code> to preview, and only touches its own <code>storebound</code> entry and skill folders. Re-running it is safe.</p>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<h3><span class="num">3</span>Ask your agent</h3>
|
|
<ul class="asks">
|
|
<li>“take my app to Google Play and the App Store”</li>
|
|
<li>“fill in the store questionnaires”</li>
|
|
<li>“add a yearly subscription with a free trial on both stores”</li>
|
|
<li>“the reviewer could not sign in”</li>
|
|
<li>“make store screenshots in 5 languages”</li>
|
|
<li>“is my app ready to submit”</li>
|
|
</ul>
|
|
<p class="fine">The matching skill takes over. No AI tool? The skills are Markdown checklists you can read yourself.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec alt" id="safety" aria-labelledby="safety-h">
|
|
<div class="wrap">
|
|
<p class="kicker">Safety model</p>
|
|
<h2 id="safety-h">Built to be handed to an agent.</h2>
|
|
<ul class="safety">
|
|
<li><h3>Credentials stay out of the repo</h3><p>Env vars or the gitignored <code>.storebound/credentials.json</code>. Official store APIs only, with keys you create and can revoke, never your password.</p></li>
|
|
<li><h3>Dry run everywhere</h3><p>Every write command takes <code>--dry</code>; every MCP write tool has <code>dry_run</code>, <strong>default true</strong>.</p></li>
|
|
<li><h3>Nothing public or irreversible through an agent</h3><p>Submitting, releasing, production releases, resuming or completing a rollout, canceling a submission, deleting and creating offer codes are not MCP tools. Demo accounts for reviewers live only in <code>.storebound/</code>. They stay terminal commands that ask first.</p></li>
|
|
<li><h3>Idempotent writes</h3><p>Re-running converges (MD5 checks for screenshots and previews, one Play edit per language), so a failed upload is fixed by running it again.</p></li>
|
|
<li><h3>Careful with your devices</h3><p>storebound shuts down only a simulator it booted itself, and never boots, wipes or reboots an Android emulator or device.</p></li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="sec" id="faq" aria-labelledby="faq-h">
|
|
<div class="wrap faq-in">
|
|
<div class="faq-side">
|
|
<p class="kicker">FAQ</p>
|
|
<h2 id="faq-h">Questions, answered.</h2>
|
|
<p class="sub">The short version. The README has the long one, with every command and what each store allows through its API.</p>
|
|
<p class="faq-links">
|
|
<a href="https://git.crt.fyi/CRT/storebound/src/branch/main/README.md" target="_blank" rel="noopener">Read the README<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
<a href="https://git.crt.fyi/CRT/storebound/issues" target="_blank" rel="noopener">Ask on the issue tracker<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
</p>
|
|
</div>
|
|
<div class="faq">
|
|
<details open>
|
|
<summary><h3>Do I need an AI agent?</h3></summary>
|
|
<div class="faq-a"><p>No. Every step is a CLI command too (<code>bunx storebound --help</code>), and the skills are Markdown checklists you can read yourself for the console-only forms. The agent integrations are optional.</p></div>
|
|
</details>
|
|
<details>
|
|
<summary><h3>Will it press Submit for me?</h3></summary>
|
|
<div class="faq-a"><p>No, never. Submitting, releasing, completing a rollout and deleting are not MCP tools, and the terminal commands for them ask first. storebound gets you to zero blockers; you press Submit for review on the App Store and Send changes for review on Play.</p></div>
|
|
</details>
|
|
<details>
|
|
<summary><h3>Is it only for games?</h3></summary>
|
|
<div class="faq-a"><p>No. <code>--profile app</code> (the default) and <code>--profile subscription-app</code> bring templates for utility and subscription apps: accounts, account deletion, demo accounts for review, subscriptions on both stores and a calm screenshot style. <code>--profile game</code> is for free-to-play games with ads and in-app purchases.</p></div>
|
|
</details>
|
|
<details>
|
|
<summary><h3>Where do my keys go? Is my store account safe?</h3></summary>
|
|
<div class="faq-a"><p>In environment variables or <code>.storebound/credentials.json</code>, which <code>init</code> gitignores. storebound uses the official store APIs with keys you create and can revoke, never your password. Use an App Store Connect key with the App Manager role and a Play service account limited to your app. Every write is a dry run until you say yes.</p></div>
|
|
</details>
|
|
<details>
|
|
<summary><h3>My app is Unity, Godot, Flutter, React Native or native.</h3></summary>
|
|
<div class="faq-a"><p>Fine. The store commands work on build numbers and listing files, not engines. Screenshots and clips come from a web build, the iOS Simulator, an Android emulator or device, or a folder of your own frames.</p></div>
|
|
</details>
|
|
<details>
|
|
<summary><h3>Does it cost anything? Any telemetry?</h3></summary>
|
|
<div class="faq-a"><p>It is free and open source under the MIT license. No account, no sign-up, no telemetry: it only talks to the Google Play and App Store Connect APIs with your keys. You still need your own Play Console and Apple Developer accounts.</p></div>
|
|
</details>
|
|
<details>
|
|
<summary><h3>Can I use it for many apps?</h3></summary>
|
|
<div class="faq-a"><p>Yes. Each app or game gets its own config in its own repo, so the same checks and store art pipeline run for every title. If you ship many similar apps, <code>storebound originality --against <folders></code> compares one against the others offline (listing text, name, icon and screenshot perceptual hashes, project structure) for Apple's spam guideline (4.3) and Play's repetitive content policy, and exits with 1 on a near-duplicate. <code>storebound scan --originality</code> runs the same check as part of the scan, and the <code>portfolio-originality</code> skill walks your agent through the fixes.</p></div>
|
|
</details>
|
|
<details>
|
|
<summary><h3>Does it run on Windows or Linux?</h3></summary>
|
|
<div class="faq-a"><p>It runs wherever Bun 1.1+ runs; it does not run on plain Node. The store commands are API calls. Capturing from the iOS Simulator needs a Mac with Xcode; a web build, an Android emulator or a folder of frames works anywhere. Icons, screenshots and video also need <code>ffmpeg</code>.</p></div>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
|
|
<footer class="foot">
|
|
<div class="wrap foot-in">
|
|
<div>
|
|
<a class="brand" href="#top" aria-label="storebound, back to top">
|
|
<img class="mark" src="img/logo-mark.png?v=2" srcset="img/logo-mark.png?v=2 1x, img/logo-mark@2x.png?v=2 2x" width="28" height="28" alt="storebound logo">
|
|
<span>storebound</span>
|
|
</a>
|
|
<p class="fine">MIT · made by CRTkafa · not affiliated with Google or Apple.<br>App Store is a trademark of Apple Inc.; Google Play is a trademark of Google LLC.</p>
|
|
</div>
|
|
<nav aria-label="Footer">
|
|
<a href="https://git.crt.fyi/CRT/storebound" target="_blank" rel="noopener">git repo<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
<a href="https://www.npmjs.com/package/storebound" target="_blank" rel="noopener">npm<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
<a href="https://git.crt.fyi/CRT/storebound/src/branch/main/README.md" target="_blank" rel="noopener">README<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
<a href="https://git.crt.fyi/CRT/storebound/src/branch/main/CHANGELOG.md" target="_blank" rel="noopener">CHANGELOG<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
<a href="https://git.crt.fyi/CRT/storebound/src/branch/main/SECURITY.md" target="_blank" rel="noopener">SECURITY<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
</nav>
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|