Skip to content

CLI reference ​

Kranz has one binary and no daemon. Running kranz with no subcommand opens the terminal UI; every other command either describes a project or talks to one running project runtime.

Outside a project directory, a bare kranz opens a list of any local runtimes already running instead of failing, and lets you attach to one from there (see switching runtimes). It only reports an error if it finds neither a configuration nor a runtime.

Synopsis ​

bash
kranz                                # open the TUI for this directory
kranz [GLOBAL OPTIONS] COMMAND [ARGS]

The first positional argument after the global options is always a subcommand.

Global options ​

OptionDescription
-f, --config PATHCompose an autonomous config or local glob. Repeatable.
--override PATHApply an ordered partial override layer. Repeatable.
--follow-symlinksFollow symlinks during discovery.
-C, --directory DIRWork in DIR instead of the current directory.
-p, --project VALUEAddress a runtime by name, ID, or unique ID prefix.
--output text|jsonChoose human output or the machine-readable envelope.
-h, --helpPrint usage for the command and exit.
-v, --versionPrint version, commit, and build time, then exit.

Environment defaults ​

The project coordinates can be supplied as one-shot environment prefixes or exported for a shell that works with one runtime repeatedly:

VariableEquivalent option
KRANZ_PROJECT-p, accepting a runtime name, ID, or unique ID prefix
KRANZ_DIRECTORY-C
KRANZ_CONFIGOne or more -f paths separated by the operating system's path-list separator (: on macOS and Linux)
KRANZ_OVERRIDEOne or more --override paths separated by the operating system's path-list separator

Explicit command-line options win over environment defaults. If any -f or --config option is present, its ordered layers replace KRANZ_CONFIG rather than being appended to a hidden inherited list. An explicit --override replaces KRANZ_OVERRIDE the same way.

bash
KRANZ_PROJECT=billing kranz status
KRANZ_DIRECTORY=~/projects/shop kranz services
KRANZ_CONFIG=kranz.yaml:kranz.local.yaml kranz config show

Choosing the runtime a command acts on ​

-p is optional everywhere. Without it, the target is the runtime named by the configuration in the working directory, which is what makes this work:

bash
cd ~/projects/shop
kranz up -d
kranz status
kranz stop api
kranz down

With -p, the explicit value always wins — including from a directory that has a project of its own, so you can drive another project without leaving this one:

bash
cd ~/projects/shop
kranz -p billing status

A directory that is not a project says so rather than reporting a missing runtime:

console
$ cd /tmp && kranz status
Kranz: no Kranz configuration was found in this directory.
Run from a project directory, pass -f PATH, or name a runtime with -p NAME_OR_ID.

Runtime names and IDs ​

Each up creates one runtime session. Its NAME comes from runtime.name, or from a lowercase slug of project when that field is absent. Its ID is generated per run and never reused, so a project restarted after down keeps its NAME and gets a new ID. -p accepts a full NAME, a full ID, or a unique ID prefix.

Commands ​

Creating a configuration ​

bash
kranz init                                   # interactive authoring wizard
kranz init ./new-project                     # start in another directory
kranz init --from Procfile                   # convert an existing source
kranz init --from process-compose.yaml
kranz init --name Shop --service api --command "npm run dev" --yes
kranz init --service api --command "npm run dev" --yes
kranz init -o kranz.local.yaml

Interactive init builds an in-memory draft. Start with the project name and directory, optionally configure the full project appearance with a live theme preview, then add any number of services and actions. Draft entries can be opened, edited, or deleted before the final YAML review. A service asks for its name, directory, command, and optional port. An action asks for its name, project-or-service owner, directory, and command.

init does not inspect package registries or infer commands. Import is explicit through --from. An existing native configuration opens a replacement choice before the wizard and a line diff before the final save; cancelling at either point leaves it byte-for-byte unchanged. The file is written atomically and reloaded before success is reported.

Appearance settings are independent. Choosing only a theme writes only ui.theme; accent, background source or custom colour, and auto/dark/light colour mode are emitted only when changed. Choosing inherited appearance omits the ui block entirely.

--name sets the project written into the new file. Global -p/--project always selects a runtime and is therefore rejected by init.

Without a terminal, the inputs must be explicit. --yes confirms that complete flag form; it does not discover commands, classify nearby files, or permit an overwrite. Existing automation can continue to use an explicit --from PATH conversion. Replacing a file without a terminal additionally requires --force.

Inspecting a project ​

These inspect configuration and, where useful, current runtime state. They never mutate a running runtime; configuration-only forms work before the first up.

bash
kranz config                        # same as config show
kranz config check                  # load, merge, and validate
kranz config show [--provenance]    # effective configuration, secrets redacted
kranz config explain [SERVICE] [--all]  # which layer set each field
kranz config sources                # configuration files in merge order, and what each contributed
kranz config sources --by-service   # each service with its defining file and later overrides
kranz doctor                        # preflight checks
kranz project                       # project details
kranz services                      # configured services
kranz services info SERVICE         # one service's configuration and live state
kranz actions [OWNER]               # configured actions; optionally by owner
kranz tags                          # configured service tags
kranz plan [SELECTOR ...]           # the waves a start would use
kranz plan --operation stop api     # services a stop would affect
kranz graph [--format text|json|dot]
kranz ports [SELECTOR ...]
kranz ports inspect PORT

ports reports both the ports a service declares and the ports a running runtime saw it open, labelled by origin, because a service that picks its port at runtime is exactly the case where the configuration cannot answer.

services info SERVICE describes the configuration, and adds what the service is doing right now when a runtime is up. project describes only the project, so the same command never changes entity based on whether an argument is present.

config show redacts environment values whose name looks like a credential and keeps services, action groups, and actions in the order the configuration declares them. config explain on a single-layer project says so instead of repeating the same filename on every field; --all lists them anyway.

config sources answers "which files became my configuration, and who wrote each field?" It lists every resolved source in deterministic order, drawn as the include tree. Each file is named below the directory of the nearest file that includes it, and a kranz.yaml base name is left off, so services/api/kranz.yaml reads as services/api; when two files would share a name, both show their full path. How the file joined the merge follows the name in parentheses: (explicit), (via glob) for a match of an include glob:, (via discover) for a file an include discover: scan found, (override), or (virtual root). An exact include path: is left untagged because the tree already shows it, and (truncated) marks a subtree an include depth limit cut.

Beneath each file it prints the services that file defined and the fields it overrode, grouped by the file whose value they replaced: overrides FILE: names that file once, and sets: lists fields no earlier file had set. A list that does not fit after its label moves beneath it, one entry per ↳ row:

console
$ kranz config sources
3 sources · 2 services · 1 override · resolved order; field writes shown below

● kranz.yaml  (explicit)
│   services: web
└─● services/api
  │   services: api
  └─● overrides/local.yaml  (override)
        overrides services/api: api.ports

Global --override layers belong to no file, so each starts its own block after a blank line below the tree.

--by-service reads the same map from the other side: one row per service with the file that defined it, and every later override layer with its fields beneath. It deliberately omits service detail such as ports and actions; config show is the effective configuration and config explain SERVICE answers per-field questions. --output json carries both directions, with machine kind values (explicit, glob, discovery, nested_include, override, virtual_root); --format renders one row per source (.Path, .Kind, .Order, .Depth, .Parent, .Truncated, .Services, .Overrides), or one row per service (.Service, .DefinedIn, .Overrides) with --by-service. The text output is the dashboard's m config map without colour — both render through the same layout — so the two never disagree.

A group runs its obvious subcommand when invoked bare: kranz config is config show, kranz services is services list, kranz actions is actions list, and kranz ports is ports list.

plan prints the dependency waves the supervisor itself gates readiness on, and pulls in the dependencies of whatever you selected:

console
$ kranz plan gateway
Wave 1:
  shared-infra
Wave 2:
  migrate  (after shared-infra)
Wave 3:
  catalog-api  (after migrate)
  billing-api  (after migrate)
Wave 4:
  gateway  (after billing-api, catalog-api)

Bare plan and --operation start work from configuration alone. Stop and restart previews require a running runtime because they report only the currently running dependents that the real operation would affect.

doctor reports every finding rather than stopping at the first, and exits 3 when any check fails.

Runtimes ​

bash
kranz ps                            # every runtime this user has running
kranz clients                       # who is attached to those runtimes
kranz up                            # empty runtime; Ctrl+C stops it
kranz up -d                         # empty background runtime; prompt returns
kranz up [SELECTOR ...]             # selected services; foreground log stream
kranz up -d [SELECTOR ...]          # selected services in the background
kranz up --start                    # every enabled service; foreground stream
kranz up --start -d                 # every enabled service in the background
kranz attach                        # open the TUI on a running runtime
kranz status [SELECTOR ...]
kranz start SELECTOR ...
kranz stop SELECTOR ...
kranz restart SELECTOR ...
kranz reload                        # re-read the configuration
kranz down                          # stop the project and end the runtime
kranz down --force                  # recover a runtime that stopped answering

down stops the whole runtime and takes no service selectors; use stop for a single service. down --force is emergency recovery for an unreachable session, not the ordinary way to stop a project.

Every runtime has an independent background supervisor. Leaving a TUI through its detach action does not stop it; confirming shutdown or running an external down stops the runtime and closes attached clients cleanly. The quit confirmation's Close & choose action stops the current runtime and opens the live chooser so another runtime can be attached without restarting the TUI.

ps lists runtimes; clients lists the CLI, TUI, and MCP connections working in them, including client identity, PID, and connection age. They are two commands because they answer two questions: what is running, and who is using it. Narrow either one to a single runtime with -p NAME|ID.

Uptime uses compact whole units: 42s, 3m12s, 10h20m, 2d4h15m, 2w4d5h, 1mo2d3h, or 1y2mo3d. Smaller units are truncated. Calendar years and months are calculated in UTC; dates clamp to the last day of a month when necessary.

console
$ kranz ps
ID        PID    NAME       PROJECT    SERVICES  CLIENTS  STATE    UPTIME
7fa21c8d  18400  shop-dev   Shop       4/4       2        running  18m0s
91bc430a  18022  billing    Billing    3/3       0        running  6m0s
3de94a71  19001  analytics  Analytics  2/2       1        running  2m0s

$ kranz clients
RUNTIME    PID    CLIENT  CONNECTED
shop-dev   18421  TUI     18m
shop-dev   18472  MCP     4m
analytics  19108  CLI     <1s

The shop-dev clients share one runtime row. TUI, foreground log streaming, CLI, and MCP connections come and go without taking ownership. The short-lived command on analytics appears only while it is connected. The runtime's background owner is infrastructure rather than a client, so kranz clients does not show it and never shows its own discovery connection. A runtime with no clients remains visible in kranz ps and the TUI Runtimes window, where its CLIENTS value is -.

up has two independent choices. With no selectors it starts no services; selectors start only what they name, and --start explicitly starts every enabled service. Without -d, the terminal owns the runtime lifetime, streams logs, and receives project exit codes; -d returns after readiness.

Built-in clients are displayed compactly as TUI, CLI, and MCP. Meaningful variants retain their identity, such as TUI: attach, CLI: foreground, or MCP: codex. An MCP launcher can set KRANZ_MCP_CLIENT=codex to produce the latter when several agent clients need to be distinguished. The label changes only what kranz clients displays; it does not select or rename a runtime.

Row-oriented inspection commands accept Docker-style Go templates. Without the table prefix, the template renders once per row with no header:

console
$ kranz ps --format '&lbrace;&lbrace;.PID}}\t&lbrace;&lbrace;.Name}}\t&lbrace;&lbrace;.State}}'
18400   shop-dev   running

$ kranz clients --format 'table &lbrace;&lbrace;.PID}}\t&lbrace;&lbrace;.Runtime}}\t&lbrace;&lbrace;.Client}}'
PID     RUNTIME    CLIENT
18421   shop-dev   TUI
18472   shop-dev   MCP: codex

ps exposes .ID, .FullID, .PID, .Name, .Project, .Services, .Clients, .State, .Uptime, .Directory, .Mode, .Version, and .StartedAt. clients exposes .Runtime, .ID, .FullID, .Project, .PID, .Client, .Surface, .Label, .Version, .Connected, and .ConnectedAt. The json, lower, upper, split, and join template functions are available. --format and --output=json cannot be combined. The same formatter is available for status, ports, doctor, config explain, services, actions, and tags. Run a command with --format '&lbrace;&lbrace;json .}}' to discover its stable fields and current values; table may be prefixed once the desired columns are selected.

The three live listings can filter exact, case-insensitive values and refresh until interrupted. Comma-separated values are alternatives for one key; different keys must all match. --count makes a watch bounded for automation, while --interval changes the one-second default:

bash
kranz ps --filter state=running --filter client=mcp
kranz clients --filter client=mcp,tui --watch --count 5
kranz status --filter state=running,unhealthy --watch --interval 2s

ps filters name, project, state, or client; clients filters runtime, project, client, surface, or label; and status filters name, state, or health after resolving selectors. Watch mode accepts text or template output because concatenated JSON documents would not be valid.

Every ps row is an independently owned lifecycle runtime. A TUI, foreground log stream, CLI command, and MCP server are clients of that session — they appear in clients, never as a second ps row. The same project can have several rows when each was given a distinct runtime name with -p. --output json still carries the launch mode, services, running, and clients counts for scripts that want them.

Serving runtimes to a coding agent ​

bash
kranz mcp                           # one global stdio MCP server
kranz mcp -C /path/to/project       # pinned to exactly one project

mcp speaks the Model Context Protocol on stdin and stdout, so stdout carries JSON-RPC framing and nothing else; --output is refused and diagnostics go to stderr.

The server needs no project. It starts in any directory, writes no registry entry, supervises nothing, and picks the runtime per call: the tool's runtime argument, then the -C/-p pin, then the directory it was started in, then an error carrying the runtimes that would have worked. -C, -f, and -p pin the connection to one project and make every other address an error. See the MCP reference.

Logs ​

bash
kranz logs [SELECTOR ...]          # the last 50 lines
kranz logs --tail 200
kranz logs --all                   # everything still buffered
kranz logs --since 5m
kranz logs api --follow
kranz logs api --with-actions      # the service and the actions it has run
kranz logs api --source stderr     # stdout, stderr, or kranz; comma-separated
kranz logs api --plain             # no time column, no label column
kranz logs clear api               # discard one buffer
kranz logs clear --force           # discard every buffer in the project

A bare kranz logs returns the last fifty lines, because every service keeps a thousand and a few services make that thousands. --all returns everything. --tail and --since compose: --since 5m --tail 50 is the last fifty lines from the past five minutes. A stopped service keeps its buffer, so logs still answer for a service that has already died. --follow resumes from a cursor rather than reprinting, and stops on Ctrl+C.

--plain is --no-timestamps and --no-labels together: both columns exist to tell interleaved streams apart, which reading one stream back does not need. logs clear narrows the same way logs does, and an unqualified clear needs --force, because it is the one shape that cannot be narrowed afterwards.

Every start of a service and every execution of an action is a numbered run, so a single buffer stays addressable after the thing that filled it restarted:

bash
kranz logs analytics/stats                # the latest run, whole
kranz logs analytics/stats --run 7        # run number 7
kranz logs analytics/stats --run -1       # the latest run
kranz logs analytics/stats --run -2       # the run before it
kranz logs analytics/stats --runs 3       # the last three runs
kranz logs api --run -1                   # only the newest start of a service
kranz runs                                # bounded run catalog
kranz runs api analytics/stats            # narrow catalog by target
kranz runs --status failed --since 2h     # filter by status and start time
kranz runs --limit 10                     # newest ten matching runs
kranz runs --format 'table &lbrace;&lbrace;.Run}}\t&lbrace;&lbrace;.Status}}\t&lbrace;&lbrace;.Duration}}'
kranz runs retention                      # per-target retention limits
kranz runs delete api#4 --confirm         # delete one completed run and retained output

Every line carries its stable absolute identity (api#7 or analytics/stats#3). Relative values are query syntax only and are never printed as identities.

A positive --run is the absolute run number Kranz assigned. A negative one is an offset from the newest run in the independent run catalog, so -1 keeps meaning "the latest" even after its output has aged out. When a retained run has lost its output prefix, text output prints the exact missing lines/bytes marker before the available tail. kranz runs publishes the oldest retained run, catalog/output budgets, evicted summary count, provenance, and output state (complete, partial, or unavailable). The catalog belongs to the live runtime session and is not restored after down.

An explicit --run that nothing retains exits 4 with run_not_retained and names the range each selected stream can answer for, so a typo, a deleted run, and a run that produced no output stay distinguishable. --runs N is a window rather than an address and stays silent when it overlaps the start of history.

Actions ​

bash
kranz actions [OWNER]
kranz actions info OWNER/ACTION
kranz actions run OWNER/ACTION [--param NAME=VALUE ...] [--no-param NAME ...] [--confirm]

An action is identified by owner and name together, so a service action and an action-group action may share a name. Running one goes through the runtime, which owns the execution slot. A failed action fails the command. Interactive actions need the real terminal and are run from the TUI.

A parameterized action takes repeated --param NAME=VALUE flags. The first = separates the name; the rest belongs to the value. Repeating one name is an error except for a checkbox group, where each repeat adds a selected value. kranz actions info prints the accepted parameters, their controls, and the limits each one accepts, and values are validated before a runtime call and again at the runtime boundary. There is no interactive prompt for a missing value. --no-param NAME leaves a parameter out of the invocation entirely, which is not the same as omitting the flag: that falls back to the declared default. A parameter's confirm turns a run into a confirmed one only for the values that carry it; kranz actions info lists them under "Values that ask".

For an action with confirm: true, the first run attempt is fail-closed and returns confirmation_required with the exact resolved plan. Review it, then repeat the same command with --confirm. The CLI obtains and consumes the supervisor's plan-bound one-shot token inside that confirmed invocation. Actions without confirm: true still run in one command. MCP exposes the same flow with its explicit confirmation_token field.

Help and version ​

bash
kranz --help                 # top-level command list
kranz COMMAND --help         # options for one command
kranz help COMMAND           # the same command help
kranz version                # version, commit, and build time
kranz --version              # short form of version

Use command-specific help when a command has subcommands: for example, kranz logs --help introduces show and clear, then kranz logs show --help lists the query options. version does not need a project or running runtime.

Shell completion ​

bash
kranz completion bash > /usr/share/bash-completion/completions/kranz
kranz completion zsh  > "${fpath[1]}/_kranz"
kranz completion fish > ~/.config/fish/completions/kranz.fish

The Linux packages install these already.

The scripts are generated from the same command tree as --help, so they offer what the binary actually has: commands, the subcommands of a group, and the options of whichever command is being typed. kranz logs --<TAB> offers the log flags, kranz logs clear --<TAB> offers only the two clear takes, and an option with a fixed set of values completes those too — --source to a stream name, --format to a graph format. A flag that takes a path completes filenames.

Machine-readable output ​

--output json wraps every successful non-interactive result in a versioned envelope:

console
$ kranz ps --output json
{"schema_version":1,"data":[]}

Commands report the result they produced rather than an empty success marker:

console
$ kranz restart api --output json
{"schema_version":1,"data":{"command":"restart","services":["api","web"]}}

$ kranz reload --output json
{"schema_version":1,"data":{"command":"reload","runtime":"shop-dev","changed":false,"added":[],"removed":[],"updated":[],"pending":[]}}

init --output json omits the human preview and reports the absolute path it wrote, the project, services, action count, and suggested next commands. up -d --output json reports the new runtime's full ID, name, PID, and mode. Foreground up and attach require a terminal; help and completion output are text artifacts rather than data envelopes.

Failures use the same envelope with an error object, and stdout stays valid JSON so a script never has to parse prose:

console
$ kranz services --output json
{"schema_version":1,"error":{"code":"no_project","message":"no Kranz configuration was found in this directory","hint":"Run from a project directory or pass -f PATH."}}

Commands that completed their work but found an unsuccessful outcome keep the useful result in data and signal failure with the exit code. In particular, a failed doctor returns findings, services_checked, problems, and warnings in one envelope with exit 3; a failed action returns its captured output and exits 1. They never append a second envelope.

kranz logs --follow --output json emits one envelope per event as it arrives. For status, services are under .data.services[]; .data.session carries the runtime identity. Unconfigured readiness and liveness probes are null, not successful booleans.

Exit codes ​

CodeMeaning
0Success
1Internal error, or an action that ran and failed
2Usage error — unknown command, missing or malformed argument
3Configuration error, including a failed doctor
4Not found — no such runtime, service, action, selector, or retained run
5Conflict — a runtime with that name is already active, or a file exists
6Runtime unavailable — unreachable, incompatible, or a refused force-down

A foreground up instead exits with whatever the project asked for through availability.exit_on_end or exit_on_failure, and dies by signal when it is signalled, so a supervisor above Kranz sees the truth.

Configuration discovery ​

With no -f, Kranz loads the first file that exists in the working directory, in this order:

  1. kranz.yaml
  2. kranz.yml
  3. process-compose.yaml
  4. process-compose.yml
  5. Procfile.dev
  6. Procfile

Native configuration wins over a Process Compose file in the same directory, so adding kranz.yaml to a project takes effect without deleting anything.

When no conventional root file exists, Kranz discovers autonomous configs below the working directory and composes them into a virtual project. If discovery finds nothing either, commands that need a project fail with config_not_found, while a bare kranz keeps its runtime picker fallback.

Layering ​

Several autonomous files compose into one effective project:

bash
kranz -f repositories/catalog/kranz.yaml -f 'repositories/*/kranz.yaml'

Each file keeps its own project metadata, defaults, .env, and relative path root. Partial files are override layers, applied in order with --override kranz.local.yaml. kranz config explain shows which source set each field. Composition and override rules are listed in the configuration reference.

Signals ​

SIGINT, SIGTERM, and SIGHUP begin an orderly shutdown: attached clients close, then every process group the runtime owns is stopped synchronously. Detached resources follow their stop_on_exit setting.

Files Kranz reads and writes ​

PathPurpose
./kranz.yaml and the other discovered namesProject configuration
Included files and override layersComposed configuration
.env beside each configuration fileEnvironment for that file's services, if present
.env beside each override layer${VAR} expansion in that layer, if present
Every file named in env_filesEnvironment
$XDG_RUNTIME_DIR or the user's temporary directoryRuntime registry, locks, and sockets
$XDG_CONFIG_HOME/kranz/settings.yaml (Linux)Personal appearance
~/Library/Application Support/kranz/settings.yaml (macOS)Personal appearance

Runtime state belongs to the invoking user. Kranz never manages another user's runtime and starts no system-wide daemon.

See the controls reference for every key binding.

Released under the MIT License.