# CLI

> HTML version: https://storeshot.art/docs/cli/

The StoreShot CLI turns raw app screenshots into every App Store and Google Play image of your
listing from a terminal, a shell agent or a CI job, and saves them into your project, fastlane
folders included. It needs Node.js 18.3 or later and runs as `storeshot` once installed. It is
published on [npm](https://www.npmjs.com/package/@draggable/storeshot-cli) as
`@draggable/storeshot-cli` under the Apache-2.0 licence, with its source on
[GitHub](https://github.com/Draggable/storeshot-cli). Writing your own integration instead? Use the
[TypeScript SDK](/docs/sdk/).

## Install

<!-- doctest: covered-by=packages/cli/test-stack/cli.stack.test.mjs -->

```sh
npx @draggable/storeshot-cli --help
```

Or install it once with `npm install -g @draggable/storeshot-cli` and run `storeshot`. Another
publisher's npm package is called `storeshot`: it is a different tool, so always give npx the
scoped name. If a global install fails because a `storeshot` command already exists, uninstall
that package or keep using `npx @draggable/storeshot-cli`.

## Link your account

<!-- doctest: covered-by=packages/cli/test-stack/cli.stack.test.mjs -->

```sh
storeshot login
```

The CLI prints a link and a code. Open the link, check the code and approve it in the console:
the CLI stores a **test** key (see [test mode](/docs/test-mode/)) in
`~/.config/storeshot/cli-credentials.json`. Pass `--env live` for a live key. An agent whose shell
waits for a command to exit can run `storeshot login --no-wait`, show the code, and run
`storeshot login` again once it is approved. In CI, set `STORESHOT_API_KEY` from a secret instead
(see [authentication](/docs/authentication/)). `storeshot logout` forgets the stored key on this
machine; revoke the key itself in the console.

## A whole listing in one command

Put the screenshots and a `captions.json` in a folder:

```json
{
  "app": { "name": "Trailmate", "description": "Offline hiking maps" },
  "screens": [
    { "file": "01-map.png", "caption": "Find every trail" },
    { "file": "02-offline.png", "caption": "Works offline" }
  ]
}
```

<!-- doctest: covered-by=packages/cli/test-stack/cli.stack.test.mjs -->

```sh
storeshot set ./screens --store app_store,google_play --out . --layout fastlane
```

This creates one [screenshot set](/docs/screenshot-sets/), waits for it and writes
`fastlane/screenshots/en-US/` and `fastlane/metadata/android/en-US/images/` under the project
root. Add `--dry-run` to see the images and credits first, `--feature-graphic` for the Google Play
feature graphic, or `--devices <id,…>` to choose the sizes (`storeshot devices` lists them).
Without `--layout fastlane`, `--out` gets `<store>/<device>/NN.png`.

## Capture the raw screenshots

`storeshot capture` (CLI 2.60.0 or later) takes the raw screenshots with the tools you already
have: `xcrun simctl` for an iOS Simulator, `adb` for an Android emulator or device, or the
Playwright of your project for a web build (see [Capture raw screenshots](/docs/capture/) for what
each needs). It writes PNG files plus a `captions.json` that `storeshot set` reads as is:

<!-- doctest: covered-by=packages/cli/test-stack/cli.stack.test.mjs -->

```sh
storeshot capture ios --name 01-map --caption "Find every trail" --clean-status-bar
storeshot capture android --name 02-offline --caption "Works offline" --clean-status-bar
storeshot capture web http://localhost:8081 --route /,/settings --wait-for '[data-testid="ready"]'
storeshot set ./screens --store app_store,google_play --app-name Trailmate --out . --layout fastlane
```

`--clean-status-bar` shows 9:41 with full signal and battery, and is undone on exit, on errors and
on Ctrl-C (after a killed run, `--reset-status-bar` restores it). Web captures use the viewport and
pixel ratio of a StoreShot device (`--device`, default `app_store_1290x2796`), so each PNG has
exactly that size; simulator and emulator captures are never resized. `capture` never installs a
tool and needs no key: a missing tool exits 12, no running simulator or device exits 5.

## Commands

| Command              | What it does                                                          |
| -------------------- | --------------------------------------------------------------------- |
| `storeshot login`    | Links this machine to your account and stores a key                   |
| `storeshot logout`   | Forgets the stored key (`--all`: test and live)                       |
| `storeshot whoami`   | The account and the key in use, never the key itself                  |
| `storeshot credits`  | Your credit balance                                                   |
| `storeshot devices`  | Output sizes and their device ids (`--store`, `--include-deprecated`) |
| `storeshot generate` | One image per screenshot (1–10 files) for one size                    |
| `storeshot set`      | A whole listing: every screen on every device, one charge             |
| `storeshot status`   | A set or generation; `--wait` waits until it is final                 |
| `storeshot pull`     | Saves the images of a set or generation into `--out`                  |
| `storeshot capture`  | Raw screenshots from a simulator, emulator or web build (2.60.0+)     |
| `storeshot schema`   | Prints the JSON Schema of a command, offline                          |

`storeshot <command> --help` lists every flag. A wait that times out leaves the job running: run
`storeshot status <id> --wait`, then `storeshot pull <id> --out .`.

## Live credits

Test mode is the default. A live charge needs a live key, `--env live` or
`STORESHOT_ENVIRONMENT=live`, and then `--yes` (or a "y" at the prompt on a terminal).
`--max-credits <n>` refuses anything above `n`. Failed images are refunded (see
[credits](/docs/credits/)).

## CI

Store an API key as the secret `STORESHOT_API_KEY` and pin the CLI version, so every run uses the
same CLI. A GitHub Actions job (a test key makes sample images for free; a live key also needs
`--yes` and a `--max-credits` cap):

```yaml
jobs:
  screenshots:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - name: Store screenshots
        env:
          STORESHOT_API_KEY: ${{ secrets.STORESHOT_API_KEY }}
        run: >
          npx --yes @draggable/storeshot-cli@2.60.0 set ./screens
          --store app_store,google_play --out . --layout fastlane
          --idempotency-key "storeshot-$GITHUB_RUN_ID" --json > storeshot.json
      - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
        with:
          name: store-screenshots
          path: |
            fastlane/screenshots
            fastlane/metadata/android
```

An `--idempotency-key` never charges twice. A rerun of the job uploads the screenshots again, so
it ends with `IDEMPOTENCY_CONFLICT` (exit 2) and the first set's id in
`error.details.screenshotSetId`: save that set with `storeshot pull <id> --out . --layout fastlane`.

## Scripts and agents

`--json` writes exactly one JSON document to stdout, with `schemaVersion`, `ok`, `exitCode`, `data`
and `error`; `storeshot schema <command>` prints its JSON Schema. Exit codes are stable: 0 ok, 3 not
logged in, 4 not enough credits, 9 some images failed, 10 still running, 11 live spend not
confirmed, 12 local file problem (see [errors](/docs/errors/) for the API codes in `error.code`).

The schemas (JSON Schema 2020-12) are also published at their `$id` URLs. Within
`schemaVersion` 1 changes are only additive: new optional fields and new error codes.

- [Index](https://storeshot.art/schemas/cli/v1/index.json): the command → schema file map that `storeshot schema` prints
- [`envelope`](https://storeshot.art/schemas/cli/v1/envelope.json): storeshot --json envelope
- [`capture`](https://storeshot.art/schemas/cli/v1/capture.json): storeshot capture --json
- [`credits`](https://storeshot.art/schemas/cli/v1/credits.json): storeshot credits --json
- [`devices`](https://storeshot.art/schemas/cli/v1/devices.json): storeshot devices --json
- [`generate`](https://storeshot.art/schemas/cli/v1/generate.json): storeshot generate --json
- [`login`](https://storeshot.art/schemas/cli/v1/login.json): storeshot login --json
- [`logout`](https://storeshot.art/schemas/cli/v1/logout.json): storeshot logout --json
- [`pull`](https://storeshot.art/schemas/cli/v1/pull.json): storeshot pull --json
- [`set`](https://storeshot.art/schemas/cli/v1/set.json): storeshot set --json
- [`status`](https://storeshot.art/schemas/cli/v1/status.json): storeshot status --json
- [`whoami`](https://storeshot.art/schemas/cli/v1/whoami.json): storeshot whoami --json
- [`captions`](https://storeshot.art/schemas/cli/v1/captions.json): storeshot set --captions captions.json
- [`capture-pages`](https://storeshot.art/schemas/cli/v1/capture-pages.json): storeshot capture web --pages pages.json
