hazelnut 0.1.0

A terminal-based automated file organizer inspired by Hazel
Documentation

πŸ“– Table of Contents

✨ Features

πŸ“ Smart File Watching

Watch any folder for new and changed files with configurable debouncing and recursive monitoring.

🎯 Flexible Rules Engine

Define powerful rules with conditions based on name, extension, size, age, and more.

⚑ Powerful Actions

Move, copy, rename, delete, archive files, or run custom scripts β€” all automated.

πŸ–₯️ Beautiful TUI

A gorgeous terminal interface for managing rules and monitoring activity in real-time.

πŸ”§ Background Daemon

Set it and forget it β€” the daemon runs quietly and applies rules 24/7.

πŸ“ Simple Configuration

Human-readable TOML config that's easy to write and maintain.

Feature Highlights

Feature Description
πŸ” Pattern Matching Glob patterns and regex for precise file matching
πŸ“Š Size Conditions Filter files by size (greater than, less than)
πŸ“… Age Conditions Match files by modification date
🏷️ Multiple Extensions Match any of multiple file types
πŸ“‚ Recursive Watching Monitor subdirectories automatically
🎨 15 Built-in Themes From Dracula to Cyberpunk
πŸ”„ Dry Run Mode Preview what would happen before applying
πŸ“‹ Activity Log Full history of all file operations

πŸš€ Quick Start

Installation

Homebrew (Recommended)

brew install ricardodantas/tap/hazelnut

Cargo

cargo install hazelnut-cli

From Source

git clone https://github.com/ricardodantas/hazelnut
cd hazelnut
cargo install --path .

First Run

  1. Create a config file at ~/.config/hazelnut/config.toml:
# Watch your Downloads folder
[[watch]]
path = "~/Downloads"

# Organize PDFs automatically
[[rule]]
name = "Organize PDFs"

[rule.condition]
extension = "pdf"

[rule.action]
type = "move"
destination = "~/Documents/PDFs"
  1. Launch the TUI to manage and monitor:
hazelnut
  1. Or start the daemon to run in the background:
hazelnutd start

βš™οΈ Configuration

Hazelnut uses TOML for configuration. The default location is:

~/.config/hazelnut/config.toml

Full Configuration Example

# ─────────────────────────────────────────────────────────────
# General Settings
# ─────────────────────────────────────────────────────────────

[general]
# Logging level: trace, debug, info, warn, error
log_level = "info"

# Optional log file path
log_file = "~/.local/share/hazelnut/hazelnut.log"

# Preview actions without executing (great for testing)
dry_run = false

# Wait time (seconds) before processing a file after change detected
debounce_seconds = 2

# Theme for the TUI
theme = "catppuccin-mocha"

# ─────────────────────────────────────────────────────────────
# Watch Folders
# ─────────────────────────────────────────────────────────────

[[watch]]
path = "~/Downloads"
recursive = false

[[watch]]
path = "~/Desktop"
recursive = false
# Only apply specific rules to this folder
rules = ["screenshots", "temp-files"]

[[watch]]
path = "~/Documents/Inbox"
recursive = true  # Watch subdirectories too

# ─────────────────────────────────────────────────────────────
# Rules
# ─────────────────────────────────────────────────────────────

[[rule]]
name = "Organize PDFs"
enabled = true
stop_processing = false  # Continue checking other rules

[rule.condition]
extension = "pdf"

[rule.action]
type = "move"
destination = "~/Documents/PDFs"
create_destination = true
overwrite = false

[[rule]]
name = "Screenshots"
enabled = true

[rule.condition]
name_matches = "Screenshot*.png"

[rule.action]
type = "move"
destination = "~/Pictures/Screenshots"

[[rule]]
name = "Clean Old Downloads"
enabled = true

[rule.condition]
age_days_greater_than = 30
extensions = ["tmp", "log", "bak"]

[rule.action]
type = "trash"

See docs/configuration.md for the complete reference.

πŸ“‹ Rules

Rules are the heart of Hazelnut. Each rule has a condition (what files to match) and an action (what to do with them).

Conditions

All conditions in a rule must match for the rule to apply.

File Name

[rule.condition]
# Glob pattern matching
name_matches = "Screenshot*.png"

# Regex pattern matching
name_regex = "^invoice_\\d{4}\\.pdf$"

File Extension

[rule.condition]
# Single extension
extension = "pdf"

# Multiple extensions (match any)
extensions = ["jpg", "jpeg", "png", "gif", "webp"]

File Size

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

File Age

[rule.condition]
# Age in days (based on modification time)
age_days_greater_than = 30  # Older than 30 days
age_days_less_than = 7      # Newer than 7 days

File Type

[rule.condition]
is_directory = false  # Match only files
is_hidden = true      # Match hidden files (starting with .)

Actions

Move

[rule.action]
type = "move"
destination = "~/Documents/Archive"
create_destination = true  # Create folder if missing
overwrite = false          # Don't overwrite existing files

Copy

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

Rename

[rule.action]
type = "rename"
pattern = "{date}_{name}.{ext}"

Available variables:

Variable Description Example
{name} Filename without extension document
{filename} Full filename document.pdf
{ext} Extension pdf
{path} Full path /home/user/document.pdf
{dir} Parent directory /home/user
{date} Current date 2024-01-15
{datetime} Current datetime 2024-01-15_14-30-00
{date:FORMAT} Custom format {date:%Y%m%d} β†’ 20240115

Trash

[rule.action]
type = "trash"

Moves files to the system trash (recoverable).

Delete

[rule.action]
type = "delete"

⚠️ Warning: This permanently deletes files!

Run Command

[rule.action]
type = "run"
command = "convert"
args = ["{path}", "-resize", "50%", "{dir}/{name}_small.{ext}"]

Archive

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

Example Rules

[[rule]]
name = "Screenshots to folder"

[rule.condition]
name_matches = "Screenshot*.png"

[rule.action]
type = "move"
destination = "~/Pictures/Screenshots"
[[rule]]
name = "PDFs to Documents"

[rule.condition]
extension = "pdf"

[rule.action]
type = "move"
destination = "~/Documents/PDFs"

[[rule]]
name = "Spreadsheets to Documents"

[rule.condition]
extensions = ["xlsx", "xls", "csv"]

[rule.action]
type = "move"
destination = "~/Documents/Spreadsheets"
[[rule]]
name = "Delete old temp files"

[rule.condition]
age_days_greater_than = 30
extensions = ["tmp", "log", "bak"]

[rule.action]
type = "trash"
[[rule]]
name = "Prefix invoices with date"

[rule.condition]
name_regex = "^invoice.*\\.pdf$"

[rule.action]
type = "rename"
pattern = "{date:YYYY-MM-DD}_{filename}"
[[rule]]
name = "Compress large images"

[rule.condition]
extensions = ["jpg", "png"]
size_greater_than = 5242880  # > 5 MB

[rule.action]
type = "run"
command = "convert"
args = ["{path}", "-quality", "80", "{path}"]

⌨️ Keybindings

Global

Key Action
Tab Next view
Shift+Tab Previous view
1 2 3 4 Jump to view (Dashboard, Rules, Watches, Log)
s Open settings
t Open theme picker
A About Hazelnut
? / F1 Show help
q Quit (from Dashboard)
Ctrl+c / Ctrl+q Force quit

Navigation

Key Action
↑ / k Move up
↓ / j Move down
g / Home Go to first item
G / End Go to last item
PageUp Page up
PageDown Page down

Rules View

Key Action
Enter / Space Toggle rule enabled/disabled
e Edit rule (coming soon)
n New rule (coming soon)
d / Delete Delete rule (coming soon)

Watches View

Key Action
a Add watch folder (coming soon)
d / Delete Remove watch (coming soon)
o / Enter Open folder

Log View

Key Action
c Clear log

Theme Picker

Key Action
↑ / k Previous theme (with live preview)
↓ / j Next theme (with live preview)
Enter Apply selected theme
Esc Cancel

🎨 Themes

Hazelnut includes 15 beautiful themes based on popular terminal and editor color schemes.

Press t in the TUI to open the theme picker with live preview.

Available Themes

Theme Description
πŸ¦‡ Dracula Dark purple aesthetic (default)
πŸŒ™ One Dark Pro Atom's iconic dark theme
❄️ Nord Arctic, bluish color palette
🐱 Catppuccin Mocha Warm pastel dark theme
β˜• Catppuccin Latte Warm pastel light theme
🎸 Gruvbox Dark Retro groove colors
πŸ“œ Gruvbox Light Retro groove, light variant
πŸŒƒ Tokyo Night Futuristic dark blue
πŸŒ… Solarized Dark Precision colors, dark
🌞 Solarized Light Precision colors, light
🎨 Monokai Pro Classic syntax highlighting
🌹 Rosé Pine All natural pine with soho vibes
🌊 Kanagawa Inspired by Katsushika Hokusai
🌲 Everforest Comfortable green forest theme
πŸŒ† Cyberpunk Neon-soaked futuristic theme

πŸ—οΈ Architecture

Hazelnut consists of two binaries that work together:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         User                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       hazelnut (TUI)                            β”‚
β”‚  β€’ Manage rules                                             β”‚
β”‚  β€’ Monitor activity                                         β”‚
β”‚  β€’ Change themes                                            β”‚
β”‚  β€’ View logs                                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                         Unix Socket
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     hazelnutd (Daemon)                          β”‚
β”‚  β€’ Watch folders                                            β”‚
β”‚  β€’ Evaluate rules                                           β”‚
β”‚  β€’ Execute actions                                          β”‚
β”‚  β€’ Run 24/7 in background                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      File System                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

hazelnut β€” The TUI

Interactive terminal interface for:

  • Viewing and managing rules
  • Monitoring watch folders
  • Viewing activity logs
  • Changing themes
  • Sending commands to the daemon
hazelnut              # Launch TUI
hazelnut list         # List all rules
hazelnut check        # Validate config
hazelnut run          # Run rules once (dry-run)
hazelnut run --apply  # Run rules once (for real)
hazelnut status       # Check daemon status

hazelnutd β€” The Daemon

Background service that does the actual work:

hazelnutd start      # Start daemon (background)
hazelnutd stop       # Stop daemon
hazelnutd restart    # Restart daemon
hazelnutd status     # Show daemon status
hazelnutd reload     # Reload configuration
hazelnutd run        # Run in foreground (for debugging)

πŸ”§ Building from Source

Requirements

  • Rust 1.93+ (uses Edition 2024 features)
  • Linux or macOS

Build

# Clone the repository
git clone https://github.com/ricardodantas/hazelnut
cd hazelnut

# Build release binary
cargo build --release

# The binaries will be at:
# - target/release/hazelnut
# - target/release/hazelnutd

# Or install directly
cargo install --path .

Development

# Run TUI in development
cargo run

# Run daemon in foreground
cargo run --bin hazelnutd run

# Run with sample config
cargo run -- --config examples/config.toml

# Run tests
cargo test

# Run linter
cargo clippy

# Format code
cargo fmt

🀝 Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Quick Start for Contributors

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Make your changes
  4. Run tests: cargo test
  5. Run clippy: cargo clippy
  6. Format: cargo fmt
  7. Commit: git commit -m "Add amazing feature"
  8. Push: git push origin feature/amazing-feature
  9. Open a Pull Request

Project Structure

hazelnut/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main.rs          # TUI entry point
β”‚   β”œβ”€β”€ daemon.rs        # Daemon entry point
β”‚   β”œβ”€β”€ lib.rs           # Library root
β”‚   β”œβ”€β”€ theme.rs         # Color themes
β”‚   β”œβ”€β”€ app/             # TUI application
β”‚   β”‚   β”œβ”€β”€ events.rs    # Key event handling
β”‚   β”‚   β”œβ”€β”€ state.rs     # Application state
β”‚   β”‚   └── ui.rs        # UI rendering
β”‚   β”œβ”€β”€ config/          # Configuration loading
β”‚   β”œβ”€β”€ rules/           # Rule engine
β”‚   β”‚   β”œβ”€β”€ action.rs    # Rule actions
β”‚   β”‚   β”œβ”€β”€ condition.rs # Rule conditions
β”‚   β”‚   └── engine.rs    # Rule evaluation
β”‚   β”œβ”€β”€ watcher/         # File system watcher
β”‚   └── ipc/             # TUI-daemon communication
β”œβ”€β”€ docs/
β”‚   └── configuration.md # Full config reference
β”œβ”€β”€ screenshots/         # Screenshots for docs
└── tests/               # Integration tests

πŸ“„ License

This project is licensed under the MIT License β€” see the LICENSE file for details.