Skip to content

CLI Reference

Home


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:

nfind "MP3 files whose title tag contains 'live', using mutagen" ~/Music --save mp3-live.py
# /// 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 run executes 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 with nfind --run, which keeps the read-only mount, network block, and resource limits.

Notes and limits:

  • --run takes no PROMPT and ignores --model; it can't be combined with --save, --confirm, or --macos-meta (using them together exits with code 2).
  • --macos-meta is not available on the replay path — META is 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 raw filterPaths code. There's no PEP 723 equivalent for Node, so the standalone uv run path is Python-only; Node filters still replay with nfind --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 to PATH (POSIX form), so --exclude build prunes any directory named build, 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_Store are skipped automatically. Pass --no-ignore to search them too.
  • --max-depth N — descend at most N levels below PATH; a direct child is depth 1. N must 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.