Taming the Chaos: Building an AI-Powered Folder Organizer
- Andres Borray
- May 6
- 7 min read
Building, Always
Since I was a kid, I’ve been trying to build things. Most of the time it didn’t look like “building” in the traditional sense—I was writing songs, chasing girls, throwing parties with friends. But beneath all that was the same impulse: to create, to connect, to shape something from nothing. I think I’ve always been trying to make my inner world real.
During COVID, when the external world shrank to the size of my apartment, I decided to teach myself how to code. I had just finished building furniture for my balcony—a bench, a little table, some shelves—and I realized I was craving a new kind of construction. Something digital. Something I could build from the couch. Something with no splinters.
That’s how this path started: trading hammer and nails for Python and APIs. Since then, with a little help from AI, I’ve been on a quiet journey of building software that actually improves my life.
I believe tech should be simple. Rooted in first principles. Its worth isn’t in how flashy and complex it is or how much data it collects-- it’s in how much easier, calmer, more intentional it makes our days. Tech should create space, not consume it. It should give us time back. If it’s not doing that, then what are we really building?
The problem
My Downloads folder has long since declared mutiny. Hundreds of files. Screenshots I don’t remember taking. PDFs with names like document(47)_final_REAL_FINAL.pdf. Subfolders with subfolders with subfolders. It was starting to feel like digital hoarding.
I knew I wasn’t going to manually sort it. That kind of tedium kills my soul. So I did what I tend to do when a task makes me want to disappear, I built something to do it for me.
The Solution
I wrote a program that scans a directory, takes a sample of its chaotic contents, and sends them off to OpenAI’s GPT-4.1 model via their API. The AI takes one look at your mess, judges you silently, and then offers a categorized folder structure to restore order to your digital junk drawer.
Thanks to GPT-4.1’s enormous context window, you can send it a huge batch of file paths—up to a million tokens worth—and it’ll handle it without breaking a sweat.
Once you read the proposed folder plan in your terminal, you type y to approve—or n if you want to pretend you have better ideas. If you say yes, the program uses Python’s shutil.move() to quietly rearrange everything into clean, logical folders. No dragging. No decision fatigue. Just instant digital feng shui.

Safety Nets
I built in a '--dryrun' flag so you can test it without actually moving anything, and a
'--revert' flag to undo any changes if you suddenly miss your chaos. Nothing gets permanently altered unless you say so.
Before & After
Before running the code, it looked like this:

Then I ran the program in Terminal. Here’s what it did:
Scanned ~/Test directory
Sent file paths to GPT-4.1
Received an AI-generated plan of action
Waited for my approval
Executed the cleanup
Seconds later, it looked like this:

Spring cleaning in command line form. Your mom would cry tears of joy.
Why Bother
There’s something deeply satisfying about turning a little problem into an elegant solution. It reminds me why I started coding in the first place—to make life more livable, not more complicated.
I hope we don’t lose that thread as AI becomes more powerful. These tools are amazing, but they’re not the point. They’re hammers and chisels. What we build with them, what kind of lives we shape, that’s what matters.
I've attached the code below and eventually I’ll publish this on GitHub and make it open-source. But for now, I’m calling it a night.
-Andres
"""
AI-Powered Folder Organizer
This script scans a directory, uses GPT-4.1 via OpenAI's API to suggest a logical folder structure,
and then organizes the files accordingly. It also supports dry runs, reversions, and logs all operations.
"""
#!/usr/bin/env python3
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
import shutil
import argparse
import logging
import sys
import openai
import json
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Set your OpenAI API key via the environment variable
# openai.api_key = os.getenv("OPENAI_API_KEY")
# Model configuration: default to 1M-token context model gpt-4.1, override via OPENAI_MODEL
MODEL_NAME = os.getenv("OPENAI_MODEL", "gpt-4.1")
# === Revert Function ===
def revert_structure(root_path):
"""
Reverts a previously organized folder by moving all files from subdirectories
(excluding 'logs') back to the root directory. Ensures changes are logged.
"""
logs_dir = os.path.join(root_path, 'logs')
os.makedirs(logs_dir, exist_ok=True)
revert_log = os.path.join(logs_dir, 'revert_moves.log')
logging.basicConfig(filename=revert_log, level=logging.INFO, format='%(asctime)s %(message)s')
for dirpath, dirnames, filenames in os.walk(root_path):
# Skip the root and logs directory
if dirpath == root_path or os.path.basename(dirpath) == 'logs':
continue
for filename in filenames:
src = os.path.join(dirpath, filename)
dst = os.path.join(root_path, filename)
try:
shutil.move(src, dst)
logging.info(f"Reverted '{src}' -> '{dst}'")
except Exception as e:
logging.error(f"Failed to revert '{src}': {e}")
print(f"Revert complete. See log at {revert_log}")
# === Directory Scanning ===
def scan_directory(root_path):
"""Recursively scan for files under the given root path."""
file_paths = []
for dirpath, dirnames, filenames in os.walk(root_path):
for filename in filenames:
file_paths.append(os.path.join(dirpath, filename))
return file_paths
# === File Classification ===
def classify_file(file_path):
"""Simple rule-based classification by file extension."""
_, ext = os.path.splitext(file_path)
ext = ext.lower().lstrip('.')
if not ext:
return 'Unknown'
return ext.upper()
# === Basic Structure Generation (Non-AI Fallback) ===
def build_structure_map(file_paths, root_path):
"""Map each file to its target folder based on classification."""
structure = {}
for path in file_paths:
rel_path = os.path.relpath(path, root_path)
category = classify_file(path)
structure.setdefault(category, []).append(rel_path)
return structure
# === Output Formatting ===
def display_proposal(structure, indent=0):
"""Recursively print the proposed folder structure."""
prefix = ' ' * indent
if isinstance(structure, dict):
for key, val in structure.items():
if isinstance(val, dict):
print(f"{prefix}{key}/")
display_proposal(val, indent + 2)
elif isinstance(val, list):
print(f"{prefix}{key}/ ({len(val)} files)")
for f in val[:5]:
print(f"{prefix} └─ {f}")
if len(val) > 5:
print(f"{prefix} └─ ... (+{len(val) - 5} more)")
else:
# Fallback for unexpected types
print(f"{prefix}{key}: {val}")
else:
print(f"{prefix}{structure}")
# === User Interaction ===
def prompt_user():
"""Prompt user for approval."""
choice = input("Proceed with moving files? (y/n): ").strip().lower()
return choice == 'y'
# === Execute AI-Suggested Structure ===
def execute_structure(structure, root_path):
"""Create folders, move files, and log actions (handles nested dicts)."""
# Set up logging
logs_dir = os.path.join(root_path, 'logs')
os.makedirs(logs_dir, exist_ok=True)
log_file = os.path.join(logs_dir, 'file_moves.log')
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s %(message)s')
def process_level(curr_struct, curr_path):
for name, content in curr_struct.items():
target_dir = os.path.join(curr_path, name)
if isinstance(content, dict):
os.makedirs(target_dir, exist_ok=True)
process_level(content, target_dir)
elif isinstance(content, list):
os.makedirs(target_dir, exist_ok=True)
for rel_path in content:
src = os.path.join(root_path, rel_path)
dst = os.path.join(target_dir, os.path.basename(rel_path))
try:
shutil.move(src, dst)
logging.info(f"Moved '{src}' -> '{dst}'")
except Exception as e:
logging.error(f"Failed to move '{src}': {e}")
process_level(structure, root_path)
print(f"Operation complete. See log at {log_file}")
# === AI Proposal via GPT ===
def propose_structure(file_paths, root_path, sample_size=20):
"""
Use GPT to generate a conversational overview and a nested folder structure.
Returns:
tuple: A friendly narrative explanation and a JSON-style structure plan.
"""
# Prepare sample relative paths
total_count = len(file_paths)
rel_paths = [os.path.relpath(p, root_path) for p in file_paths]
samples = rel_paths[:sample_size]
prompt = (
"You are a friendly assistant that helps organize files. "
f"First, in a conversational tone, say something like "
f"'Wow, I see {total_count} files—here's what I propose...' to introduce your plan. "
"Then output the suggested folder structure as a JSON object "
"inside triple backticks. "
"Use first principles to group related files, preserving project folders. "
"Here are some sample file paths:\n\n"
f"{json.dumps(samples, indent=2)}\n\n"
"Example response:\n"
"Wow... (narrative)\n"
"```\n"
"{ \"ProjectA\": { \"Docs\": [...], ... }, ... }\n"
"```\n"
)
response = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": "You organize files into logical folders."},
{"role": "user", "content": prompt}
],
temperature=0.5
)
content = response.choices[0].message.content.strip()
# Split narrative and JSON block
if "```" in content:
parts = content.split("```")
narrative = parts[0].strip()
json_block = parts[1].strip()
else:
narrative = ""
json_block = content
try:
structure = json.loads(json_block)
except json.JSONDecodeError:
print("Warning: Failed to parse JSON. Falling back to default.")
structure = build_structure_map(file_paths, root_path)
return narrative, structure
# === File Inclusion Guard ===
# Helper function to ensure all files are included in the structure
def integrate_missing_files(structure, file_paths, root_path):
"""
Ensure all scanned files are accounted for in the structure.
Files not referenced by GPT will be placed under 'Uncategorized'.
"""
# Collect all relative paths included in structure
referenced = set()
def collect_paths(node):
if isinstance(node, dict):
for val in node.values():
collect_paths(val)
elif isinstance(node, list):
for p in node:
referenced.add(p)
collect_paths(structure)
# Determine missing files
all_rel = {os.path.relpath(p, root_path) for p in file_paths}
missing = sorted(all_rel - referenced)
if missing:
structure.setdefault("Uncategorized", []).extend(missing)
return structure
# === Friendly Naming with AI ===
def suggest_new_categories(structure, sample_size=3):
"""
Use GPT to suggest human-friendly folder names for each
file category based on a few sample filenames.
Returns a dict mapping original category to suggested name.
"""
# Collect sample filenames for each category
samples = {cat: structure[cat][:sample_size] for cat in list(structure)[:10]}
prompt = (
"You are a helpful assistant that suggests concise, human-readable folder names"
" based on file extensions and sample filenames. Here is the data:\n\n"
f"{json.dumps(samples, indent=2)}\n\n"
"For each category, suggest a single folder name. "
"Respond with a JSON object mapping the original category key to your suggestion."
)
response = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": "You suggest folder names."},
{"role": "user", "content": prompt}
],
temperature=0
)
text = response.choices[0].message.content.strip()
try:
suggestions = json.loads(text)
except json.JSONDecodeError:
print("Warning: Failed to parse GPT suggestions. Using default categories.")
suggestions = {}
return suggestions
# === CLI Entry Point ===
def main():
"""Main CLI handler. Parses arguments and coordinates the file organization workflow."""
parser = argparse.ArgumentParser(description="AI-Powered Folder Organizer - Phase 1 MVP")
parser.add_argument(
'path',
nargs='?',
default='.',
help='Root directory to organize (default: current directory)'
)
parser.add_argument(
'--revert',
action='store_true',
help='Revert previous file moves by moving files back to the root directory'
)
parser.add_argument(
'--sample-size',
type=int,
default=20,
help='Number of file paths to sample when prompting the model'
)
args = parser.parse_args()
root = args.path
if not os.path.isdir(root):
print(f"Error: '{root}' is not a valid directory.")
sys.exit(1)
if args.revert:
revert_structure(root)
sys.exit(0)
print(f"Scanning directory: {root}")
files = scan_directory(root)
print(f"\nFound {len(files)} files to organize.\n")
if not files:
print("No files found to organize.")
sys.exit(0)
print("\nAnalyzing with AI-driven organization...")
narrative, structure = propose_structure(files, root, sample_size=args.sample_size)
# Ensure all files are included
structure = integrate_missing_files(structure, files, root)
if narrative:
print("\n" + narrative + "\n")
display_proposal(structure)
if prompt_user():
execute_structure(structure, root)
else:
print("No changes made.")
if __name__ == '__main__':
main()
Comments