These docs are under active development.
On this page
reference 8 min read

Invocation and flags

This page is the reference for pekit's command line: the grammar, how flag values are written, the flags every command accepts, and how pekit decides which recipe to run. For what each command does and how it selects targets, see Commands and targets.

Grammar

pekit [global flags] <command> [args and flags]
pekit [global flags] workspace [workspace flags] <command> [args and flags]

There is exactly one command per invocation, drawn from build, test, install, package, publish, clean, and workspace. Anything before the command that is not a recognised flag is an error (unknown_command / unknown_flag); a missing command is missing_command.

Flags may appear before or after the command. Pekit parses flags the same way on both sides of the command word, so these are equivalent:

$ pekit --dry-run build
$ pekit build --dry-run

The one structural exception is workspace, whose tail is parsed specially: the workspace-only flags (--fail-fast, --jobs) must come before the delegated command, and everything after the delegated command is parsed as an ordinary invocation of that command. See Workspaces.

-- stops flag parsing

A bare -- ends flag parsing. Every token after it is captured verbatim and treated as a positional selector:

$ pekit build -- ./tools ./libs

Positional selectors may not begin with -. A leading-- token before -- is read as a flag (and rejected as unknown if it is not one); after -- it is still rejected as a selector with the diagnostic "use -- before selector-like values". Use -- for selectors that merely look option-like (for example a path), not to smuggle a leading dash into a selector.

Flag value forms

Flags fall into three shapes, and the shape decides how a value may be written.

Shape Examples How a value is written
No-value --dry-run, --quiet, --verbose, --json, --allow-unused Never takes a value. --flag=x is an error (unexpected_flag_value).
Required-value --recipe, --workspace, --version (-V), --env, --keyring --flag value or --flag=value.
Optional-value --local, --prefer-local, --no-build Value only via --flag=value. The bare form is valid and means "empty value".

Required-value flags accept either the separated form (--recipe path) or the = form (--recipe=path). With the separated form pekit consumes the next token as the value — unless that token is missing, is --, or itself begins with -, in which case the flag errors with missing_flag_value. With the = form an empty value (--recipe=) is rejected the same way.

Optional-value flags take a value only through =. They never consume the following token. So:

$ pekit build --local              # --local with an empty value
$ pekit build --local=/src/mypkg   # --local with a value
$ pekit build --local /src/mypkg   # --local (empty) + /src/mypkg as a selector

-V is an alias for --version and behaves identically (it is a required-value flag; it is not a version-print flag — pekit has no version banner and no --help).

Per-key keyring values use a dotted-path form, --keyring.<path>=<value>, which always requires the = and a non-empty path.

Global flags

Seven flags are parsed in any position regardless of command; six are accepted by every command, and the seventh, --workspace, is only valid with the workspace command. The rest of pekit's flags (--version/-V, --latest, --local, --env, --keyring, --all, and so on) are command-specific and are covered on Commands and targets.

Flag Effect
--recipe <path|dir> Use this recipe instead of searching upward from the cwd.
--workspace <path|dir> Use this workspace file/root. Only valid with the workspace command.
--allow-unused Downgrade "command does not support this recognised flag" from an error to a suppressed notice.
--dry-run Build the plan and stop before any side effect.
--quiet Suppress routine progress; print only warnings and result events.
--verbose Emit extra staging/progress events.
--json Emit machine-readable NDJSON events instead of the human log.

Mutual-exclusion rules

Validation rejects contradictory combinations up front:

Rejected combination Diagnostic
--quiet with --verbose --quiet and --verbose cannot be used together
--quiet with --json --quiet and --json cannot be used together
--recipe with --workspace --recipe and --workspace cannot be used together
--workspace without the workspace command --workspace can only be used with the workspace command
More than one of --version / --latest / --all-versions mutually exclusive
--local with --prefer-local mutually exclusive
--output-only with --target-only (clean) mutually exclusive

--allow-unused

Every command declares which flags it accepts. Passing a recognised flag that the effective command does not support is normally an error (unsupported_flag), for example version selection on clean, or --all on build. --allow-unused turns each such case into a non-fatal notice: pekit records it and emits an unused_suppressed event at the start of the run instead of aborting.

--allow-unused only relaxes the "recognised-but-unused" check. It does not excuse:

  • unknown flags (unknown_flag) or unknown commands (unknown_command);
  • malformed flag values — missing values, empty = values, or =value on a no-value flag;
  • the mutual-exclusion rules above;
  • bad selectors (a selector beginning with -, or more selectors than the command accepts).

This is what lets you fan a shared set of flags across a whole workspace even when some members' commands ignore some of the flags.

--dry-run

--dry-run runs the same planning pipeline as a real invocation — it locates and loads the recipe, resolves the version selector, and prepares source state — but stops at every side-effecting boundary instead of crossing it. In place of the action pekit emits a plan event:

  • a target that would run emits target_plan ("would run target") and does not execute the command or touch the stage directory;
  • clean that would delete managed output emits clean_plan ("would remove managed output") and removes nothing;
  • package / publish emit their plan events rather than writing or shipping artifacts.

Use --dry-run to inspect exactly what a command would do. Combine it with --json to get the plan as a single structured object (below).

Output renderers

The renderer is chosen from the flags. --json selects the JSON renderer; otherwise the human renderer is used, and --quiet puts it in quiet mode.

Human (default)

Pekit's own events print as timestamped lines, [15:04:05] message, optionally prefixed with a context tag (member, command, target, package, version) when the event carries one. Live output from the targets pekit runs is streamed through with a [member target version] prefix, preserving each line's original stream — stdout stays on stdout, stderr stays on stderr. Fatal errors are written to stderr.

--quiet

Quiet mode keeps the human format but drops routine progress, emitting only warning, artifact, publish, and workspace_summary events. Target output is unaffected. --quiet cannot be combined with --verbose or --json.

--verbose

Verbose mode adds extra events (such as per-target stage preparation) to the human log. It is otherwise the human renderer.

--json

--json emits newline-delimited JSON — one event object per line — to stdout, so the run can be consumed by another program. Each object has a type and a timestamp plus whatever context fields apply; target output arrives as target_output events carrying the stream and text. Errors are emitted as an error event.

--dry-run --json is special-cased: instead of streaming events, pekit buffers them and, at the end, prints a single plan object:

{"type":"plan","time":"...","command":"build","workspace":false,"events":[ ... ]}

The events array holds the buffered plan events; command is the effective command and workspace is whether the run went through the workspace command.

Exit codes

Pekit uses two exit codes only:

Code Meaning
0 Success.
1 Any error — bad invocation, missing recipe, failed build, and so on.

There are no finer-grained codes, and there is no --help or help command: this reference and Commands and targets are the manual.

How a recipe is located

Pekit resolves exactly one recipe per (non-workspace) run:

  1. --recipe <path|dir> — if given, this wins. A directory means <dir>/pekit.toml; a file path is used as-is. A missing target is missing_recipe.
  2. A remote positional — if --recipe was not given and the first positional argument looks like a remote locator, it is taken as the recipe (and dropped from the selector list).
  3. Walking up — otherwise pekit searches from the current directory upward for the nearest pekit.toml, stopping at the filesystem root (not_found if none is found).

Once the recipe root is known, and unless --workspace was supplied, pekit also walks upward from that root for a workspace.pekit.toml; if one is found the recipe is treated as a member of that workspace.

Remote recipe locators

A remote locator is a recipe reference that points at a git repository rather than a local path. A value is treated as remote when it does not start with . or / and it either begins with github.com/, contains ://, ends with .git, or contains a // subdirectory separator. Examples:

$ pekit build github.com/acme/tools
$ pekit build github.com/acme/tools//subdir@v1.2.0
$ pekit build https://git.example.org/acme/tools.git//pkg@main

The locator may carry a //subdir to select a directory inside the repository and an @ref (branch, tag, or commit) to pin a revision. When pekit sees a remote locator — whether as the positional or as the value of --recipe — it materialises it: it mirror-clones the repository into a per-user cache under the system temp directory (fetching updates if already cached), checks out the resolved commit into a clean working tree, and then treats the checkout (plus any //subdir) as the recipe root. From there the run proceeds exactly as it would for a local recipe.