Tech

Smart File Butler: Assistant File Management yang Pintar

File berserakan? Butler ini otomatis sort, tag, dan organize file kamu berdasarkan konten.

๐Ÿ‘ค Zainul Fanani๐Ÿ“… 8 Maret 2026โฑ 1 min read

๐Ÿ“Ž 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

A File Downloaded  BFile Type
A File Downloaded BFile Type
}}%% flowchart TD A๐Ÿ“ฅ File Downloaded --> B{File Type?} style A fill:#e3f2fd,stroke:#1565c0,stroke-width:2px style B fill:#fff3e0,stroke:#e65100,stroke-width:2px

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

A Downloads  B Documents
A Downloads B Documents
}}%% graph TD A๐Ÿ“‚ Downloads --> B๐Ÿ“„ Documents A --> C๐Ÿ–ผ๏ธ Images A --> D๐Ÿ“ฆ Archives A --> E๐ŸŽฌ Media A --> F๐Ÿ’ฟ Software A --> G๐Ÿ“Š Data style A fill:#e3f2fd,stroke:#1565c0,stroke-width:3px

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

participant User as User
participant User as User
}}%% sequenceDiagram participant User as User participant FS as File System participant FB as File Butler participant AI as AI Analyzer participant Notify as Notification

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) or fswatch (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
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

Ada Pertanyaan? Yuk Ngobrol!

Butuh bantuan setup OpenClaw, konsultasi IT, atau mau diskusi project engineering? Book a call langsung โ€” gratis.

Book a Call โ€” Gratis

via Cal.com โ€ข WITA (UTC+8)

F

Zainul Fanani

Founder, Radian Group. Engineering & tech enthusiast.

Radian Group

Engineering Excellence Across Indonesia

Perusahaan

  • CV Radian Fokus Mandiri โ€” Balikpapan
  • PT UNO Solusi Teknik โ€” Balikpapan
  • PT Reka Formasi Elektrika โ€” Jakarta
  • PT Raya Fokus Solusi โ€” Sidoarjo
ยฉ 2026 Radian Group. All rights reserved.