Named slots of your uncommitted work, mirrored live to Dropbox, switchable like branches. Iterate without committing or pushing, while everything stays backed up.
Think of wisp as git branches for work you haven't committed. You keep several separate piles of changes per repo, each one auto-backed-up to Dropbox, and you switch between them. Your changes never become commits and never touch git history.
git sees as uncommitted, excluding gitignored). The same slot name on two branches is two separate piles.wisp <slot> is a true switch: it saves your current pile, then rewrites the working tree to equal the target slot.git checkout -b). Nothing in the tree changes.# from the repo where wisp lives
cd ~/Documents/GitHub/wisp
./install.sh # links `wisp` onto your PATH + loads the background daemon
wisp doctor # sanity-check Dropbox, the daemon, and your setup
Requires macOS, git, and Node 18+. The daemon survives logout and reboot. ./uninstall.sh removes it (your slots are kept).
| Command | What it does |
|---|---|
wisp | Same as wisp default — activates (or creates) the slot named default. Your quick "just start tracking this." |
wisp <slot> | Switch: save the active slot, then make the tree equal <slot>. Creates it from your current work if new. |
wisp switch | Toggle back to the slot you were on before this one (a cd - for slots), on the current branch. |
wisp status | Current branch, active slot, mirror path, dirty-file count, tracking state. |
wisp list (ls) | Every branch's slots, this branch first (active one marked). In a terminal, arrow keys + enter restore a slot, switching branch first if it's on another one. |
wisp sync | Force an immediate mirror of the active slot right now. |
wisp doctor | Diagnose Dropbox reachability, the daemon, state, and mirror mapping. |
A normal day:
cd ~/code/api
wisp redesign # name your current half-finished work "redesign"; now auto-tracked
# ...edit, edit — every change mirrors to Dropbox in the background...
wisp spike # saves "redesign", starts a fresh slot from your current work
wisp redesign # switch back; your tree becomes exactly what "redesign" held
wisp # with no slot in mind, just track under "default"
Easy to confuse, so they're deliberately distinct:
| Command | Working tree | Slots | Tracking |
|---|---|---|---|
wisp --clear | reverted to clean HEAD | all kept | stopped |
wisp detach | left exactly as-is (still dirty) | all kept | stopped |
wisp drop <slot> | untouched | that one slot deleted | unchanged |
--clear — when you want a clean tree but want to keep your saved working-sets.detach — when you want to stop the live backup but keep your messy tree as it is.drop — to discard one slot you no longer need.Every destructive command takes --dry-run. It prints the precise plan and writes nothing at all — not even the safety save. Use it freely before any switch or clear.
wisp redesign --dry-run # shows what it would restore/remove, changes nothing
Data loss is treated as the worst possible outcome. The guarantees:
sync will not overwrite it, and re-running the switch resumes safely.git restore / git reset only. It never runs git clean -x, never deletes gitignored files, never touches .git or history..env) that isn't saved in any slot. --force proceeds but moves those to a recoverable trash folder first.wisp never overwrites your working-tree files on its own. The background daemon is strictly one-way: it copies your tree into Dropbox and never the other way around. The only commands that rewrite your working tree are wisp <slot> and wisp --clear, and both save your current work into a slot first. So nothing in wisp can clobber uncommitted files behind your back — if you leave changes in the tree and do other things, wisp just keeps backing them up.
Each branch gets its own set of slots, so the same name on two branches holds two different piles and one branch's work never leaks into another's. When you change branches (e.g. in GitHub Desktop), wisp follows automatically: it notices HEAD moved, re-points to the new branch, and keeps backing up your work there — into that branch's own slot (a fresh branch starts a default slot). Nothing leaks across branches, because every branch's slots live in their own folder; the branch you left stays frozen exactly as it was. (While HEAD is detached there's no branch to track, so wisp refuses until you check one out.)
git stash empties the active slotgit stash moves your changes out of the working tree, so the active slot — which mirrors the current uncommitted set — syncs to "nothing uncommitted" and prunes those files from the slot. Your bytes are not destroyed: git's stash holds them, and Dropbox keeps the slot's version history, so git stash pop brings them back and wisp re-mirrors them. But to deliberately set work aside, prefer wisp <other-slot> — a safe save-and-restore that keeps everything visible in wisp — over git stash. Rule of thumb: drive a given pile of changes with either wisp slots or git stash, not both at once.
None of these will silently eat your work, but know them:
wisp writes your slot into the local Dropbox folder and flushes it to disk; Dropbox then uploads on its schedule. The daemon mirrors a change within a couple of seconds, and a safety sweep backstops that at 45 seconds at most. So the at-risk window is "edits in the last ~45s" plus "whatever Dropbox hasn't uploaded yet." For anything you truly cannot lose, a real git commit somewhere is still the strongest backup.
The active-slot pointer is local to each machine, so machines don't fight over it, and slot contents sync through Dropbox. But actively editing the same slot on two machines at once isn't supported — Dropbox may make conflicted copies. wisp warns you when you take over a slot another machine last wrote. Let Dropbox settle between machines.
By design (matching how your old backup script worked), wisp only tracks the uncommitted set git can see: modified, new (non-ignored), staged, and deleted files. Gitignored files — build output, .env, node_modules — are never mirrored. If a secret lives only in a gitignored file, wisp is not backing it up.
If you stage partial changes (git add -p), wisp restores that staged state on a switch. The one exception: if HEAD moved (you committed/pulled/rebased) between saving a slot and switching back to it, the staging may not replay — your file contents always come back correctly, but as unstaged changes, with a warning. Re-staging is quick.
wisp is v0.1. The known data-loss edge cases have been fixed and it has a full test suite, but treat it as new: keep genuinely irreplaceable work committed somewhere too, and lean on --dry-run when in doubt. If a switch ever refuses, that's the safety net working — read the message; it tells you the safe way forward.
~/Library/CloudStorage/Dropbox/wisp-projects/<repo>/<branch>/<slot>/ — real, browseable, Dropbox-versioned files, one folder per branch. A branch with a slash (feature/login) becomes a single readable folder (feature^login).~/.wisp/ — the per-branch active-slot pointer, daemon logs, and locks.…/<repo>/<branch>/.wisp/trash/ instead of hard-deleting.wisp doctor # checks Dropbox, daemon, state, mirror mapping
wisp status # what's active, dirty count, tracking on/off
wisp list # every slot for this repo
# daemon logs: ~/.wisp/daemon.log
A refused command is wisp protecting you — the message always names the safe next step (usually: name your work with wisp <name>, or wisp --clear).