> ## 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.

# Pulling an existing workspace

> What `xanots init --from` writes, how faithful the pull is, and how to read its report.

## Running it

A pull is `init` with `xano/` filled from an existing backend instead of the empty starter,
so it is the scaffold command with one extra flag:

```bash theme={null}
xanots init my-app --from workspace          # your real workspace (the one your login is scoped to)
xanots init my-app --from ephemeral:my-env   # a named ephemeral environment
xanots init my-app --from ./ws.json          # a bundle already on disk — offline, no login
```

Everything else about the project is unchanged: `--framework`, the theme flags, `--ai` and
`--web` mean the same thing here as they do without `--from`. The two flags that describe
the pull itself — `--report` and `--skip-roundtrip` — are refused without it rather than
silently ignored.

## The generated tree

Inside, `xano/` is shaped the way the workspace is: one directory per kind, with each
object under its parent — queries under the API group that owns them, triggers under
what they fire on. Each table gets its own `table/<name>.ts`, settings sit in `xano/workspace.ts`,
`_shared.ts` holds anything else referenced from more than one file, and `xano/README.md`
lists anything that did not translate cleanly.
Object identities (`guid`) are preserved, so cross-references stay intact. A statement
this SDK does not model yet round-trips verbatim rather than breaking the pull.

Pulled objects are authored the same way you would write them by hand — `table({...})`,
`query({...})`, `defineFunction({...})` — so the generated tree keeps its types. A pulled
table's columns still check on `fieldName`/`output`/`sortBy`, `InferInput<typeof q>` still
resolves a pulled query's payload, and a pulled agent still types `s.ai.agent.run`.

A pull states what the source workspace actually holds and leaves out what the SDK would
put back anyway. A table's `primary(id)` / `created_at` / `gin(xdo)` indexes are the
engine's standard set, so only the indexes someone created are listed. A trigger comes back
through the factory that built it (`tableTrigger`, `realtimeTrigger`, …) rather than a bare
`satisfies TriggerDef`, which keeps its typed stack handle; the two realtime types that bind
a def handle are the exception, since a stored trigger carries two guids with no way to know
they agree. And two objects that reference each other — a pair of tables joined both ways,
two functions that call each other — can't both be declared first, so the second reference
is a `{name, guid}` const hoisted to the top of the file (`const OrdersRef = {…}`) instead of
an import that would close a cycle. Only the guid is ever read, so it binds exactly.

`xano/README.md` also lists objects that were **already empty in the source** — an
endpoint someone created and never filled in pulls as a def with no `stack`, which looks
identical to a decode that gave up. The report is what tells the two apart.

A few options exist only so a pull can be *faithful*, and reading them in generated code
is the only time you should see them: `table: null` / `fn: null` (a statement whose target
was deleted or never bound), `merge` / `hidden` on a field, `paging: { enabled }` on a
query, `c.blank(tag)` (the editor's unconfigured value box — **not** a zero or an
empty collection; the engine reads `""` and `"0"` differently, so tidying one into the
other changes what the workspace stores), and `c.null("const:obj")` (the object-typed null
a `db.*` statement's `@meta` slot carries — different stored bytes from `c.obj(null)`,
which is the blank object, though both evaluate to null). They describe what the source workspace actually
stored — a pulled `table: null` is a defect to fix upstream, not a shape to copy — and
each carries that warning at the call site. A blank binding also reports, because a
statement wired to a table or function that no longer exists is worth seeing even though
it round-trips exactly.

## Verification, and the decode report

Then it checks its own work: the project it just wrote is loaded, exported, and diffed
against the workspace it came from. A mismatch names the object and fails the command
(`--skip-roundtrip` opts out). So "it compiled" and "it means the same thing" are separate
claims, and you get both.

The findings above are printed either way. Verification runs after decoding is finished,
so whether it passes, disagrees, or cannot run at all, the report describing the decode is
rendered first — and a tree that was written but does not re-export exits **2**, distinct
from the **1** you get when nothing was written because the bundle could not be read.

**Reading the report.** It opens with a headline (`27 distinct issues across 424 findings;
3 need your attention`) and splits into three sections, most actionable first: *problems in
your workspace*, *things XanoTS could not model*, and the things stated only so the output
is not ambiguous. That split is the question a reader actually has — a `raw()` passthrough
is ours to close, a lambda reading an unbound name is theirs to fix, and an empty object is
neither. Findings that repeat the same sentence across objects collapse to one line with a
count and a collapsed object list, and each names the generated file it landed in.
`--report full` prints every site instead; `--report json` prints the findings as data, and
the same data is written into `xano/.xanots-codegen.json` on every pull, so parity is
trackable release over release and gateable in CI without scraping output.

Re-pulling is a real workflow: a second `init --from` into the same directory refreshes
`xano/` and leaves the rest of the project — your `package.json`, your `frontend/` —
exactly as you left it. No `--force` needed, because the tree carries a marker saying it
was machine-written.

> ⚠️ **`xano/` is a scratch surface, and there is no `xanots workspace deploy`.**
> Regenerating rewrites it (a directory that isn't a previous pull still needs `--force`),
> it carries schema only — no table rows — and deploying it is a *full replace* of the
> target. Pull from your real workspace, edit, and `deploy` to a disposable ephemeral
> environment. Workspace env var **values** ride inline in `xano/workspace.ts` (that is what a
> deploy sends), so treat a pulled tree as secret-bearing before you commit it.

***
