Filez4Eva
⚠️ DISCLAIMER: This is a hobby/personal project. Not a commercial product. Not for production use.
Shift scans, photos, and other files into structured folders for permanent safekeeping
Filez4Eva is a command-line tool that helps you organize files by naming them correctly and placing them in the right directory structure. It's especially useful for managing account documents, scanned files, photos, and other personal records. Each destination ("cabinet") has its own naming pattern, whose placeholders are the data fields you (or an AI model) fill in for each file.
Installation
It's best to install using pipx:
pipx install filez4eva
If you don't have pipx installed, visit the pipx site for installation instructions.
Usage
Stowing Individual Files
To organize a single file (like a downloaded PDF):
filez4eva stow-file ~/Desktop/123456789SomeFileIDownloaded.pdf
Filez4Eva will interactively prompt for:
- Cabinet, only if several are configured (with tab-completion of cabinet names; see Cabinets)
- Each of the cabinet's fields, in the order they appear in its
pattern. With the default pattern,
{date}-{title}, that is the date (in YYYYMMDD format) and then a title. Fields with learned values offer tab-completion of the common ones.
Field values can be given on the command line with --date and a repeatable
--field NAME=VALUE (or -f NAME=VALUE). For example, for a cabinet whose
pattern is {date.year}/{account}/{date}_{part}:
filez4eva stow-file -d 20240213 -f account=royal-bank -f part=visa \
~/Desktop/statement.pdf
Values can also be piped in as a YAML mapping on stdin, which is handy for
other tools that produce a record for the file (such as
classify). The fields go under data::
printf 'data:\n date: 20240213\n title: visa-statement\n' \
| filez4eva stow-file ~/Desktop/statement.pdf
- Recognised keys are
cabinetanddata; any other top-level keys are ignored. Underdata, keys that aren't fields of the chosen cabinet are ignored. - Command-line flags override values from stdin.
- Anything still missing is prompted for as usual.
datemay be20240213,'20240213'or2024-02-13. If it isn't a valid date,stow-fileexits with an error.- If stdin is empty, isn't valid YAML, or isn't a mapping, it is ignored. A
datavalue that isn't a mapping is an error. - Values are read as plain text, so
0123stays0123andyesstaysyes. ~andnullare treated as missing, so that value is prompted for.- Stdin is only read by
stow-fileitself.scan-dirignores it.
However the values are supplied (flags, stdin, prompts or the AI), every
field other than date ends up hyphen-separated-lower: accents are
stripped, letters are lowercased, and each run of anything other than a-z
and 0-9 becomes a single hyphen, trimmed at both ends. So
Royal Bank of Canada becomes royal-bank-of-canada, and the value can
never contain / or ... A value with nothing left (such as ../) is
rejected: from a flag or stdin, stow-file exits with an error; typed at a
prompt, the prompt asks again. A normalized value typed at a prompt is used
without asking again.
--field names must be fields of the chosen cabinet; anything else is an
error. Use --date, not --field date=..., for the date.
Processing Multiple Files
To process all files in a directory:
filez4eva scan-dir ~/Desktop
This interactive process lets you:
- Skip a file (x)
- Preview a file (p)
- Stow a file (s)
- Delete a file (d)
- Quit processing (q)
Stowing a file works like process: the file
is transcribed (reusing an up-to-date transcript) and classified, then the
summary and doctype are shown and a prompt for each of the cabinet's fields
is pre-filled with the proposed value (the Date: prompt starts empty if
the document has no date of its own). Press Enter to accept a value, or
type a new one. With several cabinets, the cabinet is asked for once, before
anything is sent to the model.
- If transcription or classification fails (for example, no API key or an unsupported file type), the error is shown and the prompts start empty, as they would without AI.
- Empty input at any prompt returns to the action choice for the same file, without moving anything.
Transcripts written by transcribe are not offered as files of their own:
X.md is skipped when X is in the same directory (for example
scan.pdf.md next to scan.pdf). A .md file with no such companion is
offered as usual. Deleting a file also deletes its transcript. Stowing a file
moves its transcript with it (see stow-file).
Transcribing Files
To transcribe a file to Markdown using an AI model:
filez4eva transcribe ~/Desktop/scan.pdf
This writes scan.pdf.md next to the file (the full file name plus .md)
and prints its path on stdout. The transcript looks like:
---
source: scan.pdf
sha256: 3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b
model: claude-opus-4-6
transcribed: '2026-10-10T17:42:05+00:00'
---
<!-- Generated by an AI process from a binary file. ... -->
...Markdown transcription...
- Supported file types are PDF, PNG, JPEG, GIF, WebP, plain text, Markdown and CSV.
- If the transcript already exists and its
sha256matches the file, nothing is sent to the model and the existing path is printed (statusUnchangedon stderr). If the file has changed, or the transcript has no readable front matter, it is transcribed again and overwritten. --force(or-f) transcribes again even if the transcript is up to date.- If transcription fails (e.g. an unsupported file type or an API error),
transcribeexits with an error and any existing transcript is left as it was.
See Transcription settings for the model and API key.
Classifying Files
To have an AI model propose where a file should be filed:
filez4eva classify ~/Desktop/scan.pdf
classify transcribes the file first if needed (exactly as transcribe
does, reusing an up-to-date transcript), then asks the model to classify the
transcript and prints a YAML record on stdout:
file: /Users/me/Desktop/scan.pdf
transcript: /Users/me/Desktop/scan.pdf.md
cabinet: accounts
doctype: receipt
summary: Amazon.ca order receipt for a USB hub
data:
account: amazon
date: '20261002'
title: usb-hub-receipt
dataholds a value for each of the cabinet's fields, in pattern order (here the pattern is{account}/{date}-{title}).dateis the document's own date, as YYYYMMDD. If the document has no date of its own (or the model can't tell what it is),dateisnullrather than a guess. Today's date and the file's modified time are never used.- Every other field is normalized to hyphen-separated-lower, as for
stow-file. doctypeis one of the configured doctypes, orother.summaryis a single line.- The model is told the cabinet's
descriptionand each field'sguidance(see Fields). For fields with learned values, it is also given the common values (the same ones offered by tab completion) and asked to reuse one when it fits. It is also given the doctype names and descriptions. - The cabinet is chosen as for
stow-file:--cabinet NAME, the only cabinet, or a prompt. - If transcription or classification fails, or the model's answer is
missing a field, has a field value with no letters or digits, or has an
unknown doctype,
classifyexits with an error and prints nothing on stdout. A date that isn't a valid YYYYMMDD date becomesnullinstead of an error.
Nothing is moved. To file the document as proposed, pipe the record into
stow-file, which reads cabinet and data and ignores the other keys:
filez4eva classify ~/Desktop/scan.pdf | filez4eva stow-file ~/Desktop/scan.pdf
If the record has date: null, stow-file prompts for the date.
Processing a File with AI
To transcribe, classify, confirm and stow a file in one go:
filez4eva process ~/Desktop/scan.pdf
process:
- transcribes the file if needed (as
transcribedoes, reusing an up-to-date transcript) - classifies it (as
classifydoes) - shows the summary and doctype, then prompts for
Cabinet:(only if several cabinets are configured and it wasn't just chosen) and then each of the cabinet's fields in pattern order, each pre-filled with the proposed value. Press Enter to accept it, or type a new value (backspace clears the pre-filled value first). If the classification has no date, theDate:prompt starts empty. If you change the cabinet, only fields with the same name in the new cabinet are pre-filled. Tab completion works as forstow-file. - stows the file and its transcript, exactly as
stow-filedoes, and prints the destination path on stdout
- Confirmation is always interactive.
- Empty input at any prompt cancels without moving anything. An invalid value is rejected and the prompt asks again.
- The cabinet is chosen as for
classify(--cabinet NAME, the only cabinet, or a prompt) before anything is sent to the model, and the whole configuration is checked first. --dry-runtranscribes, classifies and prompts as usual, then prints where the file (and its transcript) would go, without moving anything.
Command Line Options
Global options:
--config PATH: Specify path to config file--debug: Enable debug output
stow-file command:
--cabinet, -c NAME: Specify the cabinet (destination) to stow into--date, -d DATE: Specify date in YYYYMMDD format--field, -f NAME=VALUE: Specify the value of a field (repeatable)file: Path to the file to stow
After a successful move, stow-file prints the absolute destination path on
stdout (the Done status goes to stderr), so scripts can capture it, e.g.
dest=$(filez4eva stow-file -d 20240213 -f title=receipt file.pdf).
If the file has a transcript (FILE.md, written by transcribe), stow-file
moves it too, named after the stowed file. By default it goes adjacent to the
stowed file: .../20261002-receipt.pdf gets .../20261002-receipt.pdf.md.
It can instead go into a parallel tree; see
Transcript location. If a transcript already exists at
its destination, stow-file fails before moving anything.
scan-dir command:
dir: Optional path to directory to scan (defaults to configured source)
transcribe command:
--force, -f: Transcribe again even if the transcript is up to datefile: Path to the file to transcribe
classify command:
--cabinet, -c NAME: Specify the cabinet to classify forfile: Path to the file to classify
process command:
--cabinet, -c NAME: Specify the cabinet to classify for and stow into--dry-run: Show where the file would go without moving anythingfile: Path to the file to process
Configuration
Create a filez4eva.yml file with:
filez4eva:
source: '~/Desktop' # Default source directory
target: '~/Dropbox/accounts' # Target directory for stowed files
By default, files are stowed directly under the target, named after their date and a title, keeping their extension:
~/Dropbox/accounts/<date>-<title>.<extension>
Destination pattern
The layout under target can be changed with the optional pattern key:
filez4eva:
target: '~/Dropbox/accounts'
pattern: '{date.year}/{account}/{date}_{part}'
The default is '{date}-{title}'. Quote the pattern in YAML, since it
starts with {.
The pattern is the only place the cabinet's fields are named: each
placeholder is a field, and the fields are prompted for (and shown to the
AI) in the order they first appear. A field's first placeholder sets its
position, whether or not it has a modifier, so with
'{date.year}/{account}/{date}_{part}' the order is date, account, part.
Placeholders:
{date}: the date as YYYYMMDD, e.g.20240213. Every pattern must contain{date}.{date.year}: the date's four-digit year, e.g.2024. It doesn't count as{date}, but it does set the date's position in the field order.{name}: any other field, wherenameis lowercase letters, digits and underscores, starting with a letter (e.g.{title},{account},{event}). Its value is hyphen-separated-lower.
The source file's extension (e.g. .pdf) is always added to the end, so
don't put it in the pattern. Use / to create subdirectories; missing
directories are created. {{ and }} are literal braces, and any other
text is used literally. Since field values contain hyphens, use another
separator such as _ between two fields if you want to tell them apart
later.
A pattern is a configuration error, and stow-file exits before moving
anything or prompting, if it:
- doesn't contain
{date} - has a placeholder that isn't a valid field name, such as
{},{0},{Title},{title:>4},{title!r}or{title[0]} - has a modifier other than
{date.year} - has unbalanced braces
- is absolute, starts with
~, or contains..
Fields
Each field can have settings under fields, keyed by the field name:
filez4eva:
target: '~/Dropbox/accounts'
pattern: '{date.year}/{account}/{date}_{part}'
fields:
date:
guidance: The transaction or statement date, not the print date
account:
guidance: The organization on the other side of the transaction, never me
part:
guidance: The kind of document, such as visa-statement or tax-receipt
values: learned
guidanceis shown to the AI model when it proposes a value for the field. Fordate, it replaces the built-in description, but the fixed rules always apply: the value is YYYYMMDD, the model must not guess, and it is empty if the document has no date of its own.valuessays whether filez4eva keeps a list of the field's values:learned(see Learned values) ornone.datecan't havevalues. A fixed list of values is not yet supported.
Without settings, date and title have simple built-in guidance, and other
fields have none. title defaults to values: none, and every other field
except date defaults to values: learned.
Each setting is looked up in the cabinet's own fields, then the top-level
fields, then the built-in default. A field set in a cabinet's fields but
not in its pattern is a configuration error, as is a top-level field that no
pattern uses, so typos are caught. Other configuration errors are a field
name that isn't valid, a setting other than guidance or values, guidance
that isn't text, and an unknown values.
Learned values
For fields with values: learned, filez4eva remembers the values it files,
in .filez4eva/values.yaml inside each cabinet's target:
account:
amazon:
count: 3
last_used: '2026-10-15'
- A value is recorded only when
stow-file(orprocessorscan-dir) actually moves a file, wherever the value came from. A dry run records nothing, and existing files aren't scanned, so the list starts empty. - Tab completion, and the AI model, are offered values used at least twice, most-used first, at most 100 per field. A value used once is remembered, but only offered once it is used again.
- You can seed or prune the list by editing the file. Values that aren't hyphen-separated-lower are never offered.
- The file is written to a temporary file and renamed, so a synced target never sees a half-written file. If it can't be read or written, the file is still stowed, a warning is shown, and the existing file is left as it was.
Cabinets
To file into more than one destination, configure named cabinets:
filez4eva:
fields:
date:
guidance: The date the document was issued
cabinets:
finances:
target: ~/Dropbox/finances
pattern: '{date.year}/{account}/{date}_{part}'
description: Personal financial records
fields:
account:
guidance: The organization on the other side of the transaction, never me
memories:
target: ~/Dropbox/memories
pattern: '{event}/{date}'
fields:
date:
guidance: When the photo was taken
event:
guidance: The occasion, such as grandma-80th-birthday
linkedin:
target: ~/Dropbox/linkedin
Each cabinet has:
target(required): the directory files are stowed underpattern(optional): the layout under the target, as described above, which also sets the cabinet's fields. Defaults to the top-levelpatternif set, otherwise'{date}-{title}'. The set of fields always comes from one pattern as a whole; patterns are never combined.fields(optional): settings for the fields in the cabinet's pattern, which win over the top-levelfields; see Fieldsdescription(optional): a note on what the cabinet holds, shown to the AI modeltranscripts(optional): where transcripts of files stowed in this cabinet go, withlocationandrootkeys. Each key defaults to the top-leveltranscriptsvalue; see Transcript location
Choose the cabinet with --cabinet NAME (or -c NAME), or with a cabinet
key on stdin; the flag wins if both are given. If neither is given and there
is more than one cabinet, stow-file prompts for one, with tab-completion of
the names. With only one cabinet it is used without asking. An unknown name
from the flag or stdin is an error; an unknown name typed at the prompt is
rejected and the prompt asks again. scan-dir asks for a cabinet for each
file it stows (once per file, before classifying it).
Prompts, tab completion and learned values use the chosen cabinet's fields and target.
If there is no cabinets section (or it is empty), the top-level target,
pattern and fields form a single cabinet named default. If cabinets
is present, the top-level target is ignored.
The whole configuration is checked before any prompt: a cabinet with no
target, or with an invalid pattern, fields or transcripts setting, is an
error even if it isn't the one being used.
Transcript location
When stow-file stows a file that has a transcript, the transcript goes
next to the stowed file by default (adjacent). To keep transcripts in a
parallel tree instead:
filez4eva:
transcripts:
location: parallel # or 'adjacent' (the default)
root: ~/Dropbox/transcripts
With location: parallel, the transcript goes to
<root>/<cabinet>/<path under the cabinet target>.md. For example, stowing
into the finances cabinet at 2026/amazon/20261002_receipt.pdf puts the
transcript at ~/Dropbox/transcripts/finances/2026/amazon/20261002_receipt.pdf.md.
Missing directories are created, and the root may be on another filesystem.
Without a cabinets section the single cabinet is named default, so
transcripts go under <root>/default/.
A cabinet can override either key with its own transcripts block; a key it
doesn't set falls back to the top-level value:
filez4eva:
transcripts:
location: parallel
root: ~/Dropbox/transcripts
cabinets:
accounts:
target: ~/Dropbox/accounts # transcripts in the parallel tree
memories:
target: ~/Dropbox/memories
transcripts:
location: adjacent # transcripts next to the files
A location other than adjacent or parallel, parallel without a
root, or a transcripts value (top-level or in a cabinet) that isn't a
mapping is a configuration error, and stow-file exits before prompting or
moving anything. An error in a top-level setting names the top level rather
than a cabinet. An empty transcripts: block, or an empty location, counts
as unset, but other values such as no or 0 are rejected as invalid
locations.
This only affects stow-file. transcribe and scan-dir always write the
transcript of an incoming file next to it, as FILE.md.
Transcription settings
transcribe uses these optional keys:
filez4eva:
transcribe:
model: claude-opus-4-6 # Anthropic model ID
anthropic:
key: $(op read op://vault/anthropic/credential)
transcribe: model:defaults toclaude-opus-4-6.anthropic: key:is the Anthropic API key. Like other values it may use$(...)to run a command, such as a password manager lookup. It can also be set with theFILEZ4EVA_ANTHROPIC_KEYenvironment variable, which takes precedence over the config file. If neither is set, the Anthropic SDK's ownANTHROPIC_API_KEYenvironment variable is used.
Filez4Eva only reads its own configuration; Kwark's configuration is never used.
Classification settings
classify uses these optional keys, as well as the transcribe model and
the anthropic key above:
filez4eva:
classify:
model: claude-haiku-4-5 # Anthropic model ID (the default)
doctypes:
receipt:
description: Proof of purchase or payment
bill:
description: A request for payment, usually with a due date
classify: model:defaults toclaude-haiku-4-5. Transcription still usestranscribe: model:.doctypesmaps each document type to an optionaldescription, which is shown to the model. Names must be single words (letters, digits,-and_).otheris always available; configure it only to change its description. With nodoctypes, every document's doctype isother.- A
doctypesvalue that isn't a mapping, a name that isn't a single word, or a description that isn't text is a configuration error, andclassifyexits before calling the model.
Upgrading from version 5
Version 6 replaces the fixed account and part with fields named by each
cabinet's pattern:
- The default pattern is now
{date}-{title}, directly under the target. To keep the old layout, setpattern: '{date.year}/{account}/{date}-{part}'. The prompts then come in the same order as before: date, account, part. {year}is now{date.year}, and{ext}is no longer used: the extension is always added. In a version 6 pattern,{year}or{ext}would be an ordinary field that you are prompted for.stow-file --account/-aand--part/-pare replaced by--field/-f NAME=VALUE.- In the
classifyrecord andstow-filestdin, the fields (includingdate) are underdata:. - Field values are normalized to hyphen-separated-lower, so existing directories with capitals or spaces won't match new values.
- Tab completion no longer scans existing folders; it offers learned values instead. Existing files are not moved.
Metadata
Release files for filez4eva 5.2.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 | |
|---|---|---|---|
| filez4eva-5.2.0.tar.gz | 34.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| filez4eva-5.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.5 kB
Release files / filez4eva-5.2.0.tar.gz
| Download URL | filez4eva-5.2.0.tar.gz |
|---|---|
| Size | 34.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
95ef2095e5daa37843d8e20cc6559804da83fddbafa3b9c4d52acae93eca47f5
|
|
BLAKE2b-256 checksum How to use checksums |
7f5e260b43d0b45fd58063cf8dff75462cfbaa516049fb52a980af337ee92985
|
| 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 Oct 15, 2026.
Transparency logRelease files / filez4eva-5.2.0-py3-none-any.whl
| Download URL | filez4eva-5.2.0-py3-none-any.whl |
|---|---|
| Size | 32.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0a2aaa04188f791c43dbeccf5db1418df8e5b4f95d3cf4e3d6a31abd238833b2
|
|
BLAKE2b-256 checksum How to use checksums |
ba715f46cca919a2122d80cfa6f7dbe6435bc8329e5ccd12abe205aa1d2b953a
|
| 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 Oct 15, 2026.
Transparency log