A "look busy" TUI for macOS and Linux. Plays a believable fake-coding session on your screen so you can step away without your laptop locking, your status bouncing to "away," or your screen filling with screensaver fish.
⚠️ This is a joke / personal-productivity toy. It does not edit, write to, or transmit any of your project files.
Recording produced by docs/demo.tape — see
docs/demo-recording.md for the tooling rationale
and how to re-record after UI changes.
- Keeps the screen awake — runs
caffeinate -dims(macOS) orsystemd-inhibit … cat(Linux) as a child process and cleans it up on exit (verified bypgrep-based unit tests). On platforms / sandboxes where neither is available, sleeper degrades gracefully to animated-only mode. - Fake vim — opens real files from a project you point it at, animates a cursor, fakes insert-mode keystrokes, switches files. Never persists edits.
- Fake shell — runs a tiny allowlist of real read-only commands
(
ls -la,git status,git log --oneline -20, etc.) interleaved with hard-coded fake output lines. - Fake AI chat — embedded debug/feature/refactor conversation templates, filled with real symbol names lifted from the target project.
- Instant handover — press
eto suspend the TUI and open the currently displayed file in$EDITOR. Walk back to your desk, type for a few seconds in a real editor, exit, the TUI resumes. - Panic key — double-tap
Escto immediately switch to a plaingit status-looking shell scene, then quietly quit.
go install github.com/daviddwlee84/sleeper/cmd/sleeper@latestOr from a clone:
go install ./cmd/sleeperThe binary lands in $GOPATH/bin (typically ~/go/bin). If which sleeper
returns nothing afterwards, that directory isn't on your PATH:
echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.zshrc && exec zshVia mise (optional)
If you already manage Go with mise and want sleeper to live alongside it
(no extra PATH tweak), use the go: backend — mise will install + shim it
into its own bin path:
mise use -g "go:github.com/daviddwlee84/sleeper/cmd/sleeper@latest"
sleeper --help # resolves via ~/.local/share/mise/shims/sleeperUpgrade later with mise upgrade sleeper. This requires the module to be
fetchable by go install (i.e. pushed to GitHub with a tag); for a
local-only checkout, fall back to go install ./cmd/sleeper from the repo.
Heads-up: if
which goreturns nothing on a mise host, mise's shell activation isn't installed. Addeval "$(mise activate zsh)"to your~/.zshrc, thenmise use -g go@latest. mise's own getting-started guide has the full per-shell setup.
go install works the same way. The keep-awake path uses systemd-inhibit,
which ships with systemd — no extra package needed on Ubuntu, Debian, Fedora,
Arch, RHEL, or any other systemd distro. Confirm with:
which systemd-inhibit # /usr/bin/systemd-inhibit on UbuntuIf systemd-inhibit is missing (non-systemd distros, minimal containers, WSL1)
sleeper logs no sleep inhibitor available on this platform; running as animated CLI only and the TUI continues without holding any lock.
⚠️ GNOME / Ubuntu desktop caveat.systemd-inhibitblocks logind's idle/sleep pipeline (sosystemctl suspendand lid-close are inhibited), but GNOME runs its own screen-lock idle timer viaorg.gnome.ScreenSaverindependently. Your screen may still blank/lock at the Settings → Privacy → Screen Lock delay. Workaround:gsettings set org.gnome.desktop.screensaver lock-enabled false gsettings set org.gnome.desktop.session idle-delay 0See
pitfalls/linux-systemd-inhibit-screensaver-gap.mdfor the full story.
sleeper --project ~/work/my-real-repoFlags:
| Flag | Default | Notes |
|---|---|---|
--project |
cwd |
Project to fake-edit. Must contain readable text files. |
--scene |
vim+shell |
Initial layout: vim, shell, vim+shell, vim+ai |
--ai-style |
mixed |
Filter AI templates: debug, feature, refactor, mixed |
--editor |
$EDITOR |
Override the editor used by the e handover key |
--seed |
0 |
Deterministic RNG seed (0 = real random). Useful for reproducing layouts. |
--tick |
150ms |
Base animation tick (panes add jitter on top) |
--no-caffeinate |
false |
Skip caffeinate (debug). |
--no-tui |
false |
Just hold caffeinate until Ctrl+C. |
| Key | Action |
|---|---|
Tab |
Cycle layout (vim+shell → vim+ai → vim → shell) |
n |
Force vim to switch to a new fake file |
Space |
Pause / resume animation |
e |
Open the current file in $EDITOR (handover) |
? |
Toggle help overlay |
q |
Quit |
Ctrl+C |
Hard quit (caffeinate still cleaned up) |
Esc Esc |
Panic — switch to shell scene + quit |
- Never writes to disk.
grep -r 'os.WriteFile\|os.Create' internal/ cmd/shows zero matches outsidet.TempDir()test fixtures. - No shell invocation. Real commands go through an exact-match
SafeCmdallowlist +exec.LookPath. The allowlist is checked again insideRun(). No template string is ever passed tosh -c. - Privacy filter on the project scanner. Skips
.env*,*.pem,*.key,id_rsa*,id_ed25519*,*.p12,*.pfx,credentials*,*.kdbx. Also rejects binary files (viahttp.DetectContentType) and anything > 200 KB. - Respects
.gitignore. When the project is a git repo, file discovery usesgit ls-files -co --exclude-standardso ignored paths never appear.
cmd/sleeper/ CLI entrypoint
internal/
caffeinate/ caffeinate child process manager (Setpgid + group kill)
scanner/ project file discovery, privacy filter, symbol grep
fakevim/ bubbletea pane: vim-like animation + chroma highlighting
fakeshell/ bubbletea pane: viewport + safe-cmd allowlist
fakeai/ bubbletea pane: chat-bubble template renderer
scene/ top-level model: layout, hotkeys, message routing
handover/ tea.ExecProcess wrapper for $EDITOR handover
go test ./...caffeinate_test.go (//go:build darwin) starts and stops caffeinate and
runs only on macOS. caffeinate_linux_test.go (//go:build linux) does the
equivalent for systemd-inhibit and skips itself if the binary is missing
(keeps non-systemd / minimal-container CI green).
Forward-looking work — long-term ideas, deferred items, things needing
evaluation — lives in TODO.md, prioritised P1 → P3 with effort
estimates (S/M/L/XL). Items with accompanying research, design notes, or paused
troubleshooting link to a corresponding backlog/<slug>.md doc.
Backward-looking knowledge — past traps and non-obvious debugging — lives in
pitfalls/, titled by symptom so future-you can grep the error
message and land on the root cause + workaround instead of re-debugging from
scratch.
