Skip to content
nathankrammPublic

About

A pixel deer that teaches you Omarchy. The Office Assistant, done right.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Yoru — The Omarchy Assistant

It looks like you're using a tiling window manager. Would you like help with that?

A pixel deer lives in the corner of your screen and quietly teaches you Omarchy. He knows 194 things about it, notices which app you're in, follows your theme, and — unlike his spiritual ancestor — shuts up when you tell him to.

He has company. The Dane — named in honour of Omarchy's creator — is a second character who knows everything the deer knows and says it with the same straight face: yoru --sprite dane, or Super + Ctrl + Shift + Y to swap while he's running. See The Dane.

Yoru resting, grazing, speaking and bounding
Twenty-five seconds. Smaller and sharper as MP4.


Why this isn't Clippy

Clippy wasn't killed by the paperclip. Luke Swartz's 2003 Stanford thesis, Why People Hate the Paperclip — advised by Clifford Nass, whose lab's research Clippy was built on in the first place — interviewed real users and found they barely reacted to the character at all. It died of broken etiquette.

Yoru is built against that autopsy, point by point.

Clippy did this Yoru does this
Interrupted your work with a modal prompt Never blocks anything. He's a transparent overlay; clicks pass straight through him
Re-offered help you'd already declined, forever Left click a tip to retire it permanently
Kept no memory of you between sessions known.json and seen.json persist; he gets sharper over time
Watched you while you worked Faces away from your screen, and turns to you only when he has something to say
Was hard to turn off Middle click snoozes an hour. Ctrl+C kills him. No daemon, no service
Talked to an empty chair Detects idle and stops spending tips when you're gone
Was "patronizing" to experts Teaches keybindings — skills you keep — rather than doing things for you

That turn-away behavior isn't a flourish. Rickenberg and Reeves found that an on-screen agent which watches the user measurably raises anxiety and lowers task performance; Swartz's suggested fix is literally "turn away from the user when not called into service."

The one thing the research said to keep: humor. Agents that joked were rated less tedious and more likeable, with the strongest effect in the entire study (p < 0.00005). So he's dry, not silent.


What else is out there

Omarchy is not short of ways to learn its keys, and they're good ones. Mighty-Raindeer's cheat sheets, funcoder's app shortcuts, titteerbot's, fze-fze's shortcut sheet and acrogenesis' printable page all put the bindings in front of you. dbarke's muscle memory stages them — essentials first, power moves later — and ticks off what you've learned. Dan Wahlin's Learn Omarchy is a full course: sixteen lessons, ninety-three activities, and a guide who walks you to the part of the desktop being discussed. That this many people built one is the best evidence the problem is real.

Every one of them is pull. You have to know you want help, stop what you're doing, and press a key — and a sheet can only answer a question you already knew to ask, which is no use at all for the thing you'd use daily if you knew it existed. Yoru is push. He tells you things while you work, without being asked. That's the whole of the difference, and it's why he's a pet and not a panel.

Two smaller ones. Five of those seven install into the Omarchy shell; Yoru is a standalone app. And he withholds: every tip is checked against your live bindings and what pacman says is installed, and he stays quiet about anything that isn't true on your machine. Muscle memory does the live-binding half of that too, and it's the right instinct.


What he says

Six of the 194, as yoru --list prints them:

== windows
  Super + K                          Every keybinding at once. Alt + K for tmux, Ctrl + K for Herdr. Nobody memorizes all of them.
  Super + W                          Close the window. No confirmation dialog. There was never going to be one.
  Super + Backspace                  Toggle transparency. Looks incredible, reads terribly. Use sparingly.

== updates
  pacman -Syu                        Omarchy stops you. You'd skip the snapshot, the migrations and the configs, all at once.

== shell
  ff                                 fzf with a preview. Fuzzy find any file below you. Faster than remembering where it is.
  compress [file/dir]                A tar.gz without the flag archaeology. decompress unpacks it.

And now and then, between tips, nothing to do with keybindings:

Nothing here phones home. I checked. I'm the only one watching, and I'm facing the wall.


Scope

This is the base build. Yoru knows stock Omarchy 4 (Quattro) — the desktop, the CLI, the coding agents, the shell tools and functions, tmux, Herdr, Foot, Neovim/LazyVim, lazygit, lazydocker, btop, Nautilus, browsers, updates and snapshots. Everything he says was checked against the installed release under /usr/share/omarchy — its scripts, its Lua, its menu — not against the manual or a search engine. The manual's hotkeys table lists Super + Q to close a window; it isn't bound. See tools/ for how the tips are verified and how to redo it when a new version lands.

He knows a little of your Omarchy. He checks that a binding still exists before teaching it, reads your own bindings.lua so a key you rebound is taught in your words rather than the stock ones, and asks pacman what's installed, so there are no Ghostty tips on a Foot machine (see Your bindings). What he doesn't do yet is in the Roadmap.


What it reads and runs

Everything yoru.py touches on your machine, from the source. Paths are under your home unless shown otherwise.

Reads

Path When Why
~/.config/yoru/state.json, seen.json, known.json start his position, tips shown and passes made, tips seen this pass, tips you retired
~/.config/yoru/tips.txt start, also for --list/--ask your own tips and remarks
~/.config/hypr/bindings.lua start, also for --list/--ask your o.bind lines become tips; a key in it replaces the stock tip for that key. --no-own skips it
~/.local/state/omarchy/current/theme/colors.toml mtime every 4 s (30 s when you're away); parsed only if omarchy-theme-color is absent notice a theme switch; the palette, as a fallback. Not touched under --no-theme
~/.local/state/omarchy/current/theme.name mtime on the same beat; contents only with --debug notice a theme switch; name the theme in the log. Not touched under --no-theme

Writes — all of it inside ~/.config/yoru/, which he creates

Path When What
state.json after a drag, the intro, and each tip x, y, shown, passes, introduced
seen.json each tip ids of tips said this pass
known.json left click on a tip; --forget-known empties it ids of tips you retired
.<name>.*.tmp during each write written and renamed into place, so a crash can't truncate the file

Runs — every subprocess, with its arguments

Command When Why
hyprctl -j activewindow every 2 s; every 30 s when you're away or he's snoozed the focused window's class and title, to pick a tip for the app in front of you. Matched in memory against words like nvim, then dropped; the title is never written and never logged
hyprctl -j cursorpos same beat whether the pointer has moved: the presence signal
hyprctl binds start, then every 4 s (30 s when you're away) which bindings exist here, so a rebound key's tip is withheld. --verify-report prints the result
omarchy-theme-color --all, else omarchy theme color --all start, and whenever the theme files' mtime changes the palette, through Omarchy's own resolver. Never run under --no-theme
pacman -Qq once at start which packages are installed, so tips about absent software are withheld. --no-packages skips it

Each has a timeout of five seconds or less, and each fails open: if hyprctl or pacman can't be run, nothing is withheld and the palette stays built in.

Loads — libgtk4-layer-shell.so through ctypes, before GTK, which is how a layer-shell surface has to be set up. GTK itself connects to the Wayland display socket, as any window does.

Not done

  • No network. Nothing in the file imports a socket, an HTTP client or a URL; there is no update check and nothing is reported anywhere.
  • No sudo, no root, nothing written outside ~/.config/yoru.
  • No keyboard. The surface is created with keyboard mode NONE; the compositor never delivers a key to him. Hiding him is a Hyprland binding that sends SIGUSR1 from outside.
  • No clipboard, no screen capture, no reading of other windows' contents — only the focused window's class and title, above.
  • --debug writes to stderr only: decisions, tip ids, window classes, the theme's name. Not titles.

At install time, which is separate from him running: install.sh runs pacman -Q for the four dependencies, copies yoru.py to ~/.local/bin/yoru, appends one line to ~/.config/hypr/autostart.lua and two to ~/.config/hypr/bindings.lua, and backs both files up to <file>.bak-<timestamp> first. uninstall.sh reverses all of that and asks before removing ~/.config/yoru. The package puts the binary at /usr/bin/yoru and prints the Hyprland lines instead of writing them.


Install

git clone https://github.com/nathankramm/yoru.git
cd yoru
./install.sh

No sudo. It checks the four dependencies and prints the pacman line if any are missing, puts yoru.py at ~/.local/bin/yoru, adds one o.launch_on_start line to ~/.config/hypr/autostart.lua and two o.bind lines to ~/.config/hypr/bindings.lua — Super + Ctrl + Y to hide him, `Super

  • Ctrl + Shift + Yto swap him for The Dane (backing each file up first, and only the lines that aren't there already — running it twice is safe, and a bind you made by hand for either signal is left alone). Autostart takes effect at your next login; until then,yoru &. The bindings are live after hyprctl reload`.

Try the knobs before committing to them:

yoru --interval 20 --roam 20 --idle 0

To update, pull and run it again — it reinstalls only when yoru.py has changed, and leaves everything else alone:

git pull && ./install.sh

yoru --version says what you have; put it in a bug report.

./uninstall.sh reverses it — stops him, removes the binary, the autostart line and both bindings — and asks before touching ~/.config/yoru, which is yours: his position, what he has said, what you retired.

As a package

git clone https://github.com/nathankramm/yoru.git && cd yoru && makepkg -si

The PKGBUILD in the repo builds the tagged release from GitHub, checksum and all, and installs it through pacman. What that buys over install.sh: he shows up in pacman -Q, pacman -R yoru removes him, and an upgrade is git pull && makepkg -si with the old files replaced cleanly. A package can't write the Hyprland lines that start him at login and bind his keys — those are your own config — so pacman prints them at install time with /usr/bin/yoru in place of ~/.local/bin/yoru. They're in the by-hand block below.

By hand, if you'd rather see each step
sudo pacman -S --needed python-gobject gtk4 gtk4-layer-shell python-cairo
install -Dm755 yoru.py ~/.local/bin/yoru

Then one line in ~/.config/hypr/autostart.lua, with your own home directory spelled out (the launcher runs before your shell has expanded anything):

o.launch_on_start("/home/you/.local/bin/yoru")

And two in ~/.config/hypr/bindings.lua: Super + Ctrl + Y to hide and show him, Super + Ctrl + Shift + Y to swap the character. The pattern is the full path with a boundary after it, because SIGUSR1 and SIGUSR2 kill any process that has no handler for them — a looser pattern could reach an editor that happens to have the file open:

o.bind("SUPER + CTRL + Y", "Toggle Yoru", "pkill -USR1 -f 'python3 /home/you/.local/bin/yoru( |$)'")
o.bind("SUPER + CTRL + SHIFT + Y", "Swap Yoru", "pkill -USR2 -f 'python3 /home/you/.local/bin/yoru( |$)'")

Neither key is bound in Omarchy 4.0.4 (Super + Shift + Y is YouTube; Super + Ctrl + Y and Super + Ctrl + Shift + Y are free). Checked against the installed release, not the branch.


Using him

Action What happens
Right click A tip, now
Left click a tip "I know this." Retired permanently
Left click otherwise He says something
Middle click Snooze one hour. He lies down; again to wake him. On a trackpad, three fingers — a tap or a press, both work out of the box
Drag Move him. The spot is remembered across reboots
Super + Ctrl + Y Hide him entirely. Again to bring him back
Super + Ctrl + Shift + Y Swap the character: the deer for The Dane, and back. Same spot, same way round, same everything

The last two are Hyprland bindings install.sh puts in your bindings.lua. They send SIGUSR1 and SIGUSR2 and the process keeps running, so his position, snooze state and which tips he's seen all survive either one.

The swap is a transformation, not a cut. The deer is The Dane's spirit animal, and over about half a second one dissolves into the other, a scatter of pixels at a time — the deer's coarse, The Dane's fine, because they are drawn at different sizes, so you watch one become the other rather than one replace the other. He shuts the laptop before he changes and opens it after. The choice goes in state.json; he comes back as whoever he was. yoru --swap sends the same signal from a terminal.

From the terminal, no GUI involved:

yoru --ask screenshot     # search all 194 tips
yoru --list               # everything, grouped by topic
yoru --forget-known       # un-retire everything

Flags

Flag Default
--interval 900 average seconds between utterances, tips and remarks alike — see the cadence note below the table
--roam 180 average seconds between short walks
--idle 300 seconds before he assumes you've left (0 = always on)
--cooldown 90 minimum quiet before a contextual tip
--corner br br, bl, tr, tl — where he parks on first run
--margin 24 pixels from the side edge on first run. The bottom edge is the ground, so br/bl stand on it with no gap; tr/tl keep the gap below the bar, since there's nothing to stand on up there
--scale each character's own screen pixels per sprite pixel — the deer's is 4, The Dane's is 2
--topics e.g. nvim,tmux — limit him
--quiet contextual tips only
--no-context ignore the focused window
--no-theme keep the built-in palette; the theme files are never read
--no-own don't turn your own ~/.config/hypr/bindings.lua binds into tips
--no-basics skip the eighteen first-hour tips he otherwise leads with
--no-packages teach software whether or not pacman says it's installed
--start-hidden begin off screen
--still never walk, bound, graze or reach for the coffee — for anyone who finds movement at the edge of vision distracting; he still blinks and talks
--sprite saved, else yoru which character: yoru (the deer) or dane (The Dane). Remembered in state.json, so it survives a restart; given, it overwrites the memory
--swap swap the running instance's character (SIGUSR2) and exit — what Super + Ctrl + Shift + Y does
--version print the version and exit
--monitor focused connector to live on, e.g. DP-1; if it isn't connected he warns and takes the focused one
--layer overlay overlay stays above full-screen windows; top goes under them; bottom and background sit behind everything
--verify-report list the tips this machine's bindings rule out, and exit
--debug log every decision to stderr with a timestamp — attach it to a bug report

He starts fast and slows down. While any of the eighteen first-hour tips is unseen he speaks about every five minutes, so a new user has all eighteen inside the first sitting — an hour and a half or so. After that it's --interval, fifteen minutes by default: three tips and a remark an hour, and the 194 last a couple of working weeks rather than three days. Each time he has been through the whole corpus the gap doubles, to at most four times what you asked for — a second hearing is worth less than a first, and he should never fall silent. An explicit --interval under 300 wins from the start; so does --no-basics.

Not all of it is teaching. Remarks are about 15% of what he says to a new user and climb to roughly 40% once you've worked through the manual: he teaches first and gets more opinionated as the teaching runs out. tools/exhaust.py is the model these numbers came from.


The deer

He parks facing the nearer side edge — into the corner, his back to your screen — and turns toward the middle of it only when he has something to say. His walks go the other way, into the open, and he turns back when he parks: an animal moves facing the open and settles facing its cover. Drag him to the other side and both follow; parked near the middle he keeps whichever way he last faced rather than flipping on a nudge.

Left alone he drops his head and grazes, his ears twitch, and now and then his tail gives the casual side-to-side wag that is a deer's all-clear. He walks a four-beat walk — one foot in the air at a time, three on the ground, the body level — because at half a body length a second that is what a mammal does. About one walk in five he spooks himself and bounds instead, and only then does the tail go up: the flag means danger is here, and a deer standing calmly doesn't say that. None of it does anything. It's just him.

Snoozed, or once you've been away long enough to count as gone, he lies down: legs folded under, head pulled back. He blinks, his ears keep going the way a bedded deer's do, and he chews — short irregular bouts near a whitetail's real rate, longer stills between. Bedded a while, he dozes: the eye shuts for thirty seconds to a few minutes, the cud stops, then he's awake and chewing again, by turns for as long as he's down. The head never drops. Deer lie down far more often than they sleep head-down, and the ears keep going either way; they're never lowered.

Snoozed he stays down, because you told him to be quiet for an hour. Merely away, he still gets up for the odd walk and lies back down. He gets up when the hour is over or you come back, and before he says or does anything else. Both ways go through one held frame — head back, body partway down, legs bent — and that frame earns its keep: lying down is the only visible sign of either state, so without a moment of transition there is no way to tell a middle click landed except to middle click again, which undoes it. --still keeps the pose. It isn't movement.

He lives on one monitor. A layer surface belongs to a single output, and the compositor puts him on whichever one has keyboard focus when he starts; --monitor DP-1 picks one instead, and if that output isn't connected he says so on stderr, names what is, and takes the focused one — so the flag is safe in autostart.lua on a laptop that boots undocked. His saved spot is in that monitor's own pixels, so it doesn't carry between outputs of different sizes: a corner on a 1080p screen is mid-screen on a scaled laptop panel. If the monitor he's on is unplugged, the compositor moves him to another and he pulls himself back inside its edges, so he stays visible and draggable.

He is cheap to keep. A frame is drawn only when something in it changed — a blink, an ear, a step — so a parked deer redraws a few dozen times a minute, not thirty times a second. Hidden with Super + Ctrl + Y he does nothing at all: no stepping, no polling, until you bring him back, with everything as it was. When you're away, or he's snoozed, the compositor and theme polls slow from every few seconds to every thirty, and the first sign of you snaps them back — the window you came back to still gets its tip.


The Dane

A man in a plain tee with a letter O on the chest in the theme's accent, long wavy hair to the collar, a beard. He is played the way the deer is: the humour is in the lines and never in the movement. His scene is 56×48 pixels against the deer's 24×24, drawn half the size, so the two stand the same height on your screen.

The Dane working, taking his coffee, closing the laptop to speak, and putting his head down
Twenty-five seconds of his day, drawn by the code that draws him. Smaller and sharper as MP4.

He lives at a desk, facing you. A tabletop on two slim legs, him seated behind it with his legs showing beneath, a laptop open on the desk facing him — so what you see is the back of the lid — and a mug beside it. He works, and while he works the only thing that moves is his blink: his hands are behind the lid, where a keyboard is, and stillness at a desk reads as concentration. He doesn't roam: wandering out and back is grazing behaviour, right for an animal, and a man doing it reads as pacing. The deer still roams. Dragging moves the whole scene, and unlike the deer he never faces the other way — the desk is pointed at you.

Twice in a long while he stops. Every few minutes he takes his coffee: a hand out to the mug, the mug up off the desk, two more frames on the way, and a sip — then all four again in reverse, and the mug goes back on the desk even if you interrupt him halfway. Perhaps a quarter as often he strokes his beard for a second, a hand up to his jaw and back behind the laptop. That's everything he does. They're his version of the deer's graze, and they keep a still drawing from reading as a crashed one, but they come minutes apart rather than seconds: a man at his desk who stayed perfectly still would look broken, and one who fidgeted would look like he had nothing to do.

The turn-away is in his eyes. The research is about an agent that appears to watch you, and gaze is what signals watching: while he works his eyes are down on the laptop, through the coffee and the beard stroke too, so he isn't watching you. Speaking, he closes the laptop — the lid folds toward him a frame at a time, so it reads as a lid coming down rather than a cut — looks up, and says his piece. Closing it is the point: he stops what he is doing to address you. When the bubble clears he opens it again and his eyes go back down. Snoozed, or once you've been away, the same fold happens and then his head goes down on his folded arms. The laptop is always shut before he sleeps on it, and before he turns into a deer.

The Dane working, sipping, stroking his beard, speaking, settling and asleep, on Tokyo Night and Catppuccin Latte
Working, mid-sip, mid-thought, speaking, going down and asleep — at the size he ships at, on a dark theme and a light one.

Only the drawing is new. He knows the same 194 things, notices the same apps, keeps the same cadence and answers the same clicks. His skin, hair and beard are fixed natural colours; his shirt, trousers, shoes and the O are the theme's, through the same tones the deer's coat and antlers are made of, so the two of them recolour together. On nine of the twenty-two shipped themes the background sits close enough to his hair or his skin to swallow the edge, and that colour moves one shade to keep it.

Start as him with yoru --sprite dane; swap while running with Super + Ctrl + Shift + Y (or yoru --swap). The choice is remembered. The deer stays the default.


Context awareness

He reads the focused window from hyprctl every two seconds and matches on class and title, so tools running inside your terminal are recognized separately from the terminal itself:

Focused He talks about
foot, plain shell tools, tmux, CLI, agents, Herdr
foot running nvim LazyVim only
foot running lazygit git only
org.omarchy.agent the agent tooling only
Something he has no tips for nothing at all

At most two contextual tips per app per day, and never within 90 seconds of him last speaking.


Your bindings

The manual says what a binding means; only the running compositor knows whether it exists. At startup, and again every four seconds, he reads hyprctl binds and checks every tip whose keys are a Hyprland binding — windows, workspaces, panels, capture, the app launchers — against what is actually bound. A tip whose keys you've unbound, or that this install never had, is withheld, so he never teaches you a binding you've rebound away.

Only tips in Hyprland topics are checked. tmux, Neovim, Ghostty, lazygit and shell keys look the same but belong to their own programs and are left alone, as are your own tips. So are the Chromium extension bindings (Alt + Shift + L, Alt + Shift + D): they never appear in hyprctl binds, so they're filed as browser tips rather than compositor ones and aren't checked against it. If hyprctl is missing or its output can't be read, nothing is withheld.

Your own ~/.config/hypr/bindings.lua is read too. Every o.bind in it with a description becomes a tip in your words (topic yours, at most 25, never checked against the compositor — it came from the config). A key in that file is one you rebound, so if a curated tip has it as its headline, your tip replaces it: Super + S stops being "the scratchpad" and becomes whatever you called it. The existence check above can't catch that on its own: Super + S is still bound, just to something else. --no-own turns this off.

What isn't installed is withheld too. One pacman -Qq at startup drops the tips that are useless without a package that isn't there: no Ghostty tips on a Foot machine, no 1Password tip without 1Password. Twenty tips over thirteen packages; --no-packages turns it off.

yoru --verify-report prints exactly what he's holding back and why. --list and --ask still show everything — suppression only applies to what he volunteers.


Theming

Yoru follows your Omarchy theme. He reads the live palette through omarchy-theme-color --all — the same resolver the built-in themed templates use — and repaints within four seconds of a theme switch.

Because the coat is mapped to the theme's foreground and the halo to its background, light themes work with no special handling: the deer simply goes dark.

Yoru

Themes

Tested against all 22 shipped themes.


Your own tips

~/.config/yoru/tips.txt, one per line:

Super + Shift + V | Open the vault
omarchy-restart-xcompose | After editing ~/.XCompose

A line with no | becomes idle chatter.


Roadmap

The next versions are about making him yours rather than generic.

  • Weight by what you actually use. He already watches window focus; over weeks that's a real usage model, not uniform random.
  • Generate tips from your own configs — bindings.lua is done (see Your bindings); aliases in ~/.bashrc and your scratchpad scripts are not.
  • A position that survives docking. Corner plus offset, or fractions of the surface? Deciding needs a few weeks of actually docking, and either changes what state.json means for existing users.

A line this project won't cross. Personalization here means reading files you wrote and noticing which window has focus. It will never mean watching keystrokes to infer which bindings you don't know. That's a keylogger, and it's precisely the predatory quality that made Clippy hated.


Key Details

Official Name Yoru (夜 — "night", after the Tokyo Night palette he was drawn in)
Program/Feature Name The Omarchy Assistant
Software Omarchy 4 "Quattro" · Hyprland · Wayland
Nickname The deer
The other one The Dane, named in honour of Omarchy's creator
Predecessor Clippit, the Microsoft Office Assistant (1997–2007), may he rest

Credits

  • Omarchy by DHH and contributors — every tip is traced to its shipped files
  • Omarchy's creator, for the operating system that made Yoru worth building — The Dane is named in his honour
  • Luke Swartz, Why People Hate the Paperclip, Stanford, 2003
  • gtk4-layer-shell by William Wold
  • Clippit, designed by Kevan Atteberry, 1997

Built with Claude.

License

MIT. Take it and run.

About

A pixel deer that teaches you Omarchy. The Office Assistant, done right.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages