One product per machine: the CLI and the desktop app share one version, the store accounts and the app folder list

- 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
This commit is contained in:
CRTkafa 2026-10-13 06:17:31 +08:00
parent bba46b5c7f
commit d2ce185441
60 changed files with 2443 additions and 246 deletions

View file

@ -423,7 +423,7 @@ Rejection risks: 2 blocker, 3 high, 0 medium, 0 low
<div class="wrap">
<p class="kicker">3 · The desktop app</p>
<h2 id="desktop-h">Every app on both stores, in one window.</h2>
<p class="sub">Releases, charts, catalog and prices side by side, crash health, reviews, TestFlight and the rejection checks, for every app and game you ship. Send for review from there too: a dry run first, then a confirm. Same storebound underneath; store keys stay in the system keychain.</p>
<p class="sub">Releases, charts, catalog and prices side by side, crash health, reviews, TestFlight and the rejection checks, for every app and game you ship. Send for review from there too: a dry run first, then a confirm. Same storebound underneath, with the CLI's version number, store accounts and app folders: <code>npx storebound desktop</code> installs it and opens your app folder.</p>
<div class="tabs desk-tabs" data-tabs>
<div class="tablist" role="tablist" aria-label="Desktop app screens">
<button role="tab" id="dt-send-for-review" aria-controls="dp-send-for-review" aria-selected="true">Send for review</button>
@ -516,9 +516,9 @@ Rejection risks: 2 blocker, 3 high, 0 medium, 0 low
<li>Checksum: <code>shasum -a 256 &lt;file&gt;</code> (macOS, Linux) or <code>Get-FileHash &lt;file&gt;</code> (Windows) must print the SHA-256 above.</li>
<li>Signature: every file has a <code>.sig</code> next to it (the app's update key, minisign). With <a href="https://jedisct1.github.io/minisign/" target="_blank" rel="noopener">minisign<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>: <code>base64 -d &lt;file&gt;.sig &gt; f.minisig &amp;&amp; minisign -Vm &lt;file&gt; -x f.minisig -P &lt;key&gt;</code>, with the public key
<code class="dl-key">RWQqOO/ZeIr3RBmolt/fx108m89QNEUndRTFrucjx0CBMoeFb7/xhrvW</code></li>
<li>Or let the CLI do both: <code>storebound desktop --download</code> saves the installer to ~/Downloads after checking its signature (it never installs anything).</li>
<li>Or let the CLI do both: <code>npx storebound desktop</code> downloads the app for your system, checks its size, SHA-256 and signature, installs it for your user (no admin rights; it asks first) and opens it on the current app folder. <code>--download</code> only saves the checked installer to ~/Downloads.</li>
</ol>
<p class="fine">The app updates itself from this site: each update is signed, and the app checks the signature before it replaces anything. It asks before installing. The npm package <code>storebound</code> stays the CLI, MCP server and agent skills; the app brings its own copy of the CLI.</p>
<p class="fine">One product, one version: the app and the npm package <code>storebound</code> (CLI, MCP server, agent skills) are released together with the same number, and the app runs its own copy of that CLI. They share the store accounts (<code>~/.storebound/credentials</code>) and the list of app folders. The app also updates itself from this site: each update is signed, and the app checks the signature before it replaces anything. It asks before installing.</p>
</details>
</div>
</div>
@ -625,7 +625,7 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
<ul class="safety">
<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>The owner's buttons stay yours</h3><p>Submitting, releasing, completing a rollout, canceling a submission, deleting and creating offer codes are not MCP tools; the terminal commands ask first.</p></li>
<li><h3>Credentials stay local</h3><p>Env vars or the gitignored <code>.storebound/credentials.json</code>; the desktop app uses the system keychain. Official store APIs only, with keys you create and can revoke, never your password.</p></li>
<li><h3>Credentials stay local</h3><p>Store accounts once per machine in <code>~/.storebound/credentials</code> (files only you can read, shared by the CLI and the desktop app), the rest in the project's gitignored <code>.storebound/</code>. Official store APIs only, with keys you create and can revoke, never your password.</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>Personal data stays local</h3><p>Tester lists, the App Review demo account, reply drafts and reports live in <code>.storebound/</code>, masked in every log line.</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>
@ -706,7 +706,8 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
<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="npx storebound init --profile app" aria-label="Copy step 1 command">Copy</button></div>
<pre><code><span class="p">$</span> npx storebound init --profile app</code></pre>
<pre><code><span class="p">$</span> npx storebound init --profile app
<span class="p">$</span> npx storebound desktop <span class="c"># optional: installs the app once, opens this folder</span></code></pre>
</div>
<div class="pv-row">
<span class="pv-label">Profiles</span>
@ -715,9 +716,9 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
<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>
<li><code>.storebound/</code><span>this app's upload key and review contact, gitignored</span></li>
</ul>
<p class="pv-note">Needs Node 20+ (or <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+ with <code>bunx</code>), plus <code>ffmpeg</code> for icons, screenshots and video.</p>
<p class="pv-note">storebound is installed once per machine; each project has its <code>storebound.config.ts</code> and <code>.storebound/</code>. Needs Node 20+ (or <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+ with <code>bunx</code>), 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>
@ -756,7 +757,7 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
</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>
<div class="pv-bar"><span class="pv-k">~/.storebound/credentials/default.json</span><span class="pv-tag">once per machine</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>,
@ -764,7 +765,7 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
<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>
<p class="pv-note">A Play service account and an App Store Connect API key, once for every project on this machine (the desktop app's Accounts screen writes the same file; keys already in a project: <code>npx storebound accounts import</code>). 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>