CLI Reference¶
← Home
- Synopsis
- Arguments
- Options
- Filtering what's searched
- Reviewing the generated code
- Saving & replaying filters
- Output modes
- Dependencies
- Query cache
- Configuration
- Exit codes
Synopsis¶
nfind PROMPT [PATH]... [OPTIONS]
nfind --run FILTER [PATH]... [OPTIONS] # replay a saved filter, no PROMPT
Search each PATH for files and directories matching the natural-language PROMPT
and print one path per line. Both -h and --help show usage. With --run, a
previously saved filter is replayed instead and PROMPT is omitted (see
Saving & replaying filters).
Pass several roots to search them in one run; each is searched separately, so identically named files in different roots never collide, and the results are merged into a single list of host paths. A root may be a directory (walked) or a single file, so you can target specific files directly.
nfind "directories that contain only audio files"
nfind "Python files that import requests" ./src
nfind "files that define a class" ./src/app.py ./src/models.py
nfind "TODO comments left in the code" ./src ./tests ~/scratch
nfind "files larger than 1 MB, with their size" --fields
Reading the path list from stdin¶
Pass - as a path to read the roots from standard input — one per line, or NUL-separated
(auto-detected, so it consumes find -print0 and nfind --print0 directly, safely
handling spaces and newlines in filenames). The whole list is searched in a single run
(one LLM call), and - can be mixed with explicit paths. This lets a cheap mechanical
prefilter narrow the tree before nfind does the expensive content analysis:
# Only parse the large TIFFs, not every image
find /imagery -size +50M -name '*.tif' -print0 | nfind "GeoTIFFs with no embedded CRS, using rasterio" -
# Newline-delimited input works too
fd -e pdf . ~/Documents | nfind "PDFs that contain embedded JavaScript, using pypdf" -
If stdin yields no paths, nfind prints nothing and exits 0 (it does not fall back to
searching the current directory). For repeated multi-stage pipelines, prefer chaining
saved filters (--run) so no stage pays an LLM call — see
Saving & replaying filters.
Arguments¶
| Argument | Default | Description |
|---|---|---|
PROMPT |
— (required) | Natural-language description of the paths to find. |
PATH... |
. |
One or more directories or files to search. Directories are walked recursively, with common ignored names pruned unless --no-ignore is set. With several, results are merged. Use - to read a NUL- or newline-delimited path list from stdin. Defaults to the current directory when omitted; with --save, --show-code, or --confirm and no PATH, the filter is generated but not run. |
Options¶
Running nfind -h groups these options into labelled panels — Model, Sandbox & resources,
Search scope, Generated filter, Output, Dependencies, and Query cache — and tags every
option that accepts a default from the config file with (config). The
table below lists them together.
| Option | Default | Description |
|---|---|---|
--config PATH |
XDG default | TOML file of option defaults (env: NFIND_CONFIG); command-line options override it. See Config file. |
--exclude GLOB |
— | Glob of names/paths to skip during enumeration; matching directories are pruned. Repeatable. See Filtering what's searched. |
--no-ignore |
off | Walk the complete tree instead of skipping default ignored names (.git, node_modules, __pycache__, .venv, caches, …). |
--max-depth N |
unlimited | Descend at most N directory levels below PATH (a direct child is 1). |
--max-results N |
unlimited | Return at most N complete path records. |
--max-items N |
unlimited | With --extract, emit at most N extracted item rows. |
--max-output-bytes N |
unlimited | Bound encoded stdout without partial rows or invalid JSON. |
--model |
openai/gpt-5.4 |
Model used to generate the filter. Bare name = OpenAI; provider/model for others (see Providers). |
--list-models |
off | List the model ids available for the provider in --model and exit. Needs that provider's API key. See Providers. |
--image |
per-runtime | Override the base image tag for the chosen runtime. |
--sandbox |
docker |
Sandbox backend: docker, experimental apple on macOS, experimental podman, or experimental nerdctl (containerd). |
--timeout |
180.0 |
Seconds the generated filter may run before it is killed. |
--command-timeout |
unlimited | Optional POSIX wall-clock deadline for the entire command. |
--memory |
256m |
Memory limit for the worker container. |
--cpus |
1.0 |
CPU limit for the worker container. |
--pids-limit |
64 |
Maximum number of processes inside the worker container. |
--rebuild |
off | Rebuild the worker image before searching. |
--build-timeout |
120.0 |
Seconds allowed for building the worker image. |
--show-code |
off | Print the generated filter (to stderr) before running it. Settable in the config file. |
--save PATH |
— | Save the generated filter as a self-describing, replayable script (see Saving & replaying filters). |
--run PATH |
— | Replay a previously saved filter through the sandbox instead of generating one. No PROMPT, no LLM call. |
--confirm, -i |
off | Show the generated code and ask for confirmation before running it. |
--fields, -f |
off | Show extra per-path fields alongside each path. |
--json |
off | Output results as JSON (path plus any extra fields). |
--extract |
off | Explode each result's list-valued field into one match per line (path[:line]<TAB>payload); selects items inside files. Mutually exclusive with --fields; --json stays nested. See Output modes. |
--extract-field NAME |
— | With --extract, name the list-valued field to explode when a record has more than one. Requires --extract. |
--print0, -0 |
off | Separate results with NUL bytes instead of newlines (for xargs -0). |
--yes, -y |
off | Approve any requested packages without prompting. |
--no-deps |
off | Reject any third-party packages (standard library only). |
--no-format |
off | Skip the ruff cleanup (remove unused imports, sort imports, format) applied to the generated filter. |
--macos-meta |
off | macOS only: expose Finder tags and download metadata to the filter (see macOS metadata). |
--cache / --no-cache |
on | Reuse and store generated filters in the on-disk query cache; repeat prompts skip the LLM. |
--force |
off | Ignore a cached match and regenerate with the model (the fresh result is still stored). |
-h, --help |
— | Show help and exit. |
-V, --version |
— | Show the nfind version and exit. |
--timeout limits only the generated filter's sandbox execution; it is not a
whole-command deadline. Enumeration, model generation and retries, image builds, and
rendering happen outside it. See Limits and scaling for all current defaults,
unlimited dimensions, and the separate worker-response ceiling.
--sandbox apple uses Apple Containers instead of Docker. It is currently an explicit
opt-in and prints a warning before running. On macOS 26+ nfind uses Apple Containers'
--network none support. On macOS 15, Apple's official docs say
container run --network ... is unsupported, so nfind falls back to --no-dns; raw IP
network access may still be possible there. Apple Containers also lacks
Docker-equivalent --pids-limit and --security-opt no-new-privileges flags in the
current CLI. Its --cpus option accepts whole-number CPU counts only, so values like
--cpus 1 work but fractional Docker-style limits like --cpus 0.5 are rejected
before the container runs.
--sandbox podman uses the podman CLI. Podman is drop-in compatible with Docker's
hardening flags, so nfind runs it with the same command as Docker — --network none,
--cap-drop ALL, --security-opt no-new-privileges, a read-only root, and
pids/memory/CPU/tmpfs limits — including fractional --cpus values. On rootless Podman it
additionally remaps the read-only mount to the worker user (--userns=keep-id) so the
non-root worker can read it. It is an explicit opt-in and prints a warning before running,
because it has been validated only on limited hosts and rootless isolation differs from a
rootful Docker daemon.
--sandbox nerdctl uses the nerdctl CLI to run the worker on containerd (e.g. under
Lima or Rancher Desktop). nerdctl is Docker-compatible, so nfind runs it with the same
hardened command, including --network none. It is an explicit opt-in and prints a warning
before running: it has not yet been validated against a real containerd runtime, and on
rootless nerdctl the read-only mount may be unreadable by the non-root worker (nerdctl has
no keep-id remap like Podman's).
Reviewing the generated code¶
The filter is written by an LLM, so you may want to see it before it runs:
# Print the generated filter (to stderr) before running it
nfind "files with no extension" --show-code
# Save the generated filter to a file
nfind "files with no extension" --save filter.py
# Show the code and ask for confirmation before running (aborts on "no")
nfind "files with no extension" -i # or --confirm
Combining --save, --show-code, or --confirm with no PATH generates the filter
without running it — useful when you want to capture or inspect a filter before deciding
where to run it:
# Generate and save without searching anything
nfind "MP3 files whose bitrate is below 128 kbps, using mutagen" --save filter.py
# Generate and inspect inline, then replay later
nfind "MP3 files whose bitrate is below 128 kbps, using mutagen" --show-code
nfind --run filter.py ~/Music
Without any of those flags, an omitted PATH defaults to the current directory (like
find), so the filter is generated and run.
--show-code and --confirm print the full artifact as
--save would write it — a Python PEP 723 script or a
Node.js file with comment metadata — so the preview matches the saved artifact exactly.
(On a --run replay the saved file is shown as-is.)
Before it is shown, saved, or run, the generated Python filter is tidied with ruff:
unused imports are removed, imports are sorted, and the source is reformatted. These
transforms preserve behaviour, so what you review is exactly what runs. Pass
--no-format to see the model's raw output instead (useful when debugging a
filter the model got wrong).
The code is printed to stderr, so stdout stays a clean, pipeable list of paths
even with --show-code. On a terminal the code is syntax-highlighted with Pygments;
highlighting is disabled when NO_COLOR is set or when
stderr is redirected.
Declining a --confirm prompt aborts before the container runs and exits with code
130.
Saving & replaying filters¶
--save PATH writes the generated filter as a standalone, auditable filter
program (a self-describing, replayable script) rather than a bare function.
This lets you run the exact same filter logic later with zero LLM overhead or
generation variance. For the Python runtime that's a
PEP 723 script:
# /// script
# requires-python = ">=3.11"
# dependencies = ["mutagen"]
# ///
"""
nfind filter
Prompt: MP3 files whose title tag contains 'live', using mutagen
Model: openai/gpt-5.4
Runtime: python
Saved: 2026-06-21
WARNING: running this file directly (e.g. `uv run`) executes OUTSIDE the nfind
Docker sandbox -- no read-only mount, no network block, full user privileges...
"""
def filter_paths(paths):
...
if __name__ == "__main__":
... # walks sys.argv[1] (default ".") and prints matching paths
The module docstring carries the original prompt and provenance; the # /// script
block declares the filter's dependencies. You can then run it two ways:
# Sandboxed replay through nfind — no LLM call, runs in the same hardened container
nfind --run mp3-live.py ~/Music
# Trusted fast path — runs directly via uv, OUTSIDE the sandbox (see warning below)
uv run mp3-live.py ~/Music
--run reuses the dependency whitelist: a saved filter that
declares a not-yet-approved package still prompts (or is rejected with --no-deps),
so a replayed filter can't silently pull new packages.
Saved filters preserve the generated source and dependency names. Package versions are not pinned by default, so a future replay may install newer package releases unless you run against an already-cached derived image or your own pinned base image.
Safety:
uv runexecutes the filter with your full user privileges, network access, and write access — none of nfind's sandbox protections apply. Only run files you have reviewed and trust. When in doubt, replay withnfind --run, which keeps the read-only mount, network block, and resource limits.
Notes and limits:
--runtakes noPROMPTand ignores--model; it can't be combined with--save,--confirm, or--macos-meta(using them together exits with code 2).--macos-metais not available on the replay path —METAis collected on the host during generation and isn't reconstructed for saved filters.- Node.js filters are saved with a
//provenance/safety comment header, a machine-readable// nfind-metadata: ...line carrying the runtime and npm dependencies, and the rawfilterPathscode. There's no PEP 723 equivalent for Node, so the standaloneuv runpath is Python-only; Node filters still replay withnfind --run, including the same dependency whitelist checks as fresh searches.
Filtering what's searched¶
These options shape the path list before it reaches the model's filter — they run on the host during enumeration, so they're deterministic and also make searches faster by shrinking what the sandbox has to consider.
nfind "stale config files" ~/project --exclude '*.min.js' --exclude dist
nfind "large modules" ./src --max-depth 2 # only two levels below ./src
nfind "anything referencing the old API" . --no-ignore # include .git, node_modules, …
--exclude GLOB— repeatable. Each glob is matched against every entry's name and its path relative toPATH(POSIX form), so--exclude buildprunes any directory namedbuild, while--exclude 'src/generated/*'targets one location. A matching directory is pruned entirely (its subtree is never enumerated).- Default ignored names —
.git,.hg,.svn,node_modules,.venv,venv,__pycache__,.mypy_cache,.pytest_cache,.ruff_cache,.tox, and.DS_Storeare skipped automatically. Pass--no-ignoreto search them too. --max-depth N— descend at mostNlevels belowPATH; a direct child is depth1.Nmust be ≥ 1.
All three apply to --run replays as well, and can be set as
config-file defaults.
Output modes¶
nfind "Python files that import os" # default: paths only
nfind "Python files, and for each the number of lines" -f # path + extra fields
nfind "Python files, and for each the number of lines" --json
nfind "empty directories" ~/Downloads --print0 | xargs -0 rmdir # NUL-separated
--json and --fields are mutually exclusive, and --print0 cannot be combined with
either. --print0 NUL-terminates each path (the find -print0 / xargs -0 convention)
so paths with spaces or newlines survive a pipeline. See Output modes
for details and example output.
Dependencies¶
When a prompt needs a library (e.g. reading MP3 tags), the generated filter declares the PyPI packages it imports. Packages on the approved list install without a prompt; new ones are confirmed and then remembered:
nfind "MP3 files whose title tag contains 'live', using mutagen" ~/Music # prompts if new
nfind "images larger than 4000px on a side" ~/Photos --yes # approve without asking
nfind "files containing TODO" . --no-deps # stdlib only
--yes and --no-deps are mutually exclusive. See
Dependencies & the whitelist for the approval flow, the default
package list, and the whitelist file.
Providers¶
--model selects the model that writes the filter. A bare name uses OpenAI (so
existing usage is unchanged); a provider/model selector targets any
OpenAI-compatible provider — nfind reuses the OpenAI SDK against the provider's
base URL, so no extra dependency is needed.
nfind "files with no extension" # OpenAI (default)
nfind "..." --model anthropic/claude-sonnet-4-6 # Anthropic
nfind "..." --model gemini/gemini-2.5-flash # Google Gemini
nfind "..." --model groq/llama-3.3-70b-versatile # Groq
nfind "..." --model openrouter/<vendor>/<model> # OpenRouter (near-universal)
nfind "..." --model ollama/llama3.1 # local Ollama
| Provider | Selector prefix | API key env var |
|---|---|---|
| OpenAI | (bare name) or openai/ |
OPENAI_API_KEY |
| Anthropic | anthropic/ |
ANTHROPIC_API_KEY |
| Google Gemini | gemini/ |
GEMINI_API_KEY |
| Groq | groq/ |
GROQ_API_KEY |
| Mistral | mistral/ |
MISTRAL_API_KEY |
| DeepSeek | deepseek/ |
DEEPSEEK_API_KEY |
| xAI (Grok) | xai/ |
XAI_API_KEY |
| OpenRouter | openrouter/ |
OPENROUTER_API_KEY |
| Ollama (local) | ollama/ |
(none; needs a running server) |
| LM Studio (local) | lmstudio/ |
(none; needs a running server) |
Only the selected provider's key is needed.
Listing available models¶
--list-models prints the model ids the selected provider exposes, one per line, then
exits. The provider is taken from --model, so set it to target a non-default provider:
nfind --list-models # OpenAI (default provider)
nfind --list-models --model groq/x # Groq (model name is ignored here)
nfind --list-models --model openai/x | grep codex # filter the list
Use it to discover valid model names or to check what a local Ollama/LM Studio server has installed. A provider that doesn't support listing reports an error (exit code 1).
Endpoint selection (chat completions vs. responses)¶
nfind speaks two OpenAI-compatible endpoints and picks one per model automatically — no flag to set:
- Chat Completions (
/chat/completions) is the default and is tried first, so every provider above keeps working unchanged. - Responses (
/responses) is used as an automatic fallback for OpenAI reasoning/codex models that are served only there (e.g.gpt-5.1-codex-mini). When the first request is rejected with the tell-tale "only supported in v1/responses" error, nfind switches endpoints and retries; the switch is remembered for the rest of that run.
A responses-only model costs one extra throwaway request the first time it's seen (the
probe that triggers the switch). That verdict is then cached on disk — keyed by the full
provider/model selector — in model-endpoints.json under nfind's cache directory (or
$NFIND_ENDPOINT_CACHE when set), so later runs start on /responses and skip the probe.
The cache is purely an optimisation: it only ever records the responses-only exceptions,
and every read/write is best-effort, so a missing or stale entry just means one re-probe.
Providers also vary in whether they support strict JSON mode, a custom temperature, or
max_tokens vs. max_completion_tokens; nfind adapts to each rejection automatically and
recovers the JSON from the reply when needed, so generation still works. Some non-OpenAI
models follow the filter contract less reliably — if a model misbehaves, try a stronger
one or route through openrouter/.
Query cache¶
Generated filters are stored on disk next to the prompt that produced them, so re-running
a prompt you have used before replays the stored filter and skips the LLM call. Caching is
on by default; the two per-run flags are --no-cache (skip the cache this run) and
--force (regenerate anyway, still storing the result). Semantic matching and its embedding
model/threshold are set-once preferences configured in the config file
(cache-semantic, cache-embedding-model, cache-threshold), not per-run flags. Browse and
manage stored entries with the nfind cache subcommand:
nfind cache list # stored prompts, newest first
nfind cache show 7 # one entry's prompt, provenance, and generated code
nfind cache delete 7 # delete one entry by id (ids are stable; accepts several)
nfind cache clear # empty the cache (add --yes to skip the prompt)
The default nfind "prompt" interface is unchanged; the explicit nfind search "prompt"
form is available if a prompt ever collides with a subcommand name. See the
Query Cache reference for matching rules, the semantic (embedding) option, and
configuration.
Configuration¶
The nfind config subcommand locates, reads, and edits the config file
without your having to remember its per-OS path:
nfind config init # scaffold a commented template of every key and its default
nfind config path # print the config file path
nfind config show # print the current config file
nfind config get KEY # read one value (built-in default if unset)
nfind config set KEY VAL # set a key (list keys take several values), preserving comments
nfind config unset KEY # remove a key, reverting to the built-in default
nfind config edit # open the config file in $EDITOR
nfind never creates a config file on its own — a search with no file just uses built-in defaults. See Managing configuration from the CLI for details.
Note the nfind config subcommand (which manages the file) is distinct from the
--config PATH search option (which selects the file a single run reads,
like $NFIND_CONFIG does for a session).
Exit codes¶
| Code | Meaning |
|---|---|
0 |
Search completed (zero or more matches). |
1 |
A runtime error occurred (e.g. Docker unavailable, filter failed). Message on stderr, prefixed error:. |
2 |
Invalid usage (e.g. --json and --fields together). |
130 |
A --confirm prompt was declined. |