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:
parent
bba46b5c7f
commit
d2ce185441
60 changed files with 2443 additions and 246 deletions
|
|
@ -29,7 +29,9 @@ run in the background with a log (`storebound shots render > .storebound/render.
|
|||
| `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 [--download]` | local | open the storebound desktop app (in an app folder: add or select it there); not installed: the download for this system, `--download` saves it to ~/Downloads after checking its signature (never installs) |
|
||||
| `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 |
|
||||
|
||||
|
|
|
|||
|
|
@ -13,23 +13,27 @@ How the desktop app (`apps/desktop`) is built, signed, checked and published. Fo
|
|||
updater key. The app checks the signature before it replaces anything and installs only after the owner clicks
|
||||
Install in a native dialog (Settings > Updates).
|
||||
- **It brings its own CLI.** The app bundles the storebound CLI as a sidecar, so it never depends on an npm install.
|
||||
That CLI is the npm package of the same version.
|
||||
- **The npm package stays the CLI, the MCP server and the skills.** It contains no Tauri binaries.
|
||||
- **Bridge: `storebound desktop`.**
|
||||
- Installed: it opens the app. In an app folder it sends `storebound://open?path=<folder>`, and the app selects
|
||||
that folder, or adds it after a native dialog.
|
||||
- Not installed: it prints the download for this OS and CPU.
|
||||
- `--download` saves the installer to ~/Downloads after checking its size, SHA-256 and minisign signature. It never
|
||||
installs anything.
|
||||
- **Bridge: `storebound desktop`** installs, updates and opens the app of the CLI's own version (docs/desktop.md,
|
||||
"One product with the CLI"). It reads `storebound.dev/desktop/dl/<version>/latest.json` (written by the publish
|
||||
script next to the version's files) and falls back to the channel's `latest.json`. `--download` only saves the
|
||||
checked installer to ~/Downloads.
|
||||
|
||||
## One version
|
||||
## One version, one release
|
||||
|
||||
`desktopVersion` in the root `package.json` is the desktop app's version. The npm package's own `version` belongs
|
||||
to the CLI and moves on its own.
|
||||
The CLI and the desktop app are one product with one version number: `version` in the root `package.json`. 0.8.0 is
|
||||
the first release of both under one number. (The desktop had its own `desktopVersion`, 0.1.0; its release candidate
|
||||
in `~/Desktop/storebound-dist/desktop-0.1.0` was never published, so no installed app or link depends on it.)
|
||||
|
||||
- `bun tools/desktop-version.ts` checks the copies: `apps/desktop/package.json`, `tauri.conf.json`, `Cargo.toml`
|
||||
and the app's entry in `Cargo.lock`. CI runs this check.
|
||||
- `bun tools/desktop-version.ts --set 0.1.1` writes a new version into all of them.
|
||||
- `src-tauri/tests/d5.rs` checks the same thing again from the Rust side.
|
||||
- `bun tools/desktop-version.ts` checks the copies: the agent plugin manifests (`.claude-plugin/plugin.json`,
|
||||
`marketplace.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`), `apps/desktop/package.json`,
|
||||
`tauri.conf.json`, `Cargo.toml` and the app's entry in `Cargo.lock`. CI runs this check; `src-tauri/tests/d5.rs`
|
||||
checks the app's copies from the Rust side.
|
||||
- `bun tools/desktop-version.ts --set 0.8.0` writes a new version into package.json and all of them.
|
||||
- **Release order** (one commit, one version): build and check the desktop artifacts (`mac`, `add`), `publish` them
|
||||
(it refuses a folder whose version is not package.json's), then `npm publish` of the same version (CONTRIBUTING.md).
|
||||
The app goes first, so `npx storebound desktop` of the new CLI finds its own version.
|
||||
|
||||
## Keys and private files (outside every repository)
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ Types of the protocol: `cli/lib/desktop-protocol.ts`; pure portfolio logic: `cli
|
|||
- The webview gets no plugin API. It may call the app commands (each re-checks the window label and the page origin
|
||||
in Rust) and listen to two events. Strict CSP, no remote content. `src-tauri/tests/security.rs` pins all of it.
|
||||
- Store keys (Play service account JSON, App Store Connect key id, issuer id and .p8) are picked in native dialogs,
|
||||
stored in the OS keychain (`keyring`), never shown again and passed to the sidecar per call in memory only.
|
||||
written into the machine's credential profiles (`~/.storebound/credentials`, files 0600 in folders 0700, the same
|
||||
store the CLI reads: `profiles.rs`), never shown again and passed to the sidecar per call in memory only.
|
||||
|
||||
## Screens and data (D2)
|
||||
|
||||
|
|
@ -64,7 +65,8 @@ Types of the protocol: `cli/lib/desktop-protocol.ts`; pure portfolio logic: `cli
|
|||
(`apple.testflight_status`), Checks (offline `scan`: rejection risks with guideline, evidence file:line, fix
|
||||
and skill, owner-accepted ones marked; offline `check`; `validate` with fix hints).
|
||||
- **First-run guide**: what the app does, add or start an app folder (`project.init` = `storebound init --profile`,
|
||||
confirmed in a native dialog), store keys into the keychain and which app uses which, local tools, first read.
|
||||
confirmed in a native dialog), store keys into the shared credential profiles and which app uses which, local
|
||||
tools, first read.
|
||||
- **Command palette** (Cmd/Ctrl+K): apps, tabs, screens and actions. Cmd/Ctrl+1 Portfolio, +2-5 workspace screens,
|
||||
Cmd/Ctrl+Shift+[ ] app tabs, arrow keys in the tab list. Toasts show background jobs with progress and Cancel.
|
||||
- **Unknown, never false.** Store reads run on demand (they use the account's API quota) and are saved in app data
|
||||
|
|
@ -104,7 +106,8 @@ Types of the protocol: `cli/lib/desktop-protocol.ts`; pure portfolio logic: `cli
|
|||
- A **studio** is a name, a colour and an optional logo. It groups any number of store accounts:
|
||||
- a Play service account, which can be invited to several Play developer accounts and then sees all their apps;
|
||||
- an App Store Connect team key, one per team.
|
||||
- Keys stay in the keychain. The state file keeps labels and studio ids only.
|
||||
- Keys live in the credential profiles shared with the CLI. The state file keeps labels, studio ids and the profile
|
||||
name of each account only (see "One product with the CLI" below).
|
||||
- `storebound apps check` gives each account's health, with the fix in words:
|
||||
- ok;
|
||||
- no-apps;
|
||||
|
|
@ -159,7 +162,8 @@ host state file (`overrides_set`, every key checked in `workspace.rs`).
|
|||
- Privacy, with a button that reveals the data folder.
|
||||
- Shortcuts.
|
||||
- Updates: channel (stable / beta), check and install (D5).
|
||||
- Reset: native confirmation, then the keychain entries and every saved state are removed.
|
||||
- Reset: native confirmation, then every saved state of the app is removed; the shared credential profiles and the
|
||||
app folder list stay (the CLI uses them).
|
||||
|
||||
### Writes in the window
|
||||
|
||||
|
|
@ -197,12 +201,12 @@ Each write is a dry run, then the native dialog, then the real run, with one pro
|
|||
### The same model in the terminal
|
||||
|
||||
The CLI workspace (`storebound.workspace.json`, docs/studios.md "Many apps and studios") holds the same studios, accounts
|
||||
(credential profile names instead of keychain entries), folders and overrides. The pairing rules
|
||||
(by credential profile name, the same profiles the app uses), folders and overrides. The pairing rules
|
||||
(`cli/lib/identity.ts`) and the release lanes (`cli/lib/train.ts`) are one implementation for both; the window
|
||||
re-exports them (`src/lib/identity.ts`, `src/lib/model.ts`). `storebound ws desktop import` reads `state.json` into a
|
||||
workspace file (the keychain is never read; the profiles to create are listed); `storebound ws desktop export --merge
|
||||
--apply` adds a workspace to `state.json` while the app is closed (backup kept, nothing removed, new accounts get
|
||||
their keys in the Accounts screen). Project ids in an exported state are stable per folder path (16 hex).
|
||||
workspace file (each account keeps its profile; accounts of older apps get a profile name to create);
|
||||
`storebound ws desktop export --merge --apply` adds a workspace to `state.json` while the app is closed (backup kept,
|
||||
nothing removed; accounts carry their profile, so their keys are already there). Project ids in an exported state are stable per folder path (16 hex).
|
||||
|
||||
### Fill in console
|
||||
|
||||
|
|
@ -337,6 +341,45 @@ Download sources (one click on the Dependencies screen or in the first-run guide
|
|||
- **Progress and cancel** go through the same progress events as sidecar jobs; a cancelled download removes its
|
||||
temporary file.
|
||||
|
||||
## One product with the CLI (0.8)
|
||||
|
||||
The CLI and the app are one product: one version number, one set of store accounts, one list of app folders.
|
||||
|
||||
- **One version.** `package.json`'s `version` is the app's too (`tools/desktop-version.ts` keeps the copies equal,
|
||||
CI and `src-tauri/tests/d5.rs` check it), and both are released together (docs/desktop-release.md). The app runs
|
||||
its bundled CLI (the sidecar), which is that same version. A folder whose `package.json` pins another storebound
|
||||
(`devDependencies.storebound`) gets a notice on its app page: "This project uses storebound X, the app runs Y"
|
||||
(`workspace::pin_differs`; tags, ranges and `file:` specs give none).
|
||||
- **`npx storebound desktop`** (cli/desktop.ts, the plans in cli/lib/desktop-install.ts): the app has the CLI's
|
||||
version: open it on the current folder. Not installed: download it for this OS (the CLI's own version from
|
||||
`storebound.dev/desktop/dl/<version>/latest.json`, else the channel's newest), check size, SHA-256 and the minisign
|
||||
signature, install it for this user without admin rights (macOS: the .app from the read-only mounted DMG into
|
||||
/Applications when writable, else ~/Applications, no extended attributes touched; Windows: the NSIS installer with
|
||||
`/S`, per user; Linux: the AppImage into ~/.local/bin plus `~/.local/share/applications/storebound.desktop` with
|
||||
the `storebound://` handler, registered with `update-desktop-database` / `xdg-mime` when they exist; a .deb is only
|
||||
printed, it needs root), then open it. Older app: the same path as an update (never while the app runs); newer
|
||||
app: opened with a note, a downgrade only with `--force`. In a terminal it asks once (`[Y/n]`), `--yes` skips the
|
||||
question, without a terminal and `--yes` it prints the command and exits 3. `--dry` and `--json` change nothing;
|
||||
`--download` only saves the checked installer. `storebound-desktop --version` prints the version (Windows, Linux).
|
||||
- **Accounts** live in the machine's credential profiles, `~/.storebound/credentials/<name>.json` with key files in
|
||||
`<name>/` (0600, folders 0700; `src-tauri/src/profiles.rs`, the CLI's cli/lib/credentials.ts and cli/lib/accounts.ts).
|
||||
An app account is one store of one profile (`Account.profile`); its id for a profile the CLI made is the CLI
|
||||
workspace's (`sha256("<store>:<profile>")`, 16 hex). Adding a key in the app writes the profile that already has
|
||||
that key (same key id / service account e-mail), else one named after the label; removing one moves its key file to
|
||||
`credentials/.removed/`. At start and whenever the list is shown, `profiles::sync` adds an account for every
|
||||
profile store the app does not have yet. **Migration:** accounts of earlier versions kept their keys in the OS
|
||||
keychain (`dev.storebound.desktop`); the first start of 0.8 writes each into a profile (keeping the account's id,
|
||||
label, studio and app mappings), reads it back and only then deletes the keychain entry. An account whose keychain
|
||||
entry is missing stays, marked "key not moved from the keychain yet" (host.log says which).
|
||||
Why files and not the keychain: the CLI has no runtime dependencies and runs on three systems and in agents'
|
||||
terminals; profile files were already its model, so one store means the app writes those files (owner-only modes,
|
||||
like `~/.ssh` and `~/.appstoreconnect/private_keys`) instead of the CLI learning three keychain APIs.
|
||||
- **App folders** are the machine's list `~/.storebound/projects.json` (`src-tauri/src/registry.rs`, the CLI's
|
||||
cli/lib/registry.ts): `storebound desktop` in an app folder, `storebound apps add`, `ws init` and the App folders
|
||||
screen add to it; `storebound apps folders` lists it; `storebound ws` without a workspace file works over it. The
|
||||
app keeps its per-folder extras (accounts) in `state.json` under the same id and syncs at start, when the list is
|
||||
shown and before a `storebound://open` link (a folder already on the list is selected without a question).
|
||||
|
||||
## Release, updates and the terminal bridge (D5)
|
||||
|
||||
- Releases: [desktop-release.md](desktop-release.md) (version authority, privacy gate, macOS signing and
|
||||
|
|
@ -344,9 +387,10 @@ Download sources (one click on the Dependencies screen or in the first-run guide
|
|||
- **Updates**: Settings > Updates checks storebound.dev (stable `/desktop/latest.json` or beta
|
||||
`/desktop/beta/latest.json`) only when asked; Install shows a native dialog, then the updater downloads the bundle,
|
||||
checks its minisign signature against `plugins.updater.pubkey` and restarts (`update_check`, `update_install`).
|
||||
- **`storebound://open?path=<absolute folder>`** (sent by `storebound desktop` in an app folder): the host accepts
|
||||
one plain absolute path (`policy::deep_link_open_path`), selects that app folder when it is in the list, otherwise
|
||||
asks in a native dialog and adds it (`deep-link-folder` event). A folder without a storebound config gets a message,
|
||||
- **`storebound://open?path=<absolute folder>`** (sent by `storebound desktop` in an app folder, which first puts
|
||||
the folder on the machine's list): the host accepts one plain absolute path (`policy::deep_link_open_path`), syncs
|
||||
the list, selects that app folder when it is on it, otherwise asks in a native dialog and adds it
|
||||
(`deep-link-folder` event). A folder without a storebound config gets a message,
|
||||
nothing else.
|
||||
- **`storebound-desktop --self-test`**: starts the bundled sidecar, checks the `session.hello` handshake, prints one
|
||||
JSON line and exits, before Tauri starts (no window). The release runs it on the exact artifacts.
|
||||
|
|
@ -368,7 +412,7 @@ Download sources (one click on the Dependencies screen or in the first-run guide
|
|||
from list. Portfolio reads skip them.
|
||||
- **Error screens.** A React error boundary around the whole window and one per screen and app tab show the error,
|
||||
Copy details, Reload and Reset saved state (keeps a backup: `state_reset` moves `state.json` to
|
||||
`state.json.reset-<time>.bak` after a native confirmation; keys stay in the keychain). `public/boot.js` runs before
|
||||
`state.json.reset-<time>.bak` after a native confirmation; keys stay in `~/.storebound/credentials`). `public/boot.js` runs before
|
||||
the bundle and shows the same panel in plain DOM when the bundle fails or the window stays empty.
|
||||
- **Logs** in `<app data>/logs`: `host.log` (start, saved state and migration, sidecar start / stop with its last
|
||||
stderr lines, panics) and `webview.log` (window.onerror, unhandled rejections, boundaries, through `log_webview`,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Getting started
|
||||
|
||||
**The model.** storebound is installed once per machine (`npx storebound` / the desktop app, one version number).
|
||||
Each project has its `storebound.config.ts` and a gitignored `.storebound/` folder (upload keystore, review contact
|
||||
and demo account). Store accounts live once per machine in `~/.storebound/credentials` (shared by CLI and app:
|
||||
`storebound accounts`), and so does the list of app folders (`~/.storebound/projects.json`: `storebound apps folders`,
|
||||
the desktop's App folders). `npx storebound desktop` installs the app (asks once) and opens it on the current folder.
|
||||
|
||||
## 5-minute quick start
|
||||
|
||||
You need [Node](https://nodejs.org) 20+ or [Bun](https://bun.sh) 1.1+ (and `ffmpeg` for icons, screenshots and video).
|
||||
|
|
@ -75,7 +81,22 @@ the age rating config, lanes and signing (`match`) are listed with their storebo
|
|||
|
||||
## Credentials (never in the repo)
|
||||
|
||||
Read from environment variables, or from `.storebound/credentials.json` in your project (gitignored by `init`):
|
||||
Store accounts belong to the machine, everything else to the project:
|
||||
|
||||
- **Store accounts** (App Store Connect team key, Play service account, vendor number): a credential profile in
|
||||
`~/.storebound/credentials/<name>.json`, its key files next to it in `<name>/` (mode 0600). The desktop app adds and
|
||||
reads the same profiles. Already have the keys in a project's `.storebound/credentials.json`? Move them once:
|
||||
`npx storebound accounts import --dry`, then `npx storebound accounts import --yes` in every project: the same key
|
||||
id or service account is one profile (`default` for the first one), and the project file keeps the rest and gets
|
||||
`"account": "<name>"`. `npx storebound accounts` lists the profiles and which one this folder uses.
|
||||
- **Which profile:** `--account <name>` for one run, `account: '<name>'` in the config, `"account"` in the project's
|
||||
`.storebound/credentials.json`, else the only profile on the machine (or the one named `default`).
|
||||
- **Per project:** the Android upload keystore, the App Review contact and demo account stay in the project's
|
||||
`.storebound/credentials.json`.
|
||||
- **Order:** environment variables win over everything, then a profile the project names, then the project file,
|
||||
then the default profile.
|
||||
|
||||
The names (env, profile and project file alike; the project file is gitignored by `init`):
|
||||
|
||||
| name | what |
|
||||
|---|---|
|
||||
|
|
@ -87,9 +108,8 @@ Read from environment variables, or from `.storebound/credentials.json` in your
|
|||
| `reviewContact` | (file only) App Review contact name / phone / email |
|
||||
| `reviewDemoAccount` / `REVIEW_DEMO_USER`, `REVIEW_DEMO_PASSWORD` | demo account for App Review (`reviewAccess.signInRequired`); never in the config or the repo, masked in logs |
|
||||
|
||||
Several store accounts on one machine: credential profiles (`~/.storebound/credentials/<profile>.json`, the same names
|
||||
except the per-app upload keystore)
|
||||
picked with `--account <profile>` or by a workspace ([Many apps and studios](studios.md#many-apps-and-studios)).
|
||||
Several store accounts on one machine: one profile each, picked as above or by a workspace
|
||||
([Many apps and studios](studios.md#many-apps-and-studios)).
|
||||
|
||||
|
||||
## Install for your AI tool
|
||||
|
|
|
|||
|
|
@ -48,10 +48,11 @@ a folder takes the store apps its config names, the remaining iOS and Android ap
|
|||
`unpairs` keep two apart, and the rest are store-only apps that `ws status` reads too.
|
||||
|
||||
**The desktop app** keeps the same model (studios, accounts, folders, overrides). `ws desktop import` turns its
|
||||
`state.json` (or `--state <file>`) into a workspace file and lists the credential profiles to create: the desktop's
|
||||
keychain is never read. `ws desktop export` prints the workspace as a desktop state (`--out <file>`), and
|
||||
`--merge --apply` adds it to the desktop's `state.json` while the app is closed (a backup is kept; nothing is
|
||||
removed; new accounts get their keys in the desktop's Accounts screen).
|
||||
`state.json` (or `--state <file>`) into a workspace file; its accounts name the credential profiles the app and the
|
||||
CLI share (`~/.storebound/credentials`), so the keys are already there. `ws desktop export` prints the workspace as a
|
||||
desktop state (`--out <file>`), and `--merge --apply` adds it to the desktop's `state.json` while the app is closed (a
|
||||
backup is kept; nothing is removed). Without a workspace file, `ws` works over the machine's app folder list
|
||||
(`~/.storebound/projects.json`, the desktop's App folders).
|
||||
|
||||
MCP: `workspace_list` (offline) and `workspace_status` (read-only).
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue