LVNDRby Toolfora

HelpCLI

CLI

Install

The CLI is one file with no dependencies. It needs Node 20 or newer and talks to the REST API.

npx lvndr status          # run without installing
npm i -g lvndr            # or install the `lvndr` command

# from a checkout of the agent kit (kasazen/lvndr-agent)
node cli/lvndr.mjs status

In the LVNDR app repository the same file is agent-kit/cli/lvndr.mjs.

Log in

Make a key in Settings > Agents & API (pick CLI as the client). Then:

lvndr login                                  # asks for the key (not echoed), checks it with GET /me
lvndr login --with-token < key.txt           # reads the key from stdin, for scripts
lvndr login --key lv_... --profile staging --url https://my-tunnel.example.com

lvndr login writes ~/.config/lvndr/config.json with mode 600 ($XDG_CONFIG_HOME and $LVNDR_CONFIG are honoured), one entry per profile. The first profile saved becomes the default. --no-verify skips the check.

Each setting comes from the first of these that has it:

SettingFlagEnvironmentProfileDefault
API key--keyLVNDR_API_KEYkeynone (exit 3)
App URL--urlLVNDR_API_URLurlhttps://lvndr.toolfora.com
Profile--profileLVNDR_PROFILEthe default profile, else default
Agent name (X-Lvndr-Agent)--agentLVNDR_AGENTagentlvndr-cli
Run id (X-Lvndr-Run-Id)--run-idLVNDR_RUN_IDnone

Commands

lvndr help (or no command) prints the list; lvndr <command> --help or lvndr help <command> prints one command's usage; lvndr --version the version. An unknown command prints the list and exits 1.

Every write is bound to a plan someone saw. Undo commands preview and print the exact write command with its --plan-hash; --run without --plan-hash writes nothing and fails with preview first: lvndr undo <id>. A rollback needs --confirm --plan-hash from lvndr preview. --yes previews, prints the preview once, asks Apply? [y/N] and writes only on yes; it is for a person at a terminal and is refused when stdin is not one. A preview with nothing to write says “Nothing to undo” and prints no write command. Previews list each field with its record, the field in plain words and both values.

CommandWhat it does
lvndr meStore, key label, scopes, plan and key expiry
lvndr statusPlan, tracked counts, last activity check, pending deliveries
lvndr changes [filters]Changes, newest first
lvndr change <id>One change with its diff and attribution tier
lvndr history <gid | Type-digits>A record's stored versions and changes
lvndr undo <id> [--run --plan-hash h | --yes] [--include-conflicts] [--reason text]Preview the undo of one change, or write the previewed plan
lvndr batch <id>Everything the same actor did around a change
lvndr undo-batch <id> [--run --plan-hash h | --yes] [--reason text]Preview or undo that batch, newest first; waits for the job
lvndr points [--all | --kind k] [--limit n] [--cursor c]Restore points (the daily check's only with --all)
lvndr mark <label...> [--reason text]Mark a restore point now
lvndr preview <pointId>What rolling back to a point would undo
lvndr rollback <pointId> (--confirm --plan-hash h | --yes) [--reason text]Undo everything after the point, newest first; waits for the job
lvndr job <id>One background undo job: status, counts and results
lvndr alerts [--all]Open anomaly alerts; --all adds dismissed and resolved ones
lvndr dismiss <alertId>Dismiss an alert (needs the Undo scope)
lvndr run start [--agent name] [--task text] [--client-id id] [--no-restore-point]Open a run with a restore point; saved as the profile's current run; prints what it will capture and a WARNING when it captures nothing
lvndr run end [id]End the run
lvndr run show [id]The run and its changes
lvndr run listRecent runs (lvndr runs is the same)
lvndr run undo [id] [--run --plan-hash h | --yes] [--reason text]Preview or undo every change in the run; waits for the job
lvndr export [--format csv|json] [--since] [--until] [--out file]Download the change log
lvndr guard [--agent name] [--task text] [--wait 300] -- <command...>Run a command inside a run with a restore point, then wait until its changes are attributed
lvndr login [--key k | --with-token]Save a profile (the key is not echoed; --with-token reads stdin)
lvndr openapiPrint the OpenAPI document

lvndr me

Check a key before anything else. Exit 3 means the key is missing, revoked or expired; exit 8 means the store has no active plan with the API.

$ lvndr me
example.myshopify.com  key: Claude Code  scopes: read,undo  plan: Scale  expires: 2027-02-11T09:00:00.000Z

lvndr status

$ lvndr status
example.myshopify.com  plan: Scale
tracked: product 412, collection 18, page 6, blog 1, article 14, menu 3, theme 2, metafield_definition 9
last activity check: 2026-11-10T14:05:00.000Z  pending deliveries: 0

lvndr changes

One line each: id, time, type, action, title, actor, summary. The actor column is the label, name or app title, else the tier.

FlagMeaning
--since, --until30m, 24h, 7d, 2w or an ISO timestamp
--typePRODUCT, COLLECTION, PAGE, BLOG, ARTICLE, MENU, THEME, METAFIELD_DEFINITION, any case
--actioncreate, update or delete
--resource-idProduct-123 or gid://shopify/Product/123
--actor, --qPart of the actor name; part of the record title
--hide-undosLeave out LVNDR's own undos
--run-idOnly changes in this run
--limit, --cursorPage size 1 to 100; the next page. When there is more, the next command is printed to stderr.
$ lvndr changes --since 24h --type product --limit 2
cm3abc0000000000000000001  2026-11-10 14:03:12 PRODUCT    update The Minimal Snowboard             Joe Pace                 The price of one variant went from 885.95 to 750.
cm3abc0000000000000000002  2026-11-10 13:41:50 PRODUCT    create Gift Card                         Shopify CLI Connector A  new record

lvndr change, lvndr history, lvndr batch

$ lvndr change cm3abc0000000000000000001
PRODUCT The Minimal Snowboard (gid://shopify/Product/123)
update at 2026-11-10T14:03:12.000Z by Joe Pace [exact]
The price of one variant went from 885.95 to 750.
  variants[ProductVariant/456].price: "885.95" -> "750.00"

$ lvndr history Product-123
$ lvndr batch cm3abc0000000000000000002

lvndr undo, lvndr undo-batch

$ lvndr undo cm3abc0000000000000000001
  ready        variants[ProductVariant/456].price: "750.00" -> "885.95"
  ready 1, conflict 0, noop 0, unsupported 0
preview only; nothing was written.
To run exactly this plan: lvndr undo cm3abc0000000000000000001 --run --plan-hash q3Xl0c2V4Zk9b1Rm

$ lvndr undo cm3abc0000000000000000001 --run --plan-hash q3Xl0c2V4Zk9b1Rm --reason "bad import"
  ready        variants[ProductVariant/456].price: "750.00" -> "885.95"
  ready 1, conflict 0, noop 0, unsupported 0
undo done (cm9un...)
  ok  productVariantsBulkUpdate

The preview ends with the command that runs exactly that plan: lvndr undo <id> --run --plan-hash <hash>. --run without --plan-hash is refused before anything is sent. If the store moved since the preview, the undo is refused with plan_changed (exit 7); preview again.

--include-conflicts also overwrites fields changed again since. A failed operation prints ERR with the message and exits 2.

Background jobs: undo-batch, run undo, rollback, LVNDR job

undo-batch --run --plan-hash, run undo --run --plan-hash and rollback --confirm --plan-hash (or --yes) start a background job on the server. The job starts on the server at once; the CLI polls GET /api/v1/jobs/:id as often as the job's pollAfterMs says (waiting out Retry-After on a 429) until it is done, partial or failed. Progress goes to stderr when it moves; the finished job goes to stdout, one JSON line with --json. Exit 0 when the job is done, 2 when it is partial or failed.

$ lvndr run undo cm9run0000000000000000001 --run --plan-hash yWsE5uJhTgDq3Xl0
lvndr: job cm9job0000000000000000001 queued: 0 done, 0 failed, 0 skipped of 3
lvndr: job cm9job0000000000000000001 running: 1 done, 0 failed, 0 skipped of 3
job cm9job0000000000000000001 done: 3 done, 0 failed, 0 skipped of 3
  cm9ch00000000000000000001  done     update The Minimal Snowboard ...

$ lvndr job cm9job0000000000000000001

Stopping the CLI does not stop the job; lvndr job <id> shows it later (exit 2 when it ended partial or failed). The previews of a batch, a run and a rollback print their --plan-hash command too; the job is refused if changes joined or left the set since the preview.

lvndr points, lvndr mark, lvndr preview, lvndr rollback

$ lvndr mark before the price update
marked cm3p10000000000000000000c at 2026-11-10T14:10:00.000Z  before the price update

$ lvndr preview cm3p10000000000000000000c
...
To roll back exactly this: lvndr rollback cm3p10000000000000000000c --confirm --plan-hash N8pA7wYsE5uJhTgD

$ lvndr rollback cm3p10000000000000000000c --confirm --plan-hash N8pA7wYsE5uJhTgD --reason "import went wrong"

Rolling back needs a key with the rollback scope, which new keys do not have unless you tick it. Marking a restore point needs only read. Without --confirm and --plan-hash, rollback refuses and points at preview.

lvndr alerts, lvndr dismiss

Each alert line ends with the lvndr batch command for its burst.

lvndr run

$ lvndr run start --agent nightly-import --task "supplier feed"
run cm9run0000000000000000001 started, restore point cm9pt00000000000000000001
capture all-unattributed: This run records every change Shopify left unattributed or LVNDR inferred while it is open (up to 24 h), staff edits included.
export LVNDR_RUN_ID=cm9run0000000000000000001

$ lvndr run end
run cm9run0000000000000000001 ended; 3 change(s) so far
Changes arrive over a few minutes; review with: lvndr run show cm9run0000000000000000001
To preview the undo:  lvndr run undo cm9run0000000000000000001

For run end, run show and run undo the id defaults to --run-id, then LVNDR_RUN_ID, then the run saved by run start. --no-restore-point starts a run without a point. Starting a run needs only read, but only a key with undo opens a catch-all run (all-unattributed); otherwise it captures its own OAuth client's changes (client) or nothing (none).

lvndr guard

Checks that the command exists (a typo leaves no run behind), starts a run with a restore point, runs the command with inherited stdio and LVNDR_RUN_ID and LVNDR_AGENT in its environment, ends the run, then polls the run about every 20 seconds until attributedThrough is after the run's end and pendingDeliveries is 0, for at most --wait seconds (default 300; --wait 0 skips it). Then it lists the run's changes and prints the undo preview line. Its own report goes to stderr, so the command's output is untouched. If the run cannot be started, the command is not run. The agent defaults to lvndr-guard; the task to the command line.

$ lvndr guard --agent bulk-price-fix -- shopify store execute --allow-mutations \
    --store example.myshopify.com --query-file raise-prices.graphql
lvndr: run cm9run0000000000000000001 started, restore point cm9pt00000000000000000001
... shopify output ...
lvndr: command exited 0; run cm9run0000000000000000001 ended; 3 change(s) tagged
cm9ch00000000000000000001  2026-11-13 10:02:11 PRODUCT    update The Minimal Snowboard ...
To preview the undo:  lvndr run undo cm9run0000000000000000001   (it prints the --run --plan-hash line that runs it)

While it waits, guard prints each change as it is tagged, the seconds waited and what it is still waiting for. Guard exits with the command's exit code (127 when the command does not exist). If attribution has not caught up when the wait ends, guard says so and the list says “tagged so far”. Collection rules and SEO, menus, themes and metafield definitions can take up to 10 minutes, longer than the default wait; lvndr run show <id> lists them again. With --json the report is one JSON line on stderr.

lvndr export, lvndr openapi, lvndr login

lvndr export --format json --since 30d --out changes.json
lvndr openapi > openapi.json

Global flags

FlagEffect
--jsonThe API response as one JSON line on stdout. Errors become {"error": {...}} on stdout.
--ndjsonFor list commands, one JSON object per line
--quiet, -qOnly ids (mark, run start, lists)
--key, --url, --profile, --agent, --run-idSee the settings table
--no-retryFail on the first 429 instead of waiting Retry-After (up to 3 retries of 60 s at most)
--timeout <s>Request timeout, default 120
--plan-hash <h>Required with --run (and with rollback --confirm): the preview that printed it
--yesPreview, print it, ask Apply? [y/N], then write it; only when stdin is a terminal
--help, -h; --version, -vHelp (for one command after its name); version

Every POST carries a fresh Idempotency-Key, reused on its retries, so a retried write is not applied twice. Every request sends X-Lvndr-Agent, and X-Lvndr-Run-Id when a run id is set.

Exit codes

CodeMeaning
0OK. A preview is OK.
1Usage error (including --run without --plan-hash), bad parameter (400), or server error
2An undo, batch undo, run undo or rollback finished partial or failed (also lvndr job on such a job)
3Auth: no key, 401, 403 (missing scope, app uninstalled)
4Not found (404)
5Rate limited (429) after retries
6Network: the app could not be reached
7Conflict (409): plan_changed (preview again) or an undo already running
8Plan required (402): the store's LVNDR plan does not include the API

lvndr guard exits with the wrapped command's code instead. See the error codes in REST API.

Scripting

# ids of everything changed in the last hour by an inferred actor
lvndr changes --since 1h --ndjson | jq -r 'select(.actor.tier == "inferred") | .id'

# a run around a multi-step job
RUN=$(lvndr run start --agent nightly-import --task "supplier feed" --quiet)
export LVNDR_RUN_ID=$RUN
./import.sh
lvndr run end
lvndr run show "$RUN" --json | jq '.changes | length'

# fail a pipeline when an undo does not complete
HASH=$(lvndr undo "$CHANGE" --json | jq -r .planHash)
lvndr undo "$CHANGE" --run --plan-hash "$HASH" --reason "bad import" --quiet || echo "undo exit $?"