watfile
Classify files with a decision-making AI model and sort them into category folders.
watfile sends each document's text (title/abstract-grade extract) to a
TypeSafe AI Jev (System One) Choice
question, gets back a typed answer with a selected category, per-category
probabilities and confidence, then moves the file into the matching folder.
A Classifier abstraction keeps the backend pluggable — local MLX (laya) and
other backends slot in later.
Install
Requires Python 3.12+ and uv.
From PyPI
# one-off run, no install
uvx watfile --help
# persistent CLI on your PATH
uv tool install watfile
watfile --help
Updates:
uv tool upgrade watfile # or: watfile --self-update
uvx watfile@latest ... # one-off runs always fetch the newest version
Configuration
watfile resolves its TypeSafe API key (create one at https://console.typesafe.ai/) with this precedence — first match wins:
TYPESAFE_API_KEYenvironment variable.envfile in the current directory (gitignored;TYPESAFE_API_KEY=...)~/.config/watfile/config.toml(api_key = "...", alsobase_url,model;$WATFILE_CONFIGor$XDG_CONFIG_HOMEcan relocate it)
export TYPESAFE_API_KEY=... # option 1
echo 'TYPESAFE_API_KEY=...' > .env # option 2
cat > ~/.config/watfile/config.toml <<'EOF' # option 3
api_key = "..."
EOF
Usage
Point watfile at files or folders, and either give a comma-separated category
list (-c) or a target folder whose subfolders are the categories (-d):
# explicit categories, files moved into ./sorted/<category>/
uv run watfile ~/Downloads/invoice.pdf -c invoice,donation,apartment
# folder input, recursive; categories = existing subfolders of -d
mkdir -p ~/docs/{invoice,donation,apartment}
uv run watfile ~/Downloads -r -d ~/docs
# preview without touching anything
uv run watfile ~/Downloads -r -d ~/docs -n
# actually move the files (default is symlinking into the category folders)
uv run watfile ~/Downloads -r -d ~/docs -m
# copy instead
uv run watfile ~/Downloads -r -d ~/docs --copy
# custom output root with -c
uv run watfile *.pdf -c computerscience,biology -o ~/sorted
Output per file:
bill.pdf: invoice (conf 0.94) -> symlink to ~/docs/invoice/bill.pdf
Files that can't be classified (unsupported extension, no extractable text) are
skipped with a warning; name collisions get a _1, _2… suffix.
Supported inputs
- Text formats (read directly):
.txt .md .markdown .rst .log .csv .json - PDF (via liteparse): only the first 2 pages are parsed, OCR disabled — enough for classification, ~1000x faster than a full parse. Scanned/image-only PDFs are skipped.
Backends
-
jev(default) — TypeSafe AI Jev, cloud API. NeedsTYPESAFE_API_KEY. Highest accuracy (4/4 on the arXiv fixtures). Batches automatically: documents are packed into onesystem_onecall (~256 tokens each, up to ~100 files per call in the 30k-token window), so classifying a folder costs one API call, not one per file. -
laya— local typed-decision model from the Laya family; runs on any platform. The runtime is auto-detected from what's installed (LAYA_RUNTIMEoverrides):extra runtime where speed pip install watfile[laya]MLX (GPU) Apple Silicon ~13ms/decision pip install watfile[laya-coreml]Core ML (ANE) Apple Silicon ~5ms, 2.8× lower energy pip install watfile[laya-torch]PyTorch (CPU/GPU) any OS ~45–450ms (CPU) No API key needed; checkpoints download once and then run offline. Default checkpoints: multilingual where available (torch/coreml → handles non-English documents out of the box). Because laya's context window is small (512–1024 tokens), the backend defaults to adaptive multi-chunk classification: the extract is split into ~200-token chunks; chunk 1 decides if its probability is decisive (≥0.5), otherwise further chunks are classified and probabilities aggregated until the decision is decisive (max 10). On the arXiv fixtures: 3/4 (Jev 4/4).
watfile ~/Downloads -r -d ~/docs --backend laya
# checkpoint via config or env:
# ~/.config/watfile/config.toml -> laya_model = "..."
# or LAYA_MODEL=... / LAYA_RUNTIME=torch|mlx|coreml
Options (main)
watfile --help shows only these:
usage: watfile [-h] [-r] (-c CATEGORIES | -d DIRECTORY) [-o OUTPUT]
[--backend {jev,laya}] [-n] [-m | --copy | --symlink]
[--help-all]
inputs [inputs ...]
main options:
-h, --help show this help message and exit
--help-all show advanced options too
-r, --recursive recurse into folder inputs
-v, --version print version and exit
-c CATEGORIES comma-separated categories
-d DIRECTORY target folder whose existing subfolders are the categories
-o OUTPUT output root for sorted files (default: same as -d, or ./sorted with -c)
--backend {jev,laya} classifier backend (default: jev)
-n, --dry-run print decisions without placing files
--min-confidence P don't place files classified with confidence below P
(default 0.5 — the TypeSafe-recommended floor for
genuinely uncertain answers; 0 disables gating)
-m, --move move files into the category folder (default: symlink)
--copy copy files instead of symlinking
--symlink create symlinks in category folders (default)
--self-update update watfile in place (uv tool / pipx aware)
Advanced options
Shown by watfile --help-all:
--batch N cap files per API call (default: automatic — jev packs
everything that fits the 30k-token window, ~100 docs;
laya doesn't batch)
--no-batch disable batching, one API call per file (debugging)
--chunk-tokens N per-document token budget (default: backend-specific)
--chunks N split each document into N chunks, aggregate
probabilities; 0 = adaptive. Default: 0 for laya,
1 for jev
Batching example (jev batches by default; the flag just caps batch size):
# 100 files: ~2 API calls instead of 100 (256 tokens/doc, 30k window)
uv run watfile ~/Downloads -r -d ~/docs
# cap batch size, e.g. to keep batches small for debugging
uv run watfile ~/Downloads -r -d ~/docs --batch 25
Development
From source
git clone <repo> && cd watfile
uv sync # create venv + install deps (typesafe-sdk, liteparse, laya-mlx)
uv run watfile --help
# or install the local checkout as a tool
uv tool install --from . watfile
Tests
uv sync
uv run pytest # unit tests; live API tests skip without TYPESAFE_API_KEY
tests/fixture/ contains 4 real arXiv PDFs with ground-truth categories
(derived from their arXiv subject tags) used by the integration tests.
Roadmap
done — native laya-mlxlayalocal backend (MLX via OpenAI-compatible HTTP)batching: classify 25/50/100 files in a single API calldone — jev batches automatically into the 30k-token window (default on,--no-batchto disable)
Release files for watfile 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| watfile-0.3.0.tar.gz | 17.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| watfile-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.7 kB
Release files / watfile-0.3.0.tar.gz
| Download URL | watfile-0.3.0.tar.gz |
|---|---|
| Size | 17.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4077099d87e75583ac26de2f5595e7f5be9bcbd918951844f97e72475187fc97
|
|
BLAKE2b-256 checksum How to use checksums |
62fdd508f2d41bfc42310c8ce9d7633070275605d4a61a5b742ef3637fcb31cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / watfile-0.3.0-py3-none-any.whl
| Download URL | watfile-0.3.0-py3-none-any.whl |
|---|---|
| Size | 21.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d23b37f460cc75fca2d37d8bd01455e515c1e054c29deb12f23d6c9fc48d9599
|
|
BLAKE2b-256 checksum How to use checksums |
406ca2c4e42dae94dbe9e629c043abf4ffb05d1fb1edcb2596e96e8936e9a4eb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency log