pfp 0.5.0

Prefect CLI - a fast Rust CLI for managing Prefect deployments and flow runs
pfp-0.5.0 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 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