Keep the SQL, curl commands, payloads and tokens you always end up retyping.
A macOS menu bar library for the snippets that end up in scratch files, old messages and browser tabs you never close.
⌥⌘V → type "slow query" → ↵ → ⌘V
That is the whole loop. The shortcut works from any app, a few letters narrow the library, return copies and gets out of the way — so the next keystroke is ⌘V in whatever you were already doing.
macOS 14 or later, Apple Silicon or Intel. Two ways in, depending on what you already have.
Have Xcode 16? Homebrew builds it from source in about fifteen seconds.
brew tap logcat-io/tap
brew install trove
cp -R "$(brew --prefix)/opt/trove/Trove.app" /Applications/
open /Applications/Trove.appNo Xcode? Take the built app instead — nothing else needed.
- Download
Trove-<version>.zipfrom Releases - Unzip it and move
Trove.appto/Applications - Run this once, because the app is signed but not notarised:
/usr/bin/xattr -dr com.apple.quarantine /Applications/Trove.app
open /Applications/Trove.appThen look for the mark in the menu bar. Install explains that third line, and what Homebrew is doing.
Three things worth knowing before you read any further:
- Snippets are Markdown files in a folder you choose. Point that folder at a git repository and a set becomes something a team shares.
- Blanks. Write
{{tenant}}in a query and copying asks for the value first, so one entry replaces the handful of near-duplicates you would otherwise keep. - Inspect JWT · JSON · Base64. Deciding whether something is worth keeping usually means looking at it first, and a token is not readable until it is decoded.
The four lines above are the whole of it. This section is the two questions they raise, and the two other ways in.
Why it builds instead of downloading. Trove is signed ad-hoc rather than notarised with a paid Apple Developer ID, and macOS quarantines anything downloaded — so a prebuilt copy is refused until you clear the attribute by hand. Nothing built on your own machine is quarantined, so building avoids the problem rather than working around it. It takes about fifteen seconds and needs Xcode 16 itself — Homebrew checks for it, and the command line tools alone have shipped an older Swift than the package requires. The prebuilt download above is the path that needs neither.
Why copy rather than symlink. A symlink in /Applications is not what macOS counts as an
installed app: Spotlight will not index it, Launchpad will not list it, and Finder shows it without
its icon. The copy is 2 MB and behaves like anything else in that folder. After brew upgrade trove, copy again.
No Gatekeeper prompts on this path, because locally built apps are never quarantined.
git clone https://github.com/logcat-io/trove.git
cd trove
./Scripts/install.shThat builds a release binary, installs Trove.app into /Applications, and launches it. It
needs a Swift 6 toolchain, which means Xcode 16 or command line tools of the same vintage:
xcode-select --install # if `swift --version` failsTo install somewhere else:
TROVE_DEST="$HOME/Applications" ./Scripts/install.sh- Download
Trove-<version>.zipfrom Releases. - Unzip it and move
Trove.appto/Applications. - Clear the quarantine attribute, then open it:
/usr/bin/xattr -dr com.apple.quarantine /Applications/Trove.app
open /Applications/Trove.appWhy step 3 is necessary. macOS tags every downloaded file with a com.apple.quarantine
attribute. Trove is ad-hoc signed but not notarized with an Apple Developer ID (that
requires a paid Apple Developer account), so Gatekeeper refuses to launch it while that attribute
is present. Clearing it is the same thing the "Open Anyway" button does. If you would rather not
take that on trust, install with Homebrew or build from the repository instead.
Note the absolute path /usr/bin/xattr. If you have Anaconda or a similar Python distribution on
your PATH, its xattr shadows the system one and does not support -r.
Prefer clicking to typing? Right-click the app → Open → Open works on macOS 14 and earlier. On macOS 15 and later that shortcut was removed, and you have to go to System Settings → Privacy & Security and press Open Anyway after the first blocked launch.
Verify the download if you like — shasum -a 256 Trove-<version>.zip should match the
checksum in the release notes.
Look for the stack icon in the menu bar. The panel opens on your three most recent snippets — clicking one opens the window on it, so you can read it before anything reaches the clipboard — with Save Clipboard…, Inspect JWT · JSON · Base64 and the full library underneath.
Copy something worth keeping, open the menu, press Save Clipboard…, give it a title and tags. From then on:
- ⌥⌘V from any app opens the search window
- type to filter across title, tags and contents
- ↑ ↓ to move, ↵ to copy and close, esc to close without copying
- Copy (or ⌘C) copies and leaves the window up, for when you are comparing a few
- closing returns you to the app you came from, so the next keystroke can be ⌘V
- the window opens in the middle of whichever display your pointer is on
- tag chips across the top narrow a large library down; the counter shows how many matched
- Edit changes the title, tags or body in place; Delete asks first
- if the file changed on disk while the editor was open, saving stops and asks which version wins
Snippets are shown the way an editor shows them: line numbers, syntax colouring and no wrapping. A long script scrolls inside the pane instead of stretching the window.
Most things worth keeping are not quite reusable as they stand — a query with a tenant id in it,
a curl command with a host. Write those parts as {{name}}:
SELECT * FROM orders WHERE tenant_id = '{{tenant}}' AND created_at > '{{since}}'Copying asks for the values first, shows what will be pasted, and fills every occurrence of each name. Return moves to the next blank and copies from the last one, so three blanks are three values and a return.
One entry with a blank replaces the handful of near-duplicates you would otherwise keep, so the library gets smaller as it gets more useful. A blank left empty keeps its braces rather than becoming nothing, because an empty string in the middle of a command is worse than a slot that is visibly unfilled.
The syntax is narrow on purpose: a name may only contain letters, digits, _, - and ., so
${HOME}, {} and func f() { g() } are left alone.
The language is picked when you save and can be changed later. It is guessed from the content as
a starting point. Every language in the picker is colour-coded except text, which means "no
language" and is left plain on purpose. A diff is coloured by line the way a diff tool does it.
Whatever you choose also goes into the Markdown fence, which is what your editor and your diffs
read.
A JSON or SQL snippet gets As saved / Expanded / One line above it. The pane and the Copy button follow that choice, while the file keeps whatever shape you saved — the transforms that already existed for the clipboard, now reachable from something you stored. JSON expands to indented and compacts to minified; SQL puts each clause on its own line, or puts the query back on one.
The SQL side reshapes, it does not parse. Breaking before a clause cannot change what a query
means, while aligning and indenting properly needs a parser — and a parser that is wrong rewrites
your query into a different one. Strings and comments are left exactly as they are, so a WHERE
inside a string stays data. A query holding a -- comment is not offered One line, because
joining the lines would hide everything after it.
Nothing is saved unless you press Save. There is no clipboard history — copying a token does not put it anywhere.
Snippets are plain Markdown, one file per snippet:
~/Documents/Trove/
slow-query-check.md
order-payload.md
---
title: Slow query check
tags: [sql, ops]
language: sql
created: 2026-08-09T03:51:00Z
---
```sql
SELECT id, status FROM orders WHERE created_at > now() - interval '1 day';
```So a shared set is just a git repository:
cd ~/Documents/Trove && git init && git remote add origin <your team repo>Point the folder somewhere else in Settings if you keep it elsewhere. Files edited by hand or pulled from a teammate are picked up on the next open — the reader tolerates a missing code fence, so hand-written entries work too.
Two things follow from the folder being shared:
- A snippet you are editing is re-read before it is written. If it changed in the meantime —
someone else's editor, a
git pull, a sync client — the save stops and you choose whether to keep yours or reload theirs. Nothing is replaced without you saying so. - If the folder cannot be reached, the window says so instead of showing an empty library. A path on a drive that is not mounted is never recreated locally, because the new folder would then sit on the mount point and hide the real snippets when the drive came back.
What you save is stored byte for byte. Leading indentation and a trailing newline are kept, so a script or a YAML block comes back out exactly as it went in.
Before something becomes a snippet you usually want to look at it, and a token is not readable until it is decoded.
Inspect JWT · JSON · Base64 opens a window you can paste into. Whatever goes in is decoded as you type — a JWT into its header, payload and expiry; JSON into indented form; Base64 into text. It starts with whatever is on the clipboard, because that is usually what you wanted to look at.
This is the part that replaces keeping a decoding site open in a tab. Copying first was never the hard bit; having somewhere to paste and room to read the result was. From there the decoded value can be copied, or saved as a snippet.
Nothing typed there is written anywhere, the same rule the clipboard follows.
Settings and Quit — the gear at the top right of the menu. Quit lives in there rather than on the surface, so it is not one stray click away.
Change the shortcut — gear → Settings… → Search shortcut → Record. A modifier is required, so a global hotkey can never swallow a plain key. If another app already owns your chord, the setting says so.
Start at login — gear → Settings… → Open at login. A menu bar tool is only there while it is running, so this is what makes it survive a restart. macOS may ask you to confirm it in System Settings the first time; the setting says so and links there when that happens.
Update — brew upgrade trove, or git pull && ./Scripts/install.sh if you built it yourself.
Uninstall — quit the app and rm -rf /Applications/Trove.app. Your snippet folder is
yours and is left alone; delete it separately if you want it gone.
- no clipboard history — what you copy is held in memory and replaced by the next copy
- nothing is written to disk unless you press Save, and then only into your snippet folder
- no cloud, no network transmission, no telemetry, no analytics
- clipboard contents are never written to stdout, logs or crash metadata
UserDefaultsholds two things: the snippet folder path and the shortcut chord. Never content.
The app makes zero network requests. otool -L on the binary shows only Apple system frameworks.
Snippets are plain text. That is the point — they are meant to be diffable and shareable. So before saving, Trove scans for things that look like credentials and makes you confirm: JWTs (including one sitting inside a longer command, which is how they are usually copied), private key blocks, AWS keys, GitHub and Slack tokens, OpenAI and Stripe keys, Google API keys, bearer tokens, passwords in connection strings, assigned secrets. The warning names the kind of secret only; the matched text is never displayed, logged or carried anywhere.
The check runs on every write — new snippets and edits to existing ones alike — and it is enforced below the interface, so no screen can skip it.
That check is a seatbelt, not a guarantee. If you are pointing the folder at a shared repository, review it like any other repository.
swift build # debug build
swift test # 262 unit tests
swift run # run without installing
./Scripts/bundle.sh # build/Trove.app
./Scripts/install.sh # bundle + install into /Applications
./Scripts/release.sh 0.9.0 # dist/Trove-0.9.0.zip + checksum
swift Scripts/make-icon.swift # regenerate Resources/Trove.icns
./Scripts/make-screenshots.sh # regenerate docs/images from the real viewsCI builds and tests every push. Pushing a v* tag builds the app, attaches the zip to a GitHub
release and fills in the install notes:
git tag v0.9.0 && git push origin v0.9.0 # CI builds and publishes the release
./Scripts/update-tap.sh 0.9.0 # then point the tap at itThe second line matters: brew install trove reads the tap, so until it moves everyone still gets
the previous version. The script refuses to run before the tag's tarball exists.
- Clipboard is polled every 500 ms via
NSPasteboard.changeCount; unchanged values are never re-parsed. - Plain text only. Non-text clipboard values are ignored.
- Content larger than 1 MB is never classified, transformed or offered for saving.
- Previews are capped at ~3 KB. The value written to the clipboard is never truncated.
- A transformation that fails leaves the clipboard exactly as it was — the clipboard is not even cleared until a replacement string exists.
- The global shortcut uses Carbon's
RegisterEventHotKey, which does not require the Accessibility permission.
Detection order is JWT → JSON → Base64 → unknown, so structured content is not swallowed by a looser detector. Unknown content is still savable as a snippet — a SQL query is not a recognised type but is the main thing worth keeping.
JSON. Only objects and arrays. Scalars (123, true, "hello") are rejected. Validation uses
JSONSerialization; formatting rewrites whitespace in the original text instead of re-serialising,
because JSONSerialization re-orders object keys, rewrites number literals (0.1 →
0.10000000000000001) and escapes forward slashes. Keys, number literals and string bytes survive
verbatim.
JWT. Exactly three Base64URL segments, where header and payload both decode to UTF-8 JSON
objects. An empty signature segment (header.payload.) is rejected — unsigned alg: none tokens
are rare in day-to-day work and accepting them would only widen false positives. Signatures are
never verified; the UI says so.
An exp outside 1970–2100 is treated as absent and no expiry is shown. The payload still is —
the value is simply not a date anyone meant, and the clipboard is not a trusted source.
Base64. Deliberately conservative, since false positives are worse than false negatives. A
candidate must be ≥ 16 characters, contain no internal whitespace, use the standard Base64 alphabet
(padding optional), mix at least two character classes (this is what rejects ordinary words such as
responsibilities), decode successfully, decode to valid UTF-8, and contain no control characters
(this is what rejects AAAAAAAAAAAAAAAA, which decodes to NUL bytes that are valid UTF-8).
Sources/TroveCore/ detection, transformation, clipboard, snippet store (unit tested)
Sources/Trove/ menu bar app, snippet search window, settings, global hotkey
Tests/TroveCoreTests/
Resources/Trove.icns app icon, generated by Scripts/make-icon.swift
Scripts/ bundle.sh · install.sh · release.sh · make-icon.swift
MIT. See LICENSE.



