CLI reference¶
splash # sync this checkout explicitly
splash --version
splash [--cwd PATH] [--format text|json] [--show-values] … # root options precede the command
splash sync [--force] [--setup N] # pick free ports, resolve vars, write the env file
splash status [local|all] [--check] [--verbose]
# resources + targets + health/cleanup details
splash init [--loader=…] [--env-file=PATH] [--overwrite]
splash deinit # remove checkout-local state, keep shared hook and trust
splash trust # authorize automatic handling for this clone
splash untrust # revoke clone-wide automatic handling
splash bootstrap [--rerun] # sync + run bootstrap once for this checkout
splash doctor [--fix] [--framework=…]
splash run [type] [variant] # boot target + build + launch
splash start [type] [variant] # boot target (no build/launch)
splash stop [type] [variant] # shut down
splash destroy [type] [variant] [--yes]
# delete this checkout's target instance
splash target # list declared targets + live state
splash target add <type> <variant> [--model M] [--ios V] [--device D] [--image I]
[--name N] [--id ID] [--platform ios|android] [--global]
splash target remove <type> <variant> [--keep-instance] [--global]
splash target refresh [ios|android|all]
# reconcile every registered checkout
splash target prune [ios|android|all] [--dry-run] [--yes]
# destroy sims/emulators splashdown didn't create
splash target claims [--format text|json]
# inspect machine-wide physical claims
splash target claim VARIANT [--force] [--format text|json]
splash target claim --available ios|android|any [--format text|json]
splash target release VARIANT [--force]
splash target release --all
splash env [--checkout PATH] # list a checkout's resolved keys
splash env get KEY [--checkout PATH]
splash env set KEY=VALUE [--checkout PATH]
splash env release [KEY] [--checkout PATH]
splash gc # drop dead-checkout entries (ports, vars, sims)
splash completion [bash|zsh] # print shell-completion script (eval it in your rc)
splash status answers "what's the state of this checkout?": resource keys (with [in use] /
[free] for ports), declared device variants and whether each is booted, automatic sync and
bootstrap trust, bootstrap completion, and stale registry rows.
For a bound port, detailed status also shows listener PIDs and command names when lsof can
identify them. JSON port records include owners, a list of {pid, command} objects, an empty
list for a free port, or null when the owner is unavailable. Process arguments are not collected.
Routine status, env-list, and sync JSON output hides resolved values. Add the root-level
--show-values flag when you intentionally need them. splash env get KEY remains the explicit
single-value read. splash sync --force reallocates ports. splash init scans the project and
writes the project files. It allocates nothing, so run splash trust and then bare splash
after it.
Root output options go before the command. --format applies to sync, status, init, bare env,
bare target, target claims, and target claim. --show-values applies to sync, status, and bare
env. Other combinations are usage errors instead of accepted no-ops.
In text mode, explicit --show-values prints resolved KEY=VALUE lines for sync. With
status all, it selects detailed checkout blocks so those values have a place to appear instead
of silently remaining in the compact table.
Init creates a project in the current directory, or the directory selected by --cwd, whether
at a Git worktree root, inside it, or outside Git. Replacing an existing recipe requires
--overwrite. Recipe symlinks are rejected rather than followed. Nested init leaves the
repository's post-checkout hook untouched and prints the explicit splash --cwd PATH sync
command to run after checkout.
--env-file selects the file that receives the generated values. Without it the destination is
splashdown.env, a file splashdown generates next to the recipe. The path is relative to the
checkout and must stay inside it and outside .git, so an absolute path, one that escapes, or one
inside the Git directory is rejected before init writes anything. Init records the choice as env_file in the recipe's [project] table and
reports the destination, the keys it will manage there, and any of those keys the file already
sets. Values are never printed. Nothing is written to the destination by init itself, so run
splash afterwards.
Splashdown manages only its declared keys in that file, so pointing --env-file at a dotenv file
your project already reads keeps the rest of the file intact. An existing value for a declared key
is replaced on the next sync, with no extra flag or confirmation. A key assigned twice, or
assigned in a shape splashdown cannot rewrite such as PORT: 3000, is reported as an error rather
than joined by a second competing definition.
A resource with its own writer keeps that destination and is not also copied into the default
file. See The recipe for the writer values.
--loader selects how the environment is loaded. It works with --env-file: the selected loader
is wired to read the chosen destination, so splash init --loader mise --env-file .env records
direct dotenv delivery and points mise at .env. With --loader none only the destination is
configured and no loader file is touched. Without it, init wires the sole loader whose
configuration exists in the directory and says which file decided that. With several configured
it stops before changing anything and asks you to choose. With none configured it selects
none. An installed tool is not by itself a reason to adopt it. Init reuses loading directives
the project already has rather than adding a second one, and adds only its own directive where
one is missing. splash --format json init reports the same selection, why it was made, and
every file init changed.
Evolve an existing recipe by editing it manually or with an agent. splash init --overwrite
regenerates the whole recipe, replacing manual edits. It keeps the destination the recipe already
records, so a project set up with --env-file config/.env keeps writing there. Pass --env-file
again to move it. See
adding an app for a comparison workflow.
Init always generates the recipe from a project scan. For the choices a scan cannot infer, such
as a generic PORT for any server that reads one, a per-checkout Postgres database name, or
Electron user-data isolation, add the resource yourself. See
The recipe for each pattern.
Plain splash init detects Electron in addition to the renderer framework. It configures the
renderer like any other app and asks nothing. Two checkouts of an Electron app still share one
user-data directory, because separating them needs a change in your main process that Splashdown
cannot make for you. Init points at
Electron user-data isolation, which has the resource to
declare and the code to add. A project init does not detect as Electron opts in the same way.
For a native iOS project, init records no Xcode scheme and never runs xcodebuild, so it works
with Xcode missing or broken. splash run picks the scheme instead: it uses
[project.ios] scheme when your recipe sets one, and otherwise builds the only shared scheme it
finds. With no scheme or several, the run stops before touching a simulator and asks you to set
[project.ios] scheme. See The recipe for where that goes.
Remove splashdown¶
splash deinit surgically removes checkout-local init state plus state created by sync and device runs. It
destroys simulator and emulator instances owned by this checkout, releases its registry entries,
clears splashdown-managed keys from every writer destination and deletes one left with nothing else,
and unwires the loader and managed agent instructions. From its .gitignore block it removes the rules for files it deleted and keeps the rules for files it left behind, such as a splashdown.local.toml you edited, and it removes a .gitignore left with nothing in it at all. The loader configuration is restored to the bytes the project committed, blank separator lines included. The shared
post-checkout integration and clone-wide bootstrap trust remain because linked worktrees may still
use them, and deinit names the hook configuration file it left that entry in. Deinit clears only this checkout's bootstrap completion.
It then removes splashdown.toml and an untouched splashdown.local.toml skeleton.
User-owned content is preserved: a modified local config or hook is left with a note, unrelated
dotenv keys remain, physical devices are never destroyed, and framework changes made by
splash doctor --fix are not reverted because they have no recoverable original.
A value whose resource you deleted from the recipe before running deinit cannot be chased: nothing records which file a writer that the recipe no longer declares sent it to. Deinit names those keys so you can remove them from that file yourself.
splash sync --setup NAME runs the recipe's [setup.NAME] commands after resolving and writing resources. Empty or malformed setup declarations fail during recipe validation, before those changes. An unknown requested name or failed command exits 1 after provisioning. Resource/output changes and earlier successful setup commands are not rolled back.
splash trust authorizes automatic resource sync for the whole clone. When the current recipe has
[bootstrap], it displays and authorizes those commands without running them. A later-added
[bootstrap] needs another trust operation unless the clone was already trusted for bootstrap on
an earlier ref. Existing bootstrap trust lasts until splash untrust. splash bootstrap provisions the checkout and runs
the commands once, and --rerun repeats a completed bootstrap. splash untrust revokes both
capabilities without needing a valid recipe. See
Trusted worktree bootstrap for the security and retry contract.
splash env set KEY=VALUE only accepts keys declared with type = "set" in the target checkout's recipe. It rejects invalid assignments, missing or malformed recipes, undeclared keys, and generated or allocated resources with exit 2.
For nested environment actions, --checkout PATH may appear either immediately after env or
after the action arguments. It takes precedence over root --cwd in both forms.
Commands that load configuration validate the complete document before provisioning or project-file mutation. Unknown sections and fields, wrong types, invalid templates, and malformed target definitions exit 1 with a qualified error and no traceback.
splash target add applies the same target schema as TOML files before writing. simulator accepts --model, --ios, and --name. emulator accepts --device, --image, and --name, where --device is the Android emulator hardware profile such as pixel_9. Physical device accepts --id, --name, and --platform=ios|android. Supplying a flag for the wrong target type is an error and leaves the local or global config unchanged.
Local target remove destroys its managed simulator or emulator by default. A global removal edits
the machine-wide declaration only, then splash target refresh reaps any instance the removal made
undeclared. Because global removal is already config-only, --global --keep-instance is a usage
error. Physical device removal has no managed instance, so combining it with --keep-instance is
also a usage error.
Refresh is machine-wide even when invoked from one checkout. It recreates stale or externally
deleted registered instances at each declaration's runtime or image, resolving latest live. It
also destroys undeclared instances and instances belonging to deleted checkouts without a prompt.
It does not provision declared targets that have never been run.
Physical claim commands act only on configured device targets from the recipe, local config,
and global config. splash run pixel claims a free connected target before framework build or
installation. A busy or disconnected target fails before that work starts. The claim persists
after process exit and launch failure, and splash stop device pixel does not release it.
splash target claims reads registry ownership without device discovery. Its text output and JSON
output include the target, source, platform, hardware ID, canonical owner checkout, and claim time.
Specific target claim VARIANT writes its human diagnostic to stderr. Generic allocation prints
only the chosen variant to stdout in text mode, so scripts can capture it:
For either claim form, --format json writes target, source, platform, hardware_id,
owner, claimed_at, and status to stdout. Generic allocation checks configured targets in
recipe, local, then global order and skips disconnected or busy matches. claim VARIANT --force
atomically transfers a live owner's claim. release VARIANT --force clears it without taking it.
release --all removes only claims owned by the current checkout.
A forced transfer or release queues a warning for the displaced checkout. Its next ordinary
checkout-scoped command prints and consumes the warning once. Completion, help, version output,
and the hidden post-checkout command do not consume it. splash deinit releases the checkout's
claims and pending notices. splash gc removes claims for deleted checkouts and expired or
dead-checkout notices.