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 matching —
pfp run happy-tfindshappy_terraform/happy-terraform-prod - Correct exit codes — 0 for success, 1 for flow failure, 2 for CLI errors
--watchthat works — polls until completion with state change reporting- Dotted path parameters —
--set config.action=destroybuilds nested JSON - Parameter validation — typos caught before submission with "did you mean?" suggestions
--jsonon everything — structured output for programmatic consumption- Full deployment names — no truncation, ever
Installation
From crates.io:
From source:
Configuration
pfp reads your existing Prefect configuration. No extra config files needed.
API URL is resolved from ~/.prefect/profiles.toml:
= "self-hosted"
[]
= "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:
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 run
Run a deployment by substring match:
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 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 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 pause / pfp resume
pfp cancel
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:
Produces:
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:
|
The payload must be a JSON object matching the deployment's parameters shape:
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