Geminee: my own duplicate finder for the Mac

A command-line duplicate finder with a native review window, built with Claude Code to replace Gemini 2. It links backup copies instead of deleting them, and scans 328,000 files in 21 seconds.

The Geminee app icon: 2 identical white pages with a red edelweiss on an indigo gradient square, with the name Geminee under it

Geminee finds files and folders with identical content and gets rid of the extra copies. It replaces Gemini 2 by MacPaw, which I ran through Setapp: macOS app subsciption. It's a command-line tool first: geminee scan writes a plan, geminee clean moves the extra copies to the Trash, and geminee link turns them into clones or hard links, so the content is stored once and every path still works. A native window shows the plan with thumbnails when I want to look before I clean.

I built it in 1 evening with Claude Code, on 24th October 2026. The name is Gemini plus the -ee of my other tools, like my file manager, see Filee: my own file manager for macOS.

The Geminee review window on a test plan: an indigo title strip, filter pills for All, Files, Folders and Similar, a list of duplicate groups sorted by space on the left, and on the right 3 copies of the same photo as cards, 1 marked Keep and 2 marked Remove, with Link and Move to Trash buttons at the bottom

Why I replaced Gemini

Before the build, the agent read a copy of Gemini's own databases, preferences and logs. The numbers changed what I wanted:

What I found Value
Runs since 2022 6 launches, 6 scans, 6 cleans
Lifetime totals 412.9 GB scanned, 151.9 GB removed
Last scan 1 folder of images, 5 min 27 s, 1,425 groups, 17.5 GB found
How I chose 1 bulk "keep the oldest" selection, clean 3 minutes later
File types 99% PNG and JPEG
Launch to ready 15 s
Never used Photos libraries, schedules, the Downloads monitor

So Gemini was an occasional tool, not a daily one. I trusted the automatic choice every time, and I paid a subscription for a big app I opened about once a year.

The same review measured where my duplicates actually are, read-only, with counts only. The big find wasn't in my documents. It was my backup disk: 35 dated full copies of the same folders, 1.57 TB, of which only 214 GB was unique. Deleting backups is the wrong answer there. Linking them is the right one: every dated folder stays complete, and each identical file is stored once.

What Geminee does

Feature How it works
Identical files Same size, same first 16 KB, same SHA-256. Names and dates don't matter
Identical folders Whole folders with the same names and content
Similar images Near-identical photos through Apple's Vision. Only ever suggested, never marked
Clean Extra copies go to the Trash, so Finder's Put Back works
Link Clones on APFS, hard links on HFS+. Every path keeps working
Undo geminee undo puts back the last clean, or separates the last linked copies
Plan file A TSV I can edit: change remove to keep, then clean from the file
Fast re-scan FSEvents tells Geminee what changed since the last scan
Review window Thumbnails side by side, K keeps, R removes, Space shows Quick Look
Filee action "Find duplicates here" in my file manager runs a scan on the current folder

The commands I use:

geminee scan ~/Pictures --similar   # read-only: groups, space to free, a plan
geminee show                        # the biggest groups of the latest plan
geminee review                      # the plan in the window
geminee clean                       # extra copies to the Trash (asks first)
geminee link                        # or clones and hard links (asks first)
geminee undo                        # put back the last run

What it keeps

When a group has several copies, Geminee keeps 1 by these rules, in order:

  1. A copy in a protected folder.
  2. A copy in a "keep first" folder.
  3. Not in Downloads or Desktop.
  4. Not inside a folder the same plan removes.
  5. A name without "copy", "(1)" or " 2".
  6. The oldest.
  7. The shortest path.

Rule 6 is my old Gemini habit. The others are what I did by hand when the automatic choice was wrong.

Keys in the window

Key Action
↑ ↓ Previous or next group
← → Previous or next copy
K, R Keep or remove the copy in focus
Space Quick Look
⌥⌘S Remove the suggested similar copies
⌘⌫, ⇧⌘⌫ Move marked copies to the Trash; link them
⌥⌘Z Put back the last clean or link
⌘1 to ⌘4 All, Files, Folders, Similar
⌘K Command centre

Speed

Speed was goal 1. The disk is the floor, so Geminee reads as little as it can: only files that share a size, 16 KB before a full read, each inode once, and every hash goes into a cache. Measured on my Mac on 24th October 2026:

Metric Budget Measured Gemini
Command to first output < 100 ms 60 ms 15 s to start
Documents scan, cold < 60 s 20.7 s -
Documents scan again, nothing changed < 25 s 1.8 s -
Documents scan, full walk with the cache - 11.9 s -
Memory during that scan < 400 MB 276 MB -
Window: launch to ready < 400 ms 120 to 185 ms -
Window: switch group, p95 < 30 ms 4.5 ms -
Window: memory < 150 MB 70 to 82 MB -

My documents hold 328,157 files and 406 GB. The scan found 2,656 extra copies holding 32.7 GB.

The backup disk is a different scale. It's HFS+ over USB, where opening each file is the cost. The first scan of all 7.3 million files took 2 h 11 min. With the hash cache warm, the next one took 11.6 minutes. It found 358,855 groups and 1.53 TB that geminee link can give back.

How it's built

Stack

  • CLI: 1 Python file, standard library only, runs on the macOS system Python 3.9. It owns the database and every file change.
  • Helper: a small Swift binary for what Python can't call: Vision, Accelerate, FSEvents and the Trash.
  • Window: Swift 6.3 and AppKit, frame layout, in my NicAI Design with an indigo title strip.
  • Store: SQLite in WAL mode in ~/.geminee/, with the inventory, the hash cache, the image prints, the plans and the undo log.
  • Dependencies: none.

Why Python is fast enough

I expected the agent to pick Swift or Rust. It measured first:

  • SHA-256 from hashlib runs at 2.4 GB/s on 1 core (Apple's crypto instructions) and 11.5 GB/s on 8. That's faster than every disk I own.
  • Python with 8 threads walked my documents in 20 s. The find command took 23 s. The kernel is the floor, not the language.

So the CLI starts in 60 ms and stays readable. Swift does only what needs a framework.

Rules that keep it safe

Goal 2 was "never lose data". These rules are binding: a change that breaks one needs a measured reason.

  • A scan never writes outside its own folder. Clean, link and undo are separate commands that ask first.
  • A hash belongs to what was read. Geminee checks the inode, size and date of the open file before and after the read. A mismatch gives no hash.
  • Check again right before acting. Clean and link re-check the copy and the kept copy before each move or link. A changed file is skipped.
  • A group always keeps 1 copy. The store refuses an edit that leaves a group without one.
  • Atomic links. The link appears under a temporary name, and a rename puts it over the copy. A crash leaves the copy untouched.
  • The log survives a crash. Link commits every 500 items. Undo reads that log.

Similar images

Vision computes a feature print (768 numbers) for each image, on a 512 px thumbnail. Accelerate compares them in blocks, so 20,000 images cluster in 0.8 s. A spike set the threshold:

Change to the image Average distance
Resized 0.03
Recompressed 0.06
Cropped to 85% 0.08

The default is 0.15. Only 0.6% of unrelated wallpaper pairs fell under it. Groups form around a leader and never chain, so A near B near C doesn't make A and C a pair.

Gotchas worth knowing

  • Clones need APFS. clonefile returns ENOTSUP on HFS+, so the external disks get hard links. A hard link is 1 file with several names: fine for backups nobody edits.
  • Hard links have a limit. At EMLINK, the copy becomes the source for the rest of its group.
  • A skipped folder can hide data. A folder that holds anything Geminee skips, like .git, never counts as a duplicate folder. Otherwise its twin would go to the Trash with the skipped content inside.
  • FSEvents on HFS+ folds events into older ids. Geminee also lists every folder whose date changed, so nothing is missed.
  • A synced folder syncs the delete too. A clean in a cloud folder removes the copy on every device. That's why everything goes to the Trash first.
  • AppKit undo. The undo is registered when I press K or R, not when the store answers. An async registration broke ⌘Z after an undo.

The agent interface

Every command takes --json and prints 1 JSON document, so NicAI can use Geminee inside a task (see What is NicAI). Clean and link refuse without --yes and take --dry-run:

geminee describe --json                    # every command and its effect
geminee scan ~/Downloads --json            # plan id, groups, bytes to free
geminee clean --plan 12 --dry-run --json   # what would move, nothing changes

How it was built

Geminee followed my appee process for apps:

  1. Usage review: Gemini's data, plus a read-only measurement of my disks that printed counts, never file names.
  2. Questions: 32, each with a recommended answer. I accepted all of them, with 1 change: build the whole version 2 list now, not later.
  3. Spec: the decisions, budgets and rules, written before the code.
  4. Build and QA: 8 commits between 22:58 and 23:54 on 24th October 2026.

The same pass changed my backup script: each new backup now hard-links unchanged files to the previous one, as rsync --link-dest does. So the duplicates stop growing at the source.

QA at version 1.0:

  • 16 Swift unit tests on the clustering and the plan store.
  • 33 Python tests, including 1 per defect the reviews found.
  • 61 end-to-end checks in 6 suites: CLI, FSEvents, similar images with the real Vision, and the window in light and dark mode, zoomed and at prompter size.
  • 2 independent reviews by other agents: 11 defects in the data paths and 10 findings in the window. All fixed.

QA never touches my real files. It runs on fixture folders with a fake Trash. The screenshot in this note comes from that run.

Further reading

The app Geminee replaces:

Apple's framework behind the similar images:

The tool I built it with:

NicAI
Written by NicAI, Nic's AI assistant, for his personal knowledge base. Researched and drafted by the model, not hand-written by Nic. Verify anything you plan to act on.