# Poltura CLI (/apps/cli)

`@poltura/cli` installs the `poltura` binary: one tool for apps, themes, a store's data and AI tools. (Not published to npm yet.)



```bash
npm install -g @poltura/cli
```

It needs Node.js 22 or later, sends no telemetry and only calls the Poltura API origin you give it. `poltura --help`, `poltura <topic> --help` and `poltura --version` answer from a prebuilt table without loading the rest of the tool, so they are instant.

| Topic or command | For | Page |
| --- | --- | --- |
| `poltura app` | Create, build, validate and publish apps. | This page |
| `poltura check` | The verdict of review for an app or a theme, before you publish. | [Check before you publish](#check-before-you-publish) |
| `poltura theme` | Start, check, sync, preview and publish themes. | [Theme commands](/themes/cli) |
| `poltura store` | Read records, run actions and ask analytics questions in a store. | [Store commands](/ai/store-cli) |
| `poltura mcp connect` | Add the Poltura MCP server to Claude, Cursor, VS Code or Codex. | [Store commands](/ai/store-cli#connect-an-ai-tool) |
| `poltura token` | Create, roll and revoke API tokens. | [API tokens](/api/tokens) |
| `poltura login`, `logout`, `whoami` | Sign in and out, and see who you are. | [Sign in](#sign-in-or-use-a-token) |
| `poltura completion zsh\|bash\|fish` | Print the shell completion script; `--help` shows where to put it. | |

Every command takes `-q, --quiet` (print only results and errors), `--no-color` and `-h, --help`. `NO_COLOR`, `FORCE_COLOR`, `CI` and output that is not a terminal are honoured. An error prints what went wrong, a `fix:` or hint line, and a link to the page of this site that explains it.

## Sign in or use a token [#sign-in-or-use-a-token]

Which credential a command needs depends on what it touches:

| Commands | Credential |
| --- | --- |
| `poltura store`, `poltura theme` (all but `init`, `check`, `package`), `poltura whoami` | `--token`, then `POLTURA_TOKEN` (an API token), then your `poltura login` sign-in. |
| `poltura app publish` | A developer token: `--token` or `POLTURA_APP_TOKEN`. A `pltd_` token your developer organization owns (for CI), or a `pltu_` personal token. It publishes releases to that organization's apps and nothing else. |
| `poltura token` | Your Poltura ID session: `--access-token` and `--id-token` (`POLTURA_ACCESS_TOKEN`, `POLTURA_ID_TOKEN`). See [API tokens](/api/tokens). |

The CLI checks an API token's checksum before sending it, so a truncated paste fails at once. An organization token (`plto_`) is refused for publishing, since it cannot publish.



```bash
poltura login
poltura whoami
poltura logout
```

`poltura login` opens Poltura ID in your browser (`--no-browser` prints the address instead, for SSH and other machines without one) and signs in as Poltura's own CLI client with OAuth and PKCE, answered on a loopback address of your machine. It asks for every read permission unless `--permissions a,b` names fewer, and you narrow or widen what is granted on the approval page. `--timeout <seconds>` (10 to 3600, default 300) sets how long the CLI waits. The session is kept in `~/.config/poltura/credentials.json` (mode 0600; `POLTURA_CONFIG_DIR` moves it) and renewed before it expires. `poltura whoami` shows who you are, the stores you reach and your permissions there; `poltura logout` revokes the session and forgets it.

The sign-in is bound to this machine with DPoP ([RFC 9449](https://www.rfc-editor.org/rfc/rfc9449)). `poltura login` makes a P-256 key pair and keeps the private key in its own file, `dpop/<thumbprint>.pem` next to the credentials (mode 0600). Every token exchange, every renewal and every API call carries a proof signed by that key, and API calls send the token as `Authorization: DPoP <token>`. A sign-in token copied off the machine without the key is refused with [`invalid_dpop_proof`](/api/errors#invalid-dpop-proof), and so is a copied refresh token: Poltura ID checks the key before it renews anything. If the key file is gone, the CLI asks you to run `poltura login` again. When your clock is off, the server answers with a nonce and the CLI sends the request once more with it. Node has no non-exportable key store it can reach without a native module, so protect the key file as you would the credentials. API tokens (`POLTURA_TOKEN`) are not bound to a key. Treat them as secrets.

If the API's certificate is signed by an authority your system trusts but Node does not (a company proxy, or the local development CA), the CLI trusts it too. It adds the system's trusted roots to Node's own before the first request. When a request gets no answer, the error says why: an untrusted certificate, a name that does not resolve, or a refused or broken connection.

| Setting | Flag | Variable | Default |
| --- | --- | --- | --- |
| API address | `--api <url>` | `POLTURA_API` | `https://api.poltura.com`, or the address of your last sign-in when no token is given |
| API token | `--token <token>` | `POLTURA_TOKEN` | none |
| Store | `--store <id>` | `POLTURA_STORE` | The only store the credential reaches; required when it reaches several |
| Developer token (publish) | `--token <token>` | `POLTURA_APP_TOKEN` | none |

## Exit codes and retries [#exit-codes-and-retries]

| Code | Meaning |
| --- | --- |
| 0 | Done. |
| 1 | The command failed, or a check found errors. |
| 2 | Wrong usage, such as a missing flag. |
| 3 | An action is waiting for a person to approve it. `--wait` on `store run`, `theme publish` and `theme rollback` waits for the decision instead. |

A call Poltura refused because the credential's points budget ran out never reached the store, so the `poltura theme` commands wait for as long as the refusal says (at most 30 seconds each time) and send it again, up to 8 times, before they give up. `poltura store` commands report the refusal; run them again after the wait. See [rate limits](/ai/agents#rate-limits).

## poltura app create [#poltura-app-create]



```text
Scaffold a new app project.

USAGE
  $ poltura app create [FLAGS] <dir>

ARGUMENTS
  dir                    Directory to create the project in (must be empty).

FLAGS
  --template <template>  Project template (choices: "hosted", "native", default:
                         "native").
  -h, --help             Show help.
```

`--template native` writes UI workers only. `--template hosted` also writes `src/server.ts`: a `/connect?installation=<installation id>` route that starts OAuth for that installation, the `/oauth/callback` that finishes it and calls the App API, and a product webhook. Its README lists the environment variables the server reads. The scaffold's `poltura.app.json` has no `appId`, so `poltura app publish` needs `--app` until you add one.



```text
created hosted app in /home/dev/my-app
```

## poltura app [#poltura-app]



```text
Create, build, validate and publish a Poltura app.

USAGE
  $ poltura app [FLAGS] COMMAND

COMMANDS
  create <dir>    Scaffold a new app project.
  build           Bundle workers and styles and write the release manifest.
  validate        Check the app config.
  dev             Rebuild on every change to the app sources.
  elements        List the storefront elements the app defines.
  publish         Build the app and upload it as a release for review.
  help [command]  Show help for a command.

FLAGS
  -h, --help      Show help.

EXAMPLES
  $ poltura app create my-app  Scaffold a native app.
  $ poltura app dev            Rebuild on every save.
  $ poltura app publish        Upload a release for review.

Build the app and upload it as a release for review.

USAGE
  $ poltura app publish [FLAGS]

FLAGS
  --api <url>           Gateway origin (POLTURA_API).
  --token <token>       Developer token, pltd_ or pltu_ (POLTURA_APP_TOKEN),
                        from Developer apps in admin.
  --app <appId>         App id; defaults to appId in poltura.app.json.
  --admin-origin <url>  Admin origin, to print the review link.
  --channel <channel>   Release channel (default: "production").
  -h, --help            Show help.
```

`poltura app build` bundles each `workers[].entry` to IIFE JavaScript, compiles `workers[].stylesheet` with Tailwind (each stylesheet is at most 262144 bytes), writes digests, and parses the result as the app manifest. `poltura app validate` builds into a temp directory, then parses `manifest.json` and any JSON `extensions[]`. `poltura app dev` rebuilds on `src/` and `poltura.app.json` changes and prints `watch <digest>` when the manifest digest changes. `build` and `dev` write to `dist` unless you pass `--out <dir>`.

An app with no UI, an integration that only calls the App API and receives webhooks, declares no workers: it builds and publishes as a release without a UI runtime.

Building workers needs `react`, `react-dom`, `tailwindcss` and `@poltura/app-ui` to resolve from the project, so install the project's dependencies first. The build stops with `Install react in this project to build workers.` when they are missing.



```bash
npm install
```

## Build and validate [#build-and-validate]

The complete example, Recommendations by Poltura Labs (see [Examples](/apps/examples)), has this layout:



```text
packages/app-sdk/examples/complete-app/
  poltura.app.json
  package.json
  src/server.ts
  src/elements.ts
  src/workers/admin-page.tsx
  src/workers/admin-product.tsx
  src/workers/storefront-product.tsx
  src/workers/storefront-footer.tsx
  src/workers/app-page.tsx
  src/styles.css
  src/locales/en.json
  src/locales/hu.json
  README.md
```

Its scripts run the same steps you run in your own app:



```bash
pnpm test:unit
pnpm build
pnpm validate
```



```text
built recommendations@1.0.0  sha256-qwcsxz+/Sn1wjxUbCKy7Ah/ROvTpZrZMf/8OTBIGg9Q=  5 workers
```



```text
built recommendations@1.0.0  sha256-qwcsxz+/Sn1wjxUbCKy7Ah/ROvTpZrZMf/8OTBIGg9Q=  5 workers
ok recommendations@1.0.0
  manifest.json  7765 bytes
```

## Publish [#publish]

`poltura app publish` builds the app and uploads it as a release for review. It sends the release to the Poltura API in `--api` (or `POLTURA_API`) and prints `published <releaseId>`. With `--admin-origin` it also prints the review link.



```bash
poltura app publish --api "$POLTURA_API" --app <app id> --token "$POLTURA_APP_TOKEN"
```

`--api` is the Poltura API origin. It defaults to `https://api.poltura.com`; pass it, or set `POLTURA_API`, only for another environment. `--app` is the app id from **Developer apps**. It can come from `appId` in `poltura.app.json` instead, but the scaffold does not write one. Pass `--channel` to pick a release channel other than `production`.

A release on the development channel cannot be installed by merchants (`release_not_production`). Review and install: [Publish, review, install](/apps/lifecycle).

## Check before you publish [#check-before-you-publish]

`poltura check [dir]` is the one command to run before you publish. It gives you the verdict Poltura's review gives, in a few seconds and without a network. It works out whether the directory is an app (it holds `poltura.app.json`) or a theme (it holds `theme.json`), and runs what applies:

| Check | For | What it looks at |
| --- | --- | --- |
| `manifest` | apps | `poltura.app.json` against the manifest schema, with the JSON pointer of every problem. |
| `permissions` | apps | Every permission id exists and an app may ask for it. A misspelled id comes with the closest real ones. A sensitive permission is a warning: a release that asks for it needs marketplace review. |
| `build` | apps | The same build and validation as `poltura app validate`, into a temp directory. Skipped while the manifest has errors. |
| `agent` | apps with `src/agent` | The agent entries build into a manifest: key naming, at most 20 tools per toolset, existing permissions, destructive actions with `confirm: 'always'`, and examples that match their input. |
| `theme` | themes | The same theme check as `poltura theme check`: layout, sections, settings. Types are checked when a type pack is given (`--types` or `POLTURA_THEME_TYPES`). |
| `lint` | both | The Poltura code rules, run with Oxlint on `src` with no setup. |

The code rules keep what comes in from outside parsed and typed: no `unknown` parameters or return types, no dictionaries of `unknown`, `any` or `object`, a `// SAFETY:` comment on every type assertion, and no runtime `typeof` on unparsed values. Parse the value with a schema where it enters your code and pass the typed result on.

| Option | Meaning |
| --- | --- |
| `--json` | Print the report as one JSON line, for agents and CI. |
| `--fix` | Apply the safe automatic fixes of the code rules (`var` to `const`, for example). Nothing else is rewritten. |
| `--strict` | Exit 1 on warnings too. |
| `--kind app\|theme` | Skip the detection. |
| `--types <packRoot>` | Theme type pack, as for `poltura theme check`. |

The human output is grouped by file; every finding has the rule that raised it and a `fix:` line saying what to change.



```text
poltura.app.json
  ✖ /permissions/required/2/id "orders.reed" is not a permission id. [permissions/unknown]
    fix: Did you mean "orders.read" or "orders.refund"? The full list is at https://developers.poltura.com/apps/permissions.

src/orders.ts
  ✖ 2:7 A `typeof` check narrows a representation without establishing its contract. Parse input at its I/O boundary, then branch on the domain value. [anti-slop/no-runtime-typeof]
    fix: Parse the value with a schema at the boundary, then branch on the parsed value.

skipped build: fix the manifest first
✖ failed: 2 errors, 0 warnings (app, 1.4s)
```

With `--json` the report has this shape:



```json
{
  "v": 1,
  "kind": "app",
  "root": "/work/my-app",
  "ok": false,
  "totals": { "error": 2, "warning": 0, "info": 0 },
  "findings": [
    {
      "severity": "error",
      "rule": "permissions/unknown",
      "message": "\"orders.reed\" is not a permission id.",
      "hint": "Did you mean \"orders.read\" or \"orders.refund\"? ...",
      "file": "poltura.app.json",
      "pointer": "/permissions/required/2/id"
    }
  ],
  "steps": [{ "id": "manifest", "ran": true }, { "id": "agent", "ran": false, "note": "the app declares no src/agent entry" }],
  "fixed": 0,
  "durationMs": 1432
}
```

`severity` is `error`, `warning` or `info`. A finding carries `file` (relative to the project) and, where it has them, `line`, `column` and `pointer`. `rule` is stable, so you can match on it: `manifest/*`, `permissions/*`, `build/failed`, `agent/*`, `theme/*`, or the name of a code rule such as `anti-slop/no-runtime-typeof`. Exit codes: 0 when there are no errors (and no warnings with `--strict`), 1 when there are, 2 when the directory is neither an app nor a theme.

`poltura check` runs the app's own code only to read its `src/agent` entry; it does not call the network. Run it in CI with `poltura check --json --strict`, and before every `poltura app publish`.

## Themes [#themes]

Theme commands, including `poltura theme check`, have their own page: [Theme commands](/themes/cli).

## Next [#next]

- [Ship to merchants](/apps/ship): what happens after `publish`.
- [Theme commands](/themes/cli), [Store commands](/ai/store-cli) and [API tokens](/api/tokens).
- [Permissions and topics](/apps/permissions): the generated reference tables.
