Safe, local-first file organization.
Preview changes. Apply explicitly. Undo safely.
Keep messy folders organized without giving up control.
Installation · Quick demo · Rules · Watch + Tray · Safety · Documentation
Sift is an open-source CLI for organizing messy directories safely, predictably, and locally.
It can preview changes, classify files, organize nested folders, diagnose risky entries, apply per-folder rules, keep history, undo successful moves, and continuously organize new files with Sift Watch.
preview → understand → apply → undo
↓
watch
No cloud required. No AI required. No silent filesystem changes.
no --apply
→ no filesystem mutation
| 🛡️ Safe by default | Plans are previews until you explicitly use --apply. |
| ↩️ Undo built in | Successful moves are recorded and can be safely reversed. |
| 🔍 Explainable | See why a file was classified, skipped, protected, or moved. |
| 📁 Folder-aware | Organize loose files or eligible folders without flattening directory context. |
| ⚙️ Per-folder rules | Drop a .sift.toml into a directory and give it its own policy. |
| ⚡ Watch mode | Turn selected folders into continuously organized inboxes. |
| 🖥️ Tray app | Manage watched folders visually without keeping a terminal open. |
| 🔒 Local-first | Core organization happens on your machine. |
Sift is deliberately conservative around important data:
- software projects are protected;
- hidden entries are left alone;
- symlinks are not followed for organization decisions;
- existing destinations are never silently overwritten;
- directory trees are not automatically merged;
- unsafe destination paths are rejected;
- cleanup uses the operating system trash;
- live filesystem assumptions are revalidated before mutation.
Imagine this directory:
Downloads/
├── invoice.pdf
├── vacation.jpg
├── backup.zip
├── data.json
├── model.stl
├── experiment.rs
├── notes.xyz
└── my-project/
├── Cargo.toml
└── src/
Preview first:
sift organize ~/DownloadsSift can produce a plan like:
invoice.pdf → Documents/
vacation.jpg → Images/
backup.zip → Archives/
data.json → Data/
model.stl → 3D/
experiment.rs → Code/
notes.xyz → Other/
my-project/ → protected: software project
No changes made.
Run with --apply to execute.
Happy with the plan?
sift organize ~/Downloads --apply✓ Applied successfully
7 files moved
7 directories created
1 entry skipped
0 failures
History
hist-...
Changed your mind?
sift undo hist-...✓ 7 items restored
Build a deterministic organization plan for a directory.
# Current directory
sift
# Another directory
sift ~/Downloads
# Explicit command
sift organize ~/Downloads
# Apply after reviewing
sift organize ~/Downloads --applyBuilt-in type classification includes:
Documents/
Images/
Audio/
Video/
Archives/
3D/
Code/
Data/
Other/
Unknown ordinary files go to Other/ by default.
Sift can organize eligible nested directories in their own local context:
sift organize ~/Downloads --recursive
sift organize ~/Downloads --recursive --applyGiven:
Downloads/
├── invoice.pdf
└── Client A/
├── proposal.pdf
└── logo.png
Recursive organization produces the equivalent of:
Downloads/
├── Documents/
│ └── invoice.pdf
└── Client A/
├── Documents/
│ └── proposal.pdf
└── Images/
└── logo.png
Sift does not flatten Client A/proposal.pdf into the root Documents/ folder.
sift folders reasons about immediate child folders from their contents rather than trusting the folder name.
sift folders ~/DownloadsExample:
Plan
3 folders to move
1 suggestion
1 protected
1 uncertain
Move
client-files/ → Documents/ 100%
print-jobs/ → 3D/ 100%
vacation/ → Images/ 100%
Suggestions
artwork/ → Images/ 67% medium confidence
Protected
my-app/ software project
Uncertain
misc/ mixed content
The idea is simple:
high confidence → plan the move
medium confidence → suggest
protected → leave alone
uncertain → leave alone
doctor is a read-only filesystem diagnostic.
sift doctor ~/Downloads
sift doctor ~/Downloads --recursive
sift doctor ~/Downloads --jsonIt can report:
- software projects;
- protected directories;
- symlinks;
- hidden entries;
- sensitive-looking filenames;
- files larger than 100 MB;
- archives older than one year;
- known build/dependency output directories such as
node_modules,target, and.venv.
Example:
Issues found
⚠ SLACK_TOKEN.txt
Sensitive-looking filename
⚠ my-project/
Software project detected — protected
⚠ some-link
Symlink
3 findings
The sensitive-filename check is filename-based. Sift does not need to read file contents to report it.
Find high-confidence junk and send it to the operating system trash when explicitly applied.
# Preview
sift clean ~/Downloads
# Apply
sift clean ~/Downloads --applyBuilt-in junk candidates currently include:
*.tmp
*.swp
*.swo
There is intentionally no clean --recursive today. Cleanup has a higher destructive risk, so Sift keeps that surface narrower.
Trash actions are not restored by
sift undo. Recovery belongs to the operating system trash.
Ask Sift why it would make a specific decision.
sift explain ~/Downloads/invoice.pdfExample:
Policy
built-in defaults
Strategy
type
Classification
.pdf → Document
Destination
~/Downloads/Documents/invoice.pdf
Decision
MOVE
Document
Safety
✓ regular file
✓ not protected
✓ destination available
✓ no symlink ancestor
✓ no collision
No changes were made.
With a custom rule, explain also shows the matching rule and reason.
Applied organization is recorded.
sift historyReverse a recorded move operation:
sift undo hist-<operation-id>Undo is intentionally defensive. Before restoring a file, Sift verifies that:
- the original path is still free;
- the moved destination still exists;
- the destination is still a regular file;
- the destination has not become a symlink;
- the destination has not become a directory.
If the live filesystem no longer matches those assumptions, Sift refuses the unsafe undo.
Every directory can have its own .sift.toml.
~/Music/.sift.toml
~/Pictures/.sift.toml
~/Documents/.sift.toml
~/Downloads/.sift.toml
Generate a starter configuration:
sift init ~/DownloadsA policy can define the base strategy, Watch stability, and explicit rules:
version = 1
[organize]
strategy = "type"
unknown = "other"
[watch]
stability_seconds = 3
[[rules]]
enabled = true
priority = 100
pattern = "*.tmp"
action = "Trash"
description = "Trash all .tmp files"
[[rules]]
enabled = true
priority = 90
pattern = "*.zip"
action = "Move"
destination = "Archives"
description = "Move .zip to Archives"Explicit enabled rules win over the strategy.
explicit rule
↓
organize strategy
↓
built-in behavior
Validate the configuration:
sift config check ~/DownloadsInspect a specific decision:
sift explain ~/Downloads/archive.zipRules support:
| Action | Meaning |
|---|---|
Move |
Move to a relative destination beneath the target directory |
Trash |
Send the matched file to the OS trash when applied |
Skip |
Explicitly leave the file untouched |
Higher priority values run first. The first enabled matching rule wins.
Destinations cannot escape the target directory. Absolute paths, .., unsafe root components, and symlink escapes are rejected.
Besides strategy = "type", Sift supports strategies that derive destination folders from metadata.
| Strategy | Metadata source | Example |
|---|---|---|
date |
filesystem modification time | {year}/{month} |
audio |
audio tags | {artist}/{album} |
video |
video/container metadata | {resolution}/{year} |
photos |
EXIF metadata | {camera}/{year}/{month} |
documents |
PDF / Office metadata | {author}/{year} |
Rules still take precedence over the selected strategy.
[organize]
strategy = "date"
template = "{year}/{month}"Supported placeholders:
{year}
{month}
{day}
[organize]
strategy = "audio"
template = "{artist}/{album}"Supported metadata includes:
{artist}
{album}
{album_artist}
{genre}
{track}
{title}
{year}
[organize]
strategy = "photos"
template = "{camera}/{year}/{month}"Sift reads EXIF metadata and supports:
{camera}
{year}
{month}
{day}
There is intentionally no GPS/location placeholder.
[organize]
strategy = "documents"
template = "{author}/{year}"Supported placeholders:
{author}
{title}
{year}
{month}
{day}
[organize]
strategy = "video"
template = "{resolution}/{year}"Sift can read MP4/MOV container metadata without an external tool.
If ffprobe is installed, Sift can use it automatically for broader format support and additional metadata such as duration and FPS.
Missing metadata is a skip, never a guess.
If a template needs {artist} and the file has no artist tag, Sift skips that file with a reason instead of inventing a folder such as Unknown Artist/.
Use sift explain <file> to inspect the missing field.
audio, video, photos, and documents currently do not support recursive organization.
A normal non-recursive sift organize and non-recursive Watch work normally with those strategies.
Sift Watch turns selected folders into continuously organized inboxes.
Automatic mutation requires explicit persistent authorization:
--auto-applyRegister a folder:
sift watch add ~/Inbox --auto-applyA newly registered watch starts in stopped state.
add
↓
stopped
↓ start
running
↕
pause / resume
↓ stop
stopped
Start it:
sift watch start ~/InboxFrom then on, new stable eligible files can be organized automatically.
invoice.pdf appears
↓
wait until stable
↓
evaluate policy
↓
Documents/invoice.pdf
Pre-existing files are not automatically backfilled by CLI Watch.
If you want existing content organized first:
sift organize ~/Inbox --apply
sift watch start ~/Inboxsift watch list
sift watch status ~/Inbox
sift watch pause ~/Inbox
sift watch resume ~/Inbox
sift watch stop ~/Inbox
sift watch remove ~/InboxImportant lifecycle guarantees:
start/resumeonly return success after the daemon has confirmed that the root is actually being monitored;pause/stopwait for daemon teardown acknowledgement;- filesystem events are associated with the Watch generation that observed them;
- automatic mutation revalidates that the Watch is still running and authorized before execution;
- files created while paused are not backfilled on resume.
A filesystem event does not mean a file has finished arriving.
Browsers, sync tools, and other applications may keep writing to a file for several seconds.
Sift therefore waits for a stability window before organizing a candidate.
Built-in default:
2.5 seconds unchanged
A .sift.toml can override it:
[watch]
stability_seconds = 3Accepted values are 1 through 300 seconds.
Common transient download names are ignored while temporary:
.crdownload
.part
.download
.tmp
sift-tray is a small separate desktop UI for Linux and macOS.
It lists watched folders and lets you:
- open a watched folder;
- pause or resume it;
- reapply organization immediately;
- toggle recursive mode where supported;
- remove the watch;
- see the current Watch state.
The tray app is intentionally thin: it uses the same Sift library and Watch registry rather than implementing a second organization engine.
Adding a folder through the tray is a deliberate authorization action.
Unlike CLI watch add, the tray performs one real organization pass on the folder's existing contents before starting Watch, so a folder picked in the tray is organized immediately and then kept organized from that point forward.
GitHub Releases include prebuilt sift-tray archives for:
Linux x86_64
macOS x86_64
macOS aarch64
Extract sift-tray next to the sift binary or put it somewhere on PATH.
install.sh does not install the tray automatically yet.
cargo build -p sift-tray --release
./target/release/sift-trayOn Debian/Ubuntu:
sudo apt-get install libgtk-3-dev libayatana-appindicator3-dev libxdo-devWhen sift-tray is installed next to sift or available on PATH, sift watch start and sift watch resume try to launch it automatically.
This is silent and best-effort:
- Watch still succeeds if the tray is not installed;
- headless environments do not fail because there is no display;
- only one tray instance runs at a time.
To launch it on its own, without starting or resuming a watch:
sift watch trayUnlike the auto-launch above, this reports exactly what happened — already running, started, not installed, or spawned but never came up.
curl -fsSL https://raw.githubusercontent.com/sergiocardoso/sift/main/install.sh | shThe installer downloads the latest prebuilt release for the supported OS/architecture, verifies its SHA256 checksum, and installs sift to:
~/.local/bin/sift
It never requires sudo.
Prebuilt CLI releases currently target Linux and macOS on x86_64/aarch64.
git clone https://github.com/sergiocardoso/sift.git
cd sift
cargo build --releaseRun:
./target/release/sift --helpOr install locally:
cargo install --path .Then:
sift --help# Safe preview of current directory
sift
# Safe preview of another directory
sift ~/Downloads
# Inspect entries
sift scan ~/Downloads
# Preview organization
sift organize ~/Downloads
# Apply
sift organize ~/Downloads --apply
# Recursive organization
sift organize ~/Downloads --recursive
# Analyze child folders
sift folders ~/Downloads
# Diagnose filesystem findings
sift doctor ~/Downloads
# Preview junk cleanup
sift clean ~/Downloads
# Apply cleanup
sift clean ~/Downloads --apply
# Explain a decision
sift explain ~/Downloads/invoice.pdf
# Validate local policy
sift config check ~/Downloads
# Show history
sift history| Command | Purpose | Mutates? |
|---|---|---|
sift [path] |
Safe organization preview | No |
sift scan [path] |
Inspect directory entries | No |
sift organize [path] |
Build an organization plan | Only with --apply |
sift folders [path] |
Classify immediate child folders by contents | Only when explicitly applied |
sift clean [path] |
Build a cleanup plan | Only with --apply |
sift doctor [path] |
Report filesystem findings | No |
sift explain <file> |
Explain one organization decision | No |
sift config check [path] |
Validate the effective configuration | No |
sift history |
Show recorded operations | No |
sift undo <id> |
Reverse successful recorded moves | Yes |
sift init [path] |
Create a starter .sift.toml |
Yes |
sift watch ... |
Manage continuous organization | Explicit authorization required |
Use built-in help for the exact current flags:
sift --help
sift organize --help
sift folders --help
sift clean --help
sift doctor --help
sift explain --help
sift config --help
sift watch --helpBuilt-in type classification is deterministic and case-insensitive by extension.
| Category | Destination | Examples |
|---|---|---|
| Documents | Documents/ |
pdf, txt, md, docx, xlsx, pptx, epub |
| Images | Images/ |
jpg, png, webp, svg, heic, avif |
| Audio | Audio/ |
mp3, wav, flac, m4a, ogg, opus |
| Video | Video/ |
mp4, mov, mkv, webm, m4v |
| Archives | Archives/ |
zip, rar, 7z, tar, gz, xz |
| 3D | 3D/ |
stl, obj, 3mf, step, blend, fbx, glb |
| Code | Code/ |
js, ts, py, rs, go, dart, sh, java |
| Data | Data/ |
json, yaml, toml, csv, xml, sql, sqlite |
| Other | Other/ |
ordinary unmatched files |
| Junk | OS trash candidate | tmp, swp, swo |
Sift does not read file contents for the built-in type classifier.
A directory containing common project markers is treated as a software project root.
Markers include:
.git
Cargo.toml
package.json
pyproject.toml
pubspec.yaml
Recursive traversal also stops at known build/dependency output directories such as:
node_modules
target
.venv
And it avoids descending into Sift's own category directories:
Documents
Images
Audio
Video
Archives
3D
Code
Data
Other
This prevents repeated nesting such as:
Documents/Documents/file.pdf
For a command targeting PATH, Sift currently resolves configuration in this order:
1. PATH/.sift.toml
2. platform config directory / sift / config.toml
3. built-in defaults
On a typical Linux system the global path is similar to:
~/.config/sift/config.toml
Sift does not currently walk parent directories looking for additional .sift.toml files.
For recursive organization, the configuration selected for the command root is used for that operation.
Watch resolves configuration from the registered Watch root.
Safety is part of Sift's architecture, not an optional mode.
sift organize ~/Downloadsshows the plan.
sift organize ~/Downloads --applyexecutes it.
If the destination already exists, the move is skipped.
That includes an existing:
- file;
- directory;
- symlink;
- broken symlink.
Sift uses no-follow metadata for safety-sensitive filesystem checks.
Symlinks are treated as occupied/protected filesystem entries rather than followed as normal targets.
Sift does not merge directory trees simply because destination names match.
Running organization against a recognized software project protects the project contents instead of dismantling the project into categories.
Planning-time assumptions are checked again immediately before mutation.
If a rename cannot be performed safely, Sift does not silently replace it with copy-then-delete.
Cleanup uses the operating system trash.
Machine-readable JSON is available for several command groups, including:
scan
organize
clean
doctor
watch list
watch status
Example:
sift organize ~/Downloads --json | jq .Update notices, when enabled, are written to stderr so they do not corrupt JSON stdout.
Sift's core organization is local-first and does not require a cloud service.
The optional update check is the exception.
At most once every 24 hours, Sift may spawn a detached background check for a newer GitHub release. The command that triggers the check does not wait for the network request.
Disable update checks completely:
export SIFT_NO_UPDATE_CHECK=1Without the update check, core Sift operations require no network access.
Supported by prebuilt CLI releases.
sift-tray is currently released for Linux x86_64.
Supported by prebuilt CLI releases for x86_64 and Apple Silicon.
sift-tray is also released for both architectures.
There is currently no official Windows release.
Most non-Watch functionality is built on portable Rust filesystem APIs and the cross-platform trash crate, but native Windows support is not yet considered an officially tested/supported target.
Watch's detached daemon requires platform-specific process work before Windows can be officially supported.
sift organize ~/Downloads
sift organize ~/Downloads --apply
sift historysift scan ~/Desktop --recursive
sift doctor ~/Desktop --recursive
sift organize ~/Desktop --recursivesift init ~/Downloads
$EDITOR ~/Downloads/.sift.toml
sift config check ~/Downloads
sift organize ~/Downloads~/Music/.sift.toml audio → {artist}/{album}
~/Pictures/.sift.toml photos → {camera}/{year}/{month}
~/Documents/.sift.toml documents → {author}/{year}
~/Downloads/.sift.toml type → Documents/Images/Audio/...
mkdir -p ~/Inbox
sift watch add ~/Inbox --auto-apply
sift watch start ~/Inbox
sift watch status ~/InboxThe core pipeline intentionally separates observation, planning, execution, and history.
Filesystem
│
▼
Scanner
│
▼
Classifier / metadata strategy
│
▼
Rules / policy
│
▼
Planner
│
▼
Review
│
▼
Executor
│
▼
History
│
▼
Undo
The scanner does not mutate the filesystem.
The planner decides what should happen.
The executor applies only explicitly authorized plans and revalidates safety assumptions immediately before mutation.
Watch reuses the same organization authority instead of implementing a separate, weaker organizer.
src/
├── classifier.rs
├── cli.rs
├── config.rs
├── domain.rs
├── executor.rs
├── fs.rs
├── history.rs
├── planner.rs
├── render.rs
├── scanner.rs
├── utils.rs
└── watch/
├── daemon.rs
├── eligibility.rs
├── engine.rs
├── platform.rs
├── registry.rs
└── stability.rs
sift-tray/
└── ...
Run the full local quality gate:
cargo fmt --check
cargo check --all-targets
cargo clippy --all-targets --all-features -- -D warnings
cargo testBuild the CLI:
cargo build --releaseBuild the optional tray app:
cargo build -p sift-tray --releaseContributions are welcome.
Before opening a pull request, please make sure the project is:
formatted
lint-clean
building
passing tests
See:
CONTRIBUTING.mdfor contribution guidance;SECURITY.mdfor security reports.
Sift is licensed under the Apache License 2.0.
Copyright 2026 Sérgio Cardoso.




