🌰 Hazelnut

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:

# 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:

hazelnutd start

Use the TUI

Open the terminal interface to manage rules and monitor activity:

hazelnut

Status Output

Check the daemon status with:

$ 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

[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:

[[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

KeyAction
a / nAdd new watch
eEdit selected
dDelete selected

Conditions

All conditions must match for a rule to apply. Omit conditions you don’t need.

File Name

[rule.condition]
name_matches = "Screenshot*.png"  # Glob pattern
name_regex = "^invoice_\\d+\\.pdf$"  # Regex pattern

File Type

[rule.condition]
extension = "pdf"  # Single extension
extensions = ["jpg", "jpeg", "png", "gif"]  # Multiple
is_directory = false
is_hidden = true  # Files starting with .

File Size

[rule.condition]
size_greater_than = 10485760  # > 10 MB (in bytes)
size_less_than = 1048576     # < 1 MB

File Age

[rule.condition]
age_days_greater_than = 30  # Older than 30 days
age_days_less_than = 7     # Newer than 7 days

Actions

Move

[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

[rule.action]
type = "copy"
destination = "~/Backup"
create_destination = true
overwrite = false
 

Rename

[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

[rule.action]
type = "trash"  # Safe, recoverable (uses native OS trash)
# Or permanently delete (⚠️ dangerous!)
[rule.action]
type = "delete"

Run Command

[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

[rule.action]
type = "archive"
destination = "~/Archives"
delete_original = false

Supports both files and directories (directories are recursively archived).

Examples

Organize Screenshots

[[rule]]
name = "Screenshots"
 
[rule.condition]
name_matches = "Screenshot*.png"
 
[rule.action]
type = "move"
destination = "~/Pictures/Screenshots"

Clean Old Downloads

[[rule]]
name = "Clean old downloads"
 
[rule.condition]
age_days_greater_than = 30
extensions = ["tmp", "log", "bak"]
 
[rule.action]
type = "trash"

Sort Media Files

[[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

[[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:

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

KeyAction
nCreate new rule
eEdit selected rule
dDelete selected rule
Enter / SpaceToggle rule enabled

General

KeyAction
tOpen theme picker
sOpen settings
AAbout dialog

Watches View

KeyAction
a / nAdd new watch
eEdit selected
dDelete selected

Rule Editor / Watch Editor

KeyAction
TabNext field
Shift+TabPrevious
EnterSave
EscCancel