CLI Reference
wisp help <command> or wisp <command> --help shows one command's rows. App commands run from the app's folder.
Commands
| Command | What it does |
|---|---|
wisp new [name] | create an app (options below) |
wisp dev [--port|-p <n>] | run the app, rebuild and reload on save (port 3000). With --port 0, the system picks a free port |
wisp dev --https | serve over TLS with a self-signed certificate made once in .wisp/dev-cert.pem and .wisp/dev-key.pem (renewed when you delete them, when the address in HOST is new, or when it expires). Browsers ask once to trust it. For secure-context APIs (camera, clipboard, service workers) on a LAN address with HOST=0.0.0.0. Not with --target |
wisp dev with [package.metadata.wisp.dev.proxy] | forward paths to other http:// or https:// servers, and with [package.metadata.wisp.dev] run start companion commands (Config) |
wisp dev --target node|bun|deno | the dev loop through that host's glue, built into .wisp/emulate/<host> (refuses vercel, netlify, lambda, native, cloudflare, pages) |
wisp preview [--port|-p <n>] [--fast] | build the release binary and run it as deployed (port 4173). It checks the port and waits for the app on HOST. --port 0 is an error |
wisp preview --static|--spa [--out dist] [-p <n>] | export the site and serve it like a static host (no server code, POST is 405). --spa falls back to index.html |
wisp build | one release binary with CSS and static files inside |
wisp build --sourcemap | the same, with source maps for browser code |
wisp build --fast | quicker ThinLTO build for CI and iteration. |
wisp clean [--cache] | cargo clean of target/. --cache also deletes the shared build cache (WISP_BUILD_CACHE), only a folder a build marked |
wisp build --analyze | each route's JS, CSS and wasm bytes, raw and gzipped. Builds nothing |
wisp build --static [--out dist] | the pages as plain files for a static host. Removes files of an earlier export it no longer writes. Warns of dead links and #ids (seo-link, seo-anchor) |
wisp build --static --allow-dead-links | a link to a missing page or to an #id that the linked page lacks only warns (also without --static, for the PRERENDER crawl) |
wisp build --no-crawl | the prerender and --static follow no link: only the routes and entries() say which pages are rendered, and no link is checked |
wisp build --spa [--out dist] | the same, and index.html draws the SSR = false pages |
wisp build --target|-t <host> [--edge] [--out|-o <dir>] | a folder for cloudflare, pages, deno, vercel, netlify, node, bun or lambda. --edge uses edge functions on vercel or netlify |
wisp client [-o|--out client.ts] | a typed TypeScript client of the +server.rs endpoints |
wisp openapi [-o|--out openapi.json] | print or write the OpenAPI 3.1 document |
wisp openapi --check | fail if the committed file is stale (CI) |
wisp check [--types] [--rust] [--target <host> [--edge]] [--explain-imports] | check routes and templates without compiling, and warn of SEO gaps (seo-title, seo-description, seo-canonical, seo-h1, seo-link: see /docs/design-pages). --types runs tsc (without TypeScript it prints Type checking was SKIPPED, not passed; set WISP_TSC). --rust runs cargo check. --target <host> warns of what that host cannot run. --explain-imports lists names used with no use line. Syntax errors in scripts show file:line:col |
wisp test [--browser] [args] | cargo test with its args. --browser adds browser tests |
wisp fmt [paths] | format .wisp files |
wisp fmt --check | name unformatted files and fail |
wisp fmt --stdin [path] | format stdin to stdout, for editors |
wisp package [args] | in a component library: check it, then cargo package |
wisp route add <path> [kind] | write a route: page (default), form, layout, server, rest (or api) or crud. The file is written as wisp fmt leaves it. A folder that the route scanner skips is refused with its reason: a name that ends in .bak, .tmp or .orig, a name that starts with a dot (.well-known is a route and is written), and a Windows device name such as con. A path that matches the same URLs as an existing route (/[[opt]] beside /) is refused and nothing is written. So is a rest route whose path has an id parameter (/x/[id]): a rest route serves its own /[id] |
wisp route add <path> crud | list, new and edit forms for a table, with its #[model] and Table::saved() in src/db.rs (added unless the file declares a type of that name: a comment or string with the name does not count). If its routes match the URLs of another route, or a file write fails, it writes nothing and deletes what it made. A model that would take the name of a type the file uses (/tables is Table, /strings is String) gets Row added: TableRow |
wisp route list | each route's methods, URL and file |
wisp component add <Name> | a blank src/components/Name.wisp |
wisp component list | the components with file and props |
wisp deploy init <host> [--force] | a GitHub Actions workflow that deploys on push. It runs wrangler, vercel and netlify-cli at exact versions. Each wisp release sets them. Actions use tags: pin each uses: to a commit SHA for a stricter build |
wisp deploy init fly|render|railway|cloud-run [--force] | that host's config and a Dockerfile, with a health check on /_wisp/health, a kill timeout past the drain, the host's client IP header where it is safe, and saved tables on one instance |
wisp deploy init docker [--onion] [--force] | a Dockerfile and .dockerignore. --onion adds Tor files for a Tor onion service |
wisp deploy init tor [--force] | write deploy/tor/torrc (lines for /etc/tor/torrc: HiddenServiceDir /var/lib/tor/<app>/, HiddenServicePort 80 127.0.0.1:3000) and deploy/tor/<app>.env (HOST=127.0.0.1, WISP_ONION=on) for tor and wisp service install, with no container (Tor onion service) |
wisp npm add <pkg>[@version] | add an npm package to package.json, no Node needed. wisp build pins each downloaded file by SHA-256 in npm.lock |
wisp npm remove <pkg> | remove one |
wisp npm list | the app's npm packages and versions |
wisp recipe add <name> [--force] | apply the recipe add/<name>/recipe |
wisp recipe list | the recipes in add/ |
wisp ui add <name…> [--force] | copy components (button, dialog, tabs…) into src/components |
wisp ui list | the components wisp ui add has |
wisp service install|uninstall|start|stop|status | run the release binary as a systemd unit, launchd daemon or Windows scheduled task. Options: --name, --user or --root (install needs one), --port, --dry-run. install on an installed service upgrades it: the new build runs after it. On Windows the task command must be 261 characters or fewer. status exits 1 with not installed when there is no service (Linux: not running for a stopped unit) |
wisp lsp | the language server, over stdio. A bad header or Content-Length gets a JSON-RPC error (-32700). The server then reads on from the next Content-Length |
| CLI downloads | curl stops at 512 MiB for a file and 64 MiB for a body in memory. It checks the size against Content-Length: a response with no length is not capped |
wisp update-docs | update AGENTS.md to this Wisp |
wisp upgrade [--check | --undo | --force-undo] | install the newest release and, in an app, bump its Wisp dependencies to it (see Updating) |
wisp changelog [version | --since <version>] | print what changed from the installed version to the newest, one version, or everything after one (see Updating; the same text is /changelog) |
wisp mcp | docs, routes, components and checks for AI agents (MCP, stdio) |
wisp --help | the commands by group (-h). wisp help <command> [verb] lists one command's rows |
wisp --version | the version (-V) |
Options for Wisp New
| Option | What it does |
|---|---|
-t, --template demo|minimal|api | a demo app, one empty page, or a JSON API (also --api) |
--tailwind, --no-tailwind | add Tailwind CSS, or not |
--ts, --no-ts | TypeScript only (script = "ts"). On by default. In wisp dev a violation is a warning |
--strict, --no-strict | warnings are build errors (strict = true). In wisp dev they are warnings, so a pasted docs example runs. On by default |
--typecheck, --no-typecheck | wisp build and wisp check run tsc (typecheck = true). Off by default |
--git, --no-git | create a git repository, or not |
--install, --no-install | compile dependencies now, or on the first wisp dev |
-y, --yes | use the default for each option not given |
--lib | a component library, not an app. It asks nothing |
An app also gets a rust-toolchain.toml that pins the Rust version of the Docker image (wisp deploy init docker). Local builds and the image then use one compiler. See Reproducible builds. An option given both ways (--ts --no-ts) is an error. The folder name is the package name, so it takes ASCII letters and digits only: über is refused.
Updating
wisp upgrade installs the newest release (cargo install --git https://wisp.ar0.eu/ --tag <tag> wisp-web --force). The git tag of the release is the trust anchor: cargo gets the source at that tag and builds it. In an app folder it also sets tag = "<tag>" on the Wisp git dependencies in Cargo.toml, runs cargo update for wisp-web-rt, wisp-web-build, wisp-web-macros and wisp-web-shared, then runs wisp check and wisp update-docs with the new CLI. It reports each step.
The app upgrade is all or nothing. Before it changes a file, wisp upgrade copies Cargo.toml, Cargo.lock and AGENTS.md to target/wisp-upgrade. First it checks that the installed CLI shows the new version, and stops with no change when it does not. When a step fails, it puts the copies back and says so. When wisp check fails, fix the check errors, then run wisp upgrade again. After a good upgrade, wisp upgrade --undo puts back the files of the last upgrade and deletes the copies. It refuses when Cargo.toml changed after the upgrade, as the undo would lose that change: wisp upgrade --force-undo undoes all the same. The copies are under target/, so cargo clean deletes them. The CLI stays at the new release: to get the old one back, run the install command with the old --tag.
wisp upgrade --check only reports. It exits 1 when a newer release is out, so CI can use it. WISP_RELEASES_URL sets the release list URL, an https URL (default: the Forgejo API at code.wispweb.dev). The answer must have the Forgejo or GitHub releases JSON shape. wisp new pins a new app to the tag of the CLI that made it, so an update is always a change you make.
wisp dev, wisp build and wisp new look up the newest release at most once a day (the Forgejo API of the repository, prereleases included; the answer is cached in the user cache folder as wisp/latest-release). They print one line on stderr when the CLI, or the wisp-web-rt version in the app's Cargo.lock (a git dependency; a path dependency on a Wisp checkout is never flagged), is older than that release:
! wisp 1.0.0-pre.2 is out of date: 1.0.0-pre.3 is out. Run wisp upgrade.
- security 1.0.0-pre.3: Signed cookies under WISP_BASE keep their name and base path
- 1.0.0-pre.3: Brotli for assets and baked pages, assets from other sites by URL
Changes: https://wispweb.dev/changelog/?v=1.0.0-pre.2
Under the line it lists at most five lines on what changed between the CLI and the newest release, taken from the release notes (the section of CHANGELOG.md that the release carries): the first lines of each Security section, then the summary of each newer version. The notes are cached with the answer. When they cannot be read the notice is the one line. wisp upgrade prints the same lines before it installs, and wisp changelog --since 1.0.0-pre.2 prints every entry after that version. wisp changelog alone prints the installed version and everything newer; wisp changelog 1.0.0-pre.2 prints one version. Versions up to the installed one come from the CHANGELOG.md built into the CLI, so they work offline; newer ones are read from the releases at code.wispweb.dev, and without a network the command says so and shows what it has. The link opens the changelog page with your version marked.
The lookup runs on a background thread with a 3 second limit. A command never waits for it, and the server and the built app never run it. It is off when offline, when CI is set, and with WISP_NO_UPDATE_CHECK=1. On Windows the running wisp.exe is renamed to wisp-old.exe for the install.
CLI Older Than the App
App commands warn on stderr when the installed wisp is older than the app's wisp crate, and ask before continuing in a terminal. WISP_NO_UPDATE_CHECK=1 turns the warning off. More: CLI, dev loop and security.
Is This Page Useful?