The Drawer That Haunts You
You know the one. Your ~/Downloads folder. Maybe it is a shared drive dump or a project handoff directory. Somewhere on your machine, there is a folder with over a thousand files that you have been meaning to organize since last year.
You open it. You see quarterly-report-final-v3.xlsx next to IMG_4821.png next to tax-form-2025.pdf. You close it. You tell yourself you will deal with it this weekend. You will not.
This article gives you a single command that sorts that entire mess into a clean, topic-based folder structure. An AI agent reads each file, figures out what it actually is, builds the directory tree, and puts everything where it belongs. Think of it as hiring a librarian who reads every book before shelving it — except this one works in minutes, not months.
You will walk away with a working organize.sh script, a CLAUDE.md configuration file that encodes your organization preferences, and the knowledge to adapt both to any directory you encounter.
The Problem: Entropy Always Wins
Every developer has at least one directory that looks like this:
~/Downloads/
quarterly-report-final-v3.xlsx
IMG_4821.png
meeting-notes-2026-03.md
budget_2026.csv
architecture-diagram.pdf
random-screenshot.jpg
client-contract-signed.pdf
package.json
old-resume-draft.docx
invoice-march.pdf
dataset-users-export.csv
README.md
photo_vacation_042.heic
tax-form-2025.pdf
... (987 more files)
No structure. No naming convention. Three years of downloads, project dumps, Slack exports, and shared drive syncs piled into one flat directory. It is the digital equivalent of a junk drawer — except this one grows by ten files a week and never gets cleaned.
The usual workaround is sorting by file extension. All PDFs in one folder, all images in another. This is like organizing a library by book size instead of subject. A PDF could be a contract, a tax form, an academic paper, or a receipt. Extension tells you the container. It says nothing about the contents.
What you actually want is organization by meaning. And that requires reading each file.
A human doing this for a thousand files needs a full workday. An AI agent does it in minutes. It opens each file, reads the content (or describes an image), and classifies by topic. That is the difference between sorting mail by envelope color and sorting it by who sent it.
The After: What Clean Looks Like
Here is the target structure after the agent finishes:
~/Downloads-organized/
finance/
invoices/
invoice-march.pdf
budgets/
budget_2026.csv
tax/
tax-form-2025.pdf
work/
reports/
quarterly-report-final-v3.xlsx
meeting-notes/
meeting-notes-2026-03.md
contracts/
client-contract-signed.pdf
projects/
code/
package.json
README.md
architecture/
architecture-diagram.pdf
personal/
photos/
IMG_4821.png
photo_vacation_042.heic
random-screenshot.jpg
resumes/
old-resume-draft.docx
data/
exports/
dataset-users-export.csv
Every file is in a semantically meaningful location. The directory names describe what the files are about, not what format they happen to be in.
Step 1: Configure CLAUDE.md for File Organization
Before the agent touches anything, it needs to know your rules. A CLAUDE.md file in your working directory acts like a standing instruction — think of it as a style guide for the agent that survives across sessions.
Create this file in the directory where you will run the organization command:
# File Organization Rules
## Directory Structure
Classify files into these top-level categories:
- finance/ — invoices, budgets, tax documents, receipts, financial statements
- work/ — reports, meeting notes, presentations, contracts, proposals
- projects/ — source code, documentation, architecture diagrams, configs
- personal/ — photos, resumes, personal documents, travel records
- data/ — datasets, exports, CSVs, database dumps
- misc/ — anything that does not fit the above categories
## Sub-categorization
Within each top-level category, create subcategories based on document type.
Keep subcategory depth to 2 levels maximum (e.g., finance/invoices/, not finance/invoices/2026/march/).
## Classification Rules
1. Read the file content to determine category. Do NOT rely on filename or extension alone.
2. For images: describe the image content and classify accordingly.
3. For code files: group by project if a package.json or similar manifest is nearby.
4. When uncertain, use misc/ with a descriptive subfolder name.
## Safety
- NEVER delete files. Only move them.
- ALWAYS generate a move log before executing.
- Preserve original filenames unless there are duplicates (append -1, -2, etc.).
## Output
Write a manifest.json listing every move operation as:
{"source": "original/path", "destination": "new/path"}
This configuration is reusable. Tune it once, and every future run follows the same rules. If you work in a team, the same CLAUDE.md ensures everyone's machines get organized the same way.
For a deeper guide on writing effective CLAUDE.md files, see the complete CLAUDE.md writing guide.
Step 2: The Organization Script
Here is the full script. It handles scanning, classifying, dry-run preview, execution, and undo log generation — the entire pipeline in one file.
#!/usr/bin/env bash
set -euo pipefail
# === Configuration ===
SOURCE_DIR="${1:?Usage: organize.sh <source-dir> [--dry-run]}"
DRY_RUN=false
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
DEST_DIR="${SOURCE_DIR}-organized"
UNDO_LOG="${DEST_DIR}/.undo-moves.sh"
MANIFEST="${DEST_DIR}/manifest.json"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BATCH_SIZE=50
# === Validate ===
if [[ ! -d "$SOURCE_DIR" ]]; then
echo "Error: $SOURCE_DIR is not a directory"
exit 1
fi
FILE_COUNT=$(find "$SOURCE_DIR" -maxdepth 1 -type f | wc -l | tr -d ' ')
echo "Found $FILE_COUNT files in $SOURCE_DIR"
if [[ "$FILE_COUNT" -eq 0 ]]; then
echo "No files to organize."
exit 0
fi
# === Step 1: Generate classification plan ===
echo "Generating classification plan..."
claude -p "You are a file organizer. Read the CLAUDE.md in the current directory for rules.
List every file in $SOURCE_DIR (non-recursive, files only).
For each file:
1. Read its contents (first 500 bytes for binary files, full text for small text files).
2. Classify it according to the rules in CLAUDE.md.
3. Determine its destination path under $DEST_DIR.
Output ONLY a valid JSON array to stdout:
[{\"source\": \"path/to/file\", \"destination\": \"path/to/dest\", \"reason\": \"brief classification reason\"}]
No markdown fences. No explanation. Just the JSON array." > "$MANIFEST.tmp"
# === Step 2: Validate and preview ===
if ! python3 -c "import json; json.load(open('$MANIFEST.tmp'))"; then
echo "Error: Agent output is not valid JSON. Check $MANIFEST.tmp"
exit 1
fi
mv "$MANIFEST.tmp" "$MANIFEST"
MOVE_COUNT=$(python3 -c "import json; print(len(json.load(open('$MANIFEST'))))")
echo ""
echo "=== Classification Plan ==="
echo "$MOVE_COUNT files classified."
echo ""
# Show preview of first 20 entries
python3 -c "
import json
moves = json.load(open('$MANIFEST'))
for m in moves[:20]:
print(f\" {m['source']} -> {m['destination']}\")
print(f\" reason: {m['reason']}\")
if len(moves) > 20:
print(f' ... and {len(moves) - 20} more')
"
if $DRY_RUN; then
echo ""
echo "[DRY RUN] No files moved. Full plan saved to $MANIFEST"
exit 0
fi
# === Step 3: Execute moves ===
echo ""
read -rp "Proceed with moving $MOVE_COUNT files? [y/N] " confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && echo "Aborted." && exit 0
echo "#!/usr/bin/env bash" > "$UNDO_LOG"
echo "# Undo log generated $TIMESTAMP" >> "$UNDO_LOG"
echo "# Run this script to reverse all moves" >> "$UNDO_LOG"
echo "" >> "$UNDO_LOG"
python3 -c "
import json, os, shutil
moves = json.load(open('$MANIFEST'))
undo_lines = []
for m in moves:
src = m['source']
dst = m['destination']
dst_dir = os.path.dirname(dst)
os.makedirs(dst_dir, exist_ok=True)
shutil.move(src, dst)
undo_lines.append(f'mv \"{dst}\" \"{src}\"')
print(f' moved: {os.path.basename(src)} -> {dst}')
with open('$UNDO_LOG', 'a') as f:
for line in reversed(undo_lines):
f.write(line + '\n')
print(f'\nDone. {len(moves)} files moved.')
"
chmod +x "$UNDO_LOG"
echo ""
echo "Undo log saved to $UNDO_LOG"
echo "To reverse all moves: bash $UNDO_LOG"
Save this as organize.sh and make it executable with chmod +x organize.sh.
Step 3: Dry-Run First
Never trust a script that moves a thousand files without looking at the plan first. Always start with --dry-run:
./organize.sh ~/Downloads --dry-run
The output shows every proposed move and the agent's reasoning:
Found 1037 files in /Users/you/Downloads
=== Classification Plan ===
1037 files classified.
~/Downloads/invoice-march.pdf -> ~/Downloads-organized/finance/invoices/invoice-march.pdf
reason: PDF invoice from March, contains billing line items
~/Downloads/IMG_4821.png -> ~/Downloads-organized/personal/photos/IMG_4821.png
reason: Photograph, EXIF data indicates personal camera
~/Downloads/quarterly-report-final-v3.xlsx -> ~/Downloads-organized/work/reports/quarterly-report-final-v3.xlsx
reason: Excel spreadsheet with quarterly financial data and charts
... and 1034 more
[DRY RUN] No files moved. Full plan saved to ~/Downloads-organized/manifest.json
Open manifest.json and scan the full list. Look for misclassifications. If the agent put a work-related photo in personal/photos/ instead of work/, adjust your CLAUDE.md rules and rerun. The dry run costs nothing but a few minutes of reading. Skipping it costs you an afternoon of untangling.
Step 4: Execute and Verify
Once the plan looks right:
./organize.sh ~/Downloads
The script asks for confirmation, then moves every file and generates the undo log. If anything goes wrong — even one file in the wrong place — you have a safety net:
bash ~/Downloads-organized/.undo-moves.sh
Every single move is reversed, in reverse order, restoring the original directory state exactly. It is like Ctrl+Z for your filesystem.
Handling Large Directories: Batch Processing
A thousand files in one prompt can bump against the agent's context window — like trying to read an entire novel in one glance. The solution is chunking: process files in batches of 50.
# Split the file list into batches
find "$SOURCE_DIR" -maxdepth 1 -type f > /tmp/filelist.txt
split -l 50 /tmp/filelist.txt /tmp/batch_
# Process each batch
for batch in /tmp/batch_*; do
FILES=$(cat "$batch" | tr '\n' ',' | sed 's/,$//')
claude -p "Classify these files according to CLAUDE.md rules.
Files: $FILES
Output JSON array: [{\"source\": ..., \"destination\": ..., \"reason\": ...}]" >> "$MANIFEST.partial"
done
# Merge batch results into single manifest
python3 -c "
import json
results = []
with open('$MANIFEST.partial') as f:
for line in f:
line = line.strip()
if line.startswith('['):
results.extend(json.loads(line))
with open('$MANIFEST', 'w') as f:
json.dump(results, f, indent=2)
print(f'Merged {len(results)} classifications')
"
Batching also helps with rate limits. If your API key has request caps, add a short pause between batches:
for batch in /tmp/batch_*; do
# ... process batch ...
sleep 2 # respect rate limits
done
Interactive Mode: When You Want Control
The script above is fully automated. But sometimes you want to drive. Maybe it is the first time you are organizing a directory and you are not sure what is even in there.
Start Claude Code in interactive mode:
cd ~/Downloads
claude
Then explore before committing:
> List all files in the current directory grouped by what you think
> their content is. Do not move anything yet. Just show me the groupings.
The agent reads files, shows you the proposed groupings. You refine:
> Good, but move all the architecture-*.pdf files to work/architecture
> instead of projects/architecture. And merge "data/exports" into
> "work/data" since these are all work-related exports.
Then execute:
> Now create the directory structure and move all files according to
> the updated plan. Generate an undo script at .undo-moves.sh.
Interactive mode is the right choice when you are still discovering what lives in the directory. Once your rules stabilize, switch to the scripted approach. Think of interactive mode as the prototype and the script as the production version.
Advanced: Custom Classification Rules
The default categories (finance, work, projects, personal, data) cover most cases. But your work is not most cases. Here is how to adapt.
Compliance Audit Prep
You have documents scattered across five directories and a compliance audit in two weeks. Add these rules to CLAUDE.md:
## Compliance Categories
- pii/ — any document containing personally identifiable information
(names + addresses, SSNs, email lists, customer databases)
- financial-records/ — invoices, receipts, bank statements, tax filings
- contracts/ — signed agreements, NDAs, service level agreements
- policies/ — internal policy documents, handbooks, SOPs
- evidence/ — audit trails, system logs, access records
- unclassified/ — requires manual review before filing
## Compliance Flags
If a file contains PII, add a "pii_detected": true flag in the manifest.
If a file appears to be a duplicate of another, add "possible_duplicate": "other-file.pdf".
Now the agent does not just organize files — it flags sensitive content. The unclassified/ folder becomes your review queue instead of a black hole.
Project-Aware Organization
For cleaning up a project dump with mixed codebases:
## Project Detection
Look for project root indicators:
- package.json, Cargo.toml, go.mod, pyproject.toml, Makefile
- Group all files that belong to the same project under projects/{project-name}/
- Detect the project name from the manifest file
- Preserve internal directory structure within each project
This keeps related files together. Without it, a README.md might land in projects/code/ while the Cargo.toml it belongs with ends up in projects/configs/. The agent reads the manifest files and understands project boundaries.
Workspace Switching for Different Organization Jobs
If you regularly organize files from multiple sources — a shared drive, a downloads folder, a client handoff directory — each one needs its own rules. Your personal downloads should not use the same categories as a compliance audit folder. That would be like using the same filing system for your kitchen and your office.
In Termdock, you can set up a workspace for each organization job. Each workspace remembers its directory, its terminal layout, and its CLAUDE.md configuration. Switch between workspaces and the entire context switches with you.
One workspace for the weekly downloads cleanup. Another for the quarterly compliance prep. A third for incoming client deliverables. No re-navigation, no hunting for the right config file. The workspace carries everything.
For more on terminal workspace management, see the terminal multiplexing comparison: tmux, Termdock, and Zellij.
Performance: What to Expect
Real-world numbers from organizing a 1,200-file downloads directory:
| Metric | Value |
|---|---|
| Total files | 1,200 |
| Batch size | 50 files |
| Batches | 24 |
| Time per batch | ~15-30 seconds |
| Total classification time | ~8 minutes |
| Move execution time | ~5 seconds |
| Total wall-clock time | ~9 minutes |
The bottleneck is classification, not moving. The agent spends most of its time reading file contents and deciding categories. Binary files (images, PDFs) take longer because the agent needs to interpret them visually. Text-heavy directories (code repos, markdown notes, CSV dumps) classify faster because text content is immediately parseable.
Nine minutes for 1,200 files. Compare that to the eight hours you have been putting off.
Safety Checklist
Before running on important files, walk through these five safeguards:
- Dry-run first. Always run with
--dry-runand inspect the manifest. No exceptions. - Undo log exists. Confirm
.undo-moves.shis generated before any file moves. - Source directory preserved. The script creates a new
-organizeddirectory. It does not modify the source structure in place. - No deletions. The script only uses
move, neverdelete. Your original files exist in the destination, not in a trash bin. - Manifest is your receipt.
manifest.jsonrecords every move with the agent's reasoning. If a file ends up in the wrong place, the manifest tells you exactly why, and you can adjust your rules.
Troubleshooting
Agent output is not valid JSON. Occasionally the agent wraps JSON in markdown fences or adds explanatory text. The script's validation step catches this. If it fails, open manifest.json.tmp, strip the non-JSON content, and rerun the move step manually.
Agent misclassifies files. This usually means your CLAUDE.md rules are ambiguous. If the agent puts invoices in work/ instead of finance/, add an explicit rule: "Documents with billing line items, payment amounts, or invoice numbers always go to finance/invoices/." Ambiguous rules produce ambiguous results.
Agent cannot read binary files. Some AI CLI tools have limited binary file support. Claude Code can interpret images directly. For other binary formats (.sketch, .fig, proprietary formats), add a fallback rule: "If the file content cannot be read, classify by filename and extension."
Permission errors during move. The script needs write permission on both the source and destination directories. Run with appropriate permissions, or use sudo if organizing system directories.
What You Take Away
You now have three things:
- A script (
organize.sh) that classifies and moves files with dry-run safety and one-command undo. - A configuration (
CLAUDE.md) that encodes your organization preferences as persistent agent instructions. - A pattern for adapting both to any directory: change the rules in
CLAUDE.md, rerun the script.
The first run takes ten minutes of setup. Every run after that takes one command. The messy downloads folder you pictured while reading this article? Go organize it. Right now. That single action is worth more than any amount of reading about it.
For a broader look at what AI CLI agents can automate beyond file organization, see the AI CLI tools complete guide. If you have not set up Claude Code yet, start with the first hour tutorial.
Ready to streamline your terminal workflow?
Multi-terminal drag-and-drop layout, workspace Git sync, built-in AI integration, AST code analysis — all in one app.