GitHub

piperig

Declarative pipeline runner for shell scripts.

Define steps, params, and loops in YAML — piperig expands and executes.
Single binary. Any language. No runtime dependencies.

brew install joarhal/tap/piperig
Define a pipe

Steps run sequentially. Parameters from with are passed to every step. Step-level values override pipe-level.

with:
  src: /data/photos
  dest: /data/output

steps:
  - job: scripts/download.sh
  - job: scripts/resize.py
    with:
      quality: 80
  - job: scripts/upload.js

Any language — piperig picks the interpreter by extension.
.py → python, .sh → bash, .js → node, .ts → npx tsx. No extension → direct exec.

piperig run resize.pipe.yaml

Override anything at runtime: piperig run resize.pipe.yaml quality=95

Templates

Use {key} to reference other parameters. Substitution pulls from the full pool — with, each, loop, and CLI overrides.

with:
  dest: /data/output

each:
  - { label: fullhd, size: 1920x1080 }
  - { label: thumb,  size: 128x128 }

steps:
  - job: scripts/resize.py
    with:
      output: {dest}/{label}.jpg
Resolved
call 1: output=/data/output/fullhd.jpg size=1920x1080 call 2: output=/data/output/thumb.jpg size=128x128
Loop over ranges

Time ranges, numeric ranges, or lists. piperig expands them into individual calls.

loop:
  date: -7d..-1d

steps:
  - job: scripts/report.py
$ piperig check
Step 1: scripts/report.py × 7 date = 7 calls 1. date=2026-03-16 2. date=2026-03-17 3. date=2026-03-18 4. date=2026-03-19 5. date=2026-03-20 6. date=2026-03-21 7. date=2026-03-22
Multiply with each

Combine loop and each — piperig computes the cartesian product.

loop:
  date: -3d..-1d

each:
  - { size: 1920x1080, label: fullhd }
  - { size: 128x128,   label: thumb }

steps:
  - job: scripts/resize.py
$ piperig check
Step 1: scripts/resize.py × 2 each × 3 date = 6 calls 1. date=2026-03-20 label=fullhd size=1920x1080 2. date=2026-03-20 label=thumb size=128x128 3. date=2026-03-21 label=fullhd size=1920x1080 4. date=2026-03-21 label=thumb size=128x128 5. date=2026-03-22 label=fullhd size=1920x1080 6. date=2026-03-22 label=thumb size=128x128
Handle failures

Retries, timeouts, and allow_failure — per step or for the whole pipe.

steps:
  - job: scripts/fetch.py
    retry: 3
    retry_delay: 5s
    timeout: 10m

  - job: scripts/notify.sh
    allow_failure: true
09:15:32 → scripts/fetch.py · Fetching data... ! Connection timeout ↻ retry 1/3 (5s) · Fetching data... · Done (142 records) 09:15:41 scripts/fetch.py 8.7s
Structured output

Declare log fields — piperig extracts them from JSON lines your scripts print to stdout.

log:
  - label
  - file
  - size

steps:
  - job: scripts/resize.py

Your script prints JSON:

print(json.dumps({"label": "fullhd", "file": "photo.jpg", "size": "1920x1080"}))
09:15:32 → scripts/resize.py ▸ fullhd | photo.jpg | 1920x1080 ▸ thumb | photo.jpg | 128x128 09:15:32 scripts/resize.py 0.3s

Plain text and JSON can be mixed freely. Without log, everything passes through as text.

Input modes

Control how parameters reach your scripts.

input: json                # pipe-level default

steps:
  - job: scripts/process.py   # json (from pipe)
  - job: scripts/deploy.sh
    input: args             # --key value
  - job: scripts/notify.py
    input: env              # KEY=value (default)
envSRC=/data python script.py
json{"src":"/data"} on stdin
argspython script.py --src /data
Nested pipes

When job points to a .pipe.yaml, piperig runs it as a child pipeline. Parent with overrides child parameters.

steps:
  - job: scripts/prepare.sh
  - job: pipes/images.pipe.yaml
    with:
      quality: 90
  - job: scripts/cleanup.sh
Schedule

Run pipes on a cron schedule or fixed interval.

# schedule.yaml
- name: daily-images
  cron: "0 5 * * *"
  run:
    - pipes/daily/

- name: healthcheck
  every: 10m
  run:
    - pipes/healthcheck.pipe.yaml
piperig serve schedule.yaml
CLI
piperig run <pipe> [key=value]run a pipe
piperig run <directory/>run all pipes in directory
piperig runinteractive picker
piperig check <pipe> [key=value]preview call plan
piperig serve <schedule.yaml>cron scheduler
piperig new pipe <name>scaffold a .pipe.yaml
piperig new schedule <name>scaffold a schedule.yaml
piperig initcreate .piperig.yaml