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

# naive

> Claim a cloned blueprint repo, apply its naive.config, scaffold a new one.

Installing the CLI package gives you two binaries pointed at the same bundle: `vetta` and `naive`.
The name selects nothing — every command works under either — but a cloned blueprint repository
documents the `naive` spellings below. `naive claim` is `vetta naive claim`, and so on.

> [!NOTE]
>
> The `naive` binary exists from **`@usenaive-sdk/vetta-cli` 0.4.0**. Earlier releases install only
> `vetta`, so on those every command below has to be spelled `vetta naive …`. The blueprint ×
> template table below is **bundled into the binary**, so `--template` only knows the templates the
> release you installed knew: `longform` needs **0.5.0**, and a data-only catalog template (one with
> no app, like `media` 2.x) needs **0.6.0** — older releases fail on it. A blueprint repo pins
> `^0.6.0` (and `@usenaive-sdk/blueprints` `^0.9.0`) and gets the binary from its own
> `pnpm install` — nothing global is required: `pnpm exec naive up`. To install it yourself,
> `npm i -g @usenaive-sdk/vetta-cli@^0.6.0`.

## Commands

| Command | Description |
| - | - |
| `vetta naive claim` | Bind the clone to your organization by storing a verified API key. |
| `vetta naive up` | Reconcile `naive.config.*` against the platform, record the apply, and report what changed. |
| `vetta naive init` | Scaffold a starter `naive.config.ts`. |
| `vetta naive template` | Clone a published blueprint into a directory of its own. |
| `vetta naive catalog` | List the published blueprint × template pairs and the versions they are published at. |
| `vetta naive installs` | List what this organization has applied: project, blueprint, template, revision. |
| `vetta naive installs context` | Print, or with `--set` edit, what the crew of the project in this directory knows about the business. |
| `vetta naive installs remove` | Take a template out of the company: its crew is fired and its install goes. |

## claim

```bash theme={"system"}
naive claim --key sk_live_…
# or
export NAIVE_API_KEY=sk_live_…
naive claim
# or pipe it, so the key never appears in argv or the shell history
naive claim < key.txt
```

| Flag | Description |
| - | - |
| `--key` | The platform API key. Falls back to `NAIVE_API_KEY` (or `VETTA_API_KEY`), then piped stdin. |

The key is verified with one authenticated read before anything is written; a refused key stores
nothing. On success the profile is saved owner-only (`0600`) and the output confirms the
organization and the key's 12-character display prefix — never the key itself.

The binding **is** the key: API keys are organization-scoped, so everything a later `naive up`
creates lands in that key's organization. The profile lives on this machine only — there is no
server-side record tying a repository or project name to an organization, so the same clone
claimed on another machine with another key deploys to another org. To move a project, claim
again with the other organization's key.

## up

```bash theme={"system"}
naive up --dry-run
naive up --dir ./blueprint
naive up --without analyst
naive up --only editor,site
```

| Flag | Description |
| - | - |
| `--dry-run` | Plan only: reads happen, writes do not, and the report says what apply would do. |
| `--dir` | Directory holding `naive.config.{ts,js,mjs,json}` (default: the current directory). |
| `--adopt` | Take over every app this run touches that another project owns, or that no project owns. |
| `--only` | Provision only these agents and apps, by declared name; comma-separated, repeatable. |
| `--without` | Provision everything except these agents and apps, by declared name; comma-separated, repeatable. |

The report names the `blueprint` and the `template` it applied, when the config declares them: a
crew provisioned into your organization is money, so which crew it was is not left to be inferred
from the agent list.

Loads the config, then reconciles each declared skill, identity, vault, app, agent and task by name —
in that order, so agents can reference the rest and cards can name their assignee. The report lists every resource as `created`,
`updated`, `unchanged`, `deleted` or `refused` (with the reason), and any refusal makes the exit
code non-zero. Re-running is always safe: every resource is keyed by its config name, and nothing
is deleted by omission — only a `removed` name, or a cron missing from an agent's `schedules`.

A live row the CLI cannot read — one written before a field's rules tightened, say — is listed
under `skipped` in the report and stepped over: it is neither reconciled nor deleted, and the rest
of the apply runs normally. A row in `skipped` is a row to fix or delete by hand.

### Config surface

| Key | Fields | Reconciled by |
| - | - | - |
| `name` | project name | — |
| `blueprint` | the machine this project runs | — (validated at define time) |
| `template` | which of that blueprint's templates provides the crew | — (validated at define time) |
| `title?`, `description?`, `platforms?` | what a person calls the template (≤ 80), one line on what it does (≤ 280), and the networks it publishes to | — (printed on the catalogue, never provisioned) |
| `questions[]` | `key`, `label`, `type`, `help?`, `optional?`, `options?`, `multiple?`, `other?`, `placeholder?` | — (asked before the apply, not reconciled by it) |
| `skills[]` | `slug`, `file` | slug; content hash decides `unchanged` vs `updated` |
| `identities[]` | `name`, `description?`, `legal?`, `wallet?`, `cards?` | name; `description` drift is patched |
| `identities[].legal[]` | `label`, `state`, `name_options`, `naics_code`, `description` | `label`; reported and never provisioned (a person starts a check or pays a formation) — see [Legal](https://vetta.sh/docs/identity/legal.md#infrastructure-as-code) |
| `identities[].wallet` | `network?`, `policy?: { per_tx_cap_micro_usd, daily_cap_micro_usd?, low_balance_micro_usd? }` | presence; `policy` drift is patched; never funded, frozen or swept — see [Wallet](https://vetta.sh/docs/identity/wallet.md#in-a-blueprint) |
| `identities[].cards[]` | `label`, `load_micro_usd` | `label`; issued (debiting credits) when no live card carries it; never cancelled or refunded — see [Cards](https://vetta.sh/docs/identity/cards.md#in-a-blueprint) |
| `vaults[]` | `name`, `identity?`, `credentials[]: { kind, key, host?, mcp_server_url?, value: { from_env } }` | name; credentials by presence |
| `apps[]` | `name`, `type`, `description?`, `deploy_dir?`, `env?`, `mcp?`, `migrations?`, `buckets?`, `required?` | name; `type` cannot change; `deploy_dir` by content hash; `mcp` patched on drift; `migrations` applied once each, by name; `buckets` created when missing, never deleted |
| `agents[]` | `name`, `model`, `budget`, `description?`, `system?`, `tools?`, `skills?`, `mcp_servers?`, `window?`, `multiagent?`, `handoffs?`, `output_schema?`, `structured_output_required?`, `metadata?`, `allowed_apps?`, `identity?`, `schedules?`, `intake?`, `role?`, `required?` | name; declared fields patched on drift |
| `agents[].schedules[]` | `cron`, `timezone?`, `input`, `budget_micro_usd`, `identity?`, `enabled?` | exact `cron`, as a complete set per agent |
| `agents[].intake` | `message`, `budget_micro_usd?` | sent once, on the apply that creates the agent |
| `tasks[]` | `key`, `title`, `body?`, `assignee?`, `blocked_by?` | `key`, as a card on the organization's board; `created` once, `unchanged` after |
| `removed` | `apps[]`, `agents[]`, `skills[]`, `identities[]`, `vaults[]` | name; tombstones |
| `kept` | `agents[]` | name; reported and never written (the switch rule below) |

Every cross-reference — `allowed_apps`, `identity`, a vault's `identity`, a roster's members — names a
resource declared in the same config, and a config that names one that is not declared (or declares a
name twice) is refused before anything is read. An agent's `system`, `skills`, `mcp_servers`,
`window`, `output_schema`, `structured_output_required` and `metadata` pass through to the platform
verbatim, exactly as `agents.create` takes them; `allowed_apps` names apps and is written as their ids
into the `apps` tool's config. `multiagent` is the agent's team setting as the platform spells it,
except that a roster names its members by their declared `name` (`{ type: "agent", name: "editor" }`
or just `"editor"`) instead of an id — `up` creates every agent first and then writes the roster with
the ids they were given. A roster may name an agent listed under `kept`, never one under `removed`.
`handoffs` is `true` (the platform's default: any agent of the organization), `false`, `"team"` (its own
`multiagent` roster, so the agent must declare one), or the declared `name`s this agent may
[send work to](https://vetta.sh/docs/capabilities/tools.md#handoffs) — checked against the same crew, so a name nobody declares,
the agent's own, or one written twice is refused before anything is read. A roster with
`handoffs: team` and `metadata: { role: group }` is a **channel** in the dashboard — a place whose
members are that roster. Nothing runs on it: a post with no mention asks every member, and an `@mention` asks just that member.
A field the config leaves out is never drift: the live value stays (an app's `mcp` is the one
exception — see below).

`questions` is the one key `up` does not reconcile, because it is not a resource: it is what a
person is asked **before** the crew is provisioned. They use the same shape an agent asks a question
with mid-run, so one form renders both, and the answers a person gives are recorded on the install
rather than written into the tree — the tree is identical for every organization running the same
template. Declaring them is what lets a published blueprint be set up before anything exists to
configure. A template declares at most four
([Templates](https://vetta.sh/docs/concepts/templates.md#four-questions-then-a-crew)); a hand-written config that names no
`template` is not capped.

`help` is the sentence shown under the label — where you say what a good answer looks like, so the
label can stay short. `optional: true` says the field may be left blank, and a blank one comes back
**absent**: the key is not in the answers at all, rather than present with an empty string, so an
unanswered question is simply not in the [project context](#installs-context) the crew reads. It is
for the question that would improve the work without gating it — a reference to imitate, say. Ask
that one mandatorily and you force an answer out of a person who has none, and an invented answer is
worse than an absent one, because the crew cannot tell them apart. A key you *do* send is checked
like any other: a blank string or an unlisted option is refused whether or not the field is
optional.

```ts theme={"system"}
export default defineProject({
  name: "my-project",
  skills: [{ slug: "clipping", file: "skills/clipping.md" }],
  identities: [{ name: "Ava", description: "Sales persona" }],
  agents: [
    {
      name: "clipper",
      model: "…",
      budget: { /* … */ },
      system: "Be terse.",
      skills: ["clipping"],
      mcp_servers: [{ name: "docs", url: "https://mcp.example/sse" }],
      identity: "Ava",
      tools: {
        default_config: { permission: "allow" },
        configs: {
          browser: {
            enabled: true,
            permission: "allow",
            config: { allowed_domains: ["example.com"], timeout_minutes: 30, context_name: "shop" },
          },
        },
      },
    },
  ],
});
```

An agent's `tools` is the platform's toolset verbatim, with one field checked at define time:
`tools.configs.browser.config` is the [browser's options](https://vetta.sh/docs/computer/browser.md#configuration-reference)
(`allowed_domains` optional — the open web by default, `timeout_minutes` 1–360, `context_name` to reopen a saved login, …).
A malformed one is refused by field (`agents.0.tools.configs.browser.config.allowed_domains`) rather
than applied — the runtime would otherwise open a browser that reaches nothing.

A skill's `file` (resolved against the config's directory) is pushed by slug; skills are
content-addressed, so an unchanged file is `unchanged` and a changed one mints a new version
(`updated`). An agent's `identity` names a declared identity, which `up` attaches after the agent
upsert; the grant is never revoked by `up`.

An agent's `tools` passes through as the same `configs` the API takes, so a blueprint can pin which
models `generate_image` / `generate_video` may render with — the first named is the default, and any
model must be in the live catalogue `vetta media models --kind video` lists (see
[generation tools](https://vetta.sh/docs/capabilities/tools.md#generation-tools)):

```ts theme={"system"}
agents: [
  {
    name: "clipper",
    // …
    tools: {
      configs: {
        generate_video: { enabled: true, permission: "allow", config: { models: ["example/reel"] } },
      },
    },
  },
],
```

An agent's `schedules` are its cron deployments, owned as a complete set:

```ts theme={"system"}
agents: [
  {
    name: "clipper",
    // …
    schedules: [
      { cron: "0 9 * * 1", timezone: "Europe/Berlin", input: "Post the weekly digest", budget_micro_usd: 500_000, identity: "Ava" },
      { cron: "0 * * * *", input: "Check the queue", budget_micro_usd: 50_000, enabled: false },
    ],
  },
],
```

Deployments have no name, so each schedule is matched to the agent's live deployments by its exact
`cron` string (which must be unique per agent): a match with other fields drifted is patched, a
missing cron is created, and any live deployment of that agent whose cron is not declared is
deleted. The set is bounded to agents declared in this config — an agent without `schedules` keeps
whatever it has, and other agents' deployments are never touched. Each outcome is a report line
named `<agent> @ <cron>` under `schedules`; a schedule `identity` names a declared identity.

> [!WARNING]
>
> The match is the cron **string**, not the schedule it denotes. `"0 8 * * 1"` and `"0 08 * * 1"` fire
> at the same minute and are two different keys, so re-spelling a declared cron is a delete plus a
> create, not a patch: the deployment gets a new `dep_` id and a fresh `next_run_at`, and anything the
> old row was holding — an `enabled: false`, say — is gone. `up` deliberately does not parse cron; a
> client that normalised the string would have to agree with the server's parser forever to stay
> idempotent, and the day they disagreed every apply would churn every schedule. Edit a cron and
> expect a new deployment.

### Taking part of the crew

A template's crew is provisioned whole unless you say otherwise. `--only` names the agents and apps
to take; `--without` names the ones to leave out; both take declared names and either kind. The
names left out come back in the report under **`deselected`**, apart from `skipped` (rows the CLI
could not read) and from any `refused` line (things it tried and could not do): a deselection is a
choice, and the report keeps choices apart from failures.

A deselected agent is not created, its `schedules` are not armed and its `intake` does not open — a
timer or a first session for an agent you declined is money spent on nobody's instruction. A
deselected agent that already exists is left running and reported `deselected`: narrowing the
selection is not `removed`, and retiring a live agent stays an explicit act.

A template may mark an agent or app `required` — the app every agent's tools reach through, say. A
selection that leaves one out is refused before a single route is called, and the message names
every missing one, in the same words the platform's own apply refuses it with:

```text theme={"system"}
selection leaves out required agent "editor", app "site"
```

A name the config does not declare is refused the same way. Selection is per apply and is not
remembered: a later `naive up` with neither flag provisions everything. See
[Templates](https://vetta.sh/docs/concepts/templates.md) for what a crew, its first day and its selection are.

### Tasks — the board the crew starts from

A project may declare `tasks`: the work its crew starts with, as cards on the organization's
shared board rather than as a briefing each agent reads alone.

```ts theme={"system"}
tasks: [
  { key: "draft", title: "Draft the first post", body: "Three candidates.", assignee: "clipper" },
  { key: "publish", title: "Publish the first post", assignee: "clipper", blocked_by: ["draft"] },
],
```

`key` is the task's stable name inside the project; `assignee` names an agent declared in the same
config (or one listed under `kept`); `blocked_by` names other tasks by key, and a key that is not
declared, an assignee that is not, a self-blocker, a cycle or a duplicate key is refused before
anything is read. Every card lives on the board of the organization's standing orchestrator — the
`CEO` the studio's Hire page creates — and `up` ensures that owner exists before it writes a card:
finds it by its `metadata.role`, creates it (as `CEO`, on a modest monthly budget) when the
organization has none, never renames one it finds, and seats this project's agents on its roster
so they can work the board. The owner is one line under `agents` in the report like any other
agent; the board is created if missing; each task is one line under `tasks`.

Cards are written blockers first, so `blocked_by` is always the ids of cards that already exist,
and each is keyed `<project>:<task.key>`. That key is what makes re-apply honest: a second
`naive up` finds every card already on the board and reports it `unchanged`, without a duplicate
and without touching what an agent has since moved to `doing` or `done`. Nothing in the apply
starts a session. A card with an `assignee` and no open blocker is picked up by the platform on
its next tick, which starts the assignee on that card — so a crew declared through `tasks` starts
working exactly once, and its first day is on the record where the whole crew can see it.
A card whose assignee this apply did not provision — deselected, or refused — is refused rather
than written, as are the cards behind it, so a board never names an agent that is not on it.
`--dry-run` plans the owner, the board and every card and writes none of them.

### The intake — the legacy first briefing

`intake` is the path `tasks` replaces: a private first session for one agent rather than a card
on the shared board. It keeps working exactly as it did, and a project may declare both — the
cards are seeded and the intakes opened, each reported under its own key — but a crew that starts
from a board should not also be briefed in private, so new templates declare `tasks` and leave
`intake` out.

An agent may declare an `intake`: the message it is sent **once, on the apply that creates it**.

```ts theme={"system"}
agents: [
  {
    name: "clipper",
    // …
    intake: { message: "Read the brief in the app and open your first three drafts.", budget_micro_usd: 250_000 },
  },
],
```

It is what turns a provisioned crew into a crew that has started working. It is not a schedule (a
schedule fires again and again) and not `system` (that is standing instruction, re-read every turn):
it is a session that is created and started with `message` as its first turn, reported under
`intake` as one line named after the agent whose `id` is the `ses_` that was opened.

Nothing marks an agent as briefed, because nothing needs to: an agent is created exactly once, so a
second `naive up` reports it `unchanged` and starts nothing. An agent that was merely patched is not
re-briefed. Delete the agent and apply again and it intakes again, which is the correct reading of
"this agent is new". `budget_micro_usd` is optional and caps that one session; leave it out and the
agent's own `budget` governs. Intakes are opened **last**, after every other write in the apply, so
an agent that delegates or reaches for an app finds the rest of the crew already there.

### Blueprints and templates

A **blueprint** is the machine: the screens, the `/api/*` routes, the store, the build and the
approval flow — or none of them, for a data-only blueprint whose crew works on the board. It is one
repository. A **template** is data inside that repository: the crew and its prompts, the tool
allow-lists and its schedules. A config names both:

```ts theme={"system"}
export default defineProject({
  name: "my-media",
  blueprint: "media",
  template: "clipping",
  templates: ALL_TEMPLATES, // every template this repo carries; the chosen one supplies `agents`
  apps: [/* … */],
});
```

| Blueprint | Templates | Default |
| - | - | - |
| `agency` | `blank`, `seo-geo` | `blank` |
| `media` | `faceless`, `longform`, `clipping` | none — you must choose |

`media` has no default on purpose: its three crews spend money in different ways — a long-form
piece renders a minute or more of video where a clip only cuts one — so defaulting any of them
would be a bill you did not choose. An unknown blueprint, an unknown template, or `media` with no
template at all is refused when the config is loaded — before anything is read or written.

The three `media` crews, by the id you pass to `--template` and the name the catalogue shows:

| `--template` | Name | What it makes |
| - | - | - |
| `faceless` | Faceless channel | Vertical shorts of 15–30 seconds, one render per piece. |
| `longform` | Long-form channel | Pieces of 60–180 seconds. Rendered as `ceil(seconds / 30)` segments of at most 30 seconds each — the measured cap of the model behind the render — then joined into one file by the producer before it publishes. |
| `clipping` | Clipping channel | Cuts 15–60 second vertical clips out of long videos you point it at. |

The ids are the wire: they are what a config, an install and `naive catalog --template` carry, and
they do not change when a name does. Each asks three setup questions. `faceless` and `longform` ask
the niche, a reference (optional) and how often to post; `clipping` asks the channels to cut from,
YouTube visibility (optional) and how often to post. None asks where to post: the crew posts to the
accounts you connect.

The chosen template's agents become the project's crew, and the config may declare agents of its
own alongside them. Because one repository carries every template its blueprint has, **switching
is an edit plus `naive up`** — never a re-clone, and never a new app: edit `template`, re-run, and
the machine, the app, its URL and its data stay exactly where they are.

Switching **widens and never narrows**. The new template's agents are created; an agent only the
previous template declared is reported and left alone — `defineProject` lists those names under
`kept`, and `up` reports each one that is live as `unchanged` with `not declared by template "…";
kept, never deleted`. Your own rows — clients, posts, connections, the app, its URL, its token —
are never touched by a switch. To retire an agent the old crew left behind, name it under
`removed` yourself, which is the only thing that ever deletes.

### App ownership

Every app records the project that created it, and `up` compares that stamp before it writes
anything. An app whose row names a different project — or no project, because it was made by hand
through the dashboard or the API — is **refused**, with the exit code non-zero and nothing written:

```
app "dashboard" is owned by project "seo-geo-agency"; this project is "faceless-media".
Rename the app in your config, or re-run `naive up --adopt` to transfer it.
```

App names are unique per organization, and an app is the one resource where taking a name over is
not recoverable: the deploy replaces a live site, an `env` write overwrites a secret value nothing
can read back, and a `removed` name tears down its hosting and its database. So a name collision
stops rather than converges. Fix it by renaming the app in your config, or, if the app really is
this project's, run `naive up --adopt` once: it takes every app the run touches, stamps each with
this project's `name`, and reports them `updated` with `adopted from project "…"` as the reason.
From then on ordinary `naive up` converges them and the flag is not needed again. `--adopt` covers
`removed` names too, since deleting another project's app is worse than overwriting it, and under
`--dry-run` it only says `will adopt …`.

An app's `env` is written as its secrets — a value is a literal or `{ from_env: "LOCAL_VAR" }`,
read from your shell at apply time so a secret never sits in the config file:

```ts theme={"system"}
apps: [
  {
    name: "dashboard",
    type: "fullstack",
    deploy_dir: "dist",
    env: { PUBLIC_URL: "https://dashboard.example", API_KEY: { from_env: "NAIVE_API_KEY" } },
  },
],
```

`{ from_env: "NAIVE_API_KEY" }` is the one exception: your own key never reaches the app. On the
apply that creates the app, `up` mints the app its own read-only key (listed among your API keys as
`app <name>`) and writes that instead. Minting needs an `admin` key in your shell; without one
the app is refused, naming `NAIVE_API_KEY`. A later apply leaves the minted key alone.

Secret values are write-only on the platform and never read back. Each write carries a hash of
the value, which the platform echoes on reads, so `up` can tell an unchanged secret from a changed
one without ever seeing a value: a secret whose live hash matches is skipped, and an app whose
secrets all match reports `unchanged`. Changing a `from_env` variable's value and re-running `up`
rewrites just that secret — **that is how rotation works for app secrets**. A secret set before
hashes existed (or by hand, through the dashboard or SDK) has no hash and is rewritten once. An
unset `from_env` variable refuses the app before any write, naming the variable only. Secrets that
exist on the app but are absent from `env` are listed in the report as `unmanaged secrets: …` and
left alone — nothing is deleted by omission. With `--dry-run` the report says `will set …` for
exactly the secrets an apply would write.

An app's `deploy_dir` ships as one file-map deployment carrying a hash of its contents. On the
next apply, `up` hashes the directory again and compares it with the app's newest deployment: equal
means nothing is uploaded and the app reports `unchanged` (unless `env` or `description` made it
`updated`); different, or a deployment made before hashes existed, deploys again. With `--dry-run`
the report says `will deploy <dir>` or `<dir> unchanged`.

A `fullstack` app's `mcp` is the path it serves an MCP endpoint on (`"/mcp"`): agents connected to
the app get its MCP tools automatically, and the platform mints a bearer token for that endpoint into
the app's `VETTA_MCP_TOKEN` secret, which the app should require. Declaring `mcp` on a
`frontend_only` app is refused at parse time. An unchanged path is `unchanged` (the token is never
rotated by a no-op apply); dropping `mcp` from the config clears the endpoint and forgets the token.
With `--dry-run` the report says `will set mcp <path>` or `will clear mcp`.

A `fullstack` app's `migrations` are SQL files applied to its [database](https://vetta.sh/docs/cli/database.md) after the
app is provisioned and deployed — paths under the config's directory, `*` allowed in the file name:
`["sql/*.sql"]`. Files match in name order within one entry and entries in declaration order, so
`0001_todos.sql`, `0002_index.sql` is the whole ordering story. Each file is a migration named by its
base name, applied **once**: a later apply re-sends the list and every already-applied one is a
no-op, so the report says `2 migration(s), 0 new` and the app is `unchanged` unless something was
new. Changing an applied file's contents under the same name is refused with `version_conflict` —
add a new file. `migrations` on a `frontend_only` app is refused at parse time. A hosted apply (one
the platform runs from a published blueprint) has no checkout, so the publisher ships the same files
on the app's built tree and the apply reads them from there; an artifact published before that
carries none and refuses the app by name — republish it, or run `naive up` from a clone.

A `fullstack` app's `buckets` are the buckets its [object store](https://vetta.sh/docs/cli/storage.md) must hold:
`[{ name: "avatars", public: true }, { name: "exports" }]`, names in the platform's bucket grammar
(3–63 lowercase letters, digits and hyphens), `public` defaulting to `false`. Each apply lists the
live buckets and creates the declared ones that are missing; one the app already holds is left
exactly as it is — `public` included, since flipping visibility on a bucket that is serving URLs is
a decision for a person — so the report says `2 bucket(s), 0 new` and the app is `unchanged` unless
one was made. Nothing is ever deleted here: a bucket dropped from the config, or made by the app's
own code, stays until `vetta storage rb` removes it. `buckets` on a `frontend_only` app is refused
at parse time. With `--dry-run` the report says `will ensure buckets <names>`. Migrations run
before buckets, and both run after the app's secrets and deployment are written, whether `naive up`
applies from a clone or the platform applies a published blueprint in the background.

An agent's browser is declared where every tool is — `tools.configs.browser.config` — with the
[browser options](https://vetta.sh/docs/api/browser.md) the platform accepts (`allowed_domains`, `timeout_minutes`,
`context_name`, …), checked field by field at define time (above) and otherwise passed through
unchanged. **Computers, saved-login contexts and grants are not blueprint resources** and cannot be
declared: an agent opens its own computer, a saved-login context is what a live browser session
saves, and a grant names that context's id, so all three are made by running the agent (or with
`vetta browser`), never by `up`.

A `vaults` entry provisions a credential vault by name, optionally held by a declared identity.
Credential values are real secrets, so they are `{ from_env }` **only** — never a literal — and are
read from your shell at apply time:

```ts theme={"system"}
vaults: [
  {
    name: "Ava's keys",
    identity: "Ava",
    credentials: [
      { kind: "static_bearer", key: "GitHub", mcp_server_url: "https://mcp.github.example/sse", value: { from_env: "GITHUB_TOKEN" } },
    ],
  },
],
```

Vault credentials are write-only and immutable on the platform, so `up` reconciles their
**presence**, not their value: a credential is identified by `(kind, key, host / mcp_server_url)`.
One that is live is `unchanged`, one that is missing is created (an unset `from_env` variable refuses
that one credential, naming the variable only, and never echoes a value), and credentials on the
vault that are not declared are never deleted or overwritten. Because a live value cannot be read or
replaced through `up`, **rotation is a manual operation**: create the new credential and delete the
old one on the platform (or with the SDK) — changing a `from_env` variable's value and re-running
`up` changes nothing. Each credential is a report line named `<vault> / <key>` under `vaults`; a
vault's `identity` cannot change after creation, so a live vault held by another identity is refused.

`env_var` credentials are refused at parse time: they need substitution at the sandbox egress
boundary, which the platform does not have, so the API would refuse them anyway — declare a
`static_bearer` or `mcp_oauth` credential with an `mcp_server_url` instead, which is injected
server-side before the sandbox sees it.

To delete a resource, move its name from the declaration into the config's `removed` block:

```ts theme={"system"}
export default defineProject({
  name: "my-project",
  agents: [/* … */],
  removed: {
    apps: ["old-dashboard"],
    agents: ["old-helper"],
    skills: ["old-skill"],
    identities: ["Old Persona"],
    vaults: ["Old Persona's keys"],
  },
});
```

`up` deletes each removed name it finds live and reports it as `deleted`; a name that is already
absent is `unchanged (already absent)`, so `removed` entries can stay in the config and re-running
stays safe. A name cannot be both declared and removed. Removing a vault deletes its credentials
with it.

**Renaming is a delete plus a create**: resources are keyed by name, so changing a declared name
mints a new resource and leaves the old one live — put the old name under `removed` in the same
apply, or it stays behind. **The config is the source of truth**: a field edited by hand in the
dashboard is drift, and the next `naive up` converges it back to what the config declares (a field
the config leaves out is never touched).

Two load failures stay distinct: a config that loaded but is wrong is reported as `config invalid`
(exit 2 — fix the file), while a config the toolchain could not evaluate (a missing install) is a
`config toolchain failure` (exit 1). A `.ts` config is evaluated with the CLI's own loader and
needs only the blueprint repo's install (`defineProject` comes from `@usenaive-sdk/blueprints`); a
`.json` config needs nothing installed at all.

## init

```bash theme={"system"}
naive init
naive init --force
```

| Flag | Description |
| - | - |
| `--force` | Overwrite an existing `naive.config.*`. Without it, an existing config is refused. |
| `--dir` | Where to write the scaffold (default: the current directory). |

## template

Clones a blueprint into a directory of its own. A blueprint is a public repository with a
`naive.config.ts` in it, so what you get is exactly what is on the branch — its screens, its server
and its tests — not a scaffold generated by string substitution. The git history is dropped: the
first commit of your company should be yours.

```sh theme={"system"}
naive template marketing-agency
cd marketing-agency
naive claim --key sk_...
naive up

# choose the crew as you clone
naive template media --template clipping
```

`--template` writes your choice into the clone's own `naive.config.ts`, so the next `naive up`
provisions that crew. Nothing else is fetched — the repository already carries every template its
blueprint has — and a template the blueprint does not have is refused before the clone.

| Flag | Default | |
| - | - | - |
| `--dir` | the blueprint's name | where to clone it |
| `--org` | `usenaive` | the GitHub organization to clone from, for a fork or a private mirror |
| `--template` | the blueprint's own default | which crew to provision (see [Blueprints and templates](#blueprints-and-templates)) |

## catalog

The published catalogue: one row per blueprint × template × version, carrying the setup questions
to ask first, the crew and apps that pair provisions, and the timers it runs.

```bash theme={"system"}
naive catalog
naive catalog --blueprint media --template clipping
```

| Flag | Description |
| - | - |
| `--blueprint` | Only this blueprint's rows. |
| `--template` | Only this template's rows. |
| `--limit` | Page size. |
| `--after` | Cursor from a previous page's `next_cursor`. |

Requires the `agents:read` scope. It reads and never applies — a published row becomes an install
when you clone the blueprint (`naive template`) and run `naive up`, which puts the tree in front of
you before it provisions a crew that costs money.

It answers the question `naive template` cannot: **which templates exist at which versions, right
now**, rather than which ones this binary was released knowing about. There is no catalogue inside
the CLI, deliberately — publishing a blueprint is publishing a repository and a row, not shipping a
new binary — so this is where the two meet.

## installs

One row per project this organization has applied: which blueprint and template, at which artifact
version, at which revision, and by which writer.

```bash theme={"system"}
naive installs --limit 20
naive installs --project faceless-media
```

| Flag | Description |
| - | - |
| `--project` | Only this project's row. |
| `--limit` | Page size. |
| `--after` | Cursor from a previous page's `next_cursor`. |

`naive up` reports the apply it just ran and nothing else. This reports what **anyone** applied,
which is the thing to read before an apply that expects a particular revision: an install the
dashboard advanced since you last ran `up` shows a higher `revision` here, and the row's `report`
carries what that apply did. A hosted apply the dashboard queued reads `status: "pending"` with
`report: null` until it has run; `--project` is how to watch one row until it says `applied` or
`failed`.

### installs remove

Take a template out of the company. Name the install by the id `naive installs` lists.

```bash theme={"system"}
naive installs remove bpi_0123456789abcdefghjkmnpqrs
```

Its agents are fired: their timers stop, and their open cards stay on the board with no one on
them. Its board tasks that are not done go, unless someone commented on them, and so do its mini
apps. A chat that is already running finishes first. Its apps and their data, its
personas and connected accounts, its skills, files and chats stay, and so does anything you or
another template made. Nothing is refunded or charged. Only the company's owner can do this, signed
in with `vetta login --email`; an API key is refused. A second call answers `not_found`.

### installs context

What the crew of the project in this directory knows about the business it runs: the setup answers
with the questions they answer, the apps and their URLs, and the agents and their roles — the
**project context**, the same object a template agent reads through its `project_context` tool.

```bash theme={"system"}
naive installs context
naive installs context --set offer="Landing pages for B2B SaaS"
naive installs context --set channels=seo --set channels=email
naive installs context --clear reference
```

| Flag | Description |
| - | - |
| `--dir` | Directory holding the `naive.config.*` whose `name` is the project (default: the current directory). |
| `--set` | `key=value`: rewrite one setup answer. Repeat it for several answers, or for several values of a multiple choice. |
| `--clear` | `key`: put a setup answer back to unanswered. Only a question the template marked optional may be cleared. |

The project is the config's own `name`; its latest `applied` install is the one read, so a hosted
apply still `pending` is not it. Without `--set`, the context is printed. With `--set`, the edits
are laid over the install's stored answers and the whole set is written back — the platform
validates it against the template's questions, so an undeclared key is refused — and the context is
printed as the crew now reads it. A key set more than once, or a `multiple` choice set once, is a
list.

`--clear` is how an answer goes back to unanswered, and it is not the same as setting it to nothing:
an empty value is an answer, and the platform refuses one. A question the template marked optional is
answered by the key being absent, so `--clear` removes it rather than emptying it. Clearing a question
that is not optional is refused, because the crew would then be working to a company setting nobody
gave it.

An edit is not an apply: the install's `revision` does not move, nothing is re-provisioned and no
agent is briefed again. The crew simply reads the new answer the next time it looks. A project
with no applied install has no context yet: run `naive up` first. An install applied from a checkout
rather than from the catalogue is validated against the questions of the config it applied, and
`naive up` keeps its answers when it records the next apply. The record keeps only the config's
questions and its agents' names and roles — never its `env` values.

The repository is `<org>/<blueprint>-blueprint` — `naive template agency` clones
`usenaive/agency-blueprint` — so publishing a new blueprint is publishing a repository: there is no
catalogue inside this binary to drift from the blueprints that actually exist, and no release of the
CLI is needed to add one. The suffix is the convention a fork follows too, so `--org acme` looks for
`acme/agency-blueprint`. No API key is required: a blueprint is public, and claiming it comes after.

One blueprint isn't a repository: `support` ([Support Inbox](https://vetta.sh/docs/concepts/templates.md#support-inbox)) ships inside the
CLI from 0.10.0. `naive template support` writes its `naive.config.json` instead of cloning, and `--org` is
refused for it. An older CLI tries to clone it and fails.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.