How CLI Nodes Work
CLI nodes run shell commands as pipeline steps.
How it works
Section titled “How it works”You declare the tools you need, Radhflow enters a Nix-managed shell with those packages, runs your command, and captures the output. Nothing is installed globally. The same pipeline runs identically on any machine with Nix.
- Declare packages. List Nix packages in
nix.packages. - Define the command. Write the shell command in
params.command. Use{{ }}templates for input fields. - Radhflow enters nix-shell. A transient shell is created with only the declared packages available.
- Command runs. stdin receives input data. stdout, stderr, and exit code are captured.
- Output is collected. The result is returned as a Record or Table.
No system-wide installs. No version conflicts. No “works on my machine.”
Config fields
Section titled “Config fields”| Field | Required | Default | Description |
|---|---|---|---|
command | Yes | — | Shell command to execute. Supports {{ }} templates. |
args | No | — | Additional command arguments. |
env | No | — | Environment variables passed to the command. |
stdin | No | — | Input source piped to the command’s stdin. |
stdout_format | No | text | How to parse stdout: text, json, ndjson. |
nix.packages | Yes | — | List of Nix packages available in the shell. |
nix.nixpkgs | No | Latest stable | Pinned nixpkgs revision for reproducibility. |
artifacts | No | — | Files produced by the command. |
stdin/stdout contract
Section titled “stdin/stdout contract”Data flows in through stdin and out through stdout. This is the fundamental contract for CLI nodes: data in, command runs, data out.
When stdout_format is ndjson, Radhflow parses each line of stdout as a JSON object and assembles the result into a Table. When it’s json, the entire stdout is parsed as a single JSON value. When it’s text, stdout is captured as a string.
Examples
Section titled “Examples”curl + jq pipeline
Section titled “curl + jq pipeline”fetch-and-filter: type: deterministic op: cli.run params: command: > curl -s https://api.example.com/data | jq '[.items[] | {name: .name, value: .count}]' nix: packages: [curl, jq] stdout_format: json outputs: result: { type: Table }ImageMagick resize
Section titled “ImageMagick resize”resize-images: type: deterministic op: cli.run params: command: > convert {{ source }} -resize 800x600 {{ dest }} nix: packages: [imagemagick] inputs: files: type: Table from: ref(list-images.files) outputs: results: { type: Table }When the input is a Table, the command runs once per row. Template expressions resolve against each row independently.
ffmpeg extract
Section titled “ffmpeg extract”extract-audio: type: deterministic op: cli.run params: command: > ffmpeg -i {{ input_path }} -vn -acodec mp3 artifacts/{{ name }}.mp3 nix: packages: [ffmpeg] artifacts: - path: "artifacts/{{ name }}.mp3" type: file inputs: request: type: Record from: ref(prepare.config) outputs: result: type: Record schema: exit_code: { type: number } stdout: { type: string } stderr: { type: string }Radhflow resolves ffmpeg from nixpkgs, enters a shell with it available, runs the command, and returns the result. The host system does not need ffmpeg installed.
Environment management
Section titled “Environment management”The nix block declares the shell environment. Packages are pulled from nixpkgs. Pin a revision for full reproducibility across machines and time.
nix: packages: [pandoc, texlive.combined.scheme-small] nixpkgs: github:NixOS/nixpkgs/nixos-24.05 # optional pinWithout a pin, Radhflow uses the latest stable nixpkgs. Pinning is recommended for production pipelines — it guarantees the exact same tool versions on every run.
Output capture
Section titled “Output capture”By default, CLI nodes capture stdout, stderr, and the exit code as a Record.
| Field | Type | Description |
|---|---|---|
exit_code | number | Process exit code. 0 means success. |
stdout | string | Standard output content. |
stderr | string | Standard error content. |
For commands that produce files, declare them in artifacts and reference the output path in downstream nodes.
params: command: > pandoc {{ input }} -o artifacts/{{ name }}.pdf artifacts: - path: "artifacts/{{ name }}.pdf" type: file