CLI
The vaif command-line tool signs you in to VAIF Studio, scaffolds an app from a template, changes your schema, generates TypeScript types, deploys functions and manages keys and secrets. Run vaif --version to print the installed version and vaif <command> --help for every option.
Install
Section titled “Install”npm install -g @vaifstudio/cliEvery command takes --api-url <url> for a local or self-hosted API. It defaults to VAIF_API_URL, then https://api.vaif.studio.
Sign in
Section titled “Sign in”vaif login # opens the browservaif login --email # email and password in the terminalvaif login --project-id <id> # also set the default projectvaif whoamivaif logoutvaif login --email asks for an authenticator or backup code when the account has two-factor sign-in on. The session is saved in ~/.vaif/auth.json, which only your user can read.
Agents and CI
Section titled “Agents and CI”A CLI token is a console access token with the cli scope, valid for 30 days. Billing and admin routes refuse it. Get one without a browser with POST /auth/cli/login; see Console API for agents and CI. Then either:
- Set
VAIF_AUTH_TOKENin the job’s environment, from your CI’s secret store. Every command uses it instead of~/.vaif/auth.json. It is never written to disk, printed or refreshed, and the CLI ignores aVAIF_AUTH_TOKENin a.envfile, so a cloned repository cannot sign you in as another account. - Or save it with
vaif login --token, which reads the token from standard input or asks for it without echoing it. The CLI refuses a token written on the command line, where other processes and your shell history could read it. It checks the token with the API before saving it.
vaif login --token < token.txtStart a project
Section titled “Start a project”vaif init # write vaif.config.json in this foldervaif init --template react-spa # scaffold an app from a templatevaif init --template react-spa --features auth,database,realtime,storage,functionsvaif init --template react-spa --add-features storagevaif init --force # overwrite an existing configvaif templates # list the templates (alias: vaif tpl)Each template writes a README with setup steps, an environment file example, and starter files for the features you pick: auth, database, realtime, storage and functions. See Templates for what each one includes.
Project information
Section titled “Project information”vaif info # name, region and URLsvaif status # tables, functions, storage and connectionsGenerate types
Section titled “Generate types”vaif generate # alias: vaif genvaif generate --project-id <id> # one project, through the APIvaif generate --output ./src/types/database.tsvaif generate --dry-runWhere the schema comes from (the first match wins):
--connection <url>: that database, directly.--project-id <id>: that project, through the API.database.urlinvaif.config.json, thenDATABASE_URL: that database, directly. A value that still holds an unexpanded${...}is skipped.- Otherwise the API, for the project in
vaif.config.json, thenVAIF_PROJECT_ID, then thevaif login --project-iddefault.
Through the API, vaif generate also reads the project’s row rules: a column that an insert rule fills from the signed-in user (for example author_id) is optional in the <Table>Insert type, because the server sets it.
Change the schema
Section titled “Change the schema”Each of these works on one environment of the project: the project’s default environment unless you pass --env <name>.
Pull and push a schema file
Section titled “Pull and push a schema file”vaif pull # write the environment's tables to vaif.schema.jsonvaif pull --env staging --output ./staging.schema.jsonvaif push # show the plan and the SQL, then askvaif push --dry-run # only show the planvaif push --yes --name add_todos # apply without asking (CI)vaif push sends the file to the schema engine, prints the plan and the SQL, and asks before applying it as one migration. By default nothing is dropped: tables, columns and indexes that exist only in the database are kept, and a column type change stops the push with a list of what it would change. --allow-destructive applies those changes and drops everything in the environment that the file does not list, tables included.
Pushing to a production environment asks you to type its name; with --yes it also needs --allow-production, and only an owner or admin can do it. If the environment changed after the preview, nothing is applied and you are asked to run the push again.
Migration files
Section titled “Migration files”vaif db push # apply the .sql files in the migrations foldervaif db push --dir ./migrationsvaif db push --dry-run # what each file would changevaif db push --env prod --allow-productionvaif db pull --output ./schema.json # the project's current schemavaif db push sends each file, in order, to the schema engine, the same path the console uses. The engine applies table changes (CREATE TABLE, ADD COLUMN, CREATE [UNIQUE] INDEX, and primary, unique and foreign keys), then runs the file’s INSERT, UPDATE and DELETE statements as your project’s database login. Each file is one transaction: if anything fails, none of it is applied and the push stops. A push never drops or rewrites data: DROP, ALTER COLUMN, views, functions, triggers, sequences, enum types and SQL access policies are refused with the statement number and the reason. A file already applied unchanged is skipped; --force applies it again.
Access to rows is set with row rules, which are closed by default. Put -- vaif:public-read on the line above a CREATE TABLE that signed-out visitors may read, or -- vaif:owner <column> above a table each signed-in user owns, and the table gets that rule.
Seed data
Section titled “Seed data”vaif db seed # every .json or .js file in ./seeds, ./prisma/seed or ./db/seedsvaif db seed --file ./seeds/users.json --table usersvaif db seed --truncate --yes # empty the tables firstvaif db seed --dry-runA seed file is an array of rows (the file name is the table), { "table": "users", "data": [...] }, or an object whose keys are table names. Values are sent as query parameters, never pasted into SQL. Tables load in file order and are not rolled back as a whole: if one fails, the CLI stops and lists what was already inserted. Seeding a production environment needs --allow-production.
Reset an environment
Section titled “Reset an environment”vaif db reset --dry-run # list what would be droppedvaif db reset --confirm dev # drop every table, view, sequence, function and typevaif db reset --env prod --allow-production --confirm <project-slug>vaif db reset deletes all data in the environment and cannot be undone: only the structure is saved first, not the rows. It needs --confirm with the environment’s name, or asks you to type it. For a production environment it also needs --allow-production and an owner or admin, and the confirmation is the project slug. Every reset, and every refused production attempt, is written to the audit log.
Functions
Section titled “Functions”vaif functions deploy # alias: vaif fn deployvaif functions deploy --name send-emailvaif functions deploy --runtime typescriptvaif functions deploy --dry-runvaif functions list # alias: vaif fn lsSee Creating functions.
API keys
Section titled “API keys”vaif keys generate --name "Server"vaif keys list # alias: vaif keys lsSecrets
Section titled “Secrets”Secrets are encrypted at rest and reach your functions as environment variables. Their values are write-only: they can be replaced, never read back.
vaif secrets set THIRD_PARTY_KEY # asks for the value, hiddenprintf '%s' "$THIRD_PARTY_KEY" | vaif secrets set THIRD_PARTY_KEY --from-stdin # CIvaif secrets set SERVICE_ACCOUNT --from-file ./service-account.jsonvaif secrets list # names only (alias: vaif sec ls)vaif secrets delete THIRD_PARTY_KEYA value is never taken from the command line, where other programs and your shell history could read it. --from-stdin drops one trailing newline. vaif secrets get explains that values are write-only and exits with status 1. Every secrets command takes --project-id and --env-id. A secret for a production environment, or one for every environment (--all-environments), needs --allow-production and an owner or admin.
Inside a function, a secret is an environment variable:
export default async function handler(req: Request): Promise<Response> { const key = process.env.THIRD_PARTY_KEY; return Response.json({ configured: Boolean(key) });}Move from another backend
Section titled “Move from another backend”vaif migrate --from supabase --dry-run # writes vaif-migration-plan.md and creates nothingvaif migrate --from supabase --project-id <id>vaif migrate --from firebase --dry-runA migration creates the tables, buckets and function stubs in your project. Data, users and function code are not copied. --output-dir <dir> points the CLI at another project folder. See Migrate from Supabase and Migrate from Firebase.
AI coding tools and Verament
Section titled “AI coding tools and Verament”vaif workspace pull --session <id> --output <dir>downloads an accepted Verament session as a local project.--open codeopens it in your editor.- To connect an AI coding tool to your project, see MCP server, which links the setup for each tool.
Configuration
Section titled “Configuration”vaif init writes vaif.config.json:
{ "projectId": "your-project-id", "database": { "url": "${DATABASE_URL}", "schema": "public" }, "types": { "output": "./src/types/database.ts" }, "api": { "baseUrl": "https://api.vaif.studio" }}| Variable | What it does |
|---|---|
VAIF_AUTH_TOKEN |
A CLI token used instead of ~/.vaif/auth.json. Read from the environment only, never from .env |
VAIF_PROJECT_ID |
The default project |
VAIF_API_URL |
The API’s address (default https://api.vaif.studio) |
DATABASE_URL |
A direct database connection for vaif generate |
The project is chosen in this order: --project-id, then vaif.config.json, then VAIF_PROJECT_ID, then the vaif login --project-id default. A .env file in the current folder is loaded first; a variable already set in your shell wins.