Reference

CLI Reference

Every stack subcommand, every flag it currently takes, and — wherever the answer isn't obvious from the name — why it works the way it does.

Project lifecycle

stack new <target>

Interactive scaffolding for a brand-new project.

target
Required. Directory to create the project in — . to scaffold in the current directory, or a new folder name to create. Refuses to run if a stack.toml is already there, so it never overwrites one by accident.

Languages and services are chosen from a checkbox list (space to toggle, enter to confirm) instead of a yes/no loop — nothing is committed until you confirm, so toggling the wrong box is a toggle, not a reason to start over.

stack init

Same scaffolding wizard as stack new, but for a project that already exists. Scans the current directory for composer.json (require.php), package.json (engines.node), and pyproject.toml (project.requires-python or Poetry's tool.poetry.dependencies.python) and pre-fills whatever version constraints it finds instead of asking you to retype them from memory.

No arguments or flags.
Always operates on the current directory; refuses if stack.toml already exists, same as stack new.

stack up [target] [--prompt] [--clone] [--yes]

Activates languages, starts declared services, spawns [run], and routes the domain through Caddy (both http:// and https://).

target
Defaults to .. Resolved as a directory first (so stack up . and stack up ../other-project keep working exactly as before) — only if that fails does it check whether target matches the name of a project stack has seen before, letting you run stack up my-project from anywhere, not just from inside it or via an explicit path.
--prompt
Allows interactive prompting for a {PLACEHOLDER} in command/port that isn't set in your environment. Without it, an unresolved placeholder is a hard error listing every missing name at once.
--clone
Fetches any [[clone]] entries declared in the manifest. Omitted by default — plain stack up never touches git — because cloning on every single stack up with no way to opt out or review what's about to happen first was the actual bug this flag exists to fix.
--yes
Approves this project's [run]/[service.*] commands without the interactive confirmation prompt. See the trust-on-first-use note below — this is the flag CI/scripted use needs, since a hanging confirmation would otherwise break automation outright.
Why the first run asks you to confirm

[run].command and [service.*].command are literal program invocations — stack runs them exactly as written, the same way npm install runs whatever's in package.json's scripts. Nobody reads every line of a stack.toml before running stack up, especially one they didn't write themselves. The first time a project's commands are seen (or the first time they change — say, after a git pull), stack prints them and asks you to confirm before anything executes. Approval is remembered per project in ~/.stack/trust.json; unchanged commands never prompt again. A changed command shows a -/+ diff and re-prompts rather than silently trusting the new one.

stack down [project] [--all]

Stops a project's [run] process and removes its route.

project
Name of the project (or external run) to stop; omit to use the current directory's project. Already name-addressable from anywhere — no directory tracking needed, since running state is keyed by name.
--all
Stops every project, every shared service, every external-run registration, and Caddy itself — the true end-of-day shutdown. Without it, shared services keep running for any other project that still references them; only the one named project's [run] process and route are removed.

stack restart [project] [--all] [--yes]

Stops then starts a project again, resolved by name the same way stack up is — works from anywhere, not just from inside the project.

project
Name of the project to restart; omit to use the current directory's project. Ignored if --all is passed.
--all
Restarts everything currently running. Snapshots names from both tracked projects and tracked external runs before tearing anything down — a project whose [run].external = true is only tracked as an external run, not as a project, and would otherwise have its other declared services stopped with nothing bringing them back.
--yes
Same trust-on-first-use bypass as stack up --yes, needed here too since restart re-runs up internally.

stack describe [name]

Prints everything stack knows about a project's resolved environment: the manifest and project directory paths, whether it's currently running, its log file location, resolved binary paths per language (php.exe, and for PHP specifically also php.ini and php-cgi.exe, sitting right next to it), each service's binary/data-dir path or external (BYO), and resolved tool paths.

name
Name of the project to describe; omit to describe the current directory's project. Resolves the same path-then-name way stack up's target does.

Pure toolchain resolution — nothing is spawned, so it works whether the project is up or down.

stack status alias: stack ps

Lists every running project, service, and external run, plus Caddy's own liveness and route count. Self-healing — drops entries whose process is no longer actually alive before printing anything.

No arguments or flags.

stack stats [--no-stream]

Continuously-refreshing CPU/memory view, docker stats-style.

--no-stream
Prints one snapshot and exits, instead of refreshing continuously until you interrupt it.

stack logs [name] [-f/--follow] [--tail N]

Reads a project's or service's log file directly — works even after it's stopped, since it reads the file, not the running-state list.

name
Name of the project or service whose log to read. Optional in the flag signature, but there's no useful default — omitting it just means stack has nothing to read.
-f, --follow
Keep reading as new lines are appended, tail -f-style, instead of printing what's there and exiting.
--tail N
Only print the last N lines instead of the whole file.

stack run [name] [-- args] [--yes]

Runs a [script.<name>] entry's steps in the foreground, in order, stopping at the first step that fails with its exit code. Not a service — nothing is tracked, nothing keeps running after the last step exits.

name
Which script to run. Omit to list the scripts declared in the current directory's stack.toml.
-- args
Anything after -- is forwarded to the script's last step only, e.g. stack run test -- -k something.
--yes
Approves this script's commands without the interactive trust prompt — same purpose as stack up --yes, for CI/scripted use.

Manifest editing

stack add <kind> <name> [version] [options]

Format-preserving — existing comments and key order in stack.toml survive untouched, unlike hand-editing with a script.

kind
Required. language, service, or tool.
name
Required. The entry's name, e.g. php, mysql, terraform.
version
Required unless --path is set (a BYO install doesn't need a version stack resolves itself).
--schema
Service only. Database/schema name; defaults to the project name.
--port
Service only. Defaults to the engine's conventional port (3306/5432/27017 for mysql/postgres/mongo); required for any other engine name.
--command
Service only. Start command override; required for engines with no built-in default.
--path
Any kind. Bring-your-own binary path, bypassing whatever manager would otherwise resolve it.
--manager
Language only. Which manager resolves this language (e.g. vfox, uv) — only needed when the name isn't php/node (vfox) or python (uv), which are inferred automatically.
--plugin
Language only. vfox plugin name override, for the rare mismatch between a language's name and its vfox plugin name (e.g. Node's plugin is nodejs).
--binary
Language only. Binary filename override; defaults to {name}.exe.

stack remove <kind> <name>

Removes one entry, same format-preserving guarantee as add.

kind
Required. language, service, or tool.
name
Required. The entry's name to remove.

Diagnostics & registry

stack doctor [--fix] [--project]

Always checks: vfox/uv/Caddy are present at compatible versions; pre-existing Windows Services (MySQL, MongoDB, PostgreSQL) worth registering as external instead of stack starting a redundant second instance; and, if the current directory has a stack.toml, that its declared [tool.*] entries resolve and any BYO (path-based) [language.*] entries still point at a real file.

--fix
Installs vfox/uv/Caddy at stack's pinned versions if missing, and runs caddy trust so https://*.localhost works with no browser warning. Safe to re-run.
--project
Adds the deeper checks: every declared language actually resolves (vfox/uv-managed, not just BYO — or notes it'll install on stack up), every service either has a registered/inline path or is external with something actually listening on its resolved port, [run]'s port isn't already occupied by something else, and every {PLACEHOLDER} in [run].command resolves against the current environment (loading .env first, same as stack up does).
Why --project exists

Without it, a broken manifest only shows itself one error at a time, part-way through an actual stack up — a bad port, then fixed, then an unregistered service path, then fixed, then a missing environment variable. --project turns that into one list, up front, before anything is spawned.

stack register <kind> <name> <version> [path] [--external] [--port]

Makes a BYO install reusable by any project, without repeating the path in every stack.toml.

kind
Required. service, tool, or language.
name
Required. E.g. redis.
version
Required. The version this install corresponds to.
path
Path to the existing binary. Omit when using --external.
--external
Service only. Adopt an already-running instance instead of registering a path.
--port
Required with --external. Port to verify liveness on.

stack unregister <kind> <name> <version>

Removes one entry from the global registry. Errors if nothing matches. Unlike stack prune, this removes the entry even if a project's stack.toml still references it.

kind
Required. service, tool, or language.
name
Required. E.g. redis.
version
Required. Must match exactly what stack list reports.

stack list

Live-merged view of everything installed — vfox, uv, and the registry — queried fresh each time, never cached.

No arguments or flags.

stack prune [--yes] [--purge-data]

Reports language/service versions installed but no longer referenced by any known project.

--yes
Actually uninstalls orphaned language SDKs and drops orphaned registry pointers. Without it, this is a dry-run report only — nothing is changed.
--purge-data
Requires --yes. Also deletes orphaned services' data directories. Separate from --yes deliberately — uninstalling an SDK is reversible (reinstall it), deleting a database's data directory isn't.

stack clean

Removes cached composer.phar downloads (~/.stack/tools/composer). v1 scope is intentionally narrow — just this one cache — since it's the only thing stack downloads outside of vfox/uv's own managed stores. Self-healing: the next project that needs Composer just re-downloads it.

No arguments or flags.

Shell integration

stack setup [--shell] [--default-profile]

One-time bootstrap per machine: wires the shell hook into your profile and runs the equivalent of doctor --fix. Idempotent — safe to run again.

--shell
Which shell's profile to wire (pwsh, powershell, or cmd). Auto-detected from the parent process if omitted, by walking up to 10 ancestor processes past wrapper processes like conhost.exe/Windows Terminal to find the actual shell that launched stack setup — the same technique shellingham (the reference implementation for this exact problem) uses, since there's no OS API that answers "what shell spawned me" directly.
--default-profile <name>
Makes a saved profile's languages/tools available to every new process on the machine — interactive or not — by writing their resolved paths directly into the persistent Windows user PATH (the registry), not a per-prompt shell variable. That's what makes it visible to non-interactive processes (scripts, CI runners, an AI agent's own tool calls) with zero dependency on the shell hook. Pass none to clear it. Already-open terminals/IDEs need restarting to pick up the change — standard PATH-caching behavior, not something stack can force from outside. See below.

stack hook <shell>

Prints the activation script your shell profile evaluates once at startup. You won't normally call this directly — stack setup wires it for you.

shell
Required. Which shell to print the hook script for, e.g. pwsh.

stack activate [shell]

With a shell argument, this is called by the hook on every prompt — resolves the current directory's stack.toml (if any) and prints the PATH changes for that shell to apply. Silent, no-op output when no manifest is found, or when this project was explicitly deactivated (see stack deactivate below).

With no argument, it's an explicit command: reactivates the current directory's project if stack deactivate suppressed it, without needing to leave the directory. Detects your shell itself.

shell
Which shell is invoking this, e.g. pwsh. Omit for the explicit-reactivate form.

stack deactivate

Clears an explicitly-activated profile (see stack profile <name> below), and/or suppresses the current directory's project activation (PATH, the [stack] prompt indicator) — whichever applies. One-way and idempotent: running it again while already deactivated just confirms there's nothing left to turn off, it doesn't toggle anything back on.

No arguments or flags.
Why project deactivation self-clears

Project activation is otherwise ambient — ordinarily, leaving the directory is what turns it off, with no separate on/off state to track. stack deactivate adds a way to suppress it without leaving (e.g. to briefly use a different tool version from the same terminal) — but it stays scoped to that one project directory: the moment you actually cd away, the suppression clears itself automatically, so a later visit reactivates normally. Use stack activate (no argument) to reactivate sooner, without leaving.

stack load-env [path]

Loads a .env-style file into the current shell session. Confirms which variable names were loaded, never the values. stack up/stack doctor --project now do this automatically for a project's own .env — this command is for loading one manually, into your interactive shell, for anything you're running by hand.

path
Path to the .env file. Omit to load the current directory's root .env plus any [project].env_files declared in its stack.toml — skipping whichever of those don't exist, erroring only if none do.

Profiles & ad hoc execution

Named, persisted language/tool version pins usable from any directory — not just inside a stack-managed project. A profile is stored under ~/.stack/profiles/<name>.toml, reusing stack.toml's own schema, restricted in practice to [language.*]/[tool.*].

stack profile

Interactive wizard, no arguments: pick languages (same checkbox flow as stack new), then choose to activate the result in this shell, save it under a name, or both.

No arguments or flags.

stack profile <name>

Activates a saved profile in the current shell — its languages/tools go on PATH, re-resolved fresh on every prompt the same way project activation is.

name
Required. Must be a profile already saved via the wizard or stack profile add.

stack profile <name> --exec "<command>"

Resolves a saved profile's pins (installing anything missing), runs a single one-off command with them on PATH, then exits with its status. Nothing persists — no activation, no shell state.

--exec
Required for this form. The command string to run, e.g. --exec "php artisan migrate".

stack profile list

Lists saved profiles, tagging the currently-active one and the configured default (see stack setup --default-profile).

No arguments or flags.

stack profile describe <name>

Each pinned entry's version and real resolved/installed status — kubectl describe-style.

name
Required.

stack profile edit <name>

Opens a saved profile's TOML file in $VISUAL/$EDITOR, falling back to notepad.exe. If the profile you're editing is the current default (stack setup --default-profile), the persistent PATH is refreshed automatically afterward so it can't silently drift stale.

name
Required.

stack profile add <name> <language|tool> <entry> [version] [options]

Structured modification of a saved profile — same underlying logic as stack add for stack.toml, same format-preserving guarantee, same default-profile auto-refresh as edit above.

name
Required. The profile to modify.
language|tool
Required. A profile has no [service.*]/[run]/[[clone]] — only these two kinds apply.
entry
Required. The name being added, e.g. php.
version, --path, --manager, --plugin, --binary
Same meaning as the matching stack add flags.

stack profile remove <name> <language|tool> <entry>

Removes one entry from a saved profile.

All three arguments required.

stack profile rm <name>

Deletes a saved profile entirely. If it was the current default, the default is cleared and its PATH entries removed too.

name
Required.

stack which [name]

Shows what stack would resolve a language/tool to right now, and why.

name
A language or tool name to resolve, e.g. php. Omit to summarize the whole active context instead: current project (if any), and active/default profile.

Resolution order for a named lookup: the current directory's project, if it pins that name; else an explicitly-activated profile; else a profile literally named default, if one exists — a reserved fallback name, distinct from stack setup --default-profile.

stack exec --with <name@version,...> -- <command>

Ephemeral ad hoc version pins with no named profile at all — resolves each pin (installing anything missing), runs the command with them on PATH, exits with its status.

--with
Required. Comma-separated name@version pairs, e.g. --with php@8.3.1,node@20.
-- command
Required. Everything after -- is the command and its arguments.
Why this exists alongside profiles

Both stack exec and stack profile <name> --exec share the same real motivation: a non-interactive process — an AI agent's own shell tool calls, a CI step, a one-off script — never sees any of stack's ambient PATH machinery, since that all lives inside the interactive shell's per-prompt hook, which simply never runs for a one-shot, non-interactive invocation. These two commands are the actual working answer for that case: stateless, no dependency on any shell hook or prior cd.