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

# CLI Setup

> Install and configure the LaunchMyStore CLI

# CLI Setup

The LaunchMyStore CLI (`lms`) helps you create, develop, test, and deploy apps,
extensions, and functions from the command line. The CLI is a Node 18+ ESM
package that talks to the LaunchMyStore API and signs webhooks locally for
end-to-end development.

<Note>
  **`@launchmystore/cli@2.0.3` is live.** Functions compile to WASM on
  **your** machine using [Bytecode Alliance Javy](https://github.com/bytecodealliance/javy);
  the server only validates that uploads are Javy dynamic-mode modules — it
  never runs your source through a compiler. `lms extension push -t` now
  accepts the developer-friendly type names emitted by `lms extension generate`
  (`theme_block`, `function_payment`, etc.) and maps them to the canonical
  upload types automatically.
</Note>

## Installation

Requires **Node.js 18 or higher**. To build functions you'll also need
[Javy v3+](https://github.com/bytecodealliance/javy/releases) on your `PATH`
(or pointed to by the `JAVY_BIN_PATH` environment variable).

<CodeGroup>
  ```bash npm theme={null}
  npm install -g @launchmystore/cli
  ```

  ```bash yarn theme={null}
  yarn global add @launchmystore/cli
  ```

  ```bash pnpm theme={null}
  pnpm add -g @launchmystore/cli
  ```
</CodeGroup>

Verify the install:

```bash theme={null}
lms --cli-version
```

<Note>
  The flag is `--cli-version` (not `--version`) so it doesn't collide with
  the subcommand-level `--version <semver>` flag used by `lms app version`
  and `lms app deploy`.
</Note>

<Tip>
  If `lms` is not on your `PATH` after a global install, see the troubleshooting
  section at the bottom of this page.
</Tip>

## Authentication

The CLI authenticates with email and password and stores a refreshable
credential bundle in `~/.lms/credentials.json` (override the directory with the
`LMS_CONFIG_DIR` environment variable).

```bash theme={null}
lms auth login --email you@example.com --api-base https://api.launchmystore.io
```

Inspect or end the session:

<CodeGroup>
  ```bash whoami theme={null}
  lms auth whoami
  ```

  ```bash logout theme={null}
  lms auth logout
  ```
</CodeGroup>

<Warning>
  Treat `~/.lms/credentials.json` like an SSH key — it contains a long-lived
  refresh token. Don't commit it or share it.
</Warning>

## Project Configuration

After you scaffold an app, the CLI walks up from your current working directory
looking for a `.lmsrc.json` file. This file binds the local project to a
specific app in the developer dashboard so subsequent commands know which app
they're operating on.

```json .lmsrc.json theme={null}
{
  "appId": "app_abc123",
  "handle": "my-awesome-app",
  "name": "My Awesome App",
  "clientId": "cli_xyz789",
  "scopes": ["read_products", "write_products"]
}
```

Most app/extension/function/webhook/billing commands accept an `--app` flag to
target a specific app explicitly. Without it, they fall back to the `appId` in
the nearest `.lmsrc.json`.

## Quickstart

<Steps>
  <Step title="Sign in">
    ```bash theme={null}
    lms auth login --email you@example.com --api-base https://api.launchmystore.io
    ```
  </Step>

  <Step title="Create an app">
    Pick a template (`node`, `react`, or `extension-only`). The CLI registers
    the app, scaffolds the project, and writes `.lmsrc.json` + `.env`.

    ```bash theme={null}
    lms app create --name "My Awesome App" --handle my-awesome-app
    ```
  </Step>

  <Step title="Install dependencies">
    ```bash theme={null}
    cd my-awesome-app
    npm install
    ```
  </Step>

  <Step title="Run the dev server with a tunnel">
    ```bash theme={null}
    lms app dev
    ```

    This starts your local server, opens a Cloudflare tunnel, watches
    `extensions/`, and PATCHes the app's redirect URLs to point at the tunnel.
  </Step>
</Steps>

## Templates

`lms app create` can scaffold one of three starter templates. Pass
`--no-template` to skip scaffolding and only register the app remotely.

| Template         | What you get                                                                             |
| ---------------- | ---------------------------------------------------------------------------------------- |
| `node`           | Node + Express server with `/auth/callback` and `/webhooks` routes                       |
| `react`          | Vite + React, wrapped in `@launchmystore/app-bridge-react`'s `AppBridgeProvider`         |
| `extension-only` | No backend — just an `extensions/blocks/example/` skeleton for theme/checkout extensions |

## Commands Reference

### Auth

| Command                                           | Description                                            |
| ------------------------------------------------- | ------------------------------------------------------ |
| `lms auth login --email <email> --api-base <url>` | Email/password login. Writes `~/.lms/credentials.json` |
| `lms auth logout`                                 | Clear stored credentials                               |
| `lms auth whoami`                                 | Show the currently signed-in developer                 |

### App

| Command                                                                                            | Description                                                                               |
| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `lms app create [--name] [--handle] [--no-template] [--force]`                                     | Register an app via `POST /apps/developer/create`, optionally scaffold a template         |
| `lms app list`                                                                                     | List apps you own                                                                         |
| `lms app info [appIdOrHandle]`                                                                     | Show details for one app (defaults to the project's `.lmsrc.json`)                        |
| `lms app dev [--port 3000] [--cmd "npm run dev"] [--watch <dir>] [--no-tunnel] [--no-update-urls]` | Run local dev server, open a Cloudflare tunnel, watch `extensions/`, update redirect URLs |
| `lms app deploy --version <semver> [--app] [--notes <text>] [--notes-file <path>] [--no-publish]`  | Wraps `version create` + `version publish` in one step                                    |
| `lms app rollback <installationId> <targetVersion>`                                                | Roll a single installation back; pins the install and disables auto-update                |
| `lms app resume-auto-update <installationId>`                                                      | Re-enable auto-update on a pinned installation                                            |

### App Versions

| Command                                                                                    | Description                  |
| ------------------------------------------------------------------------------------------ | ---------------------------- |
| `lms app version create --version <semver> [--app] [--notes <text>] [--notes-file <path>]` | Create a new version         |
| `lms app version publish <version> [--app]`                                                | Publish a created version    |
| `lms app version deprecate <version> [--app]`                                              | Mark a version as deprecated |
| `lms app version list [--app]`                                                             | List all versions            |
| `lms app version stats [--app]`                                                            | Show install + rollout stats |

### Function

| Command                                                                                                                       | Description                                                                                |
| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `lms function build -f src/function.js -o dist/function.wasm [--emit-wrapped <dir>]`                                          | Compile a JavaScript function to a Javy dynamic-mode `.wasm` artifact, locally             |
| `lms function test [-f src/function.js \| --wasm <path>] [-t <type>] [--input <json>] [--input-file <path>] [--timeout <ms>]` | Run a function against the hosted sandbox. Compiles locally first unless `--wasm` is given |
| `lms function deploy [--app] [-f src/function.js \| --wasm <path>] [-h <handle>] [-t <type>] [--fallback skip\|fail]`         | Build (if needed) then upload a pre-built `.wasm` artifact and register the handle         |
| `lms function list [--app]`                                                                                                   | List functions registered for the app                                                      |
| `lms function logs <handle> [--app] [--limit] [--page]`                                                                       | Tail/page execution logs for a function                                                    |

### Extension

`lms extension generate` is a local-only scaffolder. Functions are written to
`functions/<handle>/` and all other extension types to `extensions/<handle>/`.

| Command                                                                        | Description                                                                                                                                                                                                                                                         |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lms extension generate [-t <type>] [-h <handle>] [--dir <path>]`              | Scaffold an extension locally. Types: `theme_block`, `storefront_snippet`, `checkout_ui`, `post_purchase`, `admin_block`, `function_shipping`, `function_payment`, `function_delivery`, `function_cart_transform`, `function_order_validation`, `function_discount` |
| `lms extension list [--app]`                                                   | List extensions registered to the app                                                                                                                                                                                                                               |
| `lms extension push --name --handle --type --target --path [--update] [--app]` | Multipart upload of files. Types: `app_block`, `theme_embed`, `admin_block`, `checkout_extension`, `app_script`, `app_function`                                                                                                                                     |
| `lms extension remove <handle> [--app]`                                        | Remove an extension from the app                                                                                                                                                                                                                                    |

### Webhook

| Command                                                                                                         | Description                                                           |
| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `lms webhook list [--limit] [--page] [--status] [--topic]`                                                      | List webhook deliveries with filtering                                |
| `lms webhook retry <deliveryId>`                                                                                | Re-send a single delivery                                             |
| `lms webhook trigger <topic> [--url <url>] [--port 3000] [--secret] [--payload <json>] [--payload-file <path>]` | Generate a sample payload, sign with HMAC-SHA256, POST to a local URL |

### Billing

| Command                                                                             | Description                                                              |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `lms billing list [--app] [--status] [--limit] [--page]`                            | List subscriptions / charges for the app                                 |
| `lms billing summary`                                                               | Aggregate revenue summary across your apps                               |
| `lms billing pricing --monthly <amount> --yearly <amount> [--app] [--currency USD]` | Set pricing in major units (e.g. `9.99`); converted to cents server-side |

## Versioning

Use the `app version` subgroup to ship releases. The CLI separates
`create` (uploads the artifact + metadata) from `publish` (makes it available to
merchants), so you can stage a build and review it before promoting.

```bash theme={null}
lms app version create --version 1.2.0 --notes-file CHANGELOG.md
lms app version publish 1.2.0
```

If you want both steps in one command, use `lms app deploy --version 1.2.0`.
For the full lifecycle — auto-update channels, deprecation policy, rollback —
see [App Versioning](/getting-started/app-versioning).

## Local Function Compilation

Functions ship as **Javy dynamic-mode WASM** modules. Your JavaScript is
compiled on your machine into a tiny stub (typically 1–4 KB) that links
against a shared QuickJS runtime ("provider") at execution time — so each
compiled function only carries your code, not a copy of the runtime.

The server **never** runs your source through a compiler. It receives a
pre-built `.wasm`, validates the import section to confirm it's a Javy
dynamic-mode build, then stores it.

### Build a function

```bash theme={null}
lms function build -f src/discount.js -o dist/discount.wasm
```

Optional flags:

| Flag                   | Description                                                                          |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `--emit-wrapped <dir>` | Write the wrapped source the CLI handed to Javy. Handy for debugging compile errors. |

If `javy` isn't on your `PATH`, point at it explicitly:

```bash theme={null}
JAVY_BIN_PATH=/opt/javy/javy lms function build -f src/discount.js -o dist/discount.wasm
```

### Test against the sandbox

`lms function test` either compiles locally first (`-f`) or uploads a
pre-built artifact (`--wasm`), then runs it against the hosted sandbox
without persisting anything. The fastest way to iterate.

```bash theme={null}
# Compile from source on every run
lms function test -f src/discount.js -t discount \
  --input-file fixtures/cart.json --timeout 5000

# Or test an already-built artifact
lms function test --wasm dist/discount.wasm -t discount \
  --input-file fixtures/cart.json
```

Valid types: `discount`, `shipping_rate`, `payment_customization`,
`order_validation`, `cart_transform`, `delivery_customization`.

### Deploy to your app

```bash theme={null}
# Build + upload in one step
lms function deploy -f src/discount.js -h save-10 -t discount

# Upload a pre-built artifact (skip the local build)
lms function deploy --wasm dist/discount.wasm -h save-10 -t discount
```

### Function source contract

Export a `run` (or `main`) function that takes the input object and returns
the output object — the CLI wraps your source with the stdin/stdout bridge
expected by Javy:

```javascript theme={null}
// src/discount.js
export function run(input) {
  if (input.cart.totalPrice >= 100) {
    return {
      discounts: [
        { title: 'Spend $100, save 10%', value: 10, valueType: 'percentage', target: 'order' }
      ]
    };
  }
  return { discounts: [] };
}
```

The wrapper also accepts `module.exports = fn`, `function main(input) {}`, or
`exports.default = fn` for compatibility with different bundler outputs.

### Size limits

| Limit             | Default | Override env                     |
| ----------------- | ------- | -------------------------------- |
| JavaScript source | 64 KB   | `LMS_FUNCTION_MAX_SOURCE_SIZE`   |
| Compiled WASM     | 256 KB  | `LMS_FUNCTION_MAX_COMPILED_SIZE` |

A typical discount function compiles to \~3 KB. If you're approaching the
256 KB cap you're probably bundling unnecessary dependencies — strip them
out rather than asking for a higher cap.

## Local Webhook Testing

The CLI can simulate the production webhook dispatcher locally. It generates a
realistic sample payload for the topic, signs it with HMAC-SHA256 using your
client secret, and POSTs it to your local URL with the same headers your
production server will receive: `X-LMS-Topic`, `X-LMS-Webhook-Id`,
`X-LMS-Delivery-Attempt`, `X-LMS-Hmac-SHA256`.

```bash theme={null}
lms webhook trigger orders/create \
  --url http://localhost:3000/webhooks \
  --secret $LMS_CLIENT_SECRET
```

<Tip>
  If you don't pass `--secret`, the CLI falls back to the `LMS_CLIENT_SECRET`
  environment variable.
</Tip>

Subscribable topics (23 total):

```
orders/create, orders/updated, orders/cancelled, orders/paid, orders/fulfilled, orders/delete,
refunds/create,
products/create, products/update, products/delete,
customers/create, customers/update, customers/delete,
inventory_levels/update,
fulfillments/create, fulfillments/update,
subscriptions/create, subscriptions/renew, subscriptions/update,
subscriptions/cancelled, subscriptions/payment_failed,
app/uninstalled, app_subscriptions/update
```

<Note>
  GDPR compliance topics (`customers/data_request`, `customers/redact`,
  `shop/redact`) are delivered by the platform but are **not** registerable
  via the webhook API — you don't subscribe to them explicitly.
</Note>

## Configuration

The CLI reads project config from `.lmsrc.json` and credentials from
`~/.lms/credentials.json`. You can override the credentials directory globally
with `LMS_CONFIG_DIR` — useful for keeping work and personal accounts separate,
or for sandboxing CI.

```bash theme={null}
LMS_CONFIG_DIR=~/.lms-staging lms auth login --email ci@example.com \
  --api-base https://staging.api.launchmystore.io
```

## Environment Variables

| Variable                         | Description                                                                   |
| -------------------------------- | ----------------------------------------------------------------------------- |
| `LMS_API_BASE`                   | Override the default API base (`https://api.launchmystore.io`)                |
| `LMS_CONFIG_DIR`                 | Override the credentials directory (default `~/.lms/`)                        |
| `LMS_CLIENT_SECRET`              | Fallback HMAC secret used by `lms webhook trigger` when `--secret` is omitted |
| `JAVY_BIN_PATH`                  | Absolute path to the `javy` binary if it isn't on your `PATH`                 |
| `LMS_FUNCTION_MAX_SOURCE_SIZE`   | Override the JavaScript source size cap (default `65536`)                     |
| `LMS_FUNCTION_MAX_COMPILED_SIZE` | Override the compiled WASM size cap (default `262144`)                        |
| `PORT`                           | Forwarded into `npm run dev` from `lms app dev`                               |

## Troubleshooting

<AccordionGroup>
  <Accordion title="'lms' command not found">
    Make sure your npm global `bin` directory is on your `PATH`:

    ```bash theme={null}
    npm config get prefix
    # Add {prefix}/bin (or {prefix} on Windows) to your PATH
    ```

    On macOS with Homebrew Node, this is usually `/opt/homebrew/bin`. On
    Windows, it's typically `%APPDATA%\npm`.
  </Accordion>

  <Accordion title="Authentication expired or invalid">
    Long-lived sessions can be invalidated by a password change or admin
    revoke. Re-authenticate:

    ```bash theme={null}
    lms auth logout
    lms auth login --email you@example.com --api-base https://api.launchmystore.io
    ```
  </Accordion>

  <Accordion title="`lms app info` / `lms extension *` return 401 Unauthorized">
    `lms auth login` succeeds against any account on the platform — but
    the developer endpoints under `/apps/developer/*` require **(a)** a
    **merchant account** (not a storefront customer account), and **(b)**
    that you've registered at least one app. A 401 from those endpoints
    almost always means one of these prerequisites is missing.

    Run these to diagnose:

    ```bash theme={null}
    lms auth whoami
    lms app list
    ```

    Likely causes, in order:

    1. **You logged in with a storefront customer account.** Only merchant
       accounts can register developer apps. Sign up at the LaunchMyStore
       admin and re-run `lms auth login` with the merchant email.

    2. **You haven't created an app yet.** `lms app info` looks up an
       existing app — it doesn't create one. Run:
       ```bash theme={null}
       lms app create --name "My App" --handle my-app
       ```
       This registers the app server-side and writes `.lmsrc.json` in
       the current directory. After this, `lms app info`,
       `lms extension push`, and `lms function deploy` will work.

    3. **You're querying an app that belongs to a different account.**
       The CLI scopes queries to the token in
       `~/.lms/credentials.json`. Another developer's app returns 401,
       not 404. Use `lms app list` to see only the apps you own.

    If `lms app list` itself returns 401 after a fresh `lms auth login`,
    open a developer support ticket with the account email so we can
    enable developer access on our side.
  </Accordion>

  <Accordion title="`lms extension push --type theme_block` rejected as invalid type">
    Fixed in `@launchmystore/cli@2.0.3` — the developer-friendly type names
    from `lms extension generate` (`theme_block`, `storefront_snippet`,
    `checkout_ui`, `post_purchase`, and all six `function_*` variants) now
    map automatically to the canonical upload types (`app_block`,
    `checkout_extension`, `app_function`). Upgrade with
    `npm install -g @launchmystore/cli@latest` and verify with
    `lms --cli-version`.
  </Accordion>

  <Accordion title="Tunnel won't start in 'lms app dev'">
    `lms app dev` uses the `cloudflared` package. Make sure port 3000 is free
    (or pass `--port`), and that no firewall is blocking outbound connections
    to Cloudflare. To run without a tunnel — for example when your dev server
    is already publicly reachable — pass `--no-tunnel` and `--no-update-urls`.
  </Accordion>

  <Accordion title="Commands can't find my app">
    Most commands resolve the active app from `.lmsrc.json` walked up from
    `cwd`. If you're outside the project directory, pass `--app <handle>` (or
    `--app <appId>`) explicitly.
  </Accordion>

  <Accordion title="Javy compiler not found">
    `lms function build` calls out to the `javy` binary. Install it from
    [the Javy releases page](https://github.com/bytecodealliance/javy/releases)
    and either put it on your `PATH` or set `JAVY_BIN_PATH` to its absolute
    location. Verify with:

    ```bash theme={null}
    javy --version
    ```

    The CLI requires **Javy v3 or higher** (dynamic-mode support).
  </Accordion>

  <Accordion title="WASM rejected as 'not a Javy dynamic-mode module'">
    The upload endpoint checks the WASM import section for the
    `javy_quickjs_provider` marker. Static-mode builds (which embed the
    runtime instead of linking against the shared provider) are rejected
    because they exceed the size cap by orders of magnitude. Always
    compile with `javy compile -d` — the `lms function build` command
    does this for you automatically.
  </Accordion>

  <Accordion title="Webhook signature mismatches my server">
    `lms webhook trigger` uses HMAC-SHA256 with the client secret as the key,
    over the raw JSON body, and sends the result in the `X-LMS-Hmac-SHA256`
    header (base64-encoded). Verify your server is hashing the **raw** request
    body, not a re-serialized version, and that you're using the same client
    secret the CLI is loading from `--secret` or `LMS_CLIENT_SECRET`.
  </Accordion>
</AccordionGroup>

## Use with Claude Code

If you use [Claude Code](https://claude.com/claude-code), install the
**LaunchMyStore Claude Skill** to let Claude route plain-English requests
("build a discount function", "list my orders", "add a hero section") to
the right CLI command, MCP tool, App Bridge action, or Aqua snippet.

<CodeGroup>
  ```bash Plugin marketplace theme={null}
  /plugin marketplace add LaunchMyStore/skill
  /plugin install launchmystore@launchmystore
  ```

  ```bash npm theme={null}
  npm install -g @launchmystore/claude-skill
  ```

  ```bash Manual theme={null}
  git clone https://github.com/LaunchMyStore/skill ~/.claude/skills/launchmystore
  ```
</CodeGroup>

Restart Claude Code, then try `/launchmystore I want to build my first app`.

Source: [github.com/LaunchMyStore/skill](https://github.com/LaunchMyStore/skill)

## Next Steps

<CardGroup cols={2}>
  <Card title="Build Your First App" icon="rocket" href="/getting-started/first-app">
    A complete walkthrough using the `node` template
  </Card>

  <Card title="App Versioning" icon="code-branch" href="/getting-started/app-versioning">
    Release, publish, deprecate, and roll back versions
  </Card>

  <Card title="Functions Overview" icon="flask" href="/functions/overview">
    Build, test, and deploy custom function logic with the CLI
  </Card>

  <Card title="App Bridge" icon="link" href="/app-bridge/overview">
    Embed your app in the LaunchMyStore admin
  </Card>
</CardGroup>
