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 astack.tomlis 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.tomlalready exists, same asstack 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 (sostack up .andstack up ../other-projectkeep working exactly as before) — only if that fails does it check whethertargetmatches the name of a project stack has seen before, letting you runstack up my-projectfrom anywhere, not just from inside it or via an explicit path. --prompt- Allows interactive prompting for a
{PLACEHOLDER}incommand/portthat 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 — plainstack upnever touches git — because cloning on every singlestack upwith 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.
[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
--allis 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 = trueis 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-runsupinternally.
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
Nlines 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, ortool. name- Required. The entry's name, e.g.
php,mysql,terraform. version- Required unless
--pathis 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'tphp/node(vfox) orpython(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, ortool. 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 trustsohttps://*.localhostworks 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 isexternalwith something actually listening on its resolved port,[run]'s port isn't already occupied by something else, and every{PLACEHOLDER}in[run].commandresolves against the current environment (loading.envfirst, same asstack updoes).
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, orlanguage. 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, orlanguage. name- Required. E.g.
redis. version- Required. Must match exactly what
stack listreports.
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--yesdeliberately — 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, orcmd). Auto-detected from the parent process if omitted, by walking up to 10 ancestor processes past wrapper processes likeconhost.exe/Windows Terminal to find the actual shell that launchedstack setup— the same techniqueshellingham(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. Passnoneto 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.
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
.envfile. Omit to load the current directory's root.envplus any[project].env_filesdeclared in itsstack.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 addflags.
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@versionpairs, e.g.--with php@8.3.1,node@20. -- command- Required. Everything after
--is the command and its arguments.
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.