A little pixel dude who walks onto your screen and hands you a glass of water β and a delightful face for any notification you throw at him.
Because a notification is easy to ignore β a character strolling across your desktop is not.
I miss recurring self-care reminders when I'm heads-down at work. A plain macOS notification slides in, I glance past it, it's gone. A notification is easy to ignore. A character walking across your screen is not.
So Remi is a menu-bar app that sends a small pixel-art buddy walking in from the corner holding a glass of water. He waits, you tap Had it π§ (he cheers) or Snooze 15m (he sulks off), and your progress ticks up. That's v0.1 β make the nudge delightful instead of nagging, and you'll actually drink the water.
v0.2 grows him up: the same buddy is now a face for any notification β a meeting, a Slack DM that needs a reply, an email worth your attention β driven by a tiny local API that cron jobs and AI agents can call. Remi stays dumb and delightful; the smarts live outside. See Reminders vs Notifications for the mental model.
The water buddy
- π§ Water reminders on a timer, only inside your configured work hours.
- πΆ A buddy who walks in β 4-frame walk cycle into the corner, arrives holding a glass, breathes with an idle bob, walks back out. Soft two-note chime on entrance.
- β Snooze / Had-it β two buttons (or a 30s no-answer soft snooze).
- π Daily history, π₯ streaks (shown in the menu-bar title + a one-off "π₯ N-day streak!" bubble), and a ποΈ calendar view (tray β View progress, a month grid tinted by how close you got each day).
- βοΈ Live Settings submenu β interval / work-hours / goal / snooze, applied instantly.
The notification framework (new in v0.2)
- π Local notification API β a loopback HTTP server (
127.0.0.1, token-auth) any producer can call. Two kinds:infopeeks (buddy leans in from the edge, says one thing, retracts) andactionnotifications (message + buttons whose result is returned to the caller). See Notifications. - π Expressive poses β Remi reacts per state: π sad when you snooze water, π fists-up when you drink it, π hand-up when peeking in, π§ hands-folded for an action ask.
- π οΈ
remi-notifyCLI + client sample + a local test UI to drive the API by hand. - πͺΆ Stays out of your way β frameless, transparent, always-on-top corner window, hidden ~99% of the time, never steals focus, lives in the menu bar with no dock icon.
git clone https://github.com/Weekend-Labs/remi.git
cd remi
npm install # pulls Electron (dev dependency)
npm start # run the app β buddy appears on the reminder timer
npm run demo # same, but the buddy walks in ~2.5s after launch (no waiting)
npm test # pure-logic + API unit/e2e tests (node --test, 59 tests)
npm run package # build Remi.app into dist/ (macOS arm64, with the .icns icon)
# poke the notification API (with Remi running):
node examples/test-ui.js # a clickable test page at http://localhost:7788
node examples/notify-client.js # or drive it from the command lineOr just download the latest release
and drag Remi.app to /Applications (unsigned β first launch: right-click β Open, or
xattr -cr /Applications/Remi.app).
npm start puts Remi in your menu bar (no dock icon). Click the π§ 0/8 title for
the tray menu β Remind now, View progress, Pause, Settings, Test peek, Quit.
Remi runs a loopback-only HTTP server (127.0.0.1, bearer-token, never exposed to the
network). Any producer β a cron job, a shell script, or an AI agent β fires a notification
and (for actions) hears your reply back:
POST http://127.0.0.1:7777/notify Authorization: Bearer <token>
{ "type": "action", "message": "Reply to Sam's DM?",
"actions": [ { "label": "Draft reply", "result": "draft" }, { "label": "Snooze", "result": "snooze" } ] }
β { "id": "n_123" }
GET http://127.0.0.1:7777/notify/n_123 β { "reply": "draft" } # the producer hears back and actsinfo(peek) β buddy leans in, delivers, strolls off. Fire-and-forget. "Standup in 5."actionβ a message + buttons; your choice is returned to the producer, so an agent can then send the reply / archive the mail / open the meeting.
The token is generated on first run into config.json. Grab it, then drive the API from:
- AI agents β an MCP server (
claude mcp add β¦ remi β¦) or a Claude skill. - Scripts / CLI β
bin/remi-notifyor the client sample. - By hand β the clickable test UI at
localhost:7788.
Full contract: F001 spec Β· mental model: docs/CONCEPTS.md.
The one rule: Remi stays dumb β no Gmail/Slack SDKs in the app. Every "is this worth interrupting me?" decision is the producer's (the model's) job. Remi is just the megaphone.
The character is me, rendered as Stardew-style pixel art from a few selfies. You can replace it with anyone β a photo of you, your cat, a robot β with the same repeatable pipeline (AI image β magenta-keyed sprite β cutout), including per-mood poses. Copy-paste prompts and all in:
Shipped in v0.2 β the notification framework: loopback API, info peeks + action
replies, remi-notify CLI, client sample + test UI, expressive per-state poses, and
agent producers β an MCP server and a Claude skill so an AI agent can
fire notifications and read your reply.
Next β the framework's whole point is that a new reminder type is just another producer:
| What | Powered by | |
|---|---|---|
| Smart triage | An agent reads Slack & email, filters the noise, only interrupts you for what matters | Claude / Codex |
| Presence-aware delivery | Buddy at the desk; WhatsApp / push when you're away or locked | presence + bridge |
Reminder types on deck: π meetings Β· π¬ pending Slack DMs Β· π§ reply-worthy email Β· β° ad-hoc.
π‘ A design direction, not a promise of dates. Ideas and PRs welcome.
- docs/CONCEPTS.md β Reminders vs Notifications β the core mental model (start here).
- docs/DESIGN-YOUR-OWN-BUDDY.md β build your own character (AI prompts + the
tools/*.pypipeline). - docs/specs/ β feature specs (F001 notification framework, F002 Buddy V2).
- SPEC.md β the product story: goal, scope, stack decisions.
- BACKLOG.md β the code map and how features were scoped.
Producers (each lives with its code): mcp/ (MCP server) Β· skill/ (Claude skill) Β· examples/ (CLI client + test UI).
macOS on Apple Silicon (arm64) only, for now. The npm run package script builds a
darwin/arm64 bundle (Remi.app) with a macOS .icns icon. Nothing in the code is deeply
macOS-specific β it's Electron β but the transparent, click-through, always-on-top corner
overlay is only exercised on macOS, and the packaging target is hard-coded to Apple Silicon.
Other targets would need a tweak to the package script and a pass over the overlay window
flags. PRs welcome.
Remi is deliberately tiny: a timer, a loopback API, two JSON files, and some pixel sprites. No database, no framework beyond Electron, no animation library.
ββ main process (src/main.js) βββββββββββββββββββββββββββββββ
β setInterval every 30s β rollover + shouldRemind? β
β Loopback notification API (src/api.js, src/notify.js) β
β Tray menu (title + Settings submenu + calendar/pause) β
β Owns two BrowserWindows: β
β β’ overlay β transparent corner window (the buddy) β
β β’ calendar β framed month-grid window (View progress) β
ββββββββββββ¬ββββββββββββββββββββββββββββββββ¬βββββββββββββββββ
β IPC (via src/preload.js) β IPC
βΌ βΌ
ββ overlay renderer βββββββββββ ββ calendar renderer βββββββββ
β renderer/index.html + CSS β β renderer/calendar.html β
β renderer/overlay.js β β renderer/calendar.js β
β water: walk-inβreactβout β β month grid tinted by β
β peek / action / poses β β history[date] β
β sprites: walk.png + β ββββββββββββββββββββββββββββββ
β buddy-{hold,sad,peek, β
β action,cheer}.png β
βββββββββββββββββββββββββββββββ
β²
producers β POST /notify (cron Β· scripts Β· agents)
ββββββββββββ over 127.0.0.1 loopback + token
Pure, tested logic (no Electron, no I/O):
src/reminder.js rollover Β· shouldRemind Β· applyAction Β· streak Β· todayStr
src/calendar-grid.js monthGrid Β· dayLevel
src/notify.js request validation Β· queue + lifecycle Β· ttl
src/state.js load/save state.json + config.json Β· isValidConfig
test/*.test.js node --test (reminder + settings + calendar + notify + API e2e)
The split that matters: all the interesting logic is pure functions in reminder.js,
calendar-grid.js, notify.js, and state.js, tested with plain node --test and zero
Electron in the loop. main.js is just wiring β timer, tray, windows, API, IPC. The renderer
is HTML/CSS sprite animation. That's why the suite runs in milliseconds and stays green
regardless of the UI.
State and config are plain JSON under Electron's per-app userData directory
(app.getPath('userData')). On macOS:
~/Library/Application Support/Remi/state.json # date, glassesHad, goal, snooze, paused, history{}
~/Library/Application Support/Remi/config.json # intervalMinutes, workHours, snoozeMinutes, goal, apiToken, apiPort
Both are created with sane defaults on first run and migrate forward if a field is missing β
you can hand-edit them, but the Settings submenu covers everything. apiToken is the
bearer token for the notification API. Delete the files to reset.
MIT Β© 2026 Vijay Chhuttani. Free for any purpose β fork it, reskin it, ship it.
