The veni CLI
veni is the platform's command-line tool — it scaffolds a new app, grows it one capability at a time (veni enable), runs it, and registers it with a Shell. It's served by Veni builder and talks to it: the Builder URL, the Shell, and your token are saved at install time.
Run any command from anywhere inside the app — the app root is found by walking up to veni.json, so veni add from ui/ behaves exactly like veni add from the root.
Install
One line, no token required — the installer signs you in through the browser (the same device flow as veni login):
curl -fsSL https://veni-builder-api.dev.venizia.ai/api/cli/install.sh | bash
# Open this URL to approve: https://veni.dev.venizia.ai/cli?code=WDJB-MJHT
# Signed in.
# Installed veni to /home/you/.local/bin/veniAlready hold a veni_ PAT? Bring it and skip the browser: -H "X-Veni-Token: <token>" on the curl, or VENI_TOKEN=veni_… in front of it. Full install detail — local Builder, VENI_BIN_DIR, what lands where — is in Get the veni CLI.
Put ~/.local/bin on your PATH, then check the install:
veni # prints the full command list
veni doctor # bun/curl/tar, config, builder, shell, token, PATH — each ✓ or ✗ with a fixInstalling it to start a new app? Do it as part of Build a satellite → Set up, which lists the other prerequisites and hands off to the walkthrough that runs veni create.
Signing in
Every command that acts on an app needs a saved token. veni login gets one:
veni login # signs in to the saved Shell
veni login --shell http://localhost:3000 # or a specific oneIt shows a URL and a code, opens your browser, and saves the minted veni_ token. veni whoami says who you are and where; veni doctor says whether the token still works.
These commands run signed out, because they're what you need when you are: login, whoami, doctor, status, version, update, uninstall. Everything else stops with "Not signed in — run veni login first."
Commands
App state — theme, enabled capabilities, ports, the registered Shell — lives in veni.json at the app root; every command reads and writes it.
Setup
| Command | What it does |
|---|---|
veni login | Sign in via the browser and save the token. --shell <url> picks the Shell (default: saved, else http://localhost:3000). |
veni create <name> | Scaffold a new app locally in ./<name> — UI-only by default. Flags below. |
veni install | Install the app's API + UI dependencies (alias: i). |
veni dev | Run every service the app has — API + UI — and forward their output; Ctrl-C stops all. |
veni create flags — with none of them given, it prints the capability tour and asks for a theme:
| Flag | Effect |
|---|---|
--api / --no-api | Include the API service / skip the question |
--db / --no-db | Configure a database (pulls api automatically) / skip the question |
--no-ui | Leave out the UI (UI is included by default) |
--theme <brand> | Brand to apply, skipping the theme prompt |
-y, --yes | Accept the defaults, ask nothing |
veni create billing --api --db --theme veni
# Creating app "billing" · capabilities: ui, api, database · veni theme ...
# Created app "billing" (veni theme). API on port 3012, UI on port 3013.
# Next: cd billing && veni install && veni devPorts are picked for you: a free API/UI pair (UI = API + 1) plus a Postgres host port, avoiding both live listeners and ports already claimed by sibling apps.
Build
| Command | What it does |
|---|---|
veni enable <capability> | Grow the app: api, database, storage, email, authz, integration, tenancy. Prerequisites are auto-pulled in order. --dry-run prints what it would write. |
veni enable | List every capability and mark what's on (✓ on · + available · — needs a prerequisite). |
veni add <component…> | Add shared-UI components from the app's theme. --overwrite replaces changed files without asking. |
veni theme <brand> | Switch the brand theme — overwrites ui/src/themes/tokens.css and records the choice. |
veni theme | Show the current theme and the available brands. |
The theme comes from veni.json and nowhere else — veni add --theme no longer exists, because an app that mixes themes shows up as a styling bug rather than an error. Switch the whole app with veni theme <brand>, then add.
veni add dialog
# theme: veni
# veni add dialog -> 2 component(s)
# + components/ui/button.tsx # registryDependencies are pulled in
# + components/ui/dialog.tsx
# + dep radix-ui@^1.4.3
# 2 added.If a file already exists, add compares content: identical is a silent no-op, different asks you — [o]verwrite [s]kip [d]iff [a]ll [n]one, defaulting to keeping your file. With no terminal to ask in (CI, an agent), differing files are skipped and reported; --overwrite answers "all" up front. New npm deps are installed for you.
Ship
| Command | What it does |
|---|---|
veni register | Register the app with a Shell, rewrite api/.env (JWKS/gRPC/CORS), and record the Shell in veni.json. |
veni status | Show app state: capabilities, theme, ports, Shell. |
veni register flags:
| Flag | Default |
|---|---|
--shell <url> | The saved Shell, else http://localhost:3000 |
--shell-ui <url> | Derived from --shell (needed when your Shell UI isn't at the conventional host) |
--user <name> / --password <pass> | Log in as this user instead of using the saved token |
--token <jwt|veni_…> | Use this token instead of the saved one |
--slug <name> | VITE_SERVICE_ID from ui/.env |
--api-url <url> / --ui-url <url> | Derived from the app's ports |
A personal token is opaque, so the CLI can't see your roles: it registers through the developer self-service route and the app lands as a private draft until an admin publishes it. Pass --user or a session --token to take the admin route.
veni status
# veni app: billing
#
# theme veni
# ports api 3012 · ui 3013
# shell http://localhost:3000
# capabilities ✓ui ✓api ✓database ·authz ·integration ·tenancyToolchain
| Command | What it does |
|---|---|
veni whoami | Who you're signed in as, and against which Shell and Builder |
veni doctor | Check this install and everything it talks to; exits non-zero when something is wrong |
veni --version | The installed toolchain version, compared against what the Builder serves (alias: version) |
veni update | Re-pull the latest toolchain in place, keeping your saved config |
veni uninstall | Remove the global install and its launcher |
veni --version
# veni 9f2c1ab40e77 (server: 4d81e0c2aa19 — run `veni update`)Environment
| Variable | Effect |
|---|---|
VENI_SHELL_URL | Default Shell for login / register |
VENI_BASE | Builder API to pull the toolchain and catalog links from |
VENI_TOKEN | Token for one run, overriding the saved one |
VENI_BIN_DIR | Where the installer drops the veni launcher (default ~/.local/bin) |
VENI_HOME | Where the toolchain is installed (default ~/.local/share/veni) |
What the scaffold gives you
veni create gives a UI baseline — it federates into the Shell and is signed in for free, no login code. You add the rest with veni enable, and each layer ships the platform standard already wired, to use as generated rather than hand-rolled:
veni enable api— the token handshake (Shell RS256 → service HS256 exchange, JWKS verify, thejwtstrategy, the UI's exchange +401retry) and the guards inutils/auth-guards(requireAuth,requirePermission,requireAdmin,requireOwnerOrAdmin). Identity works without a database (SSO-only). Never hand-roll a login, session, or JWT verify.veni enable database— local-user provisioning, the roles + permissions tables, a working seed, and a test that covers the contract. You call the guards and seed your own codes.veni enable storage:StorageClientService, which stores files in the platform storage app for each organization. It uses Shell installation tokens and the pod's own ServiceAccount token, so there is no secret to create. See Backend · File storage.veni enable email:EmailClientService, which sends transactional email through the Shell and the organization's own SMTP (the platform's when it has none), with the same pod token as storage. See Backend · Email.authz·integration·tenancy— developer-authored: enabling records the intent inveni.jsonand prints the next steps and the doc link.
Common snags
| Symptom | Fix |
|---|---|
"Not signed in — run veni login first." | Run veni login (or set VENI_TOKEN for one run). |
veni: command not found after install | ~/.local/bin isn't on your PATH — export PATH="$HOME/.local/bin:$PATH", or re-install with VENI_BIN_DIR set to a directory that is. |
Not inside a veni app — cannot … | cd into the app folder (the one holding ui/ and veni.json), or create one with veni create <name>. |
veni add says the theme doesn't ship a component | Components resolve from the app's theme only, with no fallback. Check veni theme, and refresh a stale CLI with veni update. |
--theme is no longer supported from veni add | Switch the whole app instead: veni theme <brand>. |
No theme recorded for this app | veni theme <brand> — components are themed, so add has nothing to install from until one is set. |
veni update says No Builder URL for this install | veni login saves the Shell but never the Builder. Re-run the installer, or VENI_BASE=<base>/api veni update. |
The Builder refused the download during veni update | The token expired — veni login, then veni update again. |
Where it's used
- Build a satellite — the full flow:
veni create→veni enable …→veni install→veni dev→veni register. - Register an app — the
veni registerpath (vs. the no-CLI Shell UI).
See also
- Veni builder — the service that serves this CLI.
- Serving the veni CLI — how the toolchain is packaged, served, and extended with a new command.