π° Hazelnut
6 min read
CLI file autosorter inspired by macOS Hazel. Watches directories and automatically moves, renames, or processes files based on configurable rules (name patterns, extensions, dates, etc.).
Architecture
Hazelnut consists of two main components:
β’ hazelnut - The TUI (Terminal User Interface) for managing rules, viewing logs, and monitoring file organization activity.
β’ hazelnutd - The background daemon that watches folders and applies rules in real-time. Communicates with the TUI via Unix socket.
Quick Start
Install & Configure
Edit ~/.config/hazelnut/config.toml:
toml
# Watch your Downloads folder
[[watch]]
path = "/home/youruser/Downloads"
# Move PDFs to Documents
[[rule]]
name = "Organize PDFs"
[rule.condition]
extension = "pdf"
[rule.action]
type = "move"
destination = "/home/youruser/Documents/PDFs"
Run the Daemon
The daemon runs in the background, watching folders and applying rules:
sh
hazelnutd start
Use the TUI
Open the terminal interface to manage rules and monitor activity:
sh
hazelnut
Status Output
Check the daemon status with:
sh
$ hazelnutd status
π° Hazelnut daemon is running
PID: 12345
PID file: ~/.local/state/hazelnut/hazelnutd.pid
Log file: ~/.local/state/hazelnut/hazelnutd.log
Uptime: 2h 15m 30s
Configuration
Full Example Configuration
toml
[general]
log_level = "info"
log_file = "~/.local/share/hazelnut/hazelnut.log"
debounce_seconds = 2
# Watch folders
[[watch]]
path = "~/Downloads"
recursive = false
[[watch]]
path = "~/Desktop"
recursive = false
# Rules
[[rule]]
name = "Move PDFs"
enabled = true
[rule.condition]
extension = "pdf"
[rule.action]
type = "move"
destination = "~/Documents/PDFs"
General Settings
| Setting | Type | Default | Description |
|------|------|--------|--------| ------ | -------- | ----------------------------------------------- |
| log_level | string | "info" | Logging level (trace, debug, info, warn, error) |
| log_file | string | none | Path to log file |
| debounce_seconds | int | 2 | Wait time before processing a file |
| polling_interval_secs | int | 5 | How often to check for file changes |
| log_retention | int | 500 | Maximum activity log entries to keep |
| start_daemon_on_launch | bool | false | Auto-start daemon when TUI opens |
| notifications_enabled | bool | false | Desktop notifications on errors |
π Desktop Notifications: Enable notifications_enabled to get alerted when something goes wrong (watch errors, rule failures, command errors). Works on Linux, macOS, and Windows.
Watch Folders
Configure which directories Hazelnut monitors for changes:
toml
[[watch]]
path = "/home/user/Downloads" # Use full paths
recursive = false
rules = [] # Empty = all rules apply
π‘ TUI Tip: You can manage watches directly in the terminal interface! Press a to add, e to edit, d to delete.
Watch Fields
| Field | Type | Default | Description |
| ----------- | ------ | -------- |------|------|--------|-----------------------------------------------|
| path | string | required | Directory to watch (use full paths, ~ not expanded) |
| recursive | bool | false | Watch subdirectories |
| rules | array | [] | Rule names to apply (empty = all rules apply) |
Watch Editor Keybindings
| Key | Action |
|---|---|
a / n | Add new watch |
e | Edit selected |
d | Delete selected |
Conditions
All conditions must match for a rule to apply. Omit conditions you don't need.
File Name
toml
[rule.condition]
name_matches = "Screenshot*.png" # Glob pattern
name_regex = "^invoice_\\d+\\.pdf$" # Regex pattern
File Type
toml
[rule.condition]
extension = "pdf" # Single extension
extensions = ["jpg", "jpeg", "png", "gif"] # Multiple
is_directory = false
is_hidden = true # Files starting with .
File Size
toml
[rule.condition]
size_greater_than = 10485760 # > 10 MB (in bytes)
size_less_than = 1048576 # < 1 MB
File Age
toml
[rule.condition]
age_days_greater_than = 30 # Older than 30 days
age_days_less_than = 7 # Newer than 7 days
Actions
Move
toml
[rule.action]
type = "move"
destination = "~/Documents/PDFs"
create_destination = true # Create folder if missing
overwrite = false # Don't overwrite existing files
Works across filesystems β automatically falls back to copy + delete when needed. Supports both files and directories.
Copy
toml
[rule.action]
type = "copy"
destination = "~/Backup"
create_destination = true
overwrite = false
Rename
toml
[rule.action]
type = "rename"
pattern = "{date}_{name}.{ext}"
Available pattern variables:
β’ {name} - Filename without extension
β’ {filename} - Full filename
β’ {ext} - Extension (empty string if none)
β’ {path} - Full path
β’ {dir} - Parent directory
β’ {date} - Current date (YYYY-MM-DD)
β’ {datetime} - Current datetime
β’ {date:FORMAT} - Custom date format
Trash / Delete
toml
[rule.action]
type = "trash" # Safe, recoverable (uses native OS trash)
toml
# Or permanently delete (β οΈ dangerous!)
[rule.action]
type = "delete"
Run Command
toml
[rule.action]
type = "run"
command = "convert"
args = ["{path}", "-resize", "50%", "{dir}/{name}_small.{ext}"]
Pattern variables are automatically shell-escaped for security. Commands have a 60-second timeout.
Archive
toml
[rule.action]
type = "archive"
destination = "~/Archives"
delete_original = false
Supports both files and directories (directories are recursively archived).
Examples
Organize Screenshots
toml
[[rule]]
name = "Screenshots"
[rule.condition]
name_matches = "Screenshot*.png"
[rule.action]
type = "move"
destination = "~/Pictures/Screenshots"
Clean Old Downloads
toml
[[rule]]
name = "Clean old downloads"
[rule.condition]
age_days_greater_than = 30
extensions = ["tmp", "log", "bak"]
[rule.action]
type = "trash"
Sort Media Files
toml
[[rule]]
name = "Sort Images"
[rule.condition]
extensions = ["jpg", "jpeg", "png", "gif", "webp"]
[rule.action]
type = "move"
destination = "~/Pictures/Unsorted"
[[rule]]
name = "Sort Videos"
[rule.condition]
extensions = ["mp4", "mov", "avi", "mkv"]
[rule.action]
type = "move"
destination = "~/Videos/Unsorted"
Date-Prefix Invoices
toml
[[rule]]
name = "Date prefix invoices"
[rule.condition]
name_matches = "invoice*.pdf"
[rule.action]
type = "rename"
pattern = "{date:YYYY-MM-DD}_{name}.{ext}"
TUI Overview
The TUI provides a beautiful interface for managing Hazelnut. Launch it with:
sh
hazelnut
Views
β’ Dashboard - Overview of daemon status and recent activity
β’ Rules - Manage your file organization rules
β’ Log - View recent file operations
β’ Watches - Manage watched directories
Keyboard Shortcuts
| Key | Action |
|--|----| --------------------- |
| ? | Show help |
| q | Quit |
| 1-4 | Switch tabs |
| Tab | Next tab |
| j/k or β/β | Navigate list |
| g / G | Go to first/last item |
Rules View
| Key | Action |
|---|---|
n | Create new rule |
e | Edit selected rule |
d | Delete selected rule |
Enter / Space | Toggle rule enabled |
General
| Key | Action |
|---|---|
t | Open theme picker |
s | Open settings |
A | About dialog |
Watches View
| Key | Action |
|---|---|
a / n | Add new watch |
e | Edit selected |
d | Delete selected |
Rule Editor / Watch Editor
| Key | Action |
|---|---|
Tab | Next field |
Shift+Tab | Previous |
Enter | Save |
Esc | Cancel |