Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

CLI

The rflow binary is a pure interface over the core engine. Every command accepts --path <dir> (short -p) to point at a project directory; the default is the current directory.

rflow --help
Blazing fast EVM workflow engine built in rust
 
Usage: rflow <COMMAND>
 
Commands:
  new        Create a new rflow project (alias: init)
  templates  Browse, inspect, check and author templates (builtin + community registry)
  add        Add a workflow (from a template), network, contract, relayer or notification channel to rflow.yaml
  doctor     Check local dependencies (Docker, Foundry, Postgres, Node) and project readiness
  explain    Plain-English breakdown of what a workflow does (static, from rflow.yaml)
  abi        Fetch, inspect and reverse-lookup contract ABIs
  contract   Contract helpers - `contract add` fetches the verified ABI when --abi is omitted
  import     Convert another platform's config into an rflow project
  start      Validate then boot the engines (lazy), reconcile relayers, recover and run
  validate   Strict collect-all validation of rflow.yaml
  lint       Opinionated safety lint over rflow.yaml (policy-driven, static, no DB)
  ci         One-command CI gate: validate + lint + schema/template/plan/test checks
  schema     Print the JSON Schema for rflow.yaml (editor autocomplete/validation)
  trigger    Manually fire a workflow
  ls         List workflows: state, trigger, cursor, runs today
  status     Engine health, chains, relayer registry, recent runs
  tables     List the queryable tables (indexed event tables + the rflow journal)
  runs       Inspect the run journal
  history    Explore workflow run history (a richer `runs list`); subcommands: txs, approvals, waiting, export
  approve    Approve a parked send (by approval id or run id)
  reject     Reject a parked send (by approval id or run id)
  approvals  List send approvals
  state      Durable-state dashboard + repair (inspect | cursors/waits/delays/circuit)
  cursors    Inspect (ls) or move (set) trigger cursors
  waits      Inspect (ls) or cancel parked saga waits
  delays     Inspect (ls) or wake parked durable delays
  circuit    Inspect (ls) or reset circuit breakers
  budgets    Inspect durable spend budgets (consumed / reserved / remaining)
  spend      Native spend history: gas + value by workflow / relayer / network; subcommand: export
  token      Manage named API bearer tokens for config.server.auth
  secrets    Inspect declared secrets and verify external-provider resolution (never prints values)
  relayers   Manage relayer wallet mappings
  workflow   Pause or resume a workflow
  list       Inspect and mutate runtime watchlists (`lists:`)
  mcp        Serve the Model Context Protocol over stdio (AI agent/editor integration)
  replay     Replay a workflow over historical blocks in an isolated session (dry-run by default), or manage replay/test sessions (`ls` / `prune`)
  versions   Inspect the deployed config version history (ls | show <id> | diff <a> <b>)
  plan       Semantic diff of a config change + active runtime conflicts (the deploy gate)
  rollback   Reconstruct a stored config version back to rflow.yaml (structure; secrets stay placeholders)
  retention  Plan or run journal retention (history pruning driven by config.retention)
  archive    Export a redacted JSONL archive of runs + steps + approvals + failures in a date range
  command    Debug a single `command:` step in isolation (no signer, no db, no run)
  test       Execute one workflow once in dry-run mode from a fixture or a real transaction
  help       Print this message or the help of the given subcommand(s)
 
Options:
  -h, --help     Print help
  -V, --version  Print version

rflow new

rflow new [--output <dir>] [--name <project-name>] [--template <id>] [--yes]
          [--answer key=value ...] [--answers-file answers.yaml]
          [--registry github:owner/repo[@branch]]

Scaffolds a project from the template registry (rflow templates ls): rflow.yaml, ABIs, .env + .env.example, docker-compose.yml (postgres on localhost:5448), a .gitignore and a .rflow/template-lock.yaml recording the install (template id, version, answers). Interactive on a TTY — a template picker (id + risk + summary), then one typed prompt per template input; --yes (or a non-TTY) fills every input from its default. --answer key=value and --answers-file drive it non-interactively (CI/agents); every answer is type-checked before rendering (addresses, cron expressions, durations, …).

--template community/<id> installs from the community registry — fetched SHA-pinned and hash-verified from the rflow repo, gated by the community doctor profile and a provenance banner before anything renders (--registry points the same mechanism at a fork, loudly labeled UNTRUSTED). The interactive picker's final entry fetches the community index and lists its ids.

Legacy scaffold ids keep working: transfer-alert, cron-report and treasury-approval resolve to their registry equivalents via aliases; liquidation-keeper, webhook-relay and blank still scaffold through the pre-registry renderer until converted. Templates with requires.signer get a DEV-ONLY generated mnemonic in .env (never in .env.example). Aliased as rflow init. Refuses to overwrite an existing rflow.yaml, leaves an existing .env untouched, and ends with rflow validate + next commands.

See Scaffolding for the wizard walkthrough.

rflow templates

rflow templates ls [--category <c>] [--risk <r>] [--json]
rflow templates ls --community [--registry github:owner/repo[@branch]]
rflow templates search <query>
rflow templates show <id>
rflow templates inspect <id> --json
rflow templates doctor <id | --all>
rflow templates index [--dir <repo-root>] [--check [--against <baseline-index.json>]]
rflow templates scaffold <id> [--dir <path>]
rflow templates snapshot <id> [--dir <path>]

Browse, inspect, check and author templates. ls prints id/category/risk/summary (categories: treasury, monitoring, keeper, intents, bridge-ops, governance, security, relayers, offchain, examples; risk labels: monitor_only, prepares_tx, money_moving, admin, experimental); ls --community fetches the community registry index instead — pinned to the branch head commit, --registry points at a fork. search matches id/title/summary. show prints the human detail: generated files, typed inputs with defaults, required signer/relayers/ABIs/secrets, declared interfaces/endpoints, the safety profile and the docs page (deprecated templates are labeled). inspect prints the raw manifest as JSON for agents/tooling. doctor proves a bundled template is shippable — manifest parses strictly, declared outputs match the packaged files, interface surfaces verify (recomputed selectors/topic0s), inputs are well-formed, the sample answers render a project that passes rflow validate, the committed snapshot matches, and the docs page exists (skipped outside the repo). Exit 1 on any failure.

The last three are the community-contribution tools: index regenerates templates/community/index.json (per-file sha256 pins; --check is the CI gate — byte-exact index + immutable versions, --against enforces the same immutability vs a baseline index from the base branch). scaffold writes a compliant community-package skeleton that passes the community doctor profile as soon as snapshot generates its tests/expected-rflow.yaml.

Browse, inspect, check and author templates (builtin + community registry)
 
Usage: rflow templates <COMMAND>
 
Commands:
  ls        List templates: id, category, risk, summary
  search    Keyword search over template id/title/summary
  show      Human-readable detail for one template
  inspect   Machine-readable manifest for agents/tooling (always JSON)
  doctor    Validate bundled template(s): manifest, files, inputs, sample render, snapshot, docs page
  index     Regenerate templates/community/index.json (per-file sha256 pins over every community package)
  scaffold  Write a compliant community-package skeleton (the submission starting point - see templates/community/SUBMITTING.md)
  snapshot  Render an on-disk package with its answers.sample.yaml and write tests/expected-rflow.yaml (the community snapshot regenerator)
  help      Print this message or the help of the given subcommand(s)
 
Options:
  -h, --help     Print help
  -V, --version  Print version

See the templates guide, the community registry and the use-case recipes.

rflow add

rflow add [--path <dir>] workflow <template-id> [--name <wf>] [--answer k=v ...] [--answers-file <f>] [--dry-run] [--yes] [--registry <github:owner/repo[@branch]>]
rflow add [--path <dir>] network       [--name <n>] [--chain-id <id>] [--rpc <url|${ENV}>] [--ws <url>] [--confirmations <n>]
rflow add [--path <dir>] contract      [--name <n>] [--abi <path>] (--network <net> --address <0x..> | --addresses net=0x,net=0x)
rflow add [--path <dir>] relayer       [--name <n>] [--networks a,b] [--speed SLOW|MEDIUM|FAST|SUPER]
rflow add [--path <dir>] notification  [--name <n>] (--telegram | --slack | --discord | --pagerduty | --opsgenie | --twilio) [env flags]

add workflow composes a template into an existing project: it renders the template with your answers and merges its contracts, relayers, constants, secrets, lists, notification channels and workflow into rflow.yaml (comment-preserving), copying packaged files (ABIs) alongside. Inputs already satisfied by the project (its name, its single network) are not re-asked; on a TTY the remaining inputs are prompted. Collisions follow the registry rules — an identical entry is reused silently, a same-ABI/subset-addresses contract is reused with a warning, a conflicting entry is a hard error, and a taken workflow name demands --name <new-name>. --dry-run prints the plan without writing; a successful add appends to .rflow/template-lock.yaml, refuses any change that would introduce a validation error, and prints which env vars to set. A multi-workflow template (e.g. safe-monitor) splices every workflow it declares, in authored order; --name only applies to single-workflow templates. community/<id> composes a community template the same way — fetched SHA-pinned + hash-verified, gated by the community doctor profile first.

Add a workflow from a template into the existing rflow.yaml (see `rflow templates ls`)
 
Usage: rflow add workflow [OPTIONS] <TEMPLATE>
 
Arguments:
  <TEMPLATE>  The template id, e.g. `large-transfer-alert`
 
Options:
      --name <NAME>
          Rename the added workflow (required when the template's workflow name already exists in rflow.yaml)
  -p, --path <PATH>
          optional - The path of the project, default will be where the command is run
      --answer <KEY=VALUE>
          Template answer as key=value (repeatable)
      --answers-file <FILE>
          YAML file of template answers (`key: value` scalars)
      --dry-run
          Print the merge plan without writing anything
      --yes
          Non-interactive: no prompts, manifest defaults fill missing inputs
      --registry <GITHUB:OWNER/REPO[@BRANCH]>
          Fetch `community/<id>` templates from this registry instead of the default rflow repo (`github:owner/repo[@branch]`). Non-default registries are UNTRUSTED and require confirmation
  -h, --help
          Print help
  -V, --version
          Print version

The other four subcommands incrementally grow an existing rflow.yaml — the counterpart to rflow new. add appends to the right top-level section and preserves the rest of the file byte-for-byte (comments, ordering, formatting, trailing newline). It never parses the whole document to a value and re-serializes it, so your comments on a money config survive. It expects the 2-space indentation style rflow new scaffolds; a section in another style (zero-indented lists, 4-space indents) makes the add refuse with the file untouched.

Interactive on a TTY (a network-preset picker, a channel-kind menu, env-var prompts), and fully flag-driven for CI/agents/non-TTY. A non-TTY run with a required flag missing errors with exactly what is needed.

Before writing, add validates the merged config in memory and refuses (exit 1, file untouched) any change that would introduce a hard validation error — a duplicate name, an unknown network reference, a bad address, or a relayer with no signer. Errors that were already present before the add are surfaced but do not block. Every successful add then runs rflow validate and prints the exact next command(s), including a rflow doctor hint.

Env-var flags take the variable name and are rendered as a ${VAR} placeholder; when only the kind flag is given they default to conventional names (TG_BOT_TOKEN/TG_CHAT_ID, SLACK_WEBHOOK_URL, DISCORD_WEBHOOK_URL, PAGERDUTY_ROUTING_KEY, OPSGENIE_API_KEY, TWILIO_ACCOUNT_SID/TWILIO_AUTH_TOKEN).

Add a workflow (from a template), network, contract, relayer or notification channel to rflow.yaml
 
Usage: rflow add [OPTIONS] <COMMAND>
 
Commands:
  workflow      Add a workflow from a template into the existing rflow.yaml (see `rflow templates ls`)
  network       Add a network to `networks:`
  contract      Add a contract to `contracts:`
  relayer       Add a relayer wallet to `relayers:`
  notification  Add a notification channel to `notifications.channels:`
  help          Print this message or the help of the given subcommand(s)
 
Options:
  -p, --path <PATH>  optional - The path of the project, default will be where the command is run
  -h, --help         Print help
  -V, --version      Print version

--path is global, so it works either before or after the subcommand (rflow add --path proj network ... and rflow add network --path proj ... are equivalent).

rflow doctor

rflow doctor [--path <dir>] [--profile <name>] [--deep] [--json]

Two sections, one command:

  • Quick readiness — Docker (daemon reachable), Foundry (anvil + cast on PATH, with versions), Postgres (reachable via the project's config.db_connection), Node (optional, for the docs), plus the rflow.yaml validate summary. Every dependency is marked ok / missing / n/a with a one-line "how to install or start" hint.
  • Deep operational suite — runs automatically whenever a project is present (--deep only makes it explicit; without a project only the tool checks run): yaml validation summary, profile merge correctness (the selected --profile must apply cleanly and validate — other declared profiles are checked in passing), DB connect + rflow schema/migration presence, per-network RPC (eth_chainId must match the declared chain_id — a mismatch is an error), finalized-tag support (advice, probed only when a workflow uses finalized), best-effort archive depth (advice, probed only when a workflow backfills history), contract sanity (ABI parses, addresses EIP-55 checksum-valid), relayer mapping presence (rflow.relayers) + native balance fetch with a low-balance warning, signer shape (raw/private-key env vars resolve; cloud providers get a config-shape note — no live call), notification channel delivery probes, webhook auth warnings (no auth on a money workflow), trigger cursor lag vs head, approval-gate posture (on_timeout: proceed warns), reorg/confirmation guidance for send workflows, and rate-limit/budget/circuit coverage notes for money workflows.

Every check carries a severity: error / warning / advice / info. Exit code: non-zero iff an error-severity check fails — warnings, advice and info never block, so rflow doctor is safe to gate CI/deploys on. --json prints one stable machine-readable report (version, quick, checks, summary, ok) on stdout for exactly that.

Check local dependencies (Docker, Foundry, Postgres, Node) and project readiness
 
Usage: rflow doctor [OPTIONS]
 
Options:
  -p, --path <PATH>        optional - The path of the project, default will be where the command is run
      --profile <PROFILE>  Diagnose the merged view after applying a `profiles.<name>` overlay (a broken profile is an error-severity failure)
      --deep               Run the full operational suite (DB schema, RPC chain ids + capabilities, relayer mappings/balances, channels, webhooks, approvals, coverage). Default ON when a project is present — the flag only forces it explicitly
      --json               Print one stable machine-readable JSON report on stdout (the CI gating surface). Exit code stays: non-zero iff an error-severity check fails
  -h, --help               Print help
  -V, --version            Print version

validate --preflight runs the connectivity subset of these same checks (same functions, no duplicated logic) — but preflight keeps its stricter historical rule: any failed check fails the command. The full check catalogue, JSON shape and CI gating pattern live in Doctor & explain.

rflow explain

rflow explain [<workflow>] [--risk] [--json] [--path <dir>] [--profile <name>]

A static plain-English narrative of what a workflow does, straight from rflow.yaml — no network, no database. With no argument: one summary block per workflow (trigger sentence, step chain, risk label monitor_only / prepares_tx / money_moving — the template-registry vocabulary). With a workflow: the full breakdown — the trigger (kind, contract/event/schedule, conditions, confirmations), each step in order (what it does, what it depends on, if: conditions), WHAT SENDS MONEY (each send_transaction: network, relayer, decoded call target, value/args source), WHAT BLOCKS MONEY (simulation, assert_sim, recheck, valid_for, approval gates, gas caps, permissions/budgets, relayer policy, rate limit, circuit breaker, concurrency), ON FAILURE (retries, on_failure route, finally, workflow_error watchers), ON REORG, REPLAY / DRY-RUN behaviour (one honest paragraph: sends stop pre-broadcast; waits settle unless --with-waits) and EXTERNAL SYSTEMS (HTTP hosts, channels, local commands).

--risk prints only the money sections plus a reviewer checklist ([x]/[ ] per safeguard and a verdict: N of 11 line) — enough context to approve or reject a production change. --json emits the same data structured. Output is deterministic, and every render ends with the honesty footer: explain describes the configuration, not runtime guarantees. The PR-review pattern is in Doctor & explain.

Plain-English breakdown of what a workflow does (static, from rflow.yaml)
 
Usage: rflow explain [OPTIONS] [WORKFLOW]
 
Arguments:
  [WORKFLOW]  The workflow to explain in full. Omitted = one summary block per workflow
 
Options:
  -p, --path <PATH>        optional - The path of the project, default will be where the command is run
      --risk               Only the money sections (WHAT SENDS / WHAT BLOCKS) plus a reviewer checklist verdict
      --json               Print the same data as one structured JSON object on stdout
      --profile <PROFILE>  Explain the merged view after applying a `profiles.<name>` overlay
  -h, --help               Print help
  -V, --version            Print version

rflow abi

rflow abi fetch (--network <name> | --chain-id <id>) --address <0x..> [--out <file>] [--source sourcify|etherscan] [--name <base>]
rflow abi inspect <file.json> [--json]
rflow abi find-event    (<file.json|abis-dir> <topic0>        | --project <topic0>)        [--json]
rflow abi find-function (<file.json|abis-dir> <sel-or-sig>    | --project <sel-or-sig>)    [--json]

abi fetch pulls the verified ABI for an address and writes it pretty-printed under the project's abis/ dir. Source order: Sourcify first (keyless), then Etherscan v2 (https://api.etherscan.io/v2/api?chainid=…, needs ETHERSCAN_API_KEY — when the key is absent and sourcify had no match, the error says exactly that, per source). --network reads the chain id from rflow.yaml; --chain-id works with no project at all. The default filename is abis/<--name>.json (or abis/<address>.json); re-fetching an identical file is a no-op, but a file with different content is never clobbered — delete it or pass --out.

Reproducibility, by design. The spec'd inline-YAML shortcut (abi: { fetch: ... } inside rflow.yaml) is intentionally not implemented: a config that fetches ABIs at load time is not reproducible. rflow.yaml only ever points at committed ABI files; the CLI fetches once and materialises the file. No config surface changed (rflow.schema.json is untouched).

abi inspect prints every event (name, full signature, topic0 hash, indexed params) and every function (name, signature, 4-byte selector, mutability); --json emits the same rows as JSON.

abi find-event reverse-maps a topic0 hash to the event that emits it — against one ABI file, a directory of ABI json files, or (with --project) every ABI file the project's rflow.yaml references. Unparseable json files in a directory scan are skipped with a warning; no match exits 1 with a rflow abi fetch hint. abi find-function is the same lookup for 4-byte selectors, and also accepts a full signature (transfer(address,uint256)), which it hashes for you.

rflow abi fetch --network ethereum --address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --name usdc
rflow abi inspect ./abis/usdc.json
rflow abi find-event ./abis 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
rflow abi find-function --project 0xa9059cbb
Fetch, inspect and reverse-lookup contract ABIs
 
Usage: rflow abi [OPTIONS] <COMMAND>
 
Commands:
  fetch          Fetch the VERIFIED ABI for an address (sourcify first, then etherscan) and write it under the project's abis/ dir
  inspect        Show every event (topic0, indexed params) and function (selector, mutability) in an ABI file
  find-event     Find which event a topic0 hash belongs to (one file, a directory of ABIs, or --project for every ABI rflow.yaml references)
  find-function  Find which function a 4-byte selector (or full signature) belongs to
  help           Print this message or the help of the given subcommand(s)
 
Options:
  -p, --path <PATH>  optional - The path of the project, default will be where the command is run
  -h, --help         Print help
  -V, --version      Print version

Related: validation errors for unknown event/function names now carry a close-match suggestion — contract 'USDC' has no event 'Transferr' - did you mean 'Transfer'? — and the importers hint at rflow abi find-event / rflow abi fetch whenever they cannot name an event topic hash locally. Full guide: ABIs — fetch, inspect & discover.

rflow contract

rflow contract add <Name> --network <net> --address <0x..> [--abi <path>]

Adds a contract by address alone. With --abi it is exactly rflow add contract (the same comment-preserving, validating code path). Without it, the verified ABI is fetched first (sourcify → etherscan, see rflow abi fetch), written to abis/<name>.json, and then wired into contracts: — the command prints what was fetched, the file written, and the validate result.

rflow contract add USDC --network ethereum --address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

Full guide: ABIs — fetch, inspect & discover.

Add a contract to rflow.yaml - with --abi this is exactly `rflow add contract`; without it the verified ABI is fetched (sourcify -> etherscan), written to abis/<name>.json and wired up
 
Usage: rflow contract add [OPTIONS] --network <NETWORK> --address <ADDRESS> <NAME>
 
Arguments:
  <NAME>  Contract registry name, e.g. `USDC`
 
Options:
      --network <NETWORK>  The network (must exist in rflow.yaml; its chain_id picks the explorer chain when fetching)
  -p, --path <PATH>        optional - The path of the project, default will be where the command is run
      --address <ADDRESS>  The contract address (0x…)
      --abi <PATH>         Use this ABI file instead of fetching one
  -h, --help               Print help
  -V, --version            Print version

rflow import

rflow import defender <serverless.yml> [--output <dir>]
rflow import gelato <tasks.json> [--output <dir>]

Converts an OpenZeppelin Defender-as-Code serverless.yml or Gelato Automate task JSON into a complete rflow project that passes rflow validate as-is, with a summary table of what mapped and a MIGRATION-NOTES.md. See Migrate from Defender and Migrate from Gelato.

rflow start

rflow start [--path <dir>] [--yes] [--standby] [--watch] [--profile <name>]

The whole boot, in order: load .env → validate rflow.yaml (warnings printed) → connect Postgres + apply the rflow schema → take the project advisory lock → generate runtime engine configs → boot the relayer (only if a signer is declared) → reconcile relayers → recovery pass → boot the indexer (only if chain triggers exist) → run. Ctrl-C / SIGTERM shuts down gracefully.

  • --yes skips confirmation prompts (e.g. creating new relayer wallets).
  • --standby boots as an HA standby: wait for the project leadership lock instead of exiting when another instance holds it (overrides config.ha.standby) — see High availability.
  • --watch hot-reloads rflow.yaml on valid changes; invalid edits keep the running config (see Observability).
  • --profile <name> applies a profiles.<name> overlay before booting.

rflow validate

rflow validate [--path <dir>] [--preflight] [--preflight-notify] [--profile <name>]

Strict, collect-all: unknown keys, missing refs (networks, contracts, relayers, channels, lists), ABI/function checks, expression analysis, signer shape, and reorg advice — all errors reported at once, advice in yellow, errors in red.

--preflight additionally runs live connectivity checks — every RPC's eth_chainId compared against the declared chain_id, database connectivity, and notification channels — as an ok/fail table, exiting 1 on any failure. Slack/Discord webhooks get a TCP reachability check unless --preflight-notify is passed (which posts a real test message). It also runs command checks: each command step's executable resolves (on PATH or as a project-relative path), any local script argument exists, and a known interpreter reports its version. See Observability.

--profile <name> validates the merged view a profile boots.

rflow lint

rflow lint [--path <dir>] [--strict] [--profile <name>] [--json]

The opinionated safety linter — validate asks "is this correct?", lint asks "is this safe to run in production?": unsimulated sends, missing gas ceilings, head-fired money, unauthenticated webhooks, missing idempotency, timeout-proceed approvals, public ports on money projects. Fully static (no database, no network, and usually no .env needed — lint prefers the raw ${VAR} view).

Findings follow an honest three-tier severity: recommended defaults warn (exit 0), knobs written into the optional top-level policy: block deny (exit 1) when violated, and --strict (or policy.strict: true) promotes every warning to a failure. Suppressions live under policy.suppress and require a written reason (the file does not parse without one). Every finding prints its fix and a docs link.

Opinionated safety lint over rflow.yaml (policy-driven, static, no DB)
 
Usage: rflow lint [OPTIONS]
 
Options:
  -p, --path <PATH>        optional - The path of the project, default will be where the command is run
      --strict             Promote every warning to a failure (same as `policy.strict: true`)
      --profile <PROFILE>  Lint the merged view after applying a `profiles.<name>` overlay (a profile may carry its own stricter `policy:`)
      --json               Machine-readable report on pure stdout (errors go to stderr)
  -h, --help               Print help
  -V, --version            Print version

See Safety lint & the CI gate for every rule, the policy: reference and suppression syntax.

rflow ci

rflow ci [--path <dir>] [--profile <name>] [--strict] [--preflight] [--json]

The one-command CI gate: validate → lint → committed-schema check → template-lock check → config plan against the latest stored version (when the database is reachable) → every tests/<workflow>[.case].json fixture as a dry-run — plus the live connectivity preflight with --preflight. Stages whose prerequisites are absent are skipped with a reason, never silently passed. Exits non-zero iff a gating stage fails; --json keeps stdout pure for the versioned machine report.

One-command CI gate: validate + lint + schema/template/plan/test checks
 
Usage: rflow ci [OPTIONS]
 
Options:
  -p, --path <PATH>        optional - The path of the project, default will be where the command is run
      --profile <PROFILE>  Run every stage against a `profiles.<name>` overlay
      --strict             Lint in strict mode (warnings fail)
      --preflight          Also run the live connectivity preflight (RPCs, database, notification channels) - the only stage that dials out
      --json               Machine-readable report on pure stdout (errors go to stderr)
  -h, --help               Print help
  -V, --version            Print version

See Safety lint & the CI gate for the stage table and a ready-to-copy GitHub Actions workflow.

rflow schema

rflow schema > rflow.schema.json

Prints the JSON Schema for rflow.yaml — wire it into your editor for completion and validation (see editor setup).

rflow trigger

rflow trigger <workflow> [--input key=value ...] [--path <dir>]

Manually fire a workflow. --input pairs are exposed to expressions as inputs.*.

rflow ls

rflow ls [--path <dir>]

Workflows with their state (running/paused), trigger summary, cursor (last processed block for event triggers), and runs today.

rflow status

rflow status [--path <dir>]

Engine health, chains, relayer registry and recent runs — plus the HA leadership state (leader running / no leader, via a lock probe). Workflows with liveness: get a liveness table (last run, max_silence, OK/BREACHED), and detected indexer stalls print per network.

rflow tables

rflow tables [--path <dir>]

The discoverability command for the query layer: lists every queryable table in the project's Postgres — the indexed event tables (rflow_indexer_rflow_<workflow>.* for event triggers, rflow_indexer_rflow_idx_<contract>.* for index_events) and the rflow.* journal — with columns and row counts, plus the naming rule as a footer. Schemas the config implies but the database does not have yet (fresh project, indexer not booted) are footnoted as "declared but not created yet" so nobody mistakes a new project for a broken one.

rflow runs

rflow runs list [--failed] [--limit <n>]   # journal: run list, per-step status (default 20, newest first)
rflow runs show <id>                       # one run in detail: outputs, tx links, attempts, approvals, reorg responses
rflow runs retry <id>                      # re-fire a dead-lettered run
rflow runs diff <a> <b> [--json]           # compare two runs: trigger, step outputs, errors, tx summaries
rflow runs timeline <id> [--json]          # chronological step/attempt/approval/tx trace of one run

runs diff answers "what changed between a good run and a bad one?" — it lines up the trigger payloads, per-step outputs, errors and tx summaries of two runs (both secret-redacted). runs timeline folds one run's steps, attempts, approval decisions, reorg-response steps and tx lifecycle into a single time-ordered view — the CLI form of the history explorer's run detail. Both accept --json for automation.

rflow history

rflow history [--workflow <wf>] [--status <s>] [--trigger <kind>] [--network <net>] \
              [--since <24h|7d|RFC3339>] [--session <live|replay|test|any>] \
              [--limit <n>] [--cursor <c>] [--json]   # a richer `runs list`, keyset-paginated
rflow history txs [--workflow <wf>] [--network <net>] [--status <s>] [--since <7d>] \
              [--limit <n>] [--cursor <c>] [--json]   # every send attempted (queued/submitted/settled)
rflow history approvals [--workflow <wf>] [--all] [--since <7d>] \
              [--limit <n>] [--cursor <c>] [--json]   # approvals (pending; --all adds decided/expired)
rflow history waiting [--workflow <wf>] [--limit <n>] [--cursor <c>] [--json]
                                                       # runs parked right now (tx / delay / event / approval)
rflow history export [--workflow <wf>] [--since <30d>] [--until <ts>] \
              [--format json|csv] [--out <file>]      # a redacted audit trail (secrets always stripped)

The history explorer surface without the UI — the same core rflow_core::history query layer the /api/history/* endpoints use, so the CLI and the UI never diverge. Every subcommand is keyset-paginated (a --cursor from one page's next_cursor fetches the next, newest first — never a skip or a duplicate) and every payload is secret-redacted (declared secrets:, secret-typed config fields and ${VAR} env values are rendered <redacted>). --json is available everywhere for automation, and export is redacted by default with no raw-payload flag.

rflow versions / plan / rollback

rflow versions ls [--json]                # deployed config history: id, hash, created, run count
rflow versions show <id> [--json]         # a version's summary + per-workflow definitions
rflow versions diff <a> <b> [--json]      # semantic delta between two stored versions
 
rflow plan [--from-version <id>] [--to <path>] [--profile <name>] [--json]
                                          # semantic diff + active runtime conflicts (exits non-zero on any error)
 
rflow rollback <version-id> [--output <path>] [--yes]
                                          # reconstruct a stored version to rflow.yaml (structure; secrets stay <redacted>)

The versioning & config plan surface: every boot fingerprints the profile-merged, secret-redacted config into rflow.config_versions and stamps that config_version onto every run (visible in rflow runs show and rflow ls). rflow plan is the deploy gate — it flags dangerous changes (money-moving sends, lowered confirmations, weakened approvals/caps) and the parked work they collide with, and exits non-zero on any error-severity item for CI.

rflow approve / reject / approvals

rflow approvals ls [--all]                # pending approvals (--all includes decided/expired)
rflow approve <id> [--yes] [--token <t>]  # approve a parked send (approval id or run id)
rflow reject <id> [--reason "why"] [--token <t>]  # reject it (the reason is journaled)
rflow approvals expire <id> [--reason "why"] --yes   # expire a pending approval (repair)

The human half of approval gates. approve shows the prepared transaction and asks for confirmation (--yes skips it); an approved send is re-checked and re-simulated before it broadcasts. On an N-of-M policy approval your identity must be PROVEN: pass --token <api-token> (or set RFLOW_API_TOKEN) — the token's name maps to your approvers.<member>.cli.token, each member counts once, and the first rejection vetoes by default. approvals expire is the repair path for a gate nobody answered: it prints a plan, needs --yes, and flips a pending approval to expired so the run takes its on_timeout path (no send is broadcast).

rflow state / cursors / waits / delays / circuit

rflow state inspect [--workflow <wf>] [--json]   # durable-state dashboard (read-only)
 
rflow cursors ls [--workflow <wf>]               # trigger cursors
rflow cursors set <wf> <network> --block <n> --yes   # re-scan or skip ahead
 
rflow waits ls [--workflow <wf>]                 # runs parked on a wait_for:
rflow waits cancel <run-id> --reason "why" --yes # terminalize a stuck saga wait
 
rflow delays ls [--workflow <wf>]                # runs parked on a durable delay
rflow delays wake <run-id> --yes                 # wake a parked delay early
 
rflow circuit ls [--workflow <wf>]               # circuit-breaker state
rflow circuit reset <workflow> --yes             # clear a tripped breaker

State inspection & repair — supported, journal-aware recovery for known stuck states instead of manual SQL. state inspect (and the ls listings) are read-only. Each repair mutation prints a plan first, requires --yes, refuses on a wrong precondition or a run with an in-flight send, and writes an rflow.operator_audit row. No repair can create a duplicate send: repairs terminalize stuck work rather than re-run money-moving steps, and lowering a cursor re-scans safely (already-claimed events dedupe by trigger_key).

rflow retention / archive

rflow retention plan                      # what WOULD be pruned per class (reads only)
rflow retention prune --dry-run           # print the plan, delete nothing
rflow retention prune --yes               # prune per config.retention (deletion needs --yes)
 
rflow archive export --since 2026-01-01 [--until 2026-03-01] [--out <dir>] [--format jsonl]
                                          # dump runs+steps+approvals+failures+reorgs+sessions to redacted JSONL

Operator-driven history retention, driven by config.retention. retention plan reports per-class counts + age span without deleting; retention prune refuses to delete without --yes (it prints the plan instead) and, with archive_before_prune, exports the archive first and aborts the whole prune if that export fails. Active runs, pending approvals, parked waits/delays, in-flight sends and running reorg responses are never prunable. archive export redacts secrets by default; a relative --out (and the default ./archives) resolves against the project dir.

rflow budgets ls

rflow budgets ls                          # per-asset consumed / reserved / remaining

Shows each spend budget with its cap and, per asset, consumed (reserved in-flight + spent settled), reserved, and remaining inside the current rolling window — read straight from rflow.budget_reservations.

rflow spend

rflow spend [--workflow <wf>] [--relayer <r>] [--network <net>] \
            [--since <30d|24h|RFC3339>] [--json]
                                          # grouped summary: gas + value by workflow / relayer / network,
                                          # totals + reverted-loss subtotal
rflow spend export [--format csv|json] [--workflow <wf>] [--relayer <r>] \
            [--network <net>] [--since <30d>]
                                          # raw ledger entries to stdout (stable CSV columns; exact wei)

The operator view over the receipt-derived native spend ledger (rflow.native_spend): what rflow actually paid in gas and moved in native value, per settled send. Human tables format amounts as the native unit; --json and export keep stdout pure and carry exact wei as decimal strings. Reverted sends appear with their burned gas as the reverted loss subtotal (a revert moves no value). The same numbers back GET /api/spend, the explorer's Spend view and the max_gas_spend budget circuit breaker.

rflow relayers

rflow relayers ls                    # every name → wallet mapping (incl. orphans), live balances
rflow relayers balance [--json]      # balances per chain with low-balance flags
rflow relayers sync                  # standalone reconcile: create+clone wallets, print funding table
rflow relayers rename <old> <new>    # re-label a mapping WITHOUT creating a new wallet
 
rflow relayers discover [--json]     # list relayers in the shared rrelayer db + signer compatibility
rflow relayers import <name> --id <uuid> --network <net>
                                     # adopt an existing relayer (derived address verified FIRST)
rflow relayers import --interactive  # pick from discover, prompt for the local name
rflow relayers verify-imports        # ok/mismatch/not-found per import.id — non-zero on failure (CI)
 
rflow relayers funding-plan [--json] # balance vs estimated need per relayer/network
rflow relayers qr <name> [--network <net>] [--address-only]
                                     # the address + a terminal QR code (fund from a wallet app)
rflow relayers topup-plan [--json]   # underfunded relayers and their shortfalls
rflow relayers topup --from <name> --to "*"|<name> --network <net> \
            [--amount <wei> | --to-target] --yes
                                     # fund shortfalls from a funding relayer (plan first, --yes to send)
rflow relayers history [--since 30d] [--relayer <r>] [--json]
                                     # recent gas spend per relayer + top-up actions

sync lets you create and fund wallets before ever running a workflow. rename is the answer to the rename honesty check. The import flow adopts relayers created by other services sharing the rrelayer database — a signer mismatch refuses before anything is written. The funding operations estimate need from a documented gas-headroom heuristic (workflow count × headroom sends × safety × live gas price × typical gas — every factor overridable), and topup broadcasts normal relayer sends that land in the spend ledger + operator audit trail. funding-plan, topup-plan and topup accept --typical-gas, --sends-per-workflow and --safety-factor to tune the estimate.

rflow token

rflow token create --name <label>   # mint a named API bearer token — prints the plaintext ONCE, stores only its hash
rflow token revoke <id>             # auth stops accepting the token immediately
rflow token ls                      # id / name / created / revoked — never the token itself

Named bearer tokens for config.server.auth. Auth accepts the config shared token and any non-revoked named token, so you can hand out per-operator/per-dashboard tokens and revoke a leaked one without a restart or a yaml edit. Only the hex SHA-256 is stored — the plaintext is shown once at creation. These commands need only config.db_connection, not a running server.

rflow secrets

rflow secrets ls        # name / source (env | provider:<name>) / remote key / resolved? / version — never values
rflow secrets doctor    # verify every declared secret resolves; exit non-zero on any provider failure

Inspect the secrets: map and verify external secret provider resolution without ever printing a value. ls shows where each secret comes from and whether it resolves (provider secrets are fetched to report the resolved column and the provider's version id); doctor fetches every provider-backed secret and exits non-zero when any is missing, denied or unreachable — the same fail-closed resolution rflow start performs at boot, and the same per-secret checks rflow doctor runs in its deep suite. Neither command needs the database or a running server — only rflow.yaml (+ its .env).

rflow workflow

rflow workflow pause <workflow>     # kill-switch — persisted in Postgres
rflow workflow resume <workflow>

rflow list

rflow list add <list> <value>        # persists in rflow.list_members — visible to the next where: evaluation
rflow list remove <list> <value>     # removes a runtime member (YAML seeds are config — edit rflow.yaml)
rflow list show [<list>]             # members with provenance (seed/manual/workflow/source) + counts

Live mutation of lists: watchlists. Addresses are stored lowercased; membership checks stay case-insensitive.

rflow mcp

rflow mcp [--path <dir>]

Serves the Model Context Protocol over stdio so AI agents and editors can operate the project: validate, inspect the journal, simulate calls, trigger/pause/ retry — with no raw send tool, ever.

rflow replay

rflow replay --help
Replay a workflow over historical blocks in an isolated session (dry-run by default), or manage replay/test sessions (`ls` / `prune`)
 
Usage: rflow replay [OPTIONS] [WORKFLOW]
       rflow replay <COMMAND>
 
Commands:
  ls     List replay and test sessions (id, workflow, kind, created, run count)
  prune  Delete replay/test sessions: their rows, indexer schemas and runtime dirs
  help   Print this message or the help of the given subcommand(s)
 
Arguments:
  [WORKFLOW]
          The workflow to replay (event- or block-triggered)
 
Options:
  -p, --path <PATH>
          optional - The path of the project, default will be where the command is run
 
      --from-block <FROM_BLOCK>
          First block of the bounded range
 
      --to-block <TO_BLOCK>
          Last block of the range (default: the network's current head, pinned at start)
 
      --with-waits <WAITS.json>
          Deterministic saga wait outcomes: a JSON map of wait-step id -> {"event": …} | {"timeout": true}
 
      --diff <OTHER_PROJECT_DIR>
          Diff this range against another project's same-named workflow (prints the delta)
 
      --output <OUTPUT>
          Output format for CI: human | json | junit
 
          Possible values:
          - human: Human-readable tables (default)
          - json:  A single structured JSON object on stdout
          - junit: A JUnit `<testsuite>` XML on stdout (GitHub Actions / GitLab)
          
          [default: human]
 
      --live
          DANGER: boot the relayer engine and send REAL transactions while replaying (default is dry-run: simulate + report, never send)
 
      --yes
          Skip the --live confirmation prompt
 
      --timeout <TIMEOUT>
          Session budget in seconds (bounded backfill + run drain)
          
          [default: 600]
 
  -h, --help
          Print help (see a summary with '-h')
 
  -V, --version
          Print version

Backtest an event- or block-triggered workflow over a historical block range in an isolated session — dry-run by default (simulate + report, never send). --live sends real transactions and demands you type send at a red prompt (--yes skips it). --with-waits pins saga wait_for outcomes; --diff compares two project versions over the same range (non-zero exit on difference); --output json|junit emits a CI report, and the process exits non-zero when any replay run failed. Full guide: Backtesting.

rflow replay ls / prune

rflow replay ls --help
List replay and test sessions (id, workflow, kind, created, run count)
 
Usage: rflow replay ls [OPTIONS]
 
Options:
  -p, --path <PATH>  optional - The path of the project, default will be where the command is run
  -h, --help         Print help
  -V, --version      Print version
rflow replay prune --help
Delete replay/test sessions: their rows, indexer schemas and runtime dirs
 
Usage: rflow replay prune [OPTIONS]
 
Options:
      --older-than <DUR>  Prune sessions created longer ago than this duration (e.g. 7d, 12h)
  -p, --path <PATH>       optional - The path of the project, default will be where the command is run
      --session <ID>      Prune one session by id
      --all               Prune every replay/test session
  -h, --help              Print help
  -V, --version           Print version

ls lists every replay/test session (DB rows ∪ orphan runtime dirs). prune deletes the selected sessions — namespaced journal rows, per-session rindexer schemas and runtime dirs — and never touches live workflows. It requires a selector (--older-than 7d / --session <id> / --all).

rflow test

rflow test --help
Execute one workflow once in dry-run mode from a fixture or a real transaction
 
Usage: rflow test [OPTIONS] <WORKFLOW>
 
Arguments:
  <WORKFLOW>
          The workflow to execute once
 
Options:
  -p, --path <PATH>
          optional - The path of the project, default will be where the command is run
 
      --event <FIXTURE.json>
          Path to an EVENT fixture json: {"args": {...}, "block_number"?, ...}
 
      --from-tx <TX_HASH>
          Build the trigger from a real transaction hash (fetches the receipt and decodes the matching trigger event)
 
      --fixture <FIXTURE.json>
          Path to a fixture json for the workflow's OWN trigger kind (cron/webhook/read/query/block/event)
 
      --cron-at <ISO8601>
          Cron workflows: the scheduled tick (ISO8601), sugar for a {scheduled_for} fixture
 
      --read-output <OUTPUT.json>
          Read/query workflows: a json file with the decoded {output}, sugar for --fixture
 
      --with-waits <WAITS.json>
          Deterministic saga wait outcomes: a JSON map of wait-step id -> {"event": …} | {"timeout": true}
 
      --output <OUTPUT>
          Output format for CI: human | json | junit
 
          Possible values:
          - human: Human-readable tables (default)
          - json:  A single structured JSON object on stdout
          - junit: A JUnit `<testsuite>` XML on stdout (GitHub Actions / GitLab)
          
          [default: human]
 
      --timeout <TIMEOUT>
          Run budget in seconds
          
          [default: 120]
 
  -h, --help
          Print help (see a summary with '-h')
 
  -V, --version
          Print version

Execute one workflow once in dry-run mode from exactly one trigger source (--event / --from-tx / --fixture / --cron-at / --read-output are mutually exclusive). --fixture interprets the file against the workflow's declared trigger kind (event/cron/webhook/read/query/block). --with-waits pins saga wait outcomes; --output json|junit makes it a CI check — the process exits non-zero when the run did not succeed. --timeout bounds the run (default 120s). Full guide: Backtesting.

rflow command test

rflow command test <workflow> <step-id> [--input <fixture.json>] [--json]

Debug a single command step in isolation. The step renders exactly as a run would — --input supplies the dynamic context roots (trigger, steps, state, …); the static roots (constants/secrets/contracts/lists) come from rflow.yaml — then the command runs for real and prints the rendered invocation, the secret-redacted stdin, the duration, and the parsed output (or the taxonomy failure). Nothing is signed, sent, journaled, or persisted, and no database is needed; the process exits non-zero when the command fails, so a fixture doubles as a CI check. --json emits a single machine-readable transcript.