omo-notify and omo-remote played Apple's Glass and Ping through afplay, which exist only on macOS and are not ours to ship. They now play omo-done (task finished), omo-input (needs your input) and omo-error (stopped with an error), rendered by sfx from .config/omo/sounds/recipes.toml at the same loudness as Glass. The new omo-sound plays a sound by name or any audio file, with afplay on macOS and pw-play, paplay, aplay or ffplay on Linux, and finds the sounds through the dotfiles checkout it is linked from.
sfx is a single-file uv script that synthesises sounds from sine partials and seeded noise, so its output contains no samples, SoundFonts or model output, renders without a network once uv has installed numpy and mido, and comes out byte-identical on every run. Each sound in a recipe is normalised to a target loudness (ITU-R BS.1770, default -26 LUFS, the level of the macOS Glass alert) and written as WAV, with optional MP3 through ffmpeg and a MIDI file of the same notes. Unknown keys and bad note names stop the render with an error naming the table.
The new sound-design skill documents the recipe format and how to design notification sounds; link-ai-configs.sh links it for omo and Claude Code.
skills-update re-vendors impeccable (newest skill-v* release) and
web-design-guidelines the way the skills README describes, then commits
only .config/omo/skills and pushes when main was level with origin/main.
It also syncs the machine-local vercel-react-best-practices, docx, pdf,
pptx, xlsx and skill-creator copies from vercel-labs/agent-skills and
anthropics/skills. A failed update keeps the previous copy and exits
non-zero. skills-update --install-timer writes and enables the daily
systemd user timer.
It is Linux only, so setup-mac.sh does not link it.
impeccable-browser stands in for Chrome via IMPECCABLE_BROWSER. Public
URLs render on Browserless with the key from the Reoclo project
flowstate-tools; localhost and private origins reach it through a
cloudflared quick tunnel that lives only for the scan, with the CDP
traffic rewritten so reports name the local URL. Anything else, or a
failed tunnel or Browserless connection, falls back to local headless
Chrome with --no-sandbox.
It is Linux only, so setup-mac.sh does not link it.
Running fs in a plain Ghostty tab left the Mac silent: no local tmux reads the title, and the server tagged its pushes attached, so the ntfy agent skipped them. The agent now skips an attached push only when a tmux pane here recorded that exact event through its title, waiting up to three seconds for it; otherwise it plays the sound and posts the banner. The banner comes from the plain tab whose mosh-client uses the port the server tags (mosh-<port>), so clicking it lands on the right tab, and sessions whose tab closed while their mosh-server lingers still notify.
The server's title now reads marker first (🟢 studio · omo:done:<time>), so a plain tab shows the state at a glance; omo-remote parses that format.
mosh drops the OSC 777 banner that omo-notify posts, so omo sessions on the server only reached the phone through ntfy. The server's omo-notify now writes the session state into its tmux client's terminal title (omo:<state>:<time>:<project>), which mosh and ssh carry. A pane-title-changed hook hands it to the new omo-remote, which runs omo-notify for the local mosh pane through the new OMO_NOTIFY_PANE override, so remote sessions get the same sound, tab marker and banner as local ones.
Sessions that no terminal is showing come through ntfy instead: omo-remote install starts a launchd agent running ntfy subscribe, which plays the sound and posts the banner for omo pushes the server did not tag attached. Brewfile gains ntfy; the token lives in ~/.config/ntfy/omo-remote.env, outside the repository.
owt title names the tmux windows of the current workspace with one to three words and saves the title in the worktree's git dir so owt open reuses it. owt hook runs on omo's UserPromptSubmit and reminds the agent of the current title so it retitles the tab as the work changes.
tmux-open-path takes the path under the mouse, strips :line:col, #L, @ and file:// decorations, and resolves it against the pane directory and then its git root. Markdown opens in Markdown Preview (added to Brewfile.apps), images, PDFs and directories in yazi, and everything else in nvim at the clicked line, both in a split beside the pane.
A tab renamed in Ghostty ignores the title tmux sends, so it stops
showing its session name. omo-status focus now clears that override
through Ghostty's set_tab_title action, finding the tab by its terminal
title, so every tab shows its tmux session again.
omo names the event in "event" for UserPromptSubmit, without the
"hook_event_name" field its Stop and Notification payloads carry. The
script only read hook_event_name, so it ignored every prompt and the
blue working marker never showed. Read either field.
omo-notify now drives three signals from omo's UserPromptSubmit, Stop
and Notification hooks:
- A sound through afplay, so Focus modes cannot mute it
- A Ghostty notification (OSC 777) written to the session's tmux client
terminal, so clicking it opens the tab it came from. This replaces the
osascript banner, which opened Script Editor's file picker on click
- omo-status markers in front of the tab title: red error, amber
waiting for input, green finished, blue working. A tab shows its most
urgent agent; focusing it clears red, amber and green and selects the
pane that sent the banner
tmux puts @omo_status in the title and runs omo-status on client focus.
- owt all lists the workspaces of every repo with their branch and
source checkout, from any directory
- owt cd jumps into a workspace by name, repo/name or an fzf picker
that starts filtered to the current repo
- An owt function in .zshrc performs the cd, since a script cannot
change its parent shell's directory
Headless servers have no DISPLAY or WAYLAND_DISPLAY, so xclip and xsel
cannot reach a clipboard even when installed. These shims use OSC 52 to
hand text to the terminal emulator on the other end of the SSH connection,
which puts it on the local machine's clipboard.
Inside tmux, pbcopy uses `tmux load-buffer -w -` so the text lands in the
tmux paste buffer and is forwarded outward (requires `set-clipboard on`).
Outside tmux it writes the escape sequence to /dev/tty directly. Both fall
back to xclip/wl-copy when a real display is present.
pbpaste reads the tmux buffer only; terminals refuse OSC 52 reads, since
that would let any remote program siphon the user's clipboard.
omo keys project memory by folder path, so each workspace started with an
empty store. owt new and owt open now link the workspace's memory id to the
main checkout's store, and owt done and owt rm remove the link.
owt new creates a randomly named workspace per task under ~/worktrees/<repo>,
copies .env files, installs dependencies and opens omo in a tmux window with
the workspace's Linear conventions. owt done checks merge state (git or a
merged Gitea PR via tea) before removing the worktree, branches and window.
The failure path advised --deep and a disk-space check regardless of cause.
On the first live run that was wrong on both counts: 101 GiB were free and
--deep was irrelevant. Docker Desktop had crashed on startup, and the actual
fix was launching it from the Dock.
Branch the advice on whether any Docker process survived the relaunch. No
survivors means the app did not stay up, which no programmatic relaunch can
work around; survivors mean it is up but not answering yet.