๐ Source:smart-file-butler.md โ view on GitHub & star โญ
Smart File Butler
Auto-organize your Downloads folder with AI-powered file management.
๐ File Processing Pipeline
B -->|PDF/DOCX| C[๐ Document Analyzer]
B -->|JPG/PNG| D[๐ผ๏ธ Image Vision AI]
B -->|ZIP/TAR| E[๐ฆ Archive Inspector]
B -->|Other| F[๐ Extension Check]
style C fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style D fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px
style E fill:#fff8e1,stroke:#f57f17,stroke-width:2px
style F fill:#eceff1,stroke:#455a64,stroke-width:2px
C --> G[โ๏ธ Generate<br/>Descriptive Name]
D --> G
E --> G
F --> G
style G fill:#e1bee7,stroke:#7b1fa2,stroke-width:2px
G --> H{Duplicate?}
style H fill:#fce4ec,stroke:#c2185b,stroke-width:2px
H -->|Yes| I[_1, _2, _3...]
H -->|No| J[๐ Move to<br/>Destination Folder]
style I fill:#ffccbc,stroke:#d84315,stroke-width:2px
style J fill:#c8e6c9,stroke:#388e3c,stroke-width:2px
I --> J
J --> K[๐ Notify User]
K --> L[โ
Done!]
style K fill:#b3e5fc,stroke:#0288d1,stroke-width:2px
style L fill:#a5d6a7,stroke:#43a047,stroke-width:2px'}
๐๏ธ Folder Structure Diagram
B --> B1[๐ผ Work]
B --> B2[๐ Personal]
B --> B3[๐ Invoices]
B --> B4[๐ Manuals]
style B fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style B1 fill:#c8e6c9,stroke:#43a047
style B2 fill:#c8e6c9,stroke:#43a047
style B3 fill:#c8e6c9,stroke:#43a047
style B4 fill:#c8e6c9,stroke:#43a047
C --> C1[๐ธ Screenshots]
C --> C2[๐๏ธ Photos]
C --> C3[๐จ Designs]
C --> C4[๐ Memes]
style C fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px
style C1 fill:#e1bee7,stroke:#8e24aa
style C2 fill:#e1bee7,stroke:#8e24aa
style C3 fill:#e1bee7,stroke:#8e24aa
style C4 fill:#e1bee7,stroke:#8e24aa
D --> D1[๐ Extracted]
D --> D2[๐พ Keep]
style D fill:#fff8e1,stroke:#f9a825,stroke-width:2px
E --> E1[๐ Audio]
E --> E2[๐ฅ Video]
style E fill:#ffebee,stroke:#c62828,stroke-width:2px'}
๐ File Butler Workflow
User->>FS: Download file
FS->>FB: inotify event
FB->>FB: Skip if system file
FB->>AI: Analyze content
alt Document
AI->>AI: Extract title/metadata
else Image
AI->>AI: Vision analysis
else Archive
AI->>AI: List contents
end
AI-->>FB: Analysis result
FB->>FB: Generate filename
FB->>FB: Determine folder
FB->>FB: Check duplicates
FB->>FS: Move file
FB->>Notify: Send notification
Notify-->>User: Desktop popup'}
Overview
Your Downloads folder is a mess? Files scattered everywhere with cryptic names like download (17).pdf?
This automation:
- Monitors your Downloads folder
- Auto-sorts files by type (Documents, Images, Archives, etc.)
- Renames files with descriptive names using AI
- Archives old files to cloud storage
- Cleans up clutter automatically
Before:download (3).pdf, IMG_2024...jpg, untitled.zip scattered randomly
After: Organized folders, descriptive names, auto-archived old files
Architecture
[File downloaded]
โ
[Detect file type]
โ
[AI analyzes content]
- Document โ Extract title/topic
- Image โ Detect content/scene
- Archive โ List contents
โ
[Generate descriptive name]
โ
[Move to appropriate folder]
- Documents/Work/2026/
- Images/Screenshots/
- Archives/Software/
โ
[Old files โ Cloud archive]
โ
[Notify user of changes]
Prerequisites
- OpenClaw installed
- Python 3.8+
inotifywait(Linux) orfswatch(macOS) for file monitoring- Google Drive API (for archiving)
Step 1: Create Directory Structure
# Create organized folders
mkdir -p ~/Downloads/{Documents,Images,Archives,Media,Software,Data,Other}
mkdir -p ~/Downloads/Documents/{Work,Personal,Invoices,Manuals}
mkdir -p ~/Downloads/Images/{Screenshots,Photos,Designs,Memes}
mkdir -p ~/Downloads/Archives/{Extracted,Keep}
mkdir -p ~/Downloads/Media/{Audio,Video}
Step 2: File Analyzer Script
scripts/file-butler/analyze-file.py:
#!/usr/bin/env python3
"""
Analyze file content and generate descriptive name
Usage: python3 analyze-file.py <file_path>
"""
import sys
import os
import mimetypes
from pathlib import Path
def get_file_info(file_path):
"""Get basic file information"""
stat = os.stat(file_path)
return {
"name": os.path.basename(file_path),
"size": stat.st_size,
"mime": mimetypes.guess_type(file_path)[0] or "application/octet-stream",
"ext": Path(file_path).suffix.lower()
}
def analyze_document(file_path):
"""Extract info from PDF/DOCX/TXT"""
ext = Path(file_path).suffix.lower()
if ext == '.pdf':
return analyze_pdf(file_path)
elif ext in ['.docx', '.doc']:
return analyze_docx(file_path)
elif ext == '.txt':
return analyze_txt(file_path)
else:
return {"type": "document", "description": "Unknown document"}
def analyze_pdf(file_path):
"""Extract PDF metadata and first page text"""
try:
import PyPDF2
with open(file_path, 'rb') as f:
reader = PyPDF2.PdfReader(f)
meta = reader.metadata
# Get first page text (limited)
text = ""
if len(reader.pages) > 0:
text = reader.pages[0].extract_text()[:500]
return {
"type": "pdf",
"title": meta.get('/Title', ''),
"author": meta.get('/Author', ''),
"pages": len(reader.pages),
"preview": text,
"description": f"PDF: {meta.get('/Title', 'Untitled')} ({len(reader.pages)} pages)"
}
except:
return {"type": "pdf", "description": "PDF document"}
def analyze_image(file_path):
"""Analyze image content using AI vision"""
# Use AI to describe image
prompt = "Describe this image in 5-7 words for a filename"
# Implementation depends on your AI setup
description = call_vision_model(file_path, prompt)
return {
"type": "image",
"description": description,
"dimensions": get_image_dimensions(file_path)
}
def analyze_archive(file_path):
"""List contents of ZIP/tar files"""
import zipfile
import tarfile
ext = Path(file_path).suffix.lower()
try:
if ext == '.zip':
with zipfile.ZipFile(file_path, 'r') as zf:
files = zf.namelist()[:10] # First 10 files
return {
"type": "zip",
"contents": files,
"file_count": len(zf.namelist()),
"description": f"ZIP archive with {len(zf.namelist())} files"
}
elif ext in ['.tar', '.gz', '.bz2']:
with tarfile.open(file_path, 'r') as tf:
files = tf.getnames()[:10]
return {
"type": "archive",
"contents": files,
"description": f"Archive: {', '.join(files[:3])}..."
}
except:
return {"type": "archive", "description": "Compressed archive"}
def generate_filename(file_path, analysis):
"""Generate descriptive filename using AI"""
info = get_file_info(file_path)
prompt = f"""Generate a concise, descriptive filename (2-4 words) for this file:
Original: {info['name']}
Type: {analysis.get('type', 'file')}
Description: {analysis.get('description', 'Unknown')}
Rules:
- Use snake_case (lowercase, underscores)
- Include date if relevant: YYYY-MM-DD
- Be specific but concise
- Max 50 characters
Output only the filename without extension."""
# Call AI model
new_name = call_ai_model(prompt)
# Clean up
new_name = new_name.strip().replace(' ', '_').lower()
new_name = ''.join(c for c in new_name if c.isalnum() or c in '_-')
# Add date prefix if not present
if not new_name.startswith('20'): # No year prefix
from datetime import datetime
date_prefix = datetime.now().strftime("%Y-%m-%d")
new_name = f"{date_prefix}_{new_name}"
return new_name + info['ext']
def determine_folder(file_path, analysis):
"""Determine destination folder based on file type and content"""
info = get_file_info(file_path)
mime = info['mime']
# By MIME type
if mime.startswith('image/'):
if 'screenshot' in analysis.get('description', '').lower():
return 'Images/Screenshots'
elif 'design' in analysis.get('description', '').lower():
return 'Images/Designs'
return 'Images/Photos'
elif mime.startswith('application/pdf'):
desc = analysis.get('description', '').lower()
if any(word in desc for word in ['invoice', 'bill', 'receipt', 'payment']):
return 'Documents/Invoices'
elif any(word in desc for word in ['manual', 'guide', 'documentation']):
return 'Documents/Manuals'
elif any(word in desc for word in ['report', 'analysis', 'data']):
return 'Documents/Work'
return 'Documents'
elif mime.startswith('application/zip') or mime.startswith('application/x-'):
return 'Archives'
elif mime.startswith('video/'):
return 'Media/Video'
elif mime.startswith('audio/'):
return 'Media/Audio'
# By extension
ext = info['ext']
if ext in ['.exe', '.dmg', '.pkg', '.deb', '.rpm']:
return 'Software'
elif ext in ['.csv', '.json', '.xml', '.sql']:
return 'Data'
return 'Other'
def main():
if len(sys.argv) < 2:
print("Usage: python3 analyze-file.py <file_path>")
sys.exit(1)
file_path = sys.argv[1]
print(f"๐ Analyzing: {os.path.basename(file_path)}")
# Analyze based on type
info = get_file_info(file_path)
if info['mime'].startswith('image/'):
analysis = analyze_image(file_path)
elif info['mime'].startswith('application/pdf'):
analysis = analyze_document(file_path)
elif info['ext'] in ['.zip', '.tar', '.gz']:
analysis = analyze_archive(file_path)
else:
analysis = {"type": "file", "description": f"{info['ext']} file"}
# Generate new name
new_filename = generate_filename(file_path, analysis)
folder = determine_folder(file_path, analysis)
print(f"๐ Destination: {folder}/")
print(f"๐ New name: {new_filename}")
# Output for script processing
result = {
"original": info['name'],
"new_name": new_filename,
"folder": folder,
"analysis": analysis
}
import json
print(json.dumps(result))
if __name__ == "__main__":
main()
Step 3: File Organizer Script
scripts/file-butler/organize.sh:
#!/bin/bash
# Smart File Butler - Organize Downloads folder
DOWNLOADS_DIR="$HOME/Downloads"
LOG_FILE="$DOWNLOADS_DIR/.file-butler.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
organize_file() {
local file_path="$1"
local filename=$(basename "$file_path")
log "Processing: $filename"
# Skip system files
if [[ "$filename" == .* ]] || [[ "$filename" == *.tmp ]] || [[ "$filename" == *.crdownload ]]; then
log "Skipping system file: $filename"
return
fi
# Analyze file
local analysis=$(python3 "$HOME/scripts/file-butler/analyze-file.py" "$file_path")
local new_name=$(echo "$analysis" | python3 -c "import sys,json; print(json.load(sys.stdin)['new_name'])")
local folder=$(echo "$analysis" | python3 -c "import sys,json; print(json.load(sys.stdin)['folder'])")
# Create destination path
local dest_dir="$DOWNLOADS_DIR/$folder"
mkdir -p "$dest_dir"
# Handle duplicates
local dest_path="$dest_dir/$new_name"
local counter=1
while [ -f "$dest_path" ]; do
local base="${new_name%.*}"
local ext="${new_name##*.}"
dest_path="$dest_dir/${base}_$counter.$ext"
((counter++))
done
# Move file
mv "$file_path" "$dest_path"
log "โ
Moved to: $folder/$(basename "$dest_path")"
# Send notification
notify-user "$filename" "$(basename "$dest_path")" "$folder"
}
notify-user() {
local original="$1"
local new_name="$2"
local folder="$3"
# Telegram notification (optional)
# curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" \
# -d "chat_id=$CHAT_ID" \
# -d "text=๐ File organized:%0A$original โ $folder/$new_name"
# Desktop notification
if command -v notify-send &> /dev/null; then
notify-send "File Butler" "Organized: $original โ $folder/"
fi
}
# Process single file or watch directory
if [ "$1" == "--watch" ]; then
log "๐ Watching $DOWNLOADS_DIR for new files..."
# Using inotifywait (Linux)
inotifywait -m -e create -e moved_to --format '%w%f' "$DOWNLOADS_DIR" | while read file_path; do
# Wait for file to finish writing
sleep 2
if [ -f "$file_path" ]; then
organize_file "$file_path"
fi
done
else
# Process existing files
log "๐งน Organizing existing files..."
find "$DOWNLOADS_DIR" -maxdepth 1 -type f | while read file_path; do
organize_file "$file_path"
done
log "โ
Organization complete!"
fi
Make executable:
chmod +x scripts/file-butler/organize.sh
Step 4: Auto-Archive Old Files
scripts/file-butler/archive-old.py:
#!/usr/bin/env python3
"""
Archive files older than 30 days to Google Drive
Usage: python3 archive-old.py
"""
import os
import subprocess
from datetime import datetime, timedelta
from pathlib import Path
DOWNLOADS_DIR = os.path.expanduser("~/Downloads")
ARCHIVE_AGE_DAYS = 30
DRIVE_FOLDER_ID = "your-google-drive-folder-id"
def get_file_age(file_path):
"""Get file age in days"""
stat = os.stat(file_path)
mtime = datetime.fromtimestamp(stat.st_mtime)
return (datetime.now() - mtime).days
def upload_to_drive(file_path, folder_id):
"""Upload file to Google Drive using gog CLI"""
try:
result = subprocess.run(
["gog", "drive", "upload", file_path, "--parent", folder_id],
capture_output=True,
text=True
)
return result.returncode == 0
except:
return False
def archive_file(file_path):
"""Archive single file"""
rel_path = os.path.relpath(file_path, DOWNLOADS_DIR)
print(f"๐ฆ Archiving: {rel_path}")
if upload_to_drive(file_path, DRIVE_FOLDER_ID):
os.remove(file_path)
print(f"โ
Archived and removed: {rel_path}")
return True
else:
print(f"โ Failed to archive: {rel_path}")
return False
def main():
print("๐ Scanning for old files...")
archived = 0
failed = 0
for root, dirs, files in os.walk(DOWNLOADS_DIR):
# Skip hidden directories
dirs[:] = [d for d in dirs if not d.startswith('.')]
for file in files:
file_path = os.path.join(root, file)
# Skip system files
if file.startswith('.') or file.endswith('.tmp'):
continue
age = get_file_age(file_path)
if age > ARCHIVE_AGE_DAYS:
if archive_file(file_path):
archived += 1
else:
failed += 1
print(f"\n๐ Summary: {archived} archived, {failed} failed")
print(f"๐พ Space saved: ~{archived * 5}MB (estimated)")
if __name__ == "__main__":
main()
Step 5: Systemd Service (Auto-start)
Create ~/.config/systemd/user/file-butler.service:
[Unit]
Description=Smart File Butler - Auto-organize Downloads
After=graphical-session.target
[Service]
Type=simple
ExecStart=%h/scripts/file-butler/organize.sh --watch
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.target
Enable and start:
systemctl --user daemon-reload
systemctl --user enable file-butler.service
systemctl --user start file-butler.service
# Check status
systemctl --user status file-butler.service
Step 6: Cron Jobs
# Add to crontab
# Organize existing files daily at 2 AM
0 2 * * * /home/user/scripts/file-butler/organize.sh >> /home/user/.file-butler.log 2>&1
# Archive old files weekly on Sundays
0 3 * * 0 /usr/bin/python3 /home/user/scripts/file-butler/archive-old.py >> /home/user/.file-butler.log 2>&1
Example Output
Before organization:
Downloads/
โโโ download (17).pdf
โโโ IMG_20240308_143022.jpg
โโโ screenshot_2024-03-08.png
โโโ presentation_final_v2.pdf
โโโ software-2.3.1-linux.deb
โโโ data_export_2024.csv
โโโ archive.zip
After organization:
Downloads/
โโโ Documents/
โ โโโ Invoices/
โ โ โโโ 2024-03-08_monthly_invoice_acme.pdf
โ โโโ Work/
โ โโโ 2024-03-08_q1_sales_presentation.pdf
โโโ Images/
โ โโโ Screenshots/
โ โ โโโ 2024-03-08_dashboard_error.png
โ โโโ Photos/
โ โโโ 2024-03-08_office_meeting.jpg
โโโ Software/
โ โโโ 2024-03-08_developer_tool_linux.deb
โโโ Data/
โ โโโ 2024-03-08_customer_export.csv
โโโ Archives/
โ โโโ 2024-03-08_project_files.zip
โโโ .file-butler.log
Advanced Features
Duplicate Detection
def find_duplicates(directory):
"""Find duplicate files by hash"""
import hashlib
hashes = {}
for root, _, files in os.walk(directory):
for file in files:
file_path = os.path.join(root, file)
file_hash = hashlib.md5(open(file_path, 'rb').read()).hexdigest()
if file_hash in hashes:
print(f"Duplicate found: {file_path}")
# Handle duplicate (delete, move, etc.)
else:
hashes[file_hash] = file_path
Content-based Search
def search_by_content(query, directory):
"""Search files by AI-analyzed content"""
# Build index of file descriptions
# Search using embeddings or keywords
pass
Conclusion
You now have an intelligent file management system that:
- โ Auto-organizes downloads by type and content
- โ Generates descriptive filenames with AI
- โ Archives old files to cloud storage
- โ Runs continuously in background
Next Steps:
- Add file content indexing for search
- Integrate with more cloud providers
- Build web dashboard for file management
Tutorial created for OpenClaw Sumopod