| name | swipe |
| description | Turn a pile of digital cleanup into a Tinder-style swipe deck on your phone. Point it at a folder (or your unread texts) and it serves a card deck to your phone over local WiFi. Swipe each card left or right, and the chosen action runs in one reversible batch at the end. Files swipe to Trash (recoverable, never deleted outright), texts swipe to skip or reply (replies are staged, never auto-sent). Cards show a real preview whenever possible, not just a filename, so you are never swiping blind. Built for the kind of task paralysis where 300 screenshots in a folder is easier to ignore than to face. Trigger on "/swipe", "let me swipe through my desktop", "swipe to clean out <folder>", "swipe my screenshots", "swipe my downloads", "swipe my unread texts", "give me a deck to clean out X". |
swipe
A swipe-to-triage deck for digital cleanup. You point it at a source, it serves a card deck to your phone over your local WiFi, you swipe through the whole pile in one sitting, and the actions run as one reversible batch when you are done.
Why this exists
A folder with 300 screenshots is a wall. Cleaning it the normal way means 300 separate decisions, each with its own friction (open it, look at it, decide, find the next one), and that friction is exactly what task paralysis feeds on. So the folder just sits there, growing.
This collapses every decision into one flick of a thumb. One card, one swipe, next card. The deck shows up on your phone, so you can do it on the couch instead of staring at a file manager. Each swipe is its own tiny hit of done, and the momentum carries you through the pile that you would otherwise never start. Nothing is destructive in the moment: files go to the Trash (recoverable), text replies are staged for you to send later. The payoff is fast and visible, and you can undo.
Design rules that make it work, do not break them:
- Push, not pull. It loads the whole pile and hands it to you. It never makes you come back to fetch more.
- No guilt, no streaks, no counters that shame you. Just the deck and a running position.
- Reversible. Trash, not delete. Staged replies, not sent messages.
- One uninterrupted session. See below.
- Never make someone swipe blind. Every card should tell you enough to actually decide. See Previews below.
The engine is source-agnostic
The server and deck do not know or care what they are showing. They render whatever a manifest describes. A "source" is just an adapter script that emits that manifest. v1 ships two adapters (Files and iMessage); adding a new source (Gmail, Photos, a download queue, anything) is one new adapter that emits the same schema. That is what "point it at anything" means here.
Manifest schema (the contract)
{
"source": "files",
"title": "Downloads cleanup",
"left": { "label": "Trash", "kind": "trash" },
"right": { "label": "Keep", "kind": "keep" },
"reply": false,
"items": [
{
"id": "0",
"type": "image | file | text",
"label": "headline (file/image card)",
"sub": "subline, e.g. size and date, or a timestamp",
left / right set the two swipe actions and their on-screen labels. kind is a free-form tag your executor reads.
reply: true makes each text card show a reply box.
type controls the card: image and file both render an image thumbnail from path when thumb is true; text renders sender + preview.
id must be a unique string. Decisions come back keyed by id.
Previews: never swipe blind
A card with nothing but a filename is a coin flip, not a decision. The Files adapter tries hard to give every card something real to look at:
- Images get a real thumbnail (resized via
sips).
- PDFs, videos (
.mov/.mp4/...), Office docs (.pptx/.docx/.key/...), and .svg get a real thumbnail too, rendered on macOS by QuickLook (qlmanage -t) - first page of a PDF, poster frame of a video, first slide of a deck. SVGs are served directly since a browser can already render them.
- Text-like files (
.txt, .md, .json, .py, .jsx, source code, etc.) get an inline text snippet instead of a thumbnail.
.zip archives get a contents listing (file names inside) instead of nothing.
- Anything else falls back to a plain file icon - that is the one case where the adapter genuinely has nothing better to show.
If you write a new adapter, try to give every item a thumb or a preview. An icon-only card is a last resort, not the default.
Flow
-
Intake. Ask two things as a short multiple choice, not open-ended:
- Source: a folder (offer Desktop, Downloads, screenshots, or a custom path) or unread texts.
- Screenshots is a special folder source. Screenshots save to the path in
defaults read com.apple.screencapture location (defaults to ~/Desktop). Point the Files adapter at that folder, then filter the manifest items to screenshot-named files only (case-insensitive regex screenshot|screen shot|cleanshot|screen recording) and renumber their ids 0..n before serving. Keep each item's real path so trashing still works.
The action pair is implied by the source (files: Keep vs Trash; texts: Reply vs Skip), so do not ask a separate question about it.
Always load the FULL pile in one deck. Never cap it or drip-feed. The entire point is one uninterrupted swipe session; making someone come back to load "the next batch" kills the momentum that got them started. Only chunk if a set is genuinely huge (hundreds and up) and a single page would choke the phone, and if so, say so out loud. Default sort is oldest-first; do not ask unless they want largest-first.
-
Preflight.
- Network: the deck is served to the phone over the local network, so the phone just needs to be on the same WiFi as the computer. The server auto-detects the LAN IP (
ipconfig getifaddr en0, falling back to en1, en2). If you are on a VPN, do not hand the phone a VPN-only address; pass --host <lan-ip> explicitly or run on the computer's own browser with --host 127.0.0.1. Re-check the LAN IP if the phone can't connect - it can change mid-session (network switch, VPN toggle).
- iMessage source only: reads
~/Library/Messages/chat.db, which needs Full Disk Access for the terminal. If the adapter errors on permission, surface that.
-
Gather items. Run the adapter, writing the manifest into the run dir. Use a high --cap (e.g. 1000) as a sanity ceiling only, never as a drip limit:
- Files:
python3 <skill>/adapters/files.py "<folder>" --cap 1000 --sort oldest > <skill>/.run/manifest.json
- Screenshots: gather as Files against the screenshot folder, then filter and renumber as in Intake.
- Texts:
python3 <skill>/adapters/imessage.py --cap 1000 > <skill>/.run/manifest.json
If the manifest has zero items, say there is nothing to swipe and stop.
Hard rules
- Files go to Trash via the file manager (Put Back preserved), never
rm.
- Texts are never auto-sent. Replies (typed or drafted) are staged; sending waits for "send it."
- Always wipe
<skill>/.run/ at the end of a run. Message bodies must not persist anywhere.
- No em dashes in any output.
Platform notes
Thumbnails (sips for images, qlmanage for PDFs/video/Office docs), Trash (Finder via osascript), and the iMessage source (chat.db) are macOS-specific. The server and deck themselves are plain Python and a single HTML file, so a new adapter plus a new executor could target another OS without touching the engine.
Adding sources later
A new source is one adapter emitting the manifest schema above, plus one executor for its swipe-left action. Sketches:
- Gmail: list = unread threads; card = sender + subject + snippet; left = archive, right = keep.
- Photos: deferred. Library deletion via automation is unreliable.
Credits
The phone handoff QR code is generated by the bundled vendor/qrcode.js, a third-party library by davidshimjs, Copyright (c) 2012, MIT licensed. Its full license text is bundled at vendor/LICENSE-qrcodejs.txt. Everything else is original.