@zalify/cli
v0.26.3
Published
Zalify CLI - command-line interface for Zalify
Readme
@zalify/cli
Zalify CLI — author a brand, launch its shop, run its ads, keep the theme current.
Install
npm i -g zalify
# or
npm i -g @zalify/cli@latestAlready installed? zalify self-update.
The command model
Four nouns, four objects:
| Noun | The object | What lives under it |
| --- | --- | --- |
| brand | the data | a brand folder: catalog, voice guide, image manifest |
| shop | the shop | Zalify-hosted at <slug>.myzalify.com: repo + deployment + domain |
| ads | the ad ops | Autopilot brand instance: campaign intent repo (media plans, ads-as-code, policy) |
| theme | the code | Shopify-theme model: scaffold a copy, measure drift, upgrade |
Everything acting on a brand folder lives under brand. Brand and theme
commands default their directory to the cwd; shop refresh/open resolve
their shop from the cwd (.zalify/site.json in a scaffold, store.json in
a brand folder); ads commands run arg-free from a checkout carrying
.zalify/brand-repo.json.
The golden path (new brand, start to live)
zalify brand init stores/acme # 1. author the data (no login needed)
zalify login # 2. authorize; then: zalify workspace set <workspace>
zalify shop create acme # 3. live at acme.myzalify.com in ~4 minutes
# (already scaffolded the dir with
# `theme create`? → `zalify shop link acme`)
cd stores/acme && zalify brand push --prune
zalify brand images generate && zalify brand images attach
zalify theme status # 6. stay current; `theme upgrade` keeps your editsAuthentication
zalify login # opens the browser: authorize once
zalify login --app-url http://localhost:3000 # against a local app.zalify.com
zalify whoami # workspace, plan, and key details
zalify logout # delete local credentialslogin runs a loopback flow (like gh auth login): the CLI listens on a
random localhost port, the browser opens app.zalify.com/cli/auth, and on
approval a user key covering all your workspaces is handed back and
stored in ~/.zalify/config.json (0600). Keys are ordinary Zalify Public
API keys — list and revoke them in Settings → Developer. One login
covers every workspace; zalify workspace set switches between them
locally, no re-login.
Inside a brand checkout, the folder decides. ads commands read the
checkout's own workspace (its .zalify/brand-repo.json stamp, or
identity.yaml for folders AdsBuddy downloaded) and act as that
workspace — no workspace set first, and no risk of pulling one
brand's campaigns into another's folder. workspace set still matters
for commands run outside any checkout, and for ads create, which
provisions a NEW brand into the workspace you chose.
Command reference
Auth & workspace
| Command | What it does |
| --- | --- |
| login [--app-url <url>] | Browser-based authorization; stores a user key locally |
| logout | Deletes ~/.zalify/config.json (revoke the key itself in Settings → Developer) |
| whoami | Verifies the stored key; shows workspace, plan, app origin, key prefix |
| workspace / workspace list | List authorized workspaces (* = active) |
| workspace set <slug-or-id> | Switch the active workspace (local, no re-login) |
brand — the data
| Command | What it does |
| --- | --- |
| brand init [dir] [--name <name>] [--vertical <v>] [--currency <code>] | Scaffold a brand folder: catalog, voice guide, image manifest, AUTHORING.md rules. No login needed |
| brand validate [dir] | Lint the brand folder against the authoring rules |
| brand push [dir] [--prune] [--shop <slug>] | Push catalog.json to the connected Shopify store — products, smart collections, publish; idempotent. --prune deletes store products (by catalog vendor) absent from the catalog; --shop narrows the post-push refresh to one shop |
| brand images generate [dir] | Generate the manifest's missing images on Zalify infrastructure (resumable) |
| brand images attach [dir] [--shop <slug>] | Attach generated images to their Shopify products per the manifest (skips already-attached) |
| brand images push [dir] | Upload image masters to the Zalify asset library — presigned R2 PUTs, sha256 dedup; writes images/assets.json, the committed index that keeps binaries out of git |
| brand images pull [dir] | Download masters listed in images/assets.json that are missing locally (fresh-clone restore) |
shop — the hosted shop
| Command | What it does |
| --- | --- |
| shop create <dir> [-t <theme>] [--editor] [--to <version>] [--no-install] [--no-git] | One-command launch: provision repo + deployment + domain + storefront token, scaffold locally, push, go live at <slug>.myzalify.com. --editor wires in the Zalify canvas editor (z1). Needs an empty/absent <dir> — an existing scaffold attaches via shop link instead |
| shop link <dir> [--slug <slug>] [--no-git] | Attach an existing scaffold (from theme create) to an already-provisioned shop: fill store credentials into .env, write .zalify/site.json + theme/pixel.json, push, wait for the deploy. Also the recovery path if shop create fails after provisioning |
| shop / shop list | List the workspace's hosted shops |
| shop refresh [slug-or-dir] | Refresh the live shop's data cache (defaults to the shop linked to cwd) |
| shop open [slug-or-dir] | Open the live shop in the browser (defaults to the shop linked to cwd) |
| shop domain-sync [slug-or-dir] | Re-point the shop at the current root domain (the old domain 308-redirects) |
z1shop … (and site … before it) remain as hidden aliases for one release
and print a pointer to their new home under shop.
ads — the ad ops
| Command | What it does |
| --- | --- |
| ads create <dir> | Provision an Autopilot brand-instance GitHub repo (POST /api/repos, GitHub App), scaffold the starter locally (identity.yaml, brand.yaml, policy/, campaigns/, media-plans/, signals/, decisions/), and push with a short-lived repo-scoped token. Writes .zalify/brand-repo.json so later commands run arg-free. The directory name becomes the slug — use the workspace's store slug so the service's digest writer can find the repo |
| ads / ads list | List the workspace's brand instances and their provisioning status |
| ads new <name> [--account <folder>] [--objective] [--budget] [--country] [--link] | Scaffold a campaign FOLDER at accounts/<account>/<name>/ (account inferred when only one exists): campaign.yaml for budget/audience/copy defaults, creatives/ for the files that become ads. cp -r an existing folder to duplicate a campaign |
| ads pull [--account <act_id>] [--days <n>] [--force] | The existing accounts as a tree: every ingested campaign lands as accounts/<account-name>/<campaign>/campaign.yaml (imported form — names in label:, budgets, statuses, core audience) plus campaigns/.bindings.json mapping YAML nodes to platform ids. A hash ledger protects local edits: unedited files fast-forward, edited ones are kept and named, deletions never happen automatically |
| ads creative generate <campaign> --prompt <text> [--orientation] [--ref <url...>] [--name] | One image through the canvas pipeline (safe zones, product fidelity with refs), downloaded INTO the campaign's creatives/ and recorded in its assets.json — the filesystem stays the interface, and the asset is stored once |
| ads diff | Desired (your campaign files) vs actual (the ingested account), keyed through bindings: bound nodes compare budget/status, unbound nodes become creations. Folder campaigns upload their creatives/ first (presign + checksum dedup — the brand images push machinery) and derive server-side. Read-only against the platform |
| ads apply [-y] [--activate] | Execute the diff after showing it and asking. Creations land PAUSED — always; budget edits apply to bound entities; paused→active is refused without --activate (turning spend on is said twice; active→paused always executes). Created ids merge into the bindings ledger |
| ads remove <slug> | Deprecate a brand instance — soft-deletes the record (drops out of ads list and the service's repo-token minting; survives for history). The GitHub repo is deliberately untouched: deleting real campaign intent is a human-on-GitHub decision |
| ads status | The instance at a glance: brand + consent, platform access and credential expiry, ingestion freshness (with per-account failures), first-party join coverage, plan and pending-proposal counts. Exits non-zero when the service reports problems |
| ads report [--date <YYYY-MM-DD>] [--platform <all\|meta\|google>] | The daily report in the terminal — totals vs the day before, where the money moved, what changed in the account |
| ads digest [dir] | Print the repo's signals/latest.json — the curated numbers the service writes daily and agents draft against. Reads the LOCAL checkout |
| ads plan / ads plan list | Media plans recorded by the service, newest first |
| ads plan show <id> | One plan's content (by id, or slug@version) |
| ads open | Open the Structure canvas for the active workspace |
| ads doctor [dir] | Checks that have each broken once: checkout↔record link, workspace match, slug alignment (service brand vs repo record — misalignment silently kills digest writes), consent drift, brand-vs-account timezone match, credential expiry and degradation |
The engine commands — plan / apply / creative / report — join this
group as @zalify/ads-engine merges in. Until then, drafting and applying
happen on the Structure canvas in app.zalify.com; the instance repo is the
durable intent layer that work lands in.
theme — the code
| Command | What it does |
| --- | --- |
| theme create <dir> [-t <template>] [--editor] [--store-domain <domain>] [--storefront-token <token>] [--to <version>] [--no-install] [--no-git] [--tarball <path>] | Scaffold only — no Zalify hosting (the self-host path; shop create is the fully-hosted launch, and a scaffold can move to hosting later via shop link). Templates: liquid | hydrogen | nextjs. Without --store-domain it runs on mock.shop demo data |
| theme status [dir] | Which theme files have you edited? Checksums vs .zalify/theme.json |
| theme upgrade [dir] [--to <version>] [--dry-run] [--install] [--tarball <path>] [--base-tarball <path>] | Move to a newer theme version: untouched files overwritten, edited files 3-way-merged, theme/ + .env never touched. --dry-run classifies and reports without writing |
Maintenance
| Command | What it does |
| --- | --- |
| version | Print the CLI version and check npm for a newer one |
| self-update | Update the CLI to the latest version (auto-detects pnpm/bun/npm) |
Every invocation also runs a passive, non-blocking update check and prints a one-line notice when a newer version exists.
Development
Requires Bun.
bun install
bun run dev version # Run CLI from source (TypeScript)
bun run build # Build to dist/
bun run typecheck # Type-check onlyPublishing
CI publishes automatically (.github/workflows/publish.yml): pushing to
main with a package.json version that isn't on npm yet typechecks,
builds, publishes @zalify/cli, and tags the commit v<version>. The
zalify/ wrapper (for npm i -g zalify) is published in the same run
whenever its own version is new. Auth is npm Trusted Publishing (OIDC,
no token secret): one-time setup per package on npmjs.com — package →
Settings → Publishing access → Add trusted publisher → GitHub Actions →
repo zalify/zalify-cli, workflow publish.yml.
Release flow:
npm version minor --no-git-tag-version # or patch/major
# bump zalify/package.json in step and pin its @zalify/cli dependency
git commit -am "0.x.0" && git push