Skip to content

CLI reference

Global options

Global options bind to fm itself and go before the first task name (fm --json test, not fm test --json):

option effect
-h, --help help for fm, or the named group/task
-V, --version print the version and exit
-l, --list list tasks (flat)
--tree list tasks grouped by command group
--where TASK print the task's source file:line
-n, --dry-run print the parsed plan without running
-k, --keep-going run every branch even if one fails
--fail-fast stop at the first failure
-s, --sequential run one at a time (default: parallel)
-j, --jobs N max parallel tasks (default: cores - 1)
-y, --yes assume yes to every confirm() gate
--no-input never prompt; error if input is required
-q, --quiet suppress the per-task summary
-v, --verbose replay captured output even on success
--color WHEN when to colour: always|never|auto (default)
--no-color disable ANSI colour (same as --color=never)
--no-progress no progress bar, eta, or timing capture
--json stdout is one JSON document (captures output)
--timings show per-task durations
-C, --directory PATH run as if launched from PATH
-f, --tasks-file PATH only this tasks file, no tasks cascade
--config PATH only this config file, no config cascade
--install-completion [SHELL] install shell completion
--setup-completion [SHELL] print completion for eval
--uninstall-completion [SHELL] remove the completion hook

--help is the one global allowed anywhere before --: fm deploy --help is a read-only help request, never an execution. fm --help documents the runner and its globals, fm --help docs a group, fm --help deploy a task. Because it wins wherever it lands, help is also the one parameter name a task can't use — it would map to --help, which is always intercepted (see Typed signatures).

Decorator surface

from footman import task, group, finalize

@task                       # bare
def build(): ...

@task(name="ci-build")      # override the command name
def build(): ...

@task(pre=[fmt], post=[notify])   # dependencies (run before / after)
def check(): ...

@task(keep_going=True)      # surface every failure in the subtree, don't stop at the first
def check(): ...

@task(atomic=True)          # never kill this task's subprocesses mid-flight on a sibling failure
def format(): ...

@task(confirm="Deploy to prod?")  # a yes/no gate before the task and its prereqs run
def deploy(): ...

@task(interactive=True)     # owns the real terminal; the run goes fully sequential
def scaffold(): ...

@task(progress=False)       # duration has no pattern: never timed, only pulses
def repl(): ...

@task(infinite=True)        # runs until Ctrl-C: hinted at start, never timed
def serve(): ...

fmt.opts(atomic=True)       # override a task's policy for one use (pre=/post=/body call)

release = group("release", help="Cut a release")

@release.task
def wheel(): ...

@finalize                   # edit the merged task tree at discovery
def gate(tasks):            # (see Monorepos & config)
    for t in tasks:
        t.add_pre(tasks["audit"])

Runtime helpers

Import Purpose
run(cmd, ...) run a command or callable in the task context
parallel(*calls) fan tasks/thunks out concurrently
passthrough() arguments after -- on the command line
Context, use_context the task context; install one from your own code
Many[T], nosplit one-or-many; opt a collection out of comma-splitting
forward, Forward[T] thread a value to the tasks/groups this task dispatches — see Chaining & parallelism
ask("prompt") prompt for a missing value (CLI > env > default > prompt); CI-safe
prompt(), confirm(), select() mid-task questions — only inside an interactive=True task
.opts(**policy) per-use policy override (keep_going, atomic, …) on a task or group
Annotated[T, suggest(fn)] dynamic completion for a parameter
exists, isfile, isdir require a Path value to exist on disk
between(lo, hi) inclusive numeric bounds (a bare range works too)
env("VAR") fall back to an environment variable (CLI > env > default)
check(fn) custom post-coercion validator (ValueError rejects)
doc("…") one-line parameter help — shown in --help, completion, the catalog
footman.tools typed wrappers for ruff, basedpyright, pytest, uv, …
footman.testing Runner/Result + recording() — see Testing
include, plugin adopt tasks from modules/packages — see Composing
inherited() the task this one shadows in the cascade — call it to extend it
progress(done, total), track(iterable) report a task's own progress; the bar fills from it
fetch(url, …) download into footman's cache — revalidating, verifying, recorded as a step
@requires_dep/_tool/_env, @requires disable-but-list a task that can't run here

Configuration

Settings have their own page — the precedence ladder (user-level file, the project cascade, --config, environment, flags), the files, and every key: Configuration.