![]()
25 Oct 2026
Snippee is my text expander for macOS. I type ;sig and my signature appears before my next keystroke. It replaced Espanso, which I had used since 2022, and it took over the search-only snippets I kept in Alfred.
It's a native menu bar app of about 1 MB. It runs on 28 to 36 MB of memory and close to 0% CPU when idle. Espanso used 352 MB in 3 processes for the same job.
Every example and screenshot in this note uses dummy data ("Alex Example", "Sampletown"), not my real snippets.

Why I built it
Espanso works, and it's open source. 3 things pushed me off it:
- Weight. 352 MB of memory for a tool that watches keystrokes and swaps text.
- YAML. Editing snippets in nested YAML is slow, and a wrong indent breaks the whole file.
- Silent conflicts. 14 of my 222 triggers never fired, because a shorter trigger always matched first (
;debefore;deu). Nothing told me.
I also had 2 places for snippets: Espanso for triggers, Alfred for the ones I search. I wanted 1.
Snippee vs Espanso
30 Oct 2026 added: how the 2 compare after 6 days of daily use.
| Snippee | Espanso |
|---|---|
| macOS only | macOS, Windows and Linux |
| My own app, not public yet | Free and open source (GPL-3.0) |
| Swift and AppKit, about 1 MB | Rust |
| 28 to 36 MB of memory | 352 MB in 3 processes |
| Markdown, 1 file per group | YAML match files |
| Fires on the trigger's last character | Same, plus word triggers and regex triggers |
| Panel: search, insert, copy, edit in place | Search bar: search and insert |
| Flags every trigger that a shorter one shadows | Says nothing: the shorter trigger wins |
{{date}} and {{cursor}} |
Date, clipboard, shell, scripts, forms, choices |
| Usage log: which snippet, when, in which app | No usage log |
CLI with --json for agents |
CLI to manage the service and packages |
What Espanso still does better:
- It runs everywhere. If I needed my snippets on Windows or Linux, Espanso would win outright.
- Dynamic snippets. Forms, shell commands and scripts. I had 0 of them in daily use, so Snippee leaves them out for now.
- Packages. The Espanso Hub has ready-made packs for accents, emoji and more. I imported the 4 I used as plain Markdown files.
- ⌫ undoes an expansion. I dropped that on purpose: I never used it.
What I gained: a tenth of the memory, snippets I can read and edit like any note, 1 place instead of 2, and a warning when a trigger can never fire.
Alfred's snippets (part of the paid Powerpack) did the search side. Their keywords became aliases in Snippee, so the same words still find them.
What it does
I type a trigger and Snippee swaps it for the snippet's text. For the snippets I don't remember, ⌃⌥⌘J opens a panel where I search, insert, copy and edit them. The full list is in the next section.
Search for "sig" and only the signature is left. The panel follows light and dark mode:

⌘K lists every command with its shortcut, so I never have to remember them:

All features
02 Nov 2026 Everything Snippee does today, in version 1.0.2. This list replaces the shorter feature table of the first version of this note.
Expansion
- Triggers. Fires on the trigger's last character, case-sensitive, no space needed.
- Short text is typed. Up to 200 characters on 1 line goes in as key events.
- Long text is pasted. Multi-line or long text goes through the clipboard, marked transient so clipboard managers skip it, and the old clipboard comes back 400 ms later.
- Word works. Every snippet pastes in Microsoft Word, which ignores typed key events.
- Placeholders.
{{date}},{{date:%y%m%d}}(anystrftimeformat) and{{cursor}}. - Only physical keys count. Text that a tool types never fires a trigger.
- Pause. From the menu bar or
snippee pause 30m, before a screen share.
The panel
- Search panel.
⌃⌥⌘J, or a typed trigger, finds a snippet by name, trigger, alias or text. - Insert or copy.
↩inserts,⌘Dcopies. - Edit in place.
⌘Eedits,⌘Ssaves,⌘Nadds,⌘⌫deletes with⌘Zto undo. - Groups.
Taband⇧Tabmove between groups. - Command list.
⌘Kshows every command with its shortcut. - Zoom, light and dark. The panel follows the system or a toggle.
Snippets and checks
- Markdown files. 1 file per group, reloaded within a second of a save.
- Search-only snippets and aliases. No trigger, found by name or alias.
- Disabled triggers.
disabled: <reason>keeps a snippet without firing it. - Versions. A backup before every save from the panel, the last 200 kept.
- Conflict check. A trigger that a shorter one shadows shows struck through, and
snippee lintlists it. - Usage log. Which snippet, when, in which app. Never the text I type.
Around it
- Espanso hand-over. Snippee waits while Espanso runs, and 1 menu item hands over.
- Import. A script converted my Espanso YAML and Alfred snippets to Markdown.
- Agent CLI.
snippeewith--json: status, list, search, show, add, lint, stats, pause, resume, reload, insert.
Snippets are Markdown files
1 file per group, 1 heading per snippet, the text in a fenced block. Any editor works, and Snippee reloads a file within a second of the save. The folder sits in Dropbox, so the files have a history and a backup for free.
# Text
## Signature
trigger: ;sig
alias: regards, sign-off
```
Best regards,
Alex Example
```
## Proofread prompt
alias: proof, grammar
```
Proofread this text. Keep my voice. List every change you make.
```
- No
trigger:line makes a snippet search-only. That's where my old Alfred snippets went, with their keywords as aliases. disabled: <reason>keeps a trigger in the file without firing it.- Before every save from the panel, Snippee copies the file to a versions folder and keeps the last 200 copies.
How to add a snippet
30 Oct 2026 added: 4 ways, and each one is live within a second.
1. In the panel
- Open the panel with
⌃⌥⌘J, or type my search trigger in any app. - Press
⌘N. - Fill in the name, the trigger, the group and the text. Leave the trigger empty for a search-only snippet.
- Press
⌘S.
The panel refuses to save a snippet without a name, and it backs up the group file before it writes.
2. In a group file
Open the group's .md file in the snippet folder with any editor. Add a ## heading, the optional trigger: and alias: lines, and the text in a fenced block, as in the example in the previous section. Snippee reloads the file when I save it.
This is the fastest way to add 10 snippets at once.
3. From the terminal
snippee add --trigger ";addr" --name "Office address" --group "Text" \
--text "1 Example Street, 12345 Sampletown"
Without --text, it reads the text from stdin, so a long snippet can come from a file:
snippee add --name "Proofread prompt" --group "Prompts" < proofread.txt
It refuses a trigger that already exists.
4. Ask Claude
I tell Claude: "Add a snippet ;addr with the office address of Example GmbH." Claude runs snippee add --json and reads back the result.
After adding
Run snippee lint. It lists every trigger that a shorter one blocks, so ;deu never hides behind ;de again. The panel shows the same triggers struck through.
How it works
Native Swift and AppKit, no Electron, no runtime. 2 parts:
- SnippeeCore, a Swift package with no AppKit: the file parser, the matcher, the placeholders, the usage log. Unit tested.
- The app: the key listener, the injector, the panel, the menu bar.
The rules that keep it fast and safe:
- The key listener only listens. It's a listen-only event tap on its own thread. If Snippee stalls, my keyboard doesn't: an active tap that stalls freezes every key on the Mac.
- Matching is cheap. The last 32 typed characters sit in a ring buffer, and a reversed trie finds the longest trigger that ends there. Work per key: p99 under 1 µs.
- Only physical keys count. Text that another tool types, or that Snippee types itself, never fires a trigger.
- Keystrokes never touch the disk. The ring buffer in memory is the only place they exist. The log holds events, never text.
- The clipboard comes back. A paste marks its clipboard entry as transient, so clipboard managers skip it, and the old clipboard returns 400 ms later.
Agent interface
A CLI wraps the app, with --json on every command, so my NicAI agents can use it:
snippee status --json # running, expanding, paused, permissions
snippee search address --json # triggers and names, never the text
snippee add --trigger ";x" --name "X" --group "Text" --text "..."
snippee lint # trigger conflicts and file problems
snippee stats # which snippets I actually use
Testing in real apps
QA runs 134 end-to-end checks against a hidden test window. That can't prove anything about real apps, so that night, while I was away from the keyboard, a script opened a test document in Chrome, Ghostty, TextMate and Word. It inserted 9 test snippets (accents, emoji, cursor marks, a 265-character line, multi-line text) and read the result back from each app. It stopped at once if it saw any keyboard or mouse activity it hadn't caused.
| App | Typed | Pasted | Clipboard back |
|---|---|---|---|
| Chrome | ✅ | ✅ | ✅ |
| Ghostty | ✅ | ✅ | ✅ |
| TextMate | ✅ | ✅ | ✅ |
| Word | ❌ then ✅ | ✅ | ✅ |
Word was the surprise. It ignores every synthetic key event that carries a character string, which is how most text expanders type. It also drops key events from a private event source. The fix: post from the same source as the physical keyboard, and paste every snippet in Word. The clipboard still comes back.
Moving off Espanso
An import script converted the Espanso YAML and the Alfred database into 14 Markdown group files: 257 snippets. It flagged the dead triggers on the way, so I renamed them instead of carrying them over.
The switch-over can't expand anything twice. Snippee waits as long as an Espanso process runs, and 1 menu item stops Espanso's service and hands over. To go back, I start Espanso again and Snippee steps aside by itself.
How I built it
Claude Code built it in 1 day, in 3 steps:
- Usage review. It read my Espanso config and Alfred snippets and wrote 34 questions, each with a recommendation. I accepted them all.
- Spec and build. A spec, then the app, the CLI, the import and the QA suite: about 4,500 lines of Swift and 1,500 of Python.
- Independent review. A separate agent reviewed the code and found 13 defects, 3 of them serious (a crash after a delete, expansion inside the panel itself, a synthetic
⌘Vthat my Hammerspoon setup would have typed out). All fixed, each with a QA check.



Further reading
- Text expanders: the background on text expanders
- Espanso: my Espanso setup, 2022 to 2026
- Stickee: sticky notes that go full screen on every monitor: another native app in the same
-eefamily