Janny is a CLI tool designed to keep your home directory organized by moving files from source directories to specific target directories based on user-defined rules, and backing up important files.
- Automated Organization: Scans source directories and moves files based on extensions.
- Smart Conflict Resolution: Automatically renames files if a file with the same name exists in the target.
- Pattern Matching: Support for glob patterns, regex, and folder patterns for advanced file organization.
- Auto-Clean: Automatically delete old files from specific categories after a configurable number of days.
- Cloud File Handling: Automatically detects and skips iCloud placeholder files to prevent system hangs (macOS).
- External Handlers: Delegate unknown file types to external scripts for custom classification.
- Backup System: Integrated
rsyncwrapper to backup organized files to external storage. - Dry Run Mode: Preview changes without actually moving files.
go install github.com/evrenesat/janny@latestJanny uses a TOML configuration file located at ~/.config/janny/config.toml.
[general]
source_paths = ["~/Downloads", "~/Desktop"]
[storage]
documents = "~/Documents/Sorted"
images = "~/Pictures/Sorted"
archives = "~/Downloads/Archives"
[rules]
documents = "pdf,doc,docx,txt,md"
images = "jpg,jpeg,png,gif"
archives = "zip,tar,gz,7z"
# New: Advanced Pattern Matching
# Globs (implicit with *, ?, [], or explicit with glob: prefix)
reports = "*report*.pdf, glob:monthly_*.xls"
# Regex (must start with regex:)
scans = "regex:^scan_\\d{4}\\.pdf$"
# Note: Patterns are checked BEFORE simple extension rules.
# So a file "final_report.pdf" will match 'reports' (glob) instead of 'documents' (pdf).
# Folders (must start with folder:)
projects = "folder:project_*"
misc = "folder:*" # Catch-all for folders
[auto_clean]
# Automatically delete files older than X days in specific categories
# Note: This is a destructive action!
installers = 15
tmp = 1
[backup]
enabled = false
destination = "/Volumes/ExternalDisk/Backups"
exclude_file_types = ["tmp", "bak", "DS_Store"]
exclude_directories = ["temp", "cache", "node_modules"]
[advanced]
# Optional: Path to a script that takes a filename and returns a category
unknown_file_handler = "/path/to/script.sh"If unknown_file_handler is set, Janny will invoke the specified command for any file whose extension is not found in the configuration.
- Input:
- Argument: The full path to the file is passed as the first argument (
$1) to the script. - Stdin: The full Janny configuration (including storage paths and rules) is passed to the script's standard input (stdin) as a JSON object. This allows the script to make decisions based on existing categories.
- Argument: The full path to the file is passed as the first argument (
- Output: The script must print the category name (e.g.,
documents,images) to standard output (stdout). - Behavior:
- If the script returns a category defined in your
[storage]config, the file is moved there. - If the script returns a new category not in
[storage], Janny auto-creates a storage directory underdefault_storage_path(e.g.,~/Backup/torrents) and moves the file there. - If
default_storage_pathis not set and the category is unknown, the file is skipped. - If the script returns an empty string, the file is skipped.
- If the script exits with a non-zero status code, the error is logged to stderr and the file is skipped. Janny proceeds to the next file; the process does NOT crash.
- If the script returns a category defined in your
#!/bin/bash
FILE="$1"
# You can also read config from stdin if needed:
# CONFIG=$(cat)
# echo "Processing $FILE with config: $CONFIG" >> /tmp/janny_debug.log
MIME=$(file -b --mime-type "$FILE")
if [[ "$MIME" == "application/x-bittorrent" ]]; then
# Can be an existing or new category
echo "torrents"
fiThe auto_clean feature automatically deletes old files from specific categories after they've been organized. This is useful for temporary directories or files you don't need to keep long-term.
How it works:
- Runs automatically after each organization (unless in dry-run mode)
- Checks the modification time of files in the specified categories
- Deletes files (and directories) older than the configured number of days
- Skips cloud-backed placeholder files to avoid triggering downloads
Configuration:
[auto_clean]
installers = 15 # Delete installer files older than 15 days
downloads = 30 # Delete downloads older than 30 days
temp = 1 # Delete temp files older than 1 dayImportant Notes:
- This is a destructive operation - deleted files cannot be recovered
- Only applies to files in your configured storage directories
- Set backup = true if you want to keep copies before deletion
- Cloud-backed files are automatically skipped to prevent unwanted downloads
# different flags
janny --help
janny --dry-run
janny --verbose
janny --config ~/.config/janny/myconfig.tomlJanny can learn file extension rules from your existing organized directories.
janny --learnThis will scan your configured storage directories, identify file extensions, and update your configuration file to map those extensions to the corresponding storage categories.
You can use the --learn-from-handler flag to let an external script (e.g. an LLM wrapper) propose rules for unknown extensions in bulk.
Command:
janny --learn-from-handlerInput to Handler (Stdin):
Janny sends two parts to your configured unknown_file_handler script's stdin, separated by a newline:
- The content of
smart_learn_prompt. - A compact JSON object with the unknown extensions and current rules.
Format:
<PROMPT_STRING>
<JSON_PAYLOAD>
Example Input:
You are a file organizer...
{"unknown_extensions":["xyz","abc"],"rules":{"documents":"txt,md"}}
Expected Output from Handler (Stdout):
The script must print a JSON object containing only rules:
{
"rules": {
"documents": "txt,md,xyz",
"foos": "foo,abc"
}
}The handler can update existing categories (e.g., adding xyz to documents) or propose new ones (e.g., foos). For new categories, Janny automatically creates a storage directory under default_storage_path (e.g., ~/Backup/foos).
Configuration:
[advanced]
unknown_file_handler = "/path/to/my_llm_script.sh"
smart_learn_prompt = "You are a file organizer. Analyze these extensions..."
default_storage_path = "~/Backup"- Clone the repository
- Run
go mod tidy - Build with
go build ./cmd/janny