Skip to content

CLI reference

The complete surface, in one page. smartpipe PATH… (no verb) is reader mode: it emits the files' items as JSONL records, cut per --as - zero model calls unless an ocr-model is configured (then PDFs/images parse through it, disclosed per row; --ocr-model overrides, --max-calls caps). As of 1.0 this is a contract governed by SemVer - flags, formats, and exit codes won't change within a major version.

Synopsis

smartpipe <verb> [PROMPT] [OPTIONS]

Input comes from stdin (each line an item - or ONE redirected binary document), from named FILES after the prompt (each file an item by default; --in GLOB remains a hidden compatibility alias), or both (named files first, then the piped lines). Results go to stdout; progress and warnings go to stderr.

Verbs

Verb Purpose Page
map transform each item with a prompt one item in, one out
write route items to files (the egress door; TEMPLATE with {name} {stem} {ext} {path} {index} from __source provenance + record fields for fan-out - field paths included, {user.plan}; reserved vars always win; --field, --keep-meta, --as file\|lines) free
readable render records as blocks for eyes (--full, --bare); media previews at a color terminal free
filter keep items matching a condition semantic grep
embed items → vectors (JSONL) plumbing for top_k
top_k rank by similarity to a query sort \| head, by meaning
reduce synthesize many items into one recursive, automatic
join match stdin against a second input embed-block-judge
extend add extracted fields to each record (map that merges) 1 call per item
distinct fold near-duplicate items, first occurrence wins embeddings only
outliers rank the N items least like the rest embeddings only
cluster group items by meaning; label each group embeddings + 1 call per cluster
diff themes that distinguish stdin from --right FILE embeddings + labels
graph entity/relationship graph with cited edges --fast = free local NER
where keep rows matching a deterministic predicate free - no model calls
summarize count/avg/percentiles by field free - no model calls
sample keep N random rows, seeded + reproducible; --by FIELD stratifies free - no model calls
agree inter-rater agreement between two label files: observed, kappa, alpha, confusion matrix free - no model calls
getschema report the stream's fields, types, coverage free - no model calls
sort order records by a field free - no model calls
split break oversized items into chunk items free - no model calls
chart bar-chart a field's values; --save writes SVG or PNG free - no model calls
use set up models: interactive, or one-shot (use gemini) the setup door
using effective settings + where each comes from free - no model calls
config posture toggles; back-compat door to use free - no model calls
run execute a saved .sem stage file format
doctor check the whole setup, spend nothing (--probe adds the paid modality matrix) exit 0 = ready
demo fetch the 26 MB playground corpus into ./smartpipe-playground free - no model calls
schema braces/DSL compile FREE (--check FILE validates declared fields only - --strict forbids unknowns; --example; stdin REPL); bare at a terminal opens the workshop; plain English drafts with a model (one call, validated) ladder

Common options

These apply to the model-using verbs (map, filter, top_k, reduce; embed and top_k use --embed-model instead of --model):

Option Meaning
--model TEXT Model for this run - overrides the config and SMARTPIPE_MODEL.
--embed-model TEXT Embedding model (embed, top_k).
--concurrency N Max simultaneous outbound model/API calls (default 4); one packed batch counts as one call.
FILES… (positional) Read each named file/glob as items (quote globs). --in GLOB is a hidden compatibility alias.
--as {file,lines,jsonl,csv} Cut granularity: whole crates, text rows, strict records, or header-named csv rows (the item, csv rows). Auto: .jsonl paths cut into records, .csv/.tsv into csv rows (.tsv on tabs); other paths are one item; stdin sniffs per line.
--from-files Treat each stdin line as a filename.
--strict-rows A mixed record/text stream (or a field-less row in where/summarize) is an error, not a note. SMARTPIPE_STRICT_ROWS is the env form.
--bare Strip __ metadata from record output (map, extend, join, reader mode).
--full Terminal preview: no truncation (map, extend, join, readable).
--fallback-model TEXT Chat model to switch to if the primary looks down (circuit breaker; map, extend, filter, join).
--fields A,B Select + order columns of structured output (map, embed, top_k, reduce - never filter).
--allow-captions Let a CLOUD model convert images/audio/video to text for embedding/text verbs (paid; local models convert free; a cloud smartpipe use pick sets this by default).
@file / --prompt-file FILE Read the prompt from a file (map, extend, filter, reduce, join). Missing file = loud exit 64; @@ escapes a literal leading @.
--max-calls N Hard ceiling on model-call spend. Per-item verbs stop intake and drain; whole-set top_k/reduce treat exhaustion as fatal. Dedicated Mistral OCR is page-billed, so one unit is reserved per PDF page before upload. A capped run never exits 0.
--manifest PATH Write a JSON run manifest at run end (all model verbs): smartpipe version, verb + raw argv, resolved model roles, prompt text + sha256, compiled schema, temperature, item counts, token/conversion receipt, UTC start/end, exit status. Atomic; a second run overwrites - it records THIS run. The citable methods-section artifact (recipe).
--local-only (before the verb) Local-data fence: smartpipe --local-only map ... refuses every remote model wire at resolution time (exit 2, before spend), so execution and user input stay on this machine. It is not air-gap mode: supporting traffic without user payload, such as model downloads, may use the network. A remote OLLAMA_HOST is refused. Env form: SMARTPIPE_LOCAL_ONLY=1.

Verb-specific options

Verb Options
map --schema FILE, --schema-from DSL, --tally FIELD, --explode FIELD, --output {auto,text,json,csv,tsv}, --keep-invalid (failed validations become {"__invalid": …} rows), --dry-run (print the composed first request, spend nothing)
filter --not (invert, like grep -v)
top_k K (positional), --near TEXT (required), --threshold FLOAT, --stream (live leaderboard)
reduce --schema FILE, --schema-from DSL, --group-by FIELD, --verbose, --window N [--every M] (stream mode)
join --right FILE (required), --on 'left.F == right.F' (repeatable; alone = free key join, with a prompt = blocking; F is a field path - left.order.sku), --k N (default 5), --threshold FLOAT, --kind inner|leftouter|anti, --unmatched FILE, --embed-model, --ocr-model (both sides, --right included)
extend map's flags (braces/--schema/--schema-from/--tally/--explode/--fields/--keep-invalid/--dry-run)
map/extend video --frame-every SECONDS (density guarantee), --max-frames N (budget; smaller wins)
distinct --show-groups, --threshold F (cosine, default 0.90), --exact (hash rung only - free), --embed-model, --ocr-model
outliers N (default 5), --embed-model, --ocr-model
cluster --k N, --top N, --explode members, --model (labels), --embed-model, --ocr-model
diff --right FILE (required), --top N, --all, --model, --embed-model, --ocr-model (both sides, --right included)
graph --fast (free), --entities "a, b", --relations "pays, owns", --name-top N (hybrid), --window {sentence,chunk,document}, --min-weight N, --save PATH (.graphml/.dot/.mmd/.csv/.html or directory/ = Obsidian vault), --top N (display cap), --ocr-model
where 'PREDICATE' (has, contains, matches /re/, == != > >= < <=, and/or/not)
summarize 'AGG[, AGG…] [by FIELD,…]' (count/sum/avg/min/max/p50-p99/dcount)
sample N, --seed K (default 0 - reproducible by default), --by FIELD (stratified: proportional per value, largest-remainder rounding; missing field = a null stratum)
agree A B (two JSONL label files), --on FIELD (align by key; default row order, equal counts required), --label FIELD (default label), --output
getschema --all (scan past the first 10,000 rows)
(custom) your own verbs: ~/.config/smartpipe/verbs/*.sem or entry points
usage model usage over hour/day/week/month/lifetime; usage reset remembers when
cache stats · clear (auto-swept: 30-day TTL + 500 MB LRU cap - cache-days, cache-max-mb)
sort --by FIELD (required), --desc
split --by UNIT[:N] (tokens, pages, minutes, seconds), --media (embedded images), --max-tokens N (= --by tokens:N), --ocr-model, --max-calls N (caps OCR parsing; dedicated OCR counts billable pages)
chart FIELD (or whole lines), --facet f1,f2,…, --by-time FIELD:BUCKET, --top N, --save FILE.svg / FILE.png, --title

use

smartpipe use                    # interactive setup: text model, embeddings, OCR, speech
smartpipe use gemini             # one-shot: gemini chat + its paired embedder
smartpipe use ollama             # one-shot: the best installed local model
smartpipe use gpt-5.4-mini       # one-shot: that model + its provider's pairing

smartpipe use is the setup door. Bare, it runs four stages in order - the text model, the embedding model (the auto-pair suggestion preselected), an optional OCR model, then an optional speech-to-text choice (one keypress skips either): local whisper pins free on-device transcription, openai/whisper-1 is the paid remote wire, and skipping keeps the automatic ladder. Every provider appears with a connected badge; picking an unconnected one drops into the auth login connect flow inline and continues. Every stage has a back row (typing back or b works too), re-runs preselect your current choices and restamp only changes, and Ctrl-C anywhere leaves the config untouched. At the end it offers to verify what the chosen models can actually do (~5 tiny requests, consent first; a failed text control reports a setup fault and concludes nothing). Menu rows carry capability chips (text · image · audio) sourced probed > registry (models.dev, day-cached) > declared; a model-capabilities = ["image"] config key declares chips for self-hosted models the registry can't know. Chips are display only - runtime stays attempt-based.

With a TARGET, use stamps a COMPLETE bundle non-interactively: the provider's sensible chat model (or the model you named) plus its paired embedder. A provider whose key is absent refuses with the auth login fix and stamps nothing - never a partial setup. A cloud pick also stamps allow-captions = true (the pick is the consent; every conversion is still disclosed per row). Re-running use refreshes the whole bundle - the drift cure.

Every save writes a provenance header at the top of config.toml - e.g. # stamped by: smartpipe use (2026-07-10T21:04Z) - so the file documents how it got that way. Unknown keys are preserved on rewrite; other comments are not.

using

smartpipe using                  # the effective setup, each value with its origin

One line per setting: the effective value tagged with where it came from (env, config file, or default), ending with the config file's path. Every key the runtime reads appears - the models (model, fallback-model, embed-model), the roles (stt-model, ocr-model, media-embed-model), and the postures (concurrency, output, cache, update-check, media-previews). Unset roles show their honest default wording (e.g. ocr-model (built-in local extraction) (default)) rather than vanishing. smartpipe config show remains as an alias.

config

smartpipe config                     # back-compat door: opens the same setup as `use`
smartpipe config show                # alias of `smartpipe using`
smartpipe config cache on|off        # result caching
smartpipe config batching on|off     # request batching: small map/extend/filter
                                     # items share one model call - default on
smartpipe config update-check on|off # the daily release check + notice
smartpipe config media-previews off  # terminal media previews (thumbnails,
                                     # waveforms, play links) - default on

The model setters (config model / embed-model / stt-model / ocr-model / media-embed-model) and profiles are retired: smartpipe use and smartpipe auth login cover setup, and power users edit config.toml directly. A config file that still carries profile keys loads with one warning ("profiles were removed - run smartpipe use"); the keys are ignored and the next save removes them.

API keys are read from the environment first, then from the auth login store - never from the config file.

auth

smartpipe auth login             # pick a provider from the list (all of them)
smartpipe auth login mistral     # store a Mistral API key (masked prompt, live check)
smartpipe auth login openai      # log in with ChatGPT (browser) - back-compat
smartpipe auth login openai-api  # store an OpenAI API key
smartpipe auth login --headless  # ChatGPT device-code flow for remote machines
smartpipe auth list              # provider · type · MASKED key · live source
smartpipe auth status            # ChatGPT login state
smartpipe auth logout [PROVIDER] # remove one credential (picker when omitted)

OpenAI appears twice in the list because its wires differ: the ChatGPT login serves Codex-family chat only (no embeddings), the API key serves everything. Keys store at ~/.local/share/smartpipe/auth.json (owner-only, 0600); the ChatGPT tokens keep living at ~/.config/smartpipe/auth.json. A key entry is validated with one catalog request first - on failure you choose retry, store anyway (the provider may be down), or skip. An exported environment variable always wins over anything stored.

cite

smartpipe cite                       # print a BibTeX entry for citing smartpipe

doctor

smartpipe doctor        # config · Ollama · models · keys · login · extras · completions

One line per check with its fix inline; exit 0 all-green, 1 if anything needs attention. Never makes a paid model call; key lines report presence, never values.

demo

smartpipe demo          # ~26 MB into ./smartpipe-playground, then next steps

Downloads the smartpipe-playground corpus - 26 MB of CC0 / public-domain practice files (scanned invoices, NASA reports, photos, speech recordings, screen sessions, JSONL data) - into ./smartpipe-playground, verifies the release checksum, unpacks, and prints copy-pasteable commands to try. No model, no config needed. At a terminal it asks before downloading (Enter continues); piped and scripted runs proceed. A complete prior download is recognized (already here, exit 0), and anything else already at that path stops the command (exit 2) - it never overwrites.

run

smartpipe run extract.sem < cards.txt        # execute a saved stage
smartpipe run extract.sem --model ollama/qwen3:8b   # flags override the file

A .sem file pins one verb invocation in TOML; with a #!/usr/bin/env -S smartpipe run shebang it runs directly (./extract.sem). Unknown keys in the file are errors (scripts run unattended). Full format: .sem stage files.

schema workshop

Bare smartpipe schema at a terminal opens a small interactive workshop for building a schema. Every command is free - zero model calls. The header shows the live draft and repaints after each command (pinned at the top on a capable terminal, reprinted in the plain fallback):

schema workshop — free, no model calls
{vendor string: legal name, total number}
✓ compiles · 2 fields
/add NAME TYPE [: guidance] · /drop NAME · /test FILE · /example · /save [PATH] · /quit
Command Effect
/add NAME TYPE [: guidance] Add a field. Types are the braces vocabulary: string, number, integer, boolean, enum(a, b), string[], number[], {a, b}[] (an object list), a trailing ? for nullable.
/drop NAME Remove a field.
/test FILE Validate the file's JSONL rows: a pass/fail tally, then a coverage bar per field (presence %, type misses).
/example One deterministic instance that validates - the same machinery as --example.
/save [PATH] Write the compiled JSON Schema (default schema.json), then print the two paste-ready lines: the braces string for inline use, and --schema PATH.
/quit (or Ctrl-D) Leave; the paste-ready lines print on the way out.

Pasting a whole braces string (for example {vendor string, total number}) replaces the draft. Every edit runs through the real braces compiler, so a bad type shows the compiler's own error and leaves the draft unchanged. Bare schema with piped stdin keeps its old behavior: one expression per line, one compiled schema per line.

Output formats

--output (or SMARTPIPE_OUTPUT): auto (default), text, json, csv, tsv. See Output formats. auto shows a readable view at a terminal and JSONL when piped; csv/tsv need structured (named-field) output. --fields a,b projects structured output to just those columns, in that order, identically in every format.

Shell completion

Tab completion for bash, zsh, and fish - including live model-name suggestions on --model/--embed-model and use (providers first, then models). One-liners per shell in Installing smartpipe → Tab completion.

Environment variables

Variable Effect
SMARTPIPE_MODEL Default chat model.
SMARTPIPE_EMBED_MODEL Default embedding model.
SMARTPIPE_OUTPUT Default output format.
SMARTPIPE_MAX_CALLS Default call ceiling (see --max-calls).
SMARTPIPE_BATCH Request batching kill switch (off disables; default on). SMARTPIPE_BATCH_SIZE (2..12; default 12) and SMARTPIPE_BATCH_WINDOW_MS (default 75) tune the group size and coalesce window. --concurrency still caps actual simultaneous API calls.
SMARTPIPE_OPENAI_BASE_URL Point the OpenAI-compatible adapter at any endpoint.
SMARTPIPE_MISTRAL_BASE_URL / SMARTPIPE_GEMINI_BASE_URL / SMARTPIPE_OPENROUTER_BASE_URL Point a provider's wire elsewhere (proxies, gateways).
SMARTPIPE_CONTEXT_TOKENS Assert your model's context window (beats the table and the probe; the fix for OpenAI/Anthropic deployments the table underestimates).
SMARTPIPE_WHISPER_MODEL Local transcription size: tiny (default), base, small, medium, large-v3.
SMARTPIPE_FIGURE_CAP Embedded figures attached per document item (default 8; whole numbers ≥ 1 - the request-size guard). Figures past the cap are dropped and censused on stderr.
OPENAI_API_KEY / ANTHROPIC_API_KEY / MISTRAL_API_KEY / GEMINI_API_KEY / OPENROUTER_API_KEY / JINA_API_KEY Cloud credentials - the environment always wins over a key stored by auth login.
OLLAMA_HOST Ollama endpoint (default http://localhost:11434).
SMARTPIPE_LOCAL_ONLY The --local-only fence as an env var: refuse remote model/data wires so input stays local. Supporting data-free network requests are allowed. Any value other than 0/false/off/no arms it (fail closed).
NO_COLOR Disable color.

Exit codes

Chosen so a script can branch on how a run went, not just pass/fail:

Code Meaning
0 OK - everything succeeded (including zero matches for filter).
1 PARTIAL - some items were skipped; the rest succeeded.
2 SETUP - misconfiguration (no model, unreachable Ollama, missing key).
3 ALL_FAILED - every item failed.
64 USAGE - bad flags or input.
70 BUG - an internal error (please report it).
130 INTERRUPTED - Ctrl-C (before anything finished, or pressed twice).
141 SIGPIPE - downstream closed the pipe (normal in \| head pipelines); smartpipe prints nothing.

What Ctrl-C does

For per-item verbs (map, filter, embed), the first Ctrl-C stops new work and lets in-flight work finish for up to 10 seconds. Finished results are emitted in order.

The command then prints done: interrupted - N processed · M skipped on stderr and exits with the run's normal outcome code (0/1/3). Scripts can still tell whether the partial output is trustworthy.

A second Ctrl-C exits 130 immediately. The same drain applies to stream modes: reduce --window flushes its partial window, and top_k --stream already has its board on screen.

Whole-set reduce and top_k exit 130 at once. They produce one result at the end, so there is nothing to drain.

See also