Skip to content

Repository files navigation

filo

CI License: MIT

Your Downloads folder cleans itself — safely, predictably, and reversibly.

filo is a cross-platform command-line tool that organizes files in folders you care about (Downloads, Desktop, anywhere else) by rules you control. It can run once, or sit in the background and keep things tidy as new files land.

It is built on three promises:

  • Safety. It never overwrites an existing file. Ever. Collisions are resolved by picking a new, unique name.
  • Predictability. The rules live in a plain TOML file you can read and edit. The same input always produces the same output.
  • Trust. Every run can be previewed first. filo preview shows exactly what would move where, without touching a single byte.

Table of contents


Why filo exists

Download folders rot. They start clean, then fifteen PDFs, eight installer disk images, a dozen random screenshots, and three copies of Report_FINAL_FINAL.pdf later they're impossible to navigate. Manual cleanup is a chore nobody does.

filo is a small, boring, reliable tool that does the chore for you.

It is deliberately not clever. It does not look inside files, call machine learning models, or invent names. It routes by keyword/extension, dedupes by SHA-256, and moves things into subfolders — or into any path you name. That's the whole product — and that's the point.


Installation

filo is a command-line tool. After installing by any method below, run it from a terminal (Command Prompt / PowerShell / Terminal). The package name on crates.io is filo-rs; the installed command is always filo.

Windows

Download for Windows

Download the .msi, double-click it, and follow the wizard. Then open a new terminal and run filo --version. To uninstall: Settings → Apps → Installed apps → filo.

Prefer a package manager? With Scoop:

scoop bucket add filo https://github.com/Kanishk2207/scoop-filo
scoop install filo

Ubuntu / Debian

Download for Ubuntu/Debian

Download the .deb and install it (double-click to open in Software Install, or from a terminal):

sudo dpkg -i ~/Downloads/filo-amd64.deb

To uninstall: sudo dpkg -r filo-rs.

macOS

brew install Kanishk2207/filo/filo-rs

Arch Linux

yay -S filo-bin

Any OS — from crates.io (recommended for Rust users)

cargo install filo-rs

The package name is filo-rs, and the installed command is still filo.

From source

Requires Rust 1.85 or newer. If you don't have Rust installed yet, follow the official steps: Install Rust.

git clone https://github.com/Kanishk2207/filo
cd filo
cargo install --path .

The filo binary is installed to ~/.cargo/bin/filo (Unix) or %USERPROFILE%\.cargo\bin\filo.exe (Windows). Make sure that path is on your PATH.

Verify

filo --version
filo --help

First-time setup

Run:

filo init

init walks you through an interactive wizard:

  1. Which folders should filo watch? You'll see suggestions (Downloads, Desktop, Documents) detected from your platform, and can add custom paths.
  2. Enable smart renaming? Default: off. When enabled, filenames are lowercased, hyphenated, and stripped of adjacent repeated words (Report_FINAL_FINAL.pdfreport-final.pdf). You can also prepend a YYYY-MM-DD date.
  3. Duplicate handling. skip (leave the duplicate where it is, log it) or move (relocate it to a Duplicates subfolder next to the category).
  4. Start watching now? If yes, filo jumps straight into start mode when setup ends.
  5. Start filo on system startup/login? Default: off. If yes, the wizard installs the OS login service for you (launchd on macOS, systemd on Linux, a registry Run key on Windows) and prints where it was installed, so you never have to discover filo autostart enable on your own. If the service manager refuses, setup still finishes and prints the exact command to retry. Say no and nothing changes; you can enable it any time later.

The wizard writes config.toml to the platform config directory (see Config file). You can re-run filo init any time to rebuild the config; existing configs are never silently overwritten.


Commands

Command What it does
filo init Interactive first-time setup. Writes config.toml.
filo start Scan existing files, then start the background watcher daemon.
filo stop Stop the background watcher daemon.
filo refresh Hot-reload config without restarting the daemon.
filo scan Organize files already sitting in your configured watch folders.
filo preview Dry run: print every move filo would make. No changes on disk.
filo arrange Manual, targeted move: pick files by keyword/extension and move them.
filo watch add <path>... Add folder(s) to the watch list.
filo watch remove <path>... Remove folder(s) from the watch list.
filo watch list Show currently watched folders.
filo autostart enable Install filo as an OS login service.
filo autostart disable Remove the OS login service.
filo autostart status Check if autostart is enabled.

Most commands accept --rename to enable smart renaming for that run only, independently of the config.

Daemon lifecycle

filo start runs an initial scan of existing files, then spawns a background daemon that watches your configured folders in real time. The daemon survives closing the terminal.

filo start                 # scan + start daemon
filo start --no-scan       # skip the initial scan
filo start --foreground    # run in the current terminal (for debugging or service managers)
filo stop                  # stop the daemon
filo refresh               # hot-reload config without restarting

filo refresh is zero-downtime: the daemon re-reads config.toml, tears down old watches, and sets up new ones without restarting the process. There is no gap in monitoring.

Managing watch folders

You don't need to edit config.toml by hand to add or remove folders:

filo watch add ~/Downloads ~/Desktop
filo watch remove ~/Desktop
filo watch list

If the daemon is running, changes take effect immediately (an automatic filo refresh is triggered).

Autostart on login

Install filo as a login service so it starts automatically when you log in:

filo autostart enable      # macOS: launchd | Linux: systemd | Windows: registry
filo autostart disable
filo autostart status

filo init offers this as a prompt, so you usually never need to run enable by hand. These commands remain the way to change your mind later. On macOS and Linux, enabling also starts the watcher right away; on Windows it takes effect at your next login.

Examples

Preview what would happen if you cleaned up your Downloads folder right now:

filo preview

Apply the plan for real:

filo scan

Run with smart renaming turned on, ignoring whatever's in the config:

filo scan --rename

Start the background watcher:

filo start

Manually collect every .pdf in Downloads and Desktop containing the word "invoice" into ~/Finance/2025:

filo arrange \
  --source ~/Downloads --source ~/Desktop \
  --destination ~/Finance/2025 \
  --keyword invoice \
  --extension pdf

Same, but skip the confirmation prompt:

filo arrange -s ~/Downloads -d ~/Finance/2025 -k invoice -e pdf -y

Config file

filo uses platform-appropriate paths (via the dirs crate). It never hardcodes /home/... or C:\Users\....

OS Config Log
Linux ~/.config/filo/config.toml ~/.local/share/filo/filo.log
macOS ~/Library/Application Support/filo/config.toml ~/Library/Application Support/filo/filo.log
Windows %APPDATA%\filo\config.toml %APPDATA%\filo\filo.log

The file is human-readable TOML. Edit it freely — filo will pick up your changes on the next run.

For a complete, field-by-field reference of every supported TOML option, see docs/toml-config-reference.md.

Annotated example

# Category used when no rule matches the file's extension.
other_category = "Other"

[watch]
# Folders filo watches and scans. Must be absolute paths.
folders = [
  "/home/you/Downloads",
  "/home/you/Desktop",
]
# If true, `filo init` launches `filo start` automatically after setup.
# This is a one-off for that session, not startup behavior. To run filo on
# every login, use `filo autostart enable` (or say yes when init asks).
auto_start = false
# How long (milliseconds) a file must sit idle before filo acts on it.
# Protects against operating on files that are still downloading.
debounce_ms = 2000

[rename]
# Master switch for smart renaming. When false, filenames are preserved.
enabled = false
# "My  Cool File.pdf" -> "my-cool-file.pdf"
lowercase_with_hyphens = true
# "Report_FINAL_FINAL.pdf" -> "report-final.pdf" (collapses adjacent repeats)
strip_redundant_words = true
# "report.pdf" -> "2026-05-02-report.pdf"
prepend_date = false

[duplicates]
# "skip": leave the duplicate in place and log it.
# "move": relocate it into a `Duplicates` subfolder beside the category.
action = "skip"
folder_name = "Duplicates"

# Optional ordered keyword routing in one block (first match wins):
[keyword_rules]
rules = [
  # Default: `to` is a folder name, created inside each watched folder.
  { to = "Amazon", keywords = ["amazon"] },
  { to = "Documents", keywords = ["invoice", "receipt", "statement"] },
  # `to_type = "path"` sends matches to a path of your choosing instead.
  { to = "~/personal/kanishk-itr", to_type = "path", keywords = ["itr"] },
]

[rules]
# Category -> list of extensions (no leading dot). Case-insensitive.
# Add/remove freely. Unknown extensions fall through to `other_category`.
# Used only when no keyword rule matched.
Archives  = ["zip", "tar", "gz", "bz2", "xz", "rar", "7z", "tgz"]
Audio     = ["mp3", "flac", "wav", "ogg", "m4a", "aac", "opus"]
Code      = ["rs", "py", "js", "ts", "go", "c", "cpp", "h", "java", "rb", "sh"]
Documents = ["pdf", "doc", "docx", "odt", "rtf", "txt", "md", "epub"]
Images    = ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "tiff", "heic", "avif"]
Videos    = ["mp4", "mkv", "mov", "avi", "webm", "flv", "wmv", "m4v"]

Sending matches to a path instead of a folder

By default a keyword rule's to is a folder name. filo creates that folder inside whichever watched folder the file came from, so to = "kanishk-itr" lands files in ~/Downloads/kanishk-itr/.

Add to_type = "path" to the rule and to becomes a path instead, so matched files can leave the watched folder entirely:

[keyword_rules]
rules = [
  # ~/Downloads/kanishk-itr/  (folder inside the watched folder — the default)
  { to = "kanishk-itr", keywords = ["itr"] },

  # ~/personal/kanishk-itr/  (anywhere you like)
  { to = "~/personal/kanishk-itr", to_type = "path", keywords = ["itr"] },
]

to_type accepts exactly two values:

Value Meaning
"folder" to is a folder name; destination is <watched-folder>/<to>. Default when to_type is omitted.
"path" to is a path; destination is that path.

Details worth knowing:

  • ~ expands to your home directory, so one config works across machines and users. $HOME and other environment variables are not expanded.
  • Relative paths are anchored to the watched folder. With to_type = "path" and to = "tax/2026", files from ~/Downloads land in ~/Downloads/tax/2026/ — which is how you nest more than one level deep.
  • Missing folders are created on the first move, including parents.
  • Absolute paths work as written, including Windows paths like C:\\Users\\you\\personal\\itr.
  • Every safety guarantee still applies. Nothing is overwritten, duplicate detection runs against the path destination, and duplicates land in <path>/Duplicates/ when duplicates.action = "move".
  • A rule pointing at the folder the file is already in is a no-op. filo reports it as already in place and leaves the file alone rather than shuffling it around or treating it as a duplicate of itself.
  • Only keyword rules can target a path. Extension rules in [rules] always route to a folder inside the watched folder.

Run filo preview after editing rules — it prints the full destination path for every file, so you can confirm the routing before anything moves.


Safety guarantees

These are non-negotiable invariants of the tool:

  1. Never overwrite. Before any move, the destination is checked. If a file with the target name exists, filo picks a new unique name in this strict priority order:
    1. Original name.
    2. Append current date: name-2026-05-02.ext.
    3. Append numeric suffix: name-2026-05-02-1.ext, -2, ... -999.
    4. Append a short hash of the name plus the current timestamp.
  2. Never delete. filo moves files. It does not delete them — not even duplicates. Duplicates are either left alone or relocated to a dedicated folder, per your config.
  3. Never act on in-progress writes. Files are ignored if they end in .part, .crdownload, .download, .tmp, or .partial. Watch events are debounced, and a second-stage size-stability check catches anything still in motion.
  4. Never touch hidden files. Anything starting with . (e.g. .DS_Store, .gitignore) is left alone.
  5. Graceful failures. Permission errors, missing files, and other I/O failures are logged and skipped. One bad file never aborts a scan.
  6. Deterministic. Duplicate detection is SHA-256. Same input bytes → same result. No fuzzy matching, no heuristics.

Preview mode, in detail

filo preview runs the exact same planning pipeline as filo scan, but stops right before any file is touched. The output looks like:

== /Users/you/Downloads ==
  move    /Users/you/Downloads/invoice.pdf  ->  /Users/you/Downloads/Documents/invoice.pdf
  move    /Users/you/Downloads/cat.jpg      ->  /Users/you/Downloads/Images/cat.jpg
  move    /Users/you/Downloads/ITR-2026.pdf ->  /Users/you/personal/kanishk-itr/ITR-2026.pdf
  skip    /Users/you/Downloads/cat-copy.jpg (duplicate of /Users/you/Downloads/Images/cat.jpg)
  dup ->  /Users/you/Downloads/old.pdf      ->  /Users/you/Downloads/Documents/Duplicates/old.pdf (duplicate of ...)

Preview summary (5 files examined):
  would move:               3
  would skip (duplicate):   1
  would move to Duplicates: 1

No changes were made. Run `filo scan` to apply.

Destinations are always printed in full, so a to_type = "path" rule shows exactly where the file leaves for. A file whose rule resolves to the folder it already sits in shows as keep and is counted under already in place.

Because the planner is pure, you can:

  • Preview → read the plan → edit config.toml → preview again, risk-free.
  • Diff two configs by diffing their previews.

Logging

filo writes to both stderr and an append-only log file.

  • Level defaults to info. Override with the standard env var:

    RUST_LOG=debug filo scan
  • Log file location is listed in the config file table. Every line is timestamped with millisecond precision.

  • Logging failures never affect command execution — if the log file can't be written, filo still does the right thing on disk.


Release plan

Next release

  1. Undo last action Add a command to roll back the most recent organize/arrange action safely.
  2. Configurable scan depth Add depth controls so scans can include child/nested folders, either via config or runtime prompts/flags.
  3. Path-first command usage Allow running filo directly on paths without changing directories first (for example: filo <path> <options>).
  4. Keyword match mode in arrange Add a match mode option so multiple keywords can be evaluated as OR or AND.

Next-to-next release

  1. Undo/redo Extend rollback support to full undo/redo flows.
  2. Undo/redo tree Represent operation history as a navigable tree (not just a linear stack).
  3. Undo/redo tree visualization Add a visual history view for that tree, inspired by Emacs-style workflows.

Contributing

Start with CONTRIBUTING.md for contribution workflow, PR checklist, and reporting guidance.

Branch strategy:

  • Open feature PRs into develop.
  • Treat develop as beta/integration.
  • Promote to main only after beta validation passes.
  • Tag develop commits for prereleases (for example v0.2.0-beta.1) and main commits for stable releases (for example v0.2.0).

filo is structured as a library (filo in src/lib.rs) plus a thin binary (src/main.rs). The binary only parses arguments and dispatches; all behavior lives in the library, which makes it straightforward to:

  • add a new command (drop a file in src/commands/);
  • add a new category (edit src/config/defaults.rs or just edit your own config.toml);
  • add a new duplicate policy (extend DuplicateAction in src/config/mod.rs and handle it in src/organizer/mod.rs::resolve_duplicate).

Layout:

src/
  main.rs          # binary entry point — parse CLI, dispatch
  lib.rs           # library root — public API
  cli.rs           # clap definitions (no logic)
  daemon.rs        # PID file, spawn, kill, reload signal
  errors.rs        # thiserror types per domain
  logging.rs       # env_logger + file output
  commands/        # one module per `filo <verb>`
    init.rs
    start.rs       # scan-then-daemon flow
    stop.rs
    refresh.rs
    scan.rs
    preview.rs
    arrange.rs
    watch_cmd.rs   # add/remove/list watch folders
    autostart.rs   # OS-specific login service (launchd/systemd/registry)
  config/          # on-disk config schema + defaults
    mod.rs
    defaults.rs
  organizer/       # the engine
    mod.rs         # plan() + execute()
    rules.rs       # extension -> category
    mover.rs       # safe_move + unique_destination
    renamer.rs     # smart rename pipeline
    duplicates.rs  # size + SHA-256 duplicate check
  watcher/
    mod.rs         # debounced notify-based watcher with hot-reload
tests/
  integration.rs   # end-to-end pipeline tests

Before sending a PR:

cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --check

By participating, you agree to follow CODE_OF_CONDUCT.md.


License

Licensed under either of:

Releases

Contributors

Languages