Build an AI-Powered PDF Organizer

Use Python and Claude AI to classify and organize your PDF collection.

Introduction

30 Second Summary

Everyone's Downloads folder eventually becomes a graveyard of PDFs with names like "document(3).pdf" and "185732947.pdf". Finding that one tax form or research paper means opening files one by one until you get lucky.

In this project, you will build a Python script that uses Claude's AI to read, classify, and organize every PDF in your Downloads folder into a clean, searchable library.

What You'll Build

You run one command and watch your chaotic Downloads folder transform into a categorized library where every PDF has a descriptive name and a searchable index tells you exactly where to find it.

By the end of this project, you'll have:

  • A PDF scanner that extracts text and metadata from every file in your Downloads, exposing the core problem: most PDFs carry zero useful information in their filenames or metadata.
  • AI-powered classification that reads each document's actual content and assigns it an intelligent category and clean, descriptive filename.
  • An automated organizer that moves every PDF into a named folder and generates a searchable markdown catalog so you can find any document by topic without opening individual files.
  • Secret Mission: Add duplicate detection that flags PDFs with similar content so you can clean up redundant downloads.

Are there any prerequisites?

You need Python 3.10+ installed on your machine and an Anthropic API key configured (either as an environment variable or via ant auth login).

You also need Claude Code installed with a Max account that has API access.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters. This is the moment you commit to the problem you're solving.

Set Up Your Project

To build a PDF organizer, you need two core capabilities: reading PDF content and classifying it with AI.

Two Python libraries give you these powers. PyMuPDF handles PDF text extraction and metadata reading. The Anthropic Python SDK connects your script to Claude's API for intelligent classification.

In this step, get ready to:
  • Create your project directory.
  • Install PyMuPDF and the Anthropic Python SDK.
  • Verify both packages and your API connection.
Create your project directory
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Create your project directory and navigate into it by running these commands:
mkdir ~/pdf-organizer
cd ~/pdf-organizer
  • Confirm you're in the right place by running:
pwd

You should see /Users/yourname/pdf-organizer printed in your terminal.

Install PyMuPDF and the Anthropic SDK

Both packages install with a single pip command.

  • Install the two dependencies by running:
pip install pymupdf anthropic

You should see pip downloading and installing both packages, ending with a "Successfully installed" message.

What do these packages do?

PyMuPDF (imported as pymupdf) opens PDF files, reads their metadata (title, author, creation date), and extracts text from each page.

The Anthropic SDK (imported as anthropic) connects to Claude's API so your script can send text and receive intelligent classifications back.

Seeing a permission error?

If you see "Permission denied" or a similar error, try running pip install --user pymupdf anthropic instead. This installs the packages to your user directory without needing admin access.

help me fix a pip permission error when installing pymupdf and anthropic

Verify your setup

Before you start building, confirm both packages imported correctly and that your API credentials work.

  • Before you run the next commands, what do you expect to see if both packages installed correctly?
  • Check that PyMuPDF is installed by running:
python3 -c "import pymupdf; print(pymupdf.version)"

You should see a version number (like 1.28.0) printed without errors.

  • Check that the Anthropic SDK is installed by running:
python3 -c "import anthropic; print(anthropic.__version__)"

You should see a version number printed without errors.

  • Confirm your API credentials are working by running:
python3 -c "from anthropic import Anthropic; client = Anthropic(); print('Connected')"

You should see Connected printed. This confirms the SDK can find and use your API key.

Seeing an authentication error?

If the connection test fails, your Anthropic API key may not be configured. The SDK looks for credentials from an ANTHROPIC_API_KEY environment variable or from an ant auth login session.

help me configure my Anthropic API credentials so the SDK can connect

Your project directory is ready and both libraries are working. Next up, you'll write the script that scans your Downloads folder and reveals how little most PDF metadata actually tells you.

Scan and Extract PDF Data

Your environment is ready with PyMuPDF and the Anthropic SDK installed. Now it is time to point your script at the real problem.

Before you can organize anything, you need to see what you are working with. Most PDFs in a typical Downloads folder have filenames like "document(3).pdf" or "185732947.pdf" and completely empty metadata. Scanning exposes this gap and motivates the AI classification you will add next.

In this step, get ready to:
  • Build a scanning function that finds every PDF in ~/Downloads.
  • Build an extraction function that reads metadata and first-page text from each PDF.
  • Print a metadata quality summary that reveals how few PDFs have useful titles or authors.
Create organize.py with the scanning function

The first function walks your Downloads folder and collects every file ending in .pdf. This gives you the raw list of paths to process.

  • Create a new file called organize.py in your pdf-organizer folder with the imports and scan_pdfs() function by pasting this code:
import os
import json
import shutil
import re
from pathlib import Path
from datetime import datetime

import pymupdf
from anthropic import Anthropic


def scan_pdfs(downloads_path):
    """Scan directory for PDF files and return list of paths."""
    pdf_files = []
    for file in Path(downloads_path).iterdir():
        if file.suffix.lower() == ".pdf" and file.is_file():
            pdf_files.append(file)
    return sorted(pdf_files)

What does this code do?

The imports at the top pull in everything the script will need across all steps. You will not use all of them yet, but having them in place avoids revisiting the top of the file later.

The scan_pdfs() function uses Path.iterdir() to loop through every item in the given directory. It filters for files ending in .pdf (case-insensitive) and returns them sorted alphabetically.

  • Save organize.py.
  • Confirm the file exists by running:
ls

You should see organize.py listed in the output.

Don't see organize.py?

Make sure your terminal is inside the ~/pdf-organizer directory. Run pwd to check, and cd ~/pdf-organizer if you need to move there.

Help me find my file

Add the extraction function

Each PDF carries embedded metadata (title, author, creation date) plus the actual page content. This function opens each file with PyMuPDF, reads both, and returns a dictionary you can inspect.

  • In organize.py, add the extract_pdf_info() function below scan_pdfs():
def extract_pdf_info(pdf_path):
    """Extract metadata and first-page text from a PDF."""
    try:
        doc = pymupdf.open(str(pdf_path))
        metadata = doc.metadata

        text_snippet = ""
        if doc.page_count > 0:
            page = doc[0]
            text_snippet = page.get_text()[:1000]

        page_count = doc.page_count
        doc.close()

        return {
            "path": str(pdf_path),
            "filename": pdf_path.name,
            "title": metadata.get("title", ""),
            "author": metadata.get("author", ""),
            "creation_date": metadata.get("creationDate", ""),
            "page_count": page_count,
            "text_snippet": text_snippet,
        }
    except Exception as e:
        return {
            "path": str(pdf_path),
            "filename": pdf_path.name,
            "error": str(e),
        }

What does this code do?

The function opens a PDF with pymupdf.open() and reads doc.metadata, a dictionary containing keys like title, author, and creationDate.

It also grabs the first 1000 characters of text from page one using page.get_text(). This snippet is what Claude will read later to classify the document. The try/except block ensures corrupted or password-protected PDFs do not crash the entire script.

  • Save organize.py.
Add the main function and run the script

The main() function ties everything together. It calls scan_pdfs() to find your PDFs, runs extract_pdf_info() on each one, and prints a summary showing how many have useful metadata.

  • Add the main() function and entry point below extract_pdf_info():
def main():
    downloads_path = Path.home() / "Downloads"
    output_dir = Path.home() / "organized-pdfs"

    print("=" * 50)
    print("PDF Organizer")
    print("=" * 50)

    # Scan for PDFs
    print(f"\nScanning {downloads_path} for PDFs...")
    pdf_files = scan_pdfs(downloads_path)
    print(f"Found {len(pdf_files)} PDF files.")

    if not pdf_files:
        print("No PDFs found in Downloads. Nothing to organize.")
        return

    # Extract metadata and text
    print("\nExtracting metadata and text...")
    pdf_data = []
    for i, pdf_path in enumerate(pdf_files, 1):
        print(f"  [{i}/{len(pdf_files)}] {pdf_path.name}")
        info = extract_pdf_info(pdf_path)
        pdf_data.append(info)

    # Show metadata quality
    has_title = sum(1 for p in pdf_data if p.get("title"))
    has_author = sum(1 for p in pdf_data if p.get("author"))
    print(f"\nMetadata quality:")
    print(f"  {has_title}/{len(pdf_data)} have a title")
    print(f"  {has_author}/{len(pdf_data)} have an author")


if __name__ == "__main__":
    main()

What does this code do?

The main() function orchestrates the scan. It points at ~/Downloads, calls scan_pdfs() to collect paths, then loops through each PDF calling extract_pdf_info().

The metadata quality summary at the end counts how many PDFs have a non-empty title and author. This is where the core problem becomes visible. Most PDFs report zero for both fields, proving that filenames and metadata alone cannot organize your collection.

  • Save organize.py.

Before you run this, what do you think the metadata quality numbers will show? Will most of your PDFs have titles and authors, or will they be blank?

  • Run the script by executing:
python3 organize.py

You should see every PDF in your Downloads folder listed with its filename, followed by a metadata quality summary at the bottom. The numbers confirm what you probably predicted. Most PDFs have no title and no author.

Why is the metadata so empty?

Most software that generates PDFs (browsers saving web pages, receipt generators, academic paper tools) does not bother filling in the title or author fields. The result is that even a human could not organize these files by metadata alone.

This is exactly why the next step adds AI classification. Claude will read the actual page text and determine what each document is about.

Seeing "No PDFs found in Downloads"?

If your Downloads folder has no PDF files, the script exits early. Download any free PDF from the internet (a receipt, an ebook sample, a research paper) to have at least a few files to work with.

If you know you have PDFs but the script cannot find them, check that the script is looking in the right place. The path Path.home() / "Downloads" resolves to ~/Downloads on macOS and Linux.

Help me debug this path issue

✔️ Awesome, I've got everything!

Great. Double check you have saved organize.py and that the output shows your PDFs listed with a metadata quality summary at the bottom.

ⓧ I'd like to double check the full code

Scroll up and confirm your organize.py contains each of these sections in order:

  • The import block (lines 1-9) with os, json, shutil, re, Path, datetime, pymupdf, and Anthropic.
  • The scan_pdfs() function that iterates the directory and collects .pdf files.
  • The extract_pdf_info() function that opens each PDF, reads metadata and first-page text, and returns a dictionary.
  • The main() function that scans, extracts, and prints the metadata quality summary.
  • The if __name__ == "__main__": entry point at the very bottom.

You have exposed the core problem. Filenames and metadata are useless for most of your PDFs. Next up, you will call Claude's API to read the actual text content and classify each document intelligently.

Classify Documents with Claude

In the last step, you saw the problem firsthand: most PDFs have empty or unhelpful metadata. A file named "185732947.pdf" tells you nothing about what's inside.

Claude can read the actual text content and return a structured classification. In this step, you'll build a function that sends each PDF's first-page text to Claude and gets back a category, a clean filename, and a one-sentence summary.

In this step, get ready to:
  • Build a classify_pdf() function that prompts Claude with PDF content.
  • Update main() to classify every scanned PDF and print results.
  • Run the script and see AI-generated categories and descriptive names for each file.
Add the classify_pdf() function

The classification function builds a structured prompt that gives Claude everything it needs: the filename, any available metadata, and the actual text content from the first page.

  • In organize.py, add the following function below extract_pdf_info(). This first part defines the prompt that Claude receives:
def classify_pdf(client, pdf_info):
    """Use Claude to classify a PDF into a category and generate a clean name."""
    prompt = f"""Classify this PDF document and suggest a clean filename.

Filename: {pdf_info['filename']}
Title (from metadata): {pdf_info.get('title') or 'None'}
Author: {pdf_info.get('author') or 'None'}
Creation Date: {pdf_info.get('creation_date') or 'None'}
Page Count: {pdf_info.get('page_count', 'Unknown')}

First page text (excerpt):
{pdf_info.get('text_snippet', 'No text extracted')[:800]}

Respond in JSON format only:
{{
  "category": "<one of: finance, research, education, work, personal, legal, technical, reading, other>",
  "clean_name": "<descriptive-filename-in-kebab-case without .pdf extension>",
  "summary": "<one sentence describing the document>"
}}"""

What does this prompt do?

The prompt feeds Claude three layers of context about each PDF: the raw filename, any metadata that exists, and up to 800 characters of actual page text. Together, these give Claude enough signal to classify even a file named "doc(3).pdf".

The response format uses double braces {{ and }} because those are how you write literal curly braces inside an f-string.

  • Still inside the classify_pdf() function, add the API call and response parsing directly below the prompt variable:
    message = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=256,
        messages=[{"role": "user", "content": prompt}],
    )

    # Extract the text from Claude's response
    response_text = ""
    for block in message.content:
        if block.type == "text":
            response_text += block.text

    # Parse JSON response with fallback handling
    try:
        result = json.loads(response_text)
    except json.JSONDecodeError:
        match = re.search(r"\{.*\}", response_text, re.DOTALL)
        if match:
            result = json.loads(match.group())
        else:
            result = {
                "category": "other",
                "clean_name": pdf_info["filename"].replace(".pdf", "").lower(),
                "summary": "Could not classify document.",
            }

    return result

What does the response parsing do?

  • client.messages.create() sends the prompt to Claude and returns a message object. The response lives in message.content as a list of content blocks.
  • The try/except handles cases where Claude's response is not perfectly-formatted JSON. It tries to extract JSON from the response using a regex fallback.
  • If all parsing fails, the function falls back to a safe default using the original filename and an "other" category.
  • Save organize.py.
  • Confirm there are no syntax errors by running:
python3 organize.py

The script runs as before, scanning and printing metadata. The new function is defined but not called yet.

Seeing a syntax error?

  • Check that the triple-quoted f-string ends with }}""" (double closing braces followed by triple quotes). Missing a brace or quote causes an unterminated string error.
  • Make sure the API call and return statement are indented inside classify_pdf() at the same level as the prompt variable (4 spaces).
  • Help me debug a syntax error in my classify_pdf function
Update main() to classify each PDF

With the classification function ready, you need to wire it into main() so it runs on every PDF after extraction.

  • In organize.py, find the end of your main() function. Locate these lines at the bottom:
    print(f"  {has_title}/{len(pdf_data)} have a title")
    print(f"  {has_author}/{len(pdf_data)} have an author")
  • Add the following classification loop directly below those lines:
    # Classify with Claude
    print("\nClassifying documents with Claude...")
    client = Anthropic()
    for i, item in enumerate(pdf_data, 1):
        if "error" in item:
            print(f"  [{i}/{len(pdf_data)}] SKIPPED (error): {item['filename']}")
            continue
        print(f"  [{i}/{len(pdf_data)}] {item['filename']}")
        classification = classify_pdf(client, item)
        item["classification"] = classification
        print(f"    -> {classification['category']}: {classification['clean_name']}")

What does this code do?

  • Anthropic() initializes the client. It automatically resolves your API credentials from your environment.
  • The loop skips any PDFs that had extraction errors and sends the rest to classify_pdf() one at a time.
  • Each classification result is stored on the PDF's data dictionary under the key "classification" so later steps can use it for organizing.
  • Save organize.py.
Run the script and review Claude's classifications

Before you run this, think about what you expect to see. Do you think Claude will assign meaningful categories and names based on first-page text alone?

  • Run the updated script:
python3 organize.py

Each PDF now prints with an AI-assigned category and a descriptive clean name. Compare these to the raw filenames from Step 2 to see the improvement.

What should I see?

After the metadata summary, you'll see each PDF classified with a line like:

  [1/5] document(3).pdf
    -> finance: quarterly-tax-return-2023
  [2/5] 185732947.pdf
    -> research: machine-learning-survey-paper

Your categories and names will differ based on your actual PDFs. The key result is that every file now has a meaningful classification.

Getting an authentication error?

  • If you see an error about missing API credentials, confirm your key is set by running echo $ANTHROPIC_API_KEY in your terminal. If it's empty, export it or run ant auth login to configure credentials.
  • If the script hangs or times out, you may have a network issue. Check your internet connection and try again.
  • Help me fix an Anthropic API authentication error

✔️ Awesome, I've got everything!

Great. Double check you've saved organize.py with all changes.

ⓧ I'd like to double check the full code

Scroll up and confirm these sections are present in your organize.py, in this order:

  • Imports at the top (including pymupdf and Anthropic).
  • scan_pdfs() function (from Step 2).
  • extract_pdf_info() function (from Step 2).
  • classify_pdf() function with the prompt, API call, and JSON parsing with fallback.
  • main() function ending with the "Classify with Claude" loop that prints categories and clean names.
  • if __name__ == "__main__": block at the very end.

Your PDFs now have intelligent names and categories. Next up, you'll put those classifications to work by physically reorganizing files into folders and generating a searchable catalog.

Organize Files and Generate Catalog

Your script can now scan PDFs, extract their content, and get intelligent classifications from Claude. Each PDF has a category, a clean name, and a summary.

With those classifications sitting in memory, it is time to put them to work. In this step you will physically reorganize your filesystem and generate a searchable markdown index that makes your entire PDF collection browsable without opening individual files.

In this step, get ready to:
  • Add an organize_files() function that moves PDFs into categorized folders with clean filenames.
  • Add a generate_catalog() function that writes a searchable markdown index of your collection.
  • Update main() to call both functions and run the full pipeline end to end.
Add the organize_files() function

This function takes the classified PDF data and physically moves each file into a folder structure based on its AI-assigned category. It also handles the case where two PDFs get the same clean name by appending a counter.

  • In organize.py, find the end of the classify_pdf() function (the line return result).
  • Add the first part of the organize_files() function below it by copying the following code:


def organize_files(pdf_data, output_dir):
    """Move PDFs into categorized folders with clean names."""
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    moved_files = []

    for item in pdf_data:
        if "error" in item or "classification" not in item:
            continue

        classification = item["classification"]
        category = classification["category"]
        clean_name = classification["clean_name"]

        category_dir = output_path / category
        category_dir.mkdir(exist_ok=True)

What does this code do?

This sets up the output directory ~/organized-pdfs and starts looping through your classified PDFs. It skips any PDFs that had errors or failed classification.

For each valid PDF, it extracts the category and clean name from the classification result. It then creates a subdirectory for that category (like finance/ or research/) if it does not already exist.

  • Directly below the code you just pasted (still inside the for loop), add the filename construction and file-move logic:

        new_filename = f"{clean_name}.pdf"
        new_path = category_dir / new_filename

        counter = 1
        while new_path.exists():
            new_filename = f"{clean_name}-{counter}.pdf"
            new_path = category_dir / new_filename
            counter += 1

        shutil.move(item["path"], str(new_path))
        item["new_path"] = str(new_path)
        moved_files.append(item)

    return moved_files

What does this code do?

  • It builds the new filename from the AI-generated clean name (e.g. quarterly-tax-return-2023.pdf).
  • The while loop handles duplicates. If a file with that name already exists, it appends -1, -2, etc. until the name is unique.
  • shutil.move() physically moves the file from Downloads to its new categorized home.
  • Save organize.py.

Indentation looking wrong?

The second code block continues inside the for item in pdf_data: loop, so every line should be indented 8 spaces (two levels). Make sure return moved_files is indented only 4 spaces since it sits outside the loop but inside the function.

Help me fix my organize_files() indentation

Add the generate_catalog() function

A folder full of well-named PDFs is great, but you still need to open them to remember what each one contains. A markdown catalog solves this by giving you a searchable text file listing every PDF with its AI-generated summary.

  • In organize.py, find the end of the organize_files() function (the line return moved_files).
  • Add the first part of the generate_catalog() function below it:


def generate_catalog(pdf_data, output_dir):
    """Generate a searchable markdown catalog of all organized PDFs."""
    output_path = Path(output_dir)
    catalog_path = output_path / "catalog.md"

    lines = []
    lines.append("# PDF Library Catalog")
    lines.append("")
    lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
    classified = [p for p in pdf_data if "classification" in p]
    lines.append(f"Total documents: {len(classified)}")
    lines.append("")

    categories = {}
    for item in classified:
        cat = item["classification"]["category"]
        if cat not in categories:
            categories[cat] = []
        categories[cat].append(item)

What does this code do?

This sets up the catalog header with a generation timestamp and total document count. It then groups all classified PDFs by category into a dictionary so the catalog can be organized by section.

  • Directly below the code you just pasted, add the rendering loop and file write:

    for category in sorted(categories.keys()):
        items = categories[category]
        lines.append(f"## {category.title()} ({len(items)} files)")
        lines.append("")
        for item in sorted(items, key=lambda x: x["classification"]["clean_name"]):
            classification = item["classification"]
            lines.append(f"- **{classification['clean_name']}.pdf**")
            lines.append(f"  - {classification['summary']}")
            if item.get("author"):
                lines.append(f"  - Author: {item['author']}")
            if item.get("page_count"):
                lines.append(f"  - Pages: {item['page_count']}")
            lines.append("")

    catalog_path.write_text("\n".join(lines))
    return str(catalog_path)

What does this code do?

  • It loops through each category alphabetically and writes a markdown section header with the file count.
  • Each PDF within a category gets a bold filename, its one-sentence summary, and optional author/page-count metadata.
  • catalog_path.write_text() writes the entire catalog to catalog.md in one operation.
  • Save organize.py.

Seeing a syntax error with the f-strings?

The catalog function mixes single quotes, double quotes, and curly braces. Make sure the f-string lines use double quotes on the outside and single quotes for dictionary keys inside the braces (e.g. classification['clean_name']).

Help me fix my generate_catalog() f-strings

Update main() to organize and generate the catalog

Both functions are ready. Now you need to wire them into main() so the script calls them after classification finishes.

  • In the main() function, find the last line of the classification loop:
        print(f"    -> {classification['category']}: {classification['clean_name']}")
  • Add the following code below that line (outside the for loop, at the same indentation level as the # Classify with Claude comment):
    # Organize files
    print(f"\nOrganizing files into {output_dir}...")
    moved = organize_files(pdf_data, output_dir)
    print(f"Moved {len(moved)} files.")

    # Generate catalog
    print("\nGenerating catalog...")
    catalog_path = generate_catalog(pdf_data, output_dir)
    print(f"Catalog saved to: {catalog_path}")

    print("\n" + "=" * 50)
    print("Done! Your PDFs are organized.")
    print(f"Check: {output_dir}")
    print("=" * 50)

What does this code do?

  • organize_files() moves every classified PDF into ~/organized-pdfs/<category>/<clean-name>.pdf and reports how many files were moved.
  • generate_catalog() writes catalog.md with every PDF's summary grouped by category.
  • The closing banner prints where to find the organized output.
  • Save organize.py.

Before you run the script, think about what you expect to happen. Will your PDFs still be in ~/Downloads when it finishes, or somewhere else?

  • Run the full pipeline by executing this command:
python3 organize.py

You should see the familiar scan, extraction, and classification output, followed by new lines showing files being moved and the catalog being generated. The script ends with a banner pointing you to ~/organized-pdfs.

  • Confirm your PDFs moved into category folders by running:
ls ~/organized-pdfs

You should see folders named after the categories Claude assigned (like finance, research, education) plus a catalog.md file.

  • Open the catalog to see your searchable index by running:
cat ~/organized-pdfs/catalog.md

You should see a markdown document with a header showing the generation date and total count, followed by category sections listing each PDF with its clean name, summary, and metadata.

Seeing a FileNotFoundError or PermissionError?

  • If a PDF is open in another application (like Preview or a browser), the script cannot move it. Close the PDF and re-run.
  • If you see a permission error on ~/organized-pdfs, make sure you have write access to your home directory.
  • If you re-run the script after PDFs have already been moved, ~/Downloads will be empty and the script prints "No PDFs found." That is expected.

Help me debug my organize.py file-move errors

✔️ Awesome, I've got everything!

Great. Double check you have saved organize.py and confirmed both the folder structure and catalog look correct.

ⓧ I'd like to double check the full code

Your organize.py is too long to show in one block, so confirm you have each section in this order from top to bottom:

  • Imports (os, json, shutil, re, Path, datetime, pymupdf, Anthropic).
  • scan_pdfs() function.
  • extract_pdf_info() function.
  • classify_pdf() function.
  • organize_files() function (added in this step).
  • generate_catalog() function (added in this step).
  • main() function with scan, extract, metadata summary, classify, organize, generate catalog, and final banner.
  • if __name__ == "__main__": block at the bottom.

Your Downloads folder just went from chaos to a clean, categorized library with a searchable index. Next up, you can take this further with duplicate detection in the Secret Mission.

Secret mission

Detect Duplicate PDFs

Over seven years of downloading, you have almost certainly grabbed the same document more than once. Can you add a duplicate detection feature that finds similar PDFs without making expensive API calls?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally with no ongoing costs. The Claude API calls you already made are billed per-use, so there is nothing left running that will charge you.

Resources you used:

  • ~/pdf-organizer/ directory containing your organize.py script.
  • ~/organized-pdfs/ directory with categorized PDF subfolders and catalog.md index.
  • Python packages: pymupdf and anthropic installed via pip.

Keep everything running

No action needed. Choose this if you plan to keep organizing PDFs as new ones accumulate in your Downloads folder.

  • Your organize.py script is ready to re-run anytime. Just navigate to ~/pdf-organizer and run it again.
  • The ~/organized-pdfs/ folder and catalog stay in place for browsing.
  • The pymupdf and anthropic packages remain installed for future use.

Pause - I'll come back to this later

Since this project is entirely local with no running processes, there is nothing to pause. Everything stays on disk until you are ready to use it again.

  • Your script, organized PDFs, and catalog will remain exactly where they are.
  • When you are ready to organize new PDFs, navigate back to ~/pdf-organizer and run the script.

Delete - I don't want to use this again

Remove all project files and start fresh. Note that your organized PDFs will be deleted. If you want them back in Downloads first, the catalog.md file records the original filenames so you can move them back manually before deleting.

  • Delete the project directory and the organized PDFs folder by running:
rm -rf ~/pdf-organizer
rm -rf ~/organized-pdfs
  • Uninstall the Python packages by running:
pip uninstall pymupdf anthropic -y
  • Confirm the folders are gone by running:
ls ~/pdf-organizer ~/organized-pdfs

You should see "No such file or directory" for both paths.

Nice Work!

Nice Work!

You just built a real productivity tool from scratch. Your PDF organizer takes years of chaotic downloads and turns them into a clean, searchable library powered by AI classification.

You've learned how to:

  • Built a Python script that scans ~/Downloads and extracts text and metadata from every PDF using PyMuPDF.
  • Used Claude's API with structured prompting to intelligently classify documents by content.
  • Automated file organization by moving and renaming PDFs into a clean category-based folder structure.
  • Generated a searchable markdown catalog indexing your entire PDF collection with summaries.
  • Secret Mission: Added duplicate detection using text similarity to find redundant downloads.

Ready to quiz yourself?