SuperAPI AI Directory

shoplazza-common

Coding & development

View repository

Updated 10-29 19:13

About this skill

shoplazza CLI — shared foundation (shoplazza-common)

This skill is the foundation for every domain skill (shoplazza-discounts, shoplazza-products, …): it owns all cross-cutting mechanics so domain skills never repeat them, and every domain skill opens by requiring this file be read first. The authoritative source for commands/params is shoplazza <svc> <cmd> --help and shoplazza schema — this file teaches the mechanics, it does not memorize flags.

Reply in the user's language. Skill prose is English, but that never sets your reply language — mirror the user (Chinese question → Chinese answer), including questions you ask back, write-confirmation restatements, and error explanations. Keep CLI commands, flags, and field names verbatim (untranslated).

Access tiers

Start every operation at the highest tier that fits and stop as soon as one expresses it:

Tier Form When
1. Shortcut shoplazza <svc> +<verb> [named flags] First choice. Named flags, smart defaults, structured errors.
2. Spec leaf shoplazza <svc> <cmd> --params '{…}' [--data '{…}'] Endpoints with no shortcut; one command per endpoint.
3. api rest shoplazza api rest <METHOD> <PATH> Raw fallback, full coverage. Only when the first two can't express it.

In shoplazza <svc> --help, entries prefixed with + are shortcuts; the rest are spec leaves.

Output envelope (.data wrapper)

The structured-output contract, uniform across commands:

  • Success → stdout: {"ok":true,"data":<body>}
  • Failure → stderr: {"ok":false,"error":{"type":…,"message":…}}, non-zero exit.

Response bodies nest under .data, so every jq path starts at .data (e.g. .data.products[].id). Missing the .data prefix is the most common extraction mistake.

Exception — local commands have no envelope. auth status / auth scopes / profile list print their object at the top level (no {ok,data}, no --jq support); read fields directly (e.g. logged_in).

Common flags

Flag Notes
--dry-run Print the request that would be sent, without sending it. Always --dry-run first for destructive / batch / money-spending writes. Read commands and shortcuts support it too.
-q, --jq <expr> Filter JSON output with a jq expression. Outputs raw scalars by default (no surrounding quotes, just a trailing newline) — do not add -r: cobra parses -r as a separate flag and rejects the command. It is a single-string flag.
--fields <f1,f2,…> Response field projection on a few shortcuts only — verified: products +search (comma-separated: products +search --fields id,title). Not universal — most commands have no --fields; check <shortcut> --help before using it. Names are response keys; the server may still return extra base fields and silently ignores unknown names.
--page-limit <n> Page size, 1–250, on list shortcuts. The API default is 10 — see "Reading a filtered list" below.
--format json|pretty|table Output format, default json. Use json for scripts/jq, pretty/table for humans. Global flag.
--profile <name> Profile to use for this invocation (see "Profiles"). Global flag.

Field projection: a few search shortcuts accept --fields (verify with --help); everywhere else, project/filter with --jq.

Reading a filtered list

A read that returns {"ok":true,…} is not evidence that the filter or the page you asked for is the one you got. Two failure modes are silent, and both produce a confident wrong answer rather than an error:

1. Truncation — the page default is 10. List endpoints return 10 records unless --page-limit says otherwise (max 250), with has_more / cursor marking the rest. has_more may come back null rather than false, so treat "not true" as the end. Whenever the answer depends on completeness — "how many", "which ones", "all of X", any total you compute yourself — pass --page-limit 250 and follow the cursor. For a pure count, prefer the domain's +count shortcut over counting rows.

2. Silently-dropped filters — the API ignores query params it does not recognise. It does not 400. A flag whose param name has drifted comes back 200 with the unfiltered set, which reads exactly like "this customer really has that many orders".

So before a filtered read becomes an answer or feeds a write, confirm the filter landed:

shoplazza <svc> +search --<filter> <value> --dry-run   # 1. which param actually goes on the wire
shoplazza <svc> +count                                  # 2. unfiltered total, for comparison
shoplazza <svc> +count --<filter> <value>               # 3. filtered total — must be < the unfiltered one

A filtered result equal to the unfiltered total is the tell. Confirm it by spot-checking that the returned records actually satisfy the filter (--jq over the field you filtered on). If the filter is being dropped, schema <svc>.<cmd> --view request lists the params the endpoint really accepts — reach for the spec leaf or api rest with the documented param.

Precision ladder — when the endpoint offers more than one way to filter on the same thing, prefer the narrowest: a dedicated exact-match param (often an array, e.g. customer_emails, skus, order_tags) beats a general keyword search, which beats hand-filtering with --jq over a full page. Exact params say what they match; keyword search may match a field you did not intend.

Which tier — read the user's verb. The phrasing tells you whether they mean a whole value or a fragment, and that decides exact vs keyword:

User's signal Meaning Tier
"is / means / equals / =", "number / ID / value is X", or a self-identifying whole string (a full email, a full SKU like ABC-001, a full phone) whole value exact-match array param
"contains / includes / like / similar to / starts with… / prefix / fragment / part", X*, contains / starts-with / like fragment keyword search

"is / equals" is the authoritative exact signal even when it sits right next to a field name — match the whole value, do not fuzzy-search it.

Report the basis, not just the number. When you answer from a list read, say which filter was applied and what the completeness basis is ("3 of 3, unfiltered total 18"). It makes a dropped filter visible to the user instead of invisible.

Locate before you act. A write targets a record by its id, but users name it by something else (an order number, a code, an email, a title). Resolve the target with a read first, then branch on the count:

  • 0 matches → stop and say so; do not fall back to a broader write or invent an id.
  • exactly 1 → proceed (still --dry-run + restate for a destructive / money op).
  • 2+ matches → list the candidates and ask which one. Never guess, and never act on "the first" silently.

A number is not an id (e.g. an order number vs the internal order_id) — feeding one where the other is expected hits the wrong record or errors. Resolve it through the read, don't assume.

Schema introspection

Before calling any spec leaf or api rest, check the schema to confirm params / body / response shape — do not guess fields:

shoplazza schema                              # list all modules
shoplazza schema discounts                    # list a module's commands
shoplazza schema discounts.get                # a command's params / body / response
shoplazza schema orders.create --view request # request side only (--view all|request|response)

api rest fallback

Only when the first two tiers can't express it. Write the resolved real URL (not a {…} template):

shoplazza api rest GET  /openapi/2026-01/products
shoplazza api rest GET  /openapi/2026-01/products/gid_123 --params '{"page_size":10}'
shoplazza api rest POST /openapi/2026-01/discounts/automatic --data @body.json

Supports --params / --data (incl. - for stdin, @file for a file), --dry-run, -q/--jq.

Large integer ids → quote them in JSON. In --params / --data, pass a big numeric id as a string ({"id":"669923000979557666"}), not a bare number — bare large ints get float-coerced (→ 6.69…e+17) and the request is rejected. Applies anywhere an id exceeds ~15 digits (e.g. shop redirects).

Installation

CLI preflight

If shoplazza is not found (command not found) or shoplazza --version fails, the CLI is not installed. Installing changes the user's machine: say which method you'll use and wait for a yes. Pick by what the machine already has:

Method Command When
npm (recommended) npm install -g shoplazza-cli Node.js ≥ 16 present (node -v)
Homebrew brew install Shoplazza/tap/shoplazza-cli macOS / Linux with brew
Install script curl -fsSL https://raw.githubusercontent.com/Shoplazza/shoplazza-cli/main/install.sh | bash macOS / Linux without Node; installs to /usr/local/bin (INSTALL_DIR=<dir> to change)
Release binary download from https://github.com/Shoplazza/shoplazza-cli/releases any other platform; put it on PATH

Then confirm with shoplazza --version and continue with the login flow below. If the install fails — a sandbox blocks it, permission is denied, or it needs sudo — stop and hand the user the exact command to run in their own terminal.

  • One installer only. Two installs leave two shoplazza binaries on PATH running different versions; which -a shoplazza lists them. Keep one.
  • Update with the installer you used. shoplazza update always reinstalls via npm — on a Homebrew or script install it adds that second copy. Use brew upgrade shoplazza-cli or re-run the script instead.

First-time setup, end to end

shoplazza --version                                               # installed?
shoplazza auth login -s my-store.myshoplazza.com --domain products,orders   # login + bind a store
shoplazza auth status                                             # logged_in: true, the store under profiles

Run the login in the background and hand the user the authorization URL (Agent-driven login flow below). Ask which store and which modules the user needs; don't guess a store domain.

Authentication (auth)

Login-state preflight

Before the first store operation in a session, run shoplazza auth status (local, instant, no envelope). Two independent things must both be true:

  • logged_in: true — account credentials exist. If false → run the login flow below.
  • profiles non-empty — a store is bound. logged_in: true with profiles: [] still cannot reach any store API; ask which store and run auth login -s <store> --domain <modules>.

If you skip the preflight, an unauthenticated business command fails fast with a validation error — "no profile configured" plus a hint naming the fix. Treat that the same way: don't retry, guide the user through login first.

Login

Authorization has two layers: account-level (auth login obtains account credentials) and store-level (exchange a store token for a specific store, stored in a profile).

# Account-level login (no store bound)
shoplazza auth login --domain products,orders        # or --scope read_product,write_product
# Store-level login (select a store; an interactive login with -s must also pass --scope or --domain)
shoplazza auth login -s my-store.myshoplazza.com --domain discounts
  • --domain <module,…>: authorize by top-level command name; the CLI expands each into the OAuth scopes that module needs. Options: app, billing, checkout, customers, discounts, orders, products, shop, themes, webhook, all. Preferred — you don't memorize scope literals.
  • --scope <literal,…>: fine-grained OAuth scopes (e.g. read_product,write_product), least-privilege.
  • Re-login REPLACES the grant (verified live: a 22-scope grant re-authorized with one scope came back with one). Topping up permissions? Pass --merge-scopes to carry the prior grant along (older CLIs lack the flag — build the union yourself via auth scopes; --domain and --scope combine in one login). Only omit it when the user explicitly wants to narrow permissions.

Agent-driven login flow

When acting as an AI agent to log the user in: run auth login in the background (it blocks and polls until the user completes authorization or it times out — see --poll-interval / --timeout, defaults 2s / 300s), extract the authorization URL from its output and hand it to the user; the command returns on its own after the user authorizes.

Missing flags become on-screen questions when a terminal is present. auth login (and app init) asks for what you left out only when stdin and stderr are both real terminals and neither SHOPLAZZA_CLI_NO_INTERACTIVE nor CI is set (present and non-empty = set); otherwise a missing flag is an error pointing at that flag. A harness that hands the child process a pty passes that check and the command waits on a keypress that never comes — export SHOPLAZZA_CLI_NO_INTERACTIVE=1 once for the session if yours does.

It only switches prompting off. There is no --interactive flag and no env var to force it on — deliberately, since forcing it would hang for good wherever no terminal exists.

Re-login for a missing scope? Pass --merge-scopes. Scopes are replaced, not merged (see Login above); requesting only the missing scope drops everything else mid-task. If the flag is unknown (older CLI), fall back to building the union: auth scopes → granted, then request granted + the new scopes in one login.

Non-interactive fast path (UAT)

If you already ran auth login on another machine and have an account UAT, skip the browser:

shoplazza auth login --uat <UAT>     # or set env SHOPLAZZA_UAT=<UAT>

--uat is promptless whatever the terminal looks like — a second, whole-command off-switch on top of the gate above. Browser logins driven from a pty-allocating harness still need SHOPLAZZA_CLI_NO_INTERACTIVE=1.

Store switching and status

shoplazza auth status                                                  # auth state / identity / current store
shoplazza auth scopes                                                  # supported scopes + scopes granted to the account
shoplazza auth store use -s another-store.myshoplazza.com [--scope …]  # exchange a token for that store, set as current profile
shoplazza auth logout                                                  # log out of the current store

Which command switches the store:

Want Command
Which store am I on? auth status — the profile with current: true (store_domain)
A store used before (has a profile) profile list → profile use --name <name> (--previous switches back)
A store with no profile yet auth store use -s <store-domain> — creates or reuses its profile and makes it current
One command against another store add --profile <name> to that command; the current profile stays

auth store use and profile add need an account login first, and a profile's scopes must be a subset of the account grant — missing scopes → auth login --domain <module> --merge-scopes, then switch again.

Handling insufficient permissions

On 401/403 for a write, the error envelope's hint says what's missing. Grant it as directed: shoplazza auth login --domain <that module> (or --scope <missing scope>), then retry. Run auth scopes first to see what's already granted.

Profiles (multi-tenant)

A profile = one store execution context (its store token + scopes). Manage multiple stores by switching profiles:

shoplazza profile add --name prod-us -s my-store.myshoplazza.com [--scope …] [--use]
shoplazza profile list
shoplazza profile use --name prod-us          # or --previous to switch back to the last one
shoplazza profile info [--name prod-us]       # defaults to the current profile
shoplazza profile rename --from old --to new
shoplazza profile remove --name prod-us
shoplazza profile update --name prod-us --scope …   # adjust a profile's scopes

Per-invocation override: the --profile <name> global flag beats the SHOPLAZZA_CLI_PROFILE env var and the current profile.

Update notice

When the CLI detects a new version after running, it prints a one-line notice to stderr (⚡ shoplazza-cli X is available …). It never appears in the JSON on stdout. Do not silently ignore it: after finishing the user's current request, tell them the current / latest version and offer to update:

shoplazza update            # update via npm (--check only reports versions, no install)
# or: npm install -g shoplazza-cli@latest

Installed with Homebrew or the install script? Update the same way instead (see Installation).

(Set SHOPLAZZA_CLI_NO_UPDATE_CHECK to disable the check.)

update does three things and reports each in its JSON body:

Field Means
updated the binary was reinstalled. false = already current, not a failure
meta_updated / meta_revision the API metadata cache was rewritten, and at which revision
skill_installed the Agent Skills are installed — nothing more

skill_installed answers "are they there", not "were they just refreshed": shoplazza update does refresh them when it is true, but update --check reports the same true while refreshing nothing, and a refresh that failed also still reports true (with a warning on stderr). Never tell the user their skills were updated on the strength of this field alone — read the stderr lines (✓ Refreshed N skill(s)) for that.

The skills are opt-in: false means the user never installed them and update left them alone rather than installing them. To install:

npx skills add Shoplazza/shoplazza-cli -g

(Set SHOPLAZZA_CLI_SKIP_SKILLS=1 to suppress the skill step; the field then disappears from the body entirely — an absent field means "suppressed", false means "not installed".)

Safety protocol

shoplazza-cli has no lark-style exit-10 high-risk-write gate and no --yes confirmation flag. Do not invent a confirmation protocol. The safety model is --dry-run → restate → wait for the user's go-ahead:

  1. --dry-run first for destructive (cancel / delete), batch, or fund-moving (billing charges) operations: print the request that would be sent, restate it, then STOP and end your turn. For entity/campaign creation via a +shortcut (e.g. creating a discount), follow the domain skill — +shortcuts are structured and safe to run directly once the required, money-affecting values are confirmed (never fabricated); --dry-run only if the user asks.
  2. Consent must be a user message, not your own judgment. Showing the dry-run is not consent; your own review of it is not consent. The real command may only run in a turn that comes after the user explicitly agrees (e.g. "confirm" / "yes / go ahead"). Running dry-run and the real write in the same turn is a protocol violation even if the dry-run output was perfect.
  3. Restate intent alongside the dry-run: plain language — which store, what action, which key params — so the user knows exactly what they are agreeing to.
  4. Do not fabricate money-spending values (discount amounts / caps / prices) — omit for the default or ask; see each domain skill's required-vs-ask.
  5. Rehearse writes against dev / stg stores only; never practice on prod.

Command discovery

shoplazza --help                 # top-level modules
shoplazza <svc> --help           # a module's shortcuts + spec leaves
shoplazza <svc> <cmd> --help     # a command's flags
shoplazza schema <svc>.<cmd>     # params / body / response shape