pfp 0.5.2

Prefect CLI - a fast Rust CLI for managing Prefect deployments and flow runs
pfp-0.5.2 is not a library.

pfp

A fast CLI for managing Prefect 3 deployments and flow runs, built for both human and AI agent use.

What it does

pfp talks directly to the Prefect REST API, bypassing the official CLI's limitations: truncated output, unreliable exit codes, and awkward parameter syntax.

  • Substring matchingpfp run happy-t finds happy_terraform/happy-terraform-prod
  • Correct exit codes — 0 for success, 1 for flow failure, 2 for CLI errors
  • --watch that works — polls until completion with state change reporting
  • Dotted path parameters--set config.action=destroy builds nested JSON
  • Parameter validation — typos caught before submission with "did you mean?" suggestions
  • --json on everything — structured output for programmatic consumption
  • Full deployment names — no truncation, ever

Installation

From crates.io:

cargo install pfp

From source:

git clone https://github.com/devinbarry/pfp.git
cd pfp
cargo install --path .

Configuration

pfp reads your existing Prefect configuration. No extra config files needed.

API URL is resolved from ~/.prefect/profiles.toml:

active = "self-hosted"

[profiles.self-hosted]
PREFECT_API_URL = "https://prefect.example.com/api"

The PREFECT_API_URL environment variable takes priority if set.

Authentication is optional. If your server requires it, set PREFECT_API_AUTH_STRING with a username:password value — pfp encodes it as HTTP Basic Auth:

export PREFECT_API_AUTH_STRING="admin:secret"

Usage

pfp ls

List all deployments:

$ pfp ls
DEPLOYMENT                                         STATUS   WORK POOL
happy_ansible/happy-ansible-prod                   ACTIVE   docker-prod
happy_terraform/happy-terraform-prod               ACTIVE   docker-prod
hello_world/hello_world-dev                        ACTIVE   docker-dev
update_hosts/update_hosts-prod                     ACTIVE   docker-prod
pfp ls --json    # JSON array of deployment objects

pfp run

Run a deployment by substring match:

pfp run happy-t                          # create flow run and exit
pfp run happy-t --watch                  # poll until completion
pfp run happy-t --set config.action=plan # override parameters

Combining --watch with parameters:

$ pfp run happy-t --watch --set config.action=apply --set config.auto_approve=true
Resolved: happy_terraform/happy-terraform-prod
Created flow run 'venomous-alligator' (171a3f55-...)

17:34:00 | Pending
17:34:05 | Running
17:35:27 | Completed

With --watch, the exit code reflects the flow run outcome: 0 for Completed, 1 for Failed/Cancelled/Crashed.

pfp run happy-t --json              # JSON output of created flow run
pfp run happy-t --watch --json      # JSON object per state change

pfp runs

Show recent flow runs for a deployment:

$ pfp runs happy-t
FLOW RUN                   STATE        STARTED              DURATION   ID
production-apply           COMPLETED    2026-02-21 17:34     45s        e130c152
production-destroy         COMPLETED    2026-02-21 17:34     8s         171a3f55
production-plan            COMPLETED    2026-02-21 00:05     3s         7137cfe7
pfp runs happy-t --json    # JSON array of flow run objects

pfp inspect

Fetch one flow run by its full UUID. Unlike pfp runs, this performs an exact lookup and is not limited to recent runs, so it is safe for automation even when many runs are created concurrently.

pfp inspect e130c152-db01-428a-9698-e8404cd2c5d3
pfp inspect e130c152-db01-428a-9698-e8404cd2c5d3 --json

pfp logs

Show logs for a flow run (requires full UUID):

$ pfp logs e130c152-db01-428a-9698-e8404cd2c5d3
2026-02-21T17:34:36 | INFO     | Worker submitting flow run 'e130c152-...'
2026-02-21T17:34:41 | INFO     | Beginning flow run 'production-apply' for flow 'happy_terraform'
2026-02-21T17:34:41 | INFO     | Action: apply
2026-02-21T17:35:27 | INFO     | Flow run completed successfully

Get the flow run UUID from pfp runs <query> --json.

pfp logs e130c152-db01-428a-9698-e8404cd2c5d3 --json    # JSON array of log entries

pfp pause / pfp resume

pfp pause happy-t     # pause the deployment
pfp resume happy-t    # resume it

pfp cancel

pfp cancel e130c152-db01-428a-9698-e8404cd2c5d3    # cancel a running flow run

Substring matching

All commands that take a deployment name use unique substring matching against the full flow_name/deployment_name identifier:

Matches Behavior
0 Error: No deployment matching 'query'
1 Uses the match
2+ Error: Ambiguous match 'query', candidates: with list

Use pfp ls to discover available deployment names and find a unique substring.

Parameters

The --set flag builds nested JSON from dotted paths:

--set config.action=destroy --set config.auto_approve=true

Produces:

{"config": {"action": "destroy", "auto_approve": true}}

Values are auto-typed:

Input Type
true / false boolean
42 integer
3.14 float
["a","b"] JSON array
{"k":"v"} JSON object
anything else string

Parameters from --set are merged with the deployment's defaults. Explicit values override defaults.

Validation

Before submitting a flow run, pfp validates --set parameters against the deployment's OpenAPI schema. Typos are caught immediately:

$ pfp run happy-t --set config.dry_urn=true
Error: unknown parameter 'config.dry_urn'

Valid parameters for config:
  action, ansible_debug, ansible_limit, ansible_tags,
  deployment_name, dry_run, git_ref, inventory_name,
  playbook_name, vault_secrets

Did you mean 'config.dry_run'?

Validation is automatic — no flags needed. If a deployment has no schema (older Prefect versions), validation is skipped and parameters are passed through as before.

Params file

For large or deeply-nested parameters — an array of objects, say — that are awkward to express with repeated --set flags, pass the whole parameters object as JSON with --params-file:

pfp run happy-t --params-file payload.json     # read from a file
cat payload.json | pfp run happy-t --params-file -   # read from stdin (a single -)

The payload must be a JSON object matching the deployment's parameters shape:

{
  "environment": "production",
  "config": {
    "action": "apply",
    "vault_secrets": [
      {"path": "kv/prod/app/db", "field": "PASSWORD", "env_var": "DB_PASSWORD"},
      {"path": "kv/prod/app/r2", "field": "ACCESS_KEY", "env_var": "AWS_ACCESS_KEY_ID"}
    ]
  }
}

Precedence is deployment defaults < --params-file < --set, so a single --set config.action=plan can still override one field of a large payload.

The payload is validated against the deployment schema — the same client-side validation as --set — before the run is created. An unreadable file, malformed JSON, or a non-object top-level fails fast with exit code 2 before any API call.

Exit codes

Code Meaning
0 Success — command completed, flow run finished (if --watch)
1 Flow failure — flow run ended in Failed, Cancelled, or Crashed (only with --watch)
2 CLI error — bad arguments, no match, ambiguous match, API unreachable

License

MIT