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

# CLI

> Every `xanots` command, shell completion, and what the CLI prints when something fails.

```bash theme={null}
xanots init my-app                         # scaffold a full project (frontend/ + xano/)
xanots init my-app --framework svelte      # SvelteKit instead of the default React
xanots init my-app --ai claude --no-install  # add CLAUDE.md; skip npm install
xanots init my-app --theme zinc-blue --dark toggle --icons tabler  # the look
xanots init my-app --marketplace @xanots/auth,@xanots/vector  # install add-ons AND register them

xanots marketplace list                    # every published add-on (no login needed)
xanots marketplace search auth             # narrow by keyword
xanots marketplace details @xanots/auth    # what it installs + the registration to copy
xanots marketplace details @xanots/auth --prompt  # …as a prompt for a coding agent
xanots marketplace install @xanots/auth    # add an add-on to the project you're in

xanots export ./xano/index.ts              # bundle to stdout
xanots export ./xano/index.ts --out ws.json
xanots compile ./xano/functions/get-user.ts  # a single function's JSON

xanots export ./xano/index.ts --strict     # CI: fail the build on any warning, don't just print it
xanots export ./xano/index.ts --lock       # opt into xano.lock (created beside the entry)
xanots export ./xano/index.ts --frozen-lock  # CI guard: fail if the export would change the lock
xanots lock rename --entry=xano/index.ts table users members  # move a lock entry after renaming in code
xanots lock prune ./xano/index.ts --yes    # drop lock entries nothing exports anymore
xanots lock prune --identity-only --entry=xano/index.ts --yes dbo:notes  # …or drop named keys without running the workspace
xanots lock import live-export.json --entry=xano/index.ts --yes  # seed the lock from a live engine export

xanots login                               # OAuth sign-in (once) — pick the instance + workspace at consent
xanots login --paste                       # …when the browser can't reach this machine's 127.0.0.1 (remote shell, container, Codespace)
xanots workspace details                   # which instance/workspace am I bound to, and via which credential?
xanots deploy ./xano/index.ts              # compile + import into a live ephemeral (the dev loop) → URL
xanots deploy ./xano/index.ts --static ./frontend/dist   # also deploy a static frontend (onto the ephemeral)
xanots deploy ./xano/index.ts --static ./frontend/dist --static-env PK=pk_live_1   # + extra public config
xanots deploy --bundle ws.json             # deploy an already-exported bundle
xanots deploy ./xano/index.ts --open       # …and open the deployed URL in your browser
xanots ephemeral list                      # list your ephemeral environments (--all-workspaces spans every workspace)
xanots ephemeral get <tenant>              # base URL, state, and expiry for one (<tenant> = the tenant name, e.g. ewap-8wz9-9e13, NOT the display name — `ephemeral list` shows it in bold)
xanots ephemeral delete <tenant> --yes     # destroy one
xanots ephemeral impersonate <tenant>      # open it in the builder (--guest = read-only; --url-only prints the URL instead)
xanots release ./xano/index.ts --dry-run   # preview what promoting to your main workspace would change
xanots release ./xano/index.ts             # promote: add + update, never delete, never touch table data
xanots release ./xano/index.ts --prune     # …also delete objects this project released and no longer defines (needs xano.lock)
xanots ephemeral export <tenant>           # export a DEPLOYED ephemeral as a JSON bundle → ./<tenant>.json
xanots ephemeral export <tenant> --format multidoc --name backend    # …or as XanoScript → backend.xs
xanots ephemeral export <tenant> --format multidoc --path -          # …stream the multidoc to stdout (deploy first)

xanots workspace details                   # which workspace your token is scoped to (instance, id, name, guid)
xanots workspace export --path ws.json     # your REAL workspace as a JSON bundle (`--path -` streams to stdout)
xanots init my-app --from workspace        # …or as a runnable project (the pull direction)
xanots init my-app --from ephemeral:<tenant>   # same, from an ephemeral (the env is named inside the flag)
xanots init my-app --from ./ws.json        # …or from a bundle already on disk (offline, no auth)

xanots status                              # who am I, which workspace, and the env this project last deployed to
xanots whoami                              # print the scoped user + instance base URL (pretty on a TTY, JSON when piped)
xanots profile me                          # alias for `whoami`
xanots logout                              # revoke the refresh token + clear the shared cache (--local for the project one)
xanots version                             # print the installed @xanots/sdk version
xanots upgrade --check                     # is a newer @xanots/sdk published? exits 7 if so, 0 if current
xanots upgrade                             # install it (-g or -D, matching how this CLI is installed)
xanots help                                # grouped command reference (also the no-arg default)
xanots <command> --help                    # that command's usage, subcommands, and flags (`xanots deploy --help`)
xanots <noun> <verb> --help                # scoped to one verb (`xanots workspace export --help`)
xanots <command> --json                    # force JSON on stdout (otherwise: whenever stdout isn't a terminal)
xanots completion zsh                      # shell completion script (also bash, fish) — see below

xanots preflight ./xano/index.ts           # import into a live instance, diff each object back (needs auth + a throwaway tenant)
xanots preflight ./xano/index.ts --runtime # also run each deployed function on the engine
xanots preflight ./xano/index.ts --capture # write the fetched JSON as fixture candidates
```

## Bundle key names vs. the names everything else uses

A bundle's `payload` arrays are keyed by the engine's **storage** name, which for five kinds
is not the name the SDK, the CLI, or a release plan uses for the same thing. Scripting against
a bundle means translating:

| You author / the plan reports | `payload` array   |
| ----------------------------- | ----------------- |
| `table`                       | `payload.dbo`     |
| `api_group`                   | `payload.app`     |
| `agent`                       | `payload.toolset` |
| `mcp_server`                  | `payload.toolset` |
| `toolset`                     | `payload.tool`    |
| `realtime_channel`            | `payload.channel` |
| `realtime_message`            | `payload.message` |

Everything else (`query`, `function`, `task`, `trigger`, `middleware`, `microservice`,
`addon`, `workflow_test`, `workspace`) is keyed the same on both sides.

Two traps worth naming. A release plan reports operations as `{"type": "table", …}` while the
bundle it came from stores that object under `payload.dbo` — so a table that released
correctly looks *missing* if you go looking for `payload.table`. And `agent` and `mcp_server`
both land in `payload.toolset`, while the kind actually named `toolset` lands in
`payload.tool`; matching on the word alone will pick the wrong array.

This is also the format `xanots lock import` reads, so both directions are user-facing.

**Build warnings, and `--strict`.** `export`/`deploy` print a `xanots:` warning for the
shapes that ship clean and then do the wrong thing — a `bulk.update` zero-filling the columns
an item omits, an `ignoreEmpty` on an operand that's already empty, a `ref()` no `as` binds,
a filter name the engine can't resolve, a `s.switch` case that falls through into the next
one, a request-time timestamp filter on a `where` operand. Each stays a warning because each
has a legitimate use. Nothing fails on a message nobody reads, though, so pass `--strict` in CI and in
unattended agent builds: every warning becomes a hard failure and the exit code carries it.
The programmatic equivalents are `emitBundle(app, { strict: true })` and
`app.export({ strict: true })`. The bundle bytes are identical either way.

## Shell completion

`xanots completion <bash|zsh|fish>` prints a completion script covering every command, verb, flag,
and closed value set (`--env ephemeral|workspace`, `--format json|multidoc`, `--ai claude|codex|cursor|none`).
It is generated from the CLI's own command table, so it never drifts from what the CLI accepts — but it
is baked at generation time, so re-run it after upgrading.

```bash theme={null}
# zsh
xanots completion zsh > "${fpath[1]}/_xanots"   # then restart your shell

# bash
xanots completion bash > ~/.xanots-completion.bash
echo 'source ~/.xanots-completion.bash' >> ~/.bashrc

# fish
xanots completion fish > ~/.config/fish/completions/xanots.fish
```

After a command succeeds the CLI checks npm (at most once an hour, cached in
`~/.xanots/update-check.json`) and prints a one-line nudge to **stderr** when a newer
`@xanots/sdk` is published — never to stdout, so piped bundles stay clean. The suggested
command adapts to how you installed it (`npm i -g …` for a global install, `npm i -D …`
when it's a project dependency). The check is best-effort and bounded (a slow or offline
registry never delays a command), and stays silent under CI or when stderr isn't a terminal.
Opt out with `XANOTS_NO_UPDATE_CHECK=1` (or the conventional `NO_UPDATE_NOTIFIER=1`).

`xanots upgrade` is the same question asked on purpose, and it answers under all the
conditions the nudge stays quiet for — CI, a piped stderr, the opt-out variables — reading the
registry live rather than serving the hourly cache. `--check` reports without installing and
exits **7** when a newer version is published, **0** when you are current, so a pipeline can
branch on it:

```bash theme={null}
xanots upgrade --check || echo "an upgrade is waiting"
xanots upgrade --check | jq -r .latest        # piped stdout is JSON already
```

A registry it cannot reach is an error (exit 1), never a quiet "you are up to date". Without
`--check` it installs, matching how this CLI is installed — and for a project-local install it
then restores the `@xanots/sdk` range your project was scaffolded with (npm rewrites it to a
caret, which on the `0.0.x` line pins you exactly) and restamps the managed block in your
agent files so the guidance matches the version you now have. Set `XANOTS_INSTALL_MODE` to
`global` or `local` to override the detection.

## When something fails

Failures are written for the person reading the terminal. A request the instance refused
prints the server's own sentence on one line (`deploy failed (403 Forbidden): Access denied
for this workspace.`) rather than the whole JSON envelope; a request that never arrived names
what it could not reach and why (`workspace list could not reach https://…: fetch failed
(ECONNRESET)`), and a request that timed out says so separately, because "slow" and
"unreachable" call for different next steps. Set `XANOTS_DEBUG=1` to append the untouched
response body underneath — nothing is discarded, only folded away.

A malformed invocation fails before any work happens: a missing or misspelled entry file, an
argument the command has nowhere to put (`xanots export --lock xano.lock` — that flag takes
its value attached, as `--lock=xano.lock`), or a missing credential, each answered with the
command's usage block instead of a stack trace. `deploy` and `release` check that you are
signed in *before* compiling, so a lapsed session costs you a message, not a build.

Emitters that write to disk (and the programmatic CLI) are Node-only — import them from
`@xanots/sdk/node`. The string emitters (`emit`, `emitBundle`) stay on the browser-safe
`@xanots/sdk` entry.

```ts theme={null}
import { emitBundle } from "@xanots/sdk";        // pure string — browser-safe
import { writeBundle } from "@xanots/sdk/node";  // writes a file — Node only
```

**Four entry points, and only the first is the authoring API.** `@xanots/sdk` is what you
define a workspace with; `@xanots/sdk/node` adds the filesystem half; `@xanots/sdk/codegen`
is what a generated tree imports; and `@xanots/sdk/internal` holds the compiler machinery —
the per-kind encoders, the kind and statement registries, the bundle serializer, the
`xano.lock` model. Nothing on `/internal` is needed to author anything, and it is kept off
the root so an agent scanning the package's exports sees the surface rather than the guts.

All three run the same build-time checks, including seed validation of a literal
`seed: [...]` array. A **deferred** seed (a thunk, or `seedFile()`) needs an await or the
filesystem, so it is materialised and checked only by `xanots export` / `xanots deploy`.
