keeper opens a folder of photographs and helps you pick the good ones and cut them to the shapes a website or an instagram post needs. it runs on your own computer. it does not need an account and it does not need the internet.
a shoot comes back as 1,768 frames and a website has sixteen holes. every hour between those two numbers goes on scrolling a finder window at 200px a thumbnail, and that is the hour this takes back.
every frame in these shots is a generated stand in. real work does not go in a readme.
download the one for your computer from the releases page. there is nothing to install alongside it.
| mac | open the .dmg, drag keeper to applications, open it |
| windows | run the .exe setup, then open keeper from the start menu |
your computer will warn you the first time, and here is why. letting a program pass without a warning costs money every year, on each platform, and that money has not been paid. so:
- mac says the developer cannot be verified. open system settings, go to privacy and security, scroll down, press open anyway.
- windows says it protected your pc. press more info, then run anyway.
you should not take that on trust from a stranger. everything keeper is made of is in this repository, the downloads are built by a script you can read, and every file has a checksum next to it so you can prove the one you downloaded is the one that was built.
- open keeper. it opens in your web browser. that is just how the screen is drawn. nothing is on the internet and the page is coming from your own computer.
- drag a folder of photographs onto the page. any folder. keeper opens the folder where it already is and does not copy or move anything.
- wait once. keeper makes a small copy of each picture so the page can show hundreds at a time without crawling. a few thousand photographs takes a few minutes. it only ever happens once per folder.
- the shelf appears. every photograph in the folder, on one screen.
next time you open keeper it opens the same folder again.
move with the arrow keys. press k to keep the one you are on. press space to see it big.
that is the whole loop, and it is meant to be done with one hand without looking down. a thousand photographs is about twenty minutes this way and an afternoon with a mouse.
delete does not delete. press it and the photograph disappears from the shelf and the file does not move. it goes to keeper's own bin, which is a list, not a folder. you can put it back.
there is one button that really removes a file. it lives inside the bin, it asks first, it tells you how many, and it uses your computer's own delete, so the files land in the trash or the recycle bin and you can still get them back from there.
this is the second version of that and the first one was wrong. delete used to go straight to the trash. putting the fastest key in the app on the one thing you cannot undo is how a tool eats somebody's photographs.
instagram wants a tall picture. a youtube thumbnail wants a wide one. a banner across the top of a website wants something wider still. the same photograph does not survive all three, and you cannot tell which ones it survives by looking at it.
so click a photograph and keeper shows it in every shape at once.
drag it into the shape you want, drag inside that box to move the picture,
hold option and scroll to zoom in. press export and the cut picture lands
in your downloads folder, in a folder called keeper. the original is not
touched.
the shapes are already there on the first run: instagram post, square, story and reel, x, youtube thumbnail, link preview, pinterest, and a few sizes of web banner. each one is the size that platform actually wants.
if you zoom in too far, keeper tells you before you export. past a certain point there are not enough pixels left to fill the shape, so somebody else's website would stretch it and it would look soft. keeper compares the two and says so in red.
a pile is what you build while you browse. click photographs into it, keep going, start another one when the subject changes, then export that pile as a real folder of real files you can hand to anyone.
exporting never moves your originals. keeper reads from your folder and writes somewhere else. your folder comes out of it byte for byte identical.
this one needs a little more from you, and it is optional. skip it if you do not already use a coding assistant.
keeper has no ai in it and does not want your credit card for one. instead it lays your photographs out as contact sheets, which are big grids of small pictures, sized so a machine can still tell one face from another. you hand those sheets to an assistant you already pay for, like claude or cursor, along with the instructions in AGENTS.md. it writes back a file of tags. keeper reads that file and labels every photograph.
if the assistant miscounts a sheet, keeper refuses that whole sheet rather than applying it. one bad count would silently shift every label after it onto the wrong photograph, which would make the whole thing worthless.
it is the first thing to check about anything you point at your photographs, so it is written out rather than claimed in a badge.
your photographs never leave your computer. there is no upload, no account, no login, no key, and no licence check. it works with the wifi turned off. you can test that: turn the wifi off and use it.
nothing is counted or reported. no analytics, no crash reports, no usage tracking. not turned off by default, not in there at all.
one thing can reach the internet and it asks you first. keeper can check whether a newer keeper exists. a card asks you once, in words. say no and it never asks again and never makes the request. say yes and it fetches one small file that carries no name, no photographs, and nothing about you.
your photographs are read and never written to. keeper makes one folder
inside yours, called .keeper, and puts its small copies, your labels and
your crops in there. delete that folder and your archive is exactly as it was.
nothing is generated. no ai model runs here and no picture of yours is sent anywhere to be looked at by one.
keeper can check itself over and tell you what is missing.
on windows there is a keeper doctor shortcut in the start menu, next
to keeper itself. on a mac it needs a terminal: keeper doctor.
either way it answers in plain sentences rather than error codes, and that is the thing to copy and send on when you ask anyone for help.
on windows, keeper cannot read raw camera files on its own, because windows does not come with anything that reads them. install ffmpeg and it will read most of them. jpg, png, heic and the rest work either way. macs read everything already.
everything above works without one. this part is for people who want it.
npm install
node bin/keeper.mjs ~/Archive
keeper <folder> scan, thumbnail, and open the shelf
keeper app [folder] the way the icon opens it: remembers the last
archive, takes its own port, and reuses the
copy that is already running
keeper sheets <folder> contact sheets for a coding agent to read
keeper tag <folder> <file> apply the tags that agent wrote
keeper export <folder> write the placed crops out
keeper trays <folder> what is in the trays, and how much
keeper init [folder] create keeper.config.json
keeper doctor what this machine can and cannot do
--port defaults to 7777, --cols and --rows set the contact sheet grid at
6 by 4, --rescan rebuilds an index that already exists, and --no-open
leaves the browser alone. typed bare, with no folder after it, keeper
prints this list: a terminal opens in your home folder, and thumbnailing
every file you own is not what someone asking what the command does had in
mind.
| arrows | move the cursor, and a letter tags whatever it is on |
k | keeps the frame |
space | quick look, the way it works everywhere else on this machine |
| click | picks, shift+click a range, cmd+click one, cmd+drag a box |
cmd+a | everything the filters left, and not one frame more |
| a letter, with frames picked | tags all of them at once, and moves nothing |
cmd+return | sends the picked frames to the tray |
option+r | reveals the original in finder, selected |
cmd+f | finds, option+o opens another folder, option+1 and option+2 change view |
delete | sets the frame aside in keeper's bin, nothing on the drive moves |
the modifier is option and not cmd on three of those because chrome resolves
cmd+r, cmd+o and cmd+1 above the page: preventDefault runs and
the tab reloads anyway. the physical key is read off e.code rather than
e.key, because option on a mac rewrites the character and option+r
arrives as ®.
the built in formats live in src/formats.mjs. your own go
in keeper.config.json:
{
"slots": [
{ "id": "hero", "aspect": "16/9", "width": 2400 },
{ "id": "about-1", "aspect": "3/2", "width": 1200,
"note": "the room rather than a wall of faces" }
]
}the config is read from the folder you run keeper in, not from the archive,
because the holes belong to the project and the photographs do not. keeper init writes the example there for you. a slot of yours with the same id as a
built in one replaces it quietly, and "formats": false turns the whole
standard set off.
if you have not zoomed in and you have a config, the bench also prints the
exact object-position line for your stylesheet, so the original ships uncut
and the browser does the cropping.
keeper trays ~/Archive
keeper trays ~/Archive --export tray-1 --to ~/Desktop/for-the-site
keeper trays ~/Archive --export tray-1 --to ~/Desktop/live --mode symlink
copies are real files, yours to hand to anyone. symlinks and finder aliases copy nothing and point at the originals, the first breaking if you move them and the second following them. the terminal names the mode it just ran every time, because a folder of links and a folder of copies look identical in a listing and weigh nothing alike.
keeper refuses to export into a folder inside the archive. it would work once. the next scan would find the copies, give them their own ids, thumbnail them, and you would be browsing every exported frame twice with the tags on only one of the pair.
the server binds to 127.0.0.1. not 0.0.0.0, which means it cannot be
reached from the other laptop on your own wifi, never mind from outside. one
browser, one machine. every fetch in the source names either a path on this
machine or github, and grep -rn "fetch(" src web bin is the whole audit.
a placement is stored in the negative's own coordinates, not the screen's. three numbers: where the centre of the crop sits in the source, and how wide it is as a fraction of the source. the height falls out of the slot's aspect at the moment of painting. that is what lets a layout change a frame's aspect at a breakpoint without silently meaning something different. a placement written in screen pixels would.
a frame's id is a hash of its path, not its position. drop 200 new photographs into the archive and every tag written last month still points at the same picture. position based ids would have slid by 200 and repointed the lot, quietly. it is also why a frame you take back out of the bin comes back to its own tags.
a browser is never told where a dropped folder lives. it gets the name,
and for a folder it may list what sits directly inside, and that is the end of
it. so keeper recovers the path three ways: the drag's own file:// url when
the source writes one, the machine's search index, and a read of the handful
of folders an archive is ever kept in. if all three come up empty the real
folder dialog opens, which is the only thing on the machine that can hand a
browser a path and mean it. nothing in it ever says the drop failed when what
happened is that the browser withheld the path.
the two machines differ in five places and only five: the file manager,
the wastebasket, the shortcut a tray exports as, the search index, and the
list of folders to look in. those live in src/os/ and everything above them
is one codebase.
reads jpg, png, webp, avif, tif, heic and dng, and mov, mp4, m4v, mkv, avi,
webm and mts. raw files come in through their embedded preview, which is all a
contact sheet needs. clips need ffmpeg on the path, and their poster frame
is cut from a third of the way in, because frame one is a lens cap or a whip
pan often enough that it is not worth trusting.
keeper can update itself, and it asks before it ever looks. what comes down is about a quarter of a megabyte, because it is keeper and not the runtime under it. it is checked against the checksum the release published before a file moves, the copy being replaced is set aside rather than deleted so a failure puts it back, and a release that changes what keeper depends on cannot be installed this way, says so, and sends you to the downloads instead of installing half of something.
everything keeper writes goes in <archive>/.keeper/: the index, the small
copies, the contact sheets, your tags, your placements and your trays. delete
that folder and the archive is exactly as it was.
opened from its icon it keeps two more files, and they are everything it has
ever written outside an archive: which archive was open last, and which port
it is on. ~/Library/Application Support/keeper on macos,
%LOCALAPPDATA%\keeper on windows. deleting them loses the memory of where
you were and nothing else.
a red square and the word keeper beside it, and a photograph is the only
thing on the screen worth looking at.
one accent, #e1062c, over #131315 raised and #0b0b0c ground. it means
one of exactly two things anywhere it appears: you chose this, or you have to
deal with this. a tab you are already looking at is neither, and neither is a
photograph.
type is geist and geist mono, self hosted in web/font, ofl, nothing fetched
at runtime. the mono carries the wordmark, the nav, every label, every chip
and every number, and the sans is left to do the one thing a mono cannot,
which is read as a sentence.
apache 2.0.
it was mit until there was something to download. a dmg is not source: it
carries node, sharp and libvips, and libvips is lgpl, so shipping that binary
means saying whose work is inside it and letting somebody swap it.
NOTICE does both and names the file. nothing about how you may
use keeper changed, and it is still free.


