> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shoppex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Theme CLI

> Inspect ThemeDocument settings and schemas with the Shoppex CLI.

Run the CLI with `npx @shoppexio/cli`, or install it globally. Authenticate with
a Shoppex API key that has the required theme scopes.

```bash theme={"system"}
shoppex theme list
shoppex theme settings get --theme <id>
shoppex theme settings update --theme <id> --file <settings.json>
shoppex theme document get --theme <id>
shoppex theme document schema --theme <id>
```

`settings update` validates the complete settings object. Fetch the current
settings first, make a focused change, and keep a local copy for review.

The CLI can also install the Shoppex MCP server configuration for supported AI
clients. Run `shoppex --help` for the exact commands available in your installed
version.

## Editing an Easy theme locally

An Easy theme is a ThemeDocument, not a source tree. It is not one opaque file
either. `theme pull` splits it into files you can read, diff, and commit.
`theme push` puts them back.

```bash theme={"system"}
shoppex theme pull --theme <id> --dir ./theme
cd theme
shoppex theme dev
shoppex theme push
shoppex theme publish
```

A checkout looks like this:

```
theme/
|-- theme.json              name, version, style slots, fonts, presets, custom CSS
|-- global-blocks.json      navigation, footer, cart drawer
|-- AGENTS.md               rules for an AI assistant editing this checkout
|-- pages/
|   |-- home.json
|   |-- product.json
|   `-- collections/summer.json
`-- .shoppex/
    |-- document.json       tracking: theme id, revision, checksums
    `-- schema/             JSON Schema for each file kind
```

Every authored file carries a `$schema` pointer into `.shoppex/schema/`. VS Code
and other JSON language servers use it to complete block types, element types,
and settings. They also flag mistakes while you type. The schemas come from the
server on each pull, so they always match the contract that validates your push.

`pull` requires an empty target directory. Add `--force` to overwrite matching
files in a non-empty one. Pages deleted upstream are removed locally, and each
removal is printed.

`theme.json` carries a `pageOrder` array. Page aliases resolve in document page
order, so the checkout records that order instead of inferring it from the
directory listing. Reorder the entries to reorder the document. A page file you
add locally is appended, and the push output names it. Move it inside
`pageOrder` to place it somewhere else.

`pull` also writes `AGENTS.md`, so Cursor, Claude Code, and Codex pick up the
rules that the files do not show: which block types exist, that `.shoppex/` is
off limits, and that a push conflict means pull-and-reapply. A pull refreshes
the guide it wrote itself. It never touches an `AGENTS.md` you wrote: it says so
and leaves your file alone.

`push` sends the reassembled document with the revision recorded at pull time.
If the Builder saved the theme in the meantime, the push is rejected with a
conflict. It does not overwrite that edit. Pull again, reconcile, then push. The
server is the only validator. A contract violation comes back as a validation
error that names the offending path, so the CLI keeps no second copy of the
schema.

`push` writes the draft. `publish` promotes exactly the revision the checkout
recorded, so a Builder save in between is a conflict instead of a silent
promotion of an edit this checkout never saw.

### Which themes can be pushed

`pull` works for every Easy theme. `push` only works for themes whose document
is authoritative, which means themes imported from a ThemeDocument. A theme
installed from the catalog renders its document from Builder settings. The
server refuses direct document writes to it, and `pull` says so up front. That
checkout is still useful for reading, diffing, and backing up. Edit it in the
Builder instead.

### Live preview while you edit

`theme dev` watches the checkout and saves the draft after each change. It also
serves a local page that frames the hosted preview and reloads it on every save.

```bash theme={"system"}
shoppex theme dev --dir ./theme --port 4300
```

It prints a `http://localhost:<port>` address. Open that address, not the
preview URL, because the local page performs the reload. The preview renders on
the server from the draft, so you see the real renderer, not a local
approximation.

A save that would store an identical document is skipped, so no-op writes do not
spend a revision. If the Builder saves the same theme while `dev` runs, the next
save conflicts and `dev` stops. Pull again, reconcile, then restart. A file that
is not valid JSON yet is normal mid-edit. It is reported once and does not stop
the loop.

The API key needs `themes.read` for `pull`. It needs `themes.write` for `push`,
`dev`, and `publish`. A key bound to one theme can use the equivalent
theme-specific scopes.

## Advanced storefronts

Advanced storefronts use merchant-owned Vite and React source instead of a
ThemeDocument. The CLI can pull the current source, push a complete local
project, build it, and publish the completed artifact.

```bash theme={"system"}
shoppex storefront list
shoppex storefront pull --theme <id> --dir ./storefront
cd storefront
shoppex storefront push
shoppex storefront push --delete
shoppex storefront status
shoppex storefront deploy
shoppex storefront export --out ./backup.zip
```

`pull` requires an empty target directory. Add `--force` when you intentionally
want to overwrite matching files in a non-empty directory.

After a pull, the CLI writes `.shoppex/storefront.json`:

```json theme={"system"}
{
  "themeId": "11111111-1111-4111-8111-111111111111",
  "sourceRevision": "a1b2c3d4e5f67890",
  "files": {
    "package.json": "5f8f04f6a3a892aaabbddb6cf2738948f8f4d46fdc2f5e58d3a8f8f40f6f9f25",
    "src/App.tsx": "8c73402ad40b0a4f8fd900e5d8ebf32e071c70af879a82a44cbbbc7f0c7f3a31"
  }
}
```

The revision is the optimistic concurrency guard. The `files` map records the
last successful pull or push. On every pull, the CLI writes the full remote
manifest and removes locally tracked files that were deleted remotely. It
prints each removed path. `--force` still controls overwriting in a non-empty
directory.

`pull` downloads the whole revision as a single archive instead of one request
per file, so even large storefronts arrive in one round trip. Every extracted
file is verified against the manifest checksum, and the pull stops without
writing anything if the archive and the manifest disagree.

`push` sends every tracked file plus new local files. New files under
`node_modules`, `dist`, `.git`, `.turbo`, or `.shoppex`, and `.DS_Store` files
are ignored. A `shoppex.theme.json` at the storefront root is ignored too. The
name is reserved for theme package metadata, and the API rejects it as source.
Already tracked files are never ignored, so a tracked file such as
`dist/vendor.js` remains part of the source tree.

## Exporting a storefront package

`export` downloads the complete source tree of the current revision as a single
ZIP. It adds one `shoppex.theme.json` package manifest, holding the theme
metadata and the storefront's content. It is the portable form of a storefront:
back it up, hand it to another developer, or import it into another shop.

```bash theme={"system"}
shoppex storefront export --theme <id>
shoppex storefront export --dir ./storefront --out ./backup.zip
```

Inside a pulled storefront directory the theme id comes from
`.shoppex/storefront.json`, so `--theme` is optional there. Without `--out` the
CLI writes the server-provided filename into the current directory. It refuses
to overwrite an existing file unless you pass `--force`, and never writes
through a symlink. `export` needs only read access, so `themes.read` or the
theme-specific read scope is enough.

Pushes above about 10 MB automatically upload the complete source tree
as a single ZIP archive. This transport switch requires no flags and keeps the
same revision, deletion-confirmation, and build behavior as smaller pushes.

If a tracked file is missing locally, `push` prints the complete deletion list
and stops. Re-run with `--delete` to confirm those deletions. The full snapshot
then removes them remotely. If the remote revision changed, pull the latest
source before you push again. Pulling also applies remote deletions locally.

By default, `push` starts a build and prints its job ID. Use `--no-build` to
upload source without starting one. `deploy` checks the current revision's
build every five seconds, waits up to 600 seconds, and publishes only after the
build reaches `COMPLETED`. Override the wait with `--timeout <seconds>`.
Immediately before publishing, `deploy` re-checks that the server is still on
the revision recorded in `.shoppex/storefront.json`. If another developer
pushed, deploy stops and asks you to pull or push first.

When a build fails, `deploy` and `status` print the failure kind and the
reason recorded by the build. They also print what to do about it, plus the
tail of the build log. The most common failure is `lockfile_out_of_sync`: your `package.json`
and lockfile disagree, because a dependency changed without a matching
`bun install`. Run `bun install`, then push again so the updated lockfile ships
with your source. `workspace_unavailable` is ours, not yours: retry the build.

The API key needs `themes.read` for `list`, `pull`, `status`, and the build
polling in `deploy`. It needs `themes.write` for `push`, build enqueueing, and
the publish step in `deploy`, so `deploy` needs both scopes. A key bound to one
theme can use the equivalent theme-specific read and write scopes.
