Skip to content
gapmissPublic

About

A tiny always-on-top pixel companion for Obsidian users on macOS. Hop between vaults, deliver notes, capture thoughts. No network, no telemetry.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Chestnut

Chestnut, a pixel-art treasure chest creature

A native macOS desktop companion for Obsidian users. gapmiss.github.io/chestnut

Chestnut is a pixel-art treasure chest creature that sits on top of your other windows. It reacts while you write, and it gives you one place to open, capture into and move notes between all your vaults. It watches the filesystem directly, so there is no Obsidian plugin to install and none of Obsidian's settings are changed.

It is native Swift and SpriteKit: about 1.2 MB to download, 2 MB installed, and 3% of one CPU core when idle. There is no Electron, no bundled browser and no network access.

Important

Project status: Chestnut is pre-1.0 and changing quickly. Expect frequent releases.

Features

  • Vault Hopper (⌃⌥V) lists every vault Obsidian knows about. ⏎ opens the selected vault, ⌘⏎ opens today's daily note in it, and ⌥⏎ reveals it in Finder.
  • Pin a vault with the pin icon or ⌘P. The pinned vault sorts first in every list and is pre-selected for captures and deliveries.
  • Note Courier moves or copies files between vaults. Drag a file onto Chestnut and pick the destination vault. A note lands at the vault root, and the attachments it embeds (![[…]], ![](…)) travel with it, with the links rewritten to match the new vault. Other files go to the destination's attachment folder. Nothing is ever overwritten: a name conflict gets an Obsidian-style suffix instead. Every delivery can be undone from the right-click menu. Hold ⌥ while dropping to switch between move and copy.
  • Quick Capture (⌃⌥Space) is a floating panel for writing markdown into any vault's daily note or inbox. It has a formatting toolbar, ⌘B/⌘I/⌘K shortcuts, and ⌘1 to ⌘9 to pick the vault. A draft stays in the panel until you submit it.
  • Plugins are shell scripts that turn a dropped file or the clipboard (⌃⌥C) into a note, a capture, a clipboard result or a notice. See the User Guide for the reference, PLUGINS.md for the author's guide, and Examples/plugins/ for plugins you can copy.
  • A speech bubble tells you where a capture or delivery went. Click it, or press ⌃⌥O, to open the note in Obsidian. This uses the obsidian CLI when it is installed, and otherwise opens the vault.
  • Animations for idle, peeking, writing, chomping, carrying, delivering and sleeping, drawn as hand-coded pixel art with swappable color themes.

A plugin never takes a dropped file away from the courier. When a plugin and the courier could both handle a drop, Chestnut asks, with the plugin selected and "Deliver to a vault" one arrow key away.

Drag folders from Finder, not from Obsidian. Obsidian's file explorer hands over a folder as its name only, without a path, so Chestnut cannot find it and tells you so. Notes and attachments dragged from Obsidian work, including several at once.

Install

Homebrew

brew trust --cask gapmiss/tap/chestnut   # required for third-party taps
brew install --cask gapmiss/tap/chestnut

brew trust arrived in Homebrew 6.0. On older versions it fails as an unknown command, so run brew install on its own.

Manual

Download Chestnut.dmg from the latest release, open it, and drag Chestnut.app into Applications.

First launch

Chestnut is ad-hoc signed and not notarized, so macOS may block the first launch. To allow it, use one of these:

  • macOS 15 and later: open the app once, then go to System Settings → Privacy & Security and click Open Anyway.
  • macOS 14: right-click the app, choose Open, then click Open in the dialog.
  • Terminal: remove the quarantine flag:
xattr -dr com.apple.quarantine /Applications/Chestnut.app

A later brew upgrade does not ask again at the time. The first restart after an upgrade may ask once, with a dialog titled "Chestnut.app Not Opened". Click Done, then use Open Anyway again or run the xattr command above. Do not click Move to Trash, which deletes the app. Restarting again without an upgrade in between does not ask.

To start Chestnut automatically, right-click it and turn on Settings → Launch at Login.

Uninstall

If you turned on Settings → Launch at Login, turn it off before you quit. Turning it off removes the login item, and deleting the app does not. Then quit Chestnut (right-click → Quit Chestnut, ⌃⌥M → Quit Chestnut, or pkill -x Chestnut) and remove the app:

brew uninstall --cask chestnut      # installed with Homebrew
rm -rf /Applications/Chestnut.app   # installed manually

Chestnut writes to four places outside its own bundle, and nowhere else:

Path Contents
~/Library/Application Support/Chestnut/ config.json, state.json, the undo journals (journal.jsonl, captures.jsonl, plugins.jsonl), and any backups beside them: .bak, .bak.1 and so on from a settings file that failed to parse, or a .pre-0.3 left by an old build
~/.config/chestnut/plugins/ Installed plugins. Created at every launch, so it exists even if you never installed one
~/Library/Logs/Chestnut/ chestnut.log and chestnut.log.1, written only while debug is on in config.json
$TMPDIR/chestnut-plugins/ Temporary copies of pasted images on their way to a plugin. Cleared at every launch, and macOS clears it too
rm -rf ~/Library/Application\ Support/Chestnut \
       ~/.config/chestnut \
       ~/Library/Logs/Chestnut

None of those paths contains a vault or a note, so removing them leaves your writing where it is.

The undo journals contain your note text, whether or not you uninstall. captures.jsonl keeps the exact text of each capture. journal.jsonl keeps a note's text from before a delivery whenever the delivery rewrote its links, because that text is what Undo puts back. Each journal keeps at most 20 records or 1 MB, so older text is dropped as you keep working. plugins.jsonl records only file paths and sizes, not text. Nothing in any journal is ever sent anywhere.

Configuration

Chestnut keeps two files in ~/Library/Application Support/Chestnut/:

  • config.json is yours: hotkeys, capture destination, custom sprite themes, and vaults to ignore. Chestnut never writes to it, except to create it on first run. Edit it by hand; changes take effect the next time Chestnut launches. Right-click → Settings → Edit Configuration… opens it.
  • state.json is Chestnut's: window position, size, opacity, theme, notice duration, pinned vault and disabled plugins. Everything in it has a control in the right-click menu.

Because Chestnut never writes config.json, you can edit it while Chestnut is running. Upgrading replaces only the .app, so neither file is touched and nothing resets. If a release ever moves a setting from one file to the other, that one value goes back to its default and the release notes say so. It is always something you can pick again from the menu.

See the User Guide for every setting.

Using Chestnut with VoiceOver

VoiceOver takes all of Chestnut's default shortcuts. Control-Option is VoiceOver's own modifier key (the "VO key"). While VoiceOver is running, ⌃⌥V changes speech verbosity, ⌃⌥M opens the menu bar, and none of Chestnut's five hotkeys reach Chestnut. That includes ⌃⌥M, which is otherwise the keyboard route to Chestnut's menu.

Rebind them in config.json to a combination VoiceOver does not use, then relaunch Chestnut:

{
  "hotkeys": {
    "capture": "control+shift+space",
    "hopper":  "control+shift+v",
    "paste":   "control+shift+c",
    "notice":  "control+shift+o",
    "menu":    "control+shift+m"
  }
}

Until you do, you can still open the menu by right-clicking Chestnut. The click has to land on the sprite itself, not the transparent space around it. Once open, the menu, the Quick Capture panel and the vault palettes all read correctly, and moving through a palette announces each vault with its path.

Build from source

You need macOS 14 or later and a Swift 6 toolchain (Xcode or the Command Line Tools). There is no Xcode project; the build uses Swift Package Manager and Make.

make build          # swift build (CONFIG=debug|release)
make bundle         # build → .build/Chestnut.app (ad-hoc codesign)
make run            # bundle + open the app
make dmg            # release build → .build/Chestnut.dmg
make check          # runtime checks (no XCTest dependency)
make clean

Quit with right-click → Quit Chestnut, ⌃⌥M → Quit Chestnut, or pkill -x Chestnut.

Architecture

Chestnut is a single Swift executable that the Makefile bundles into a .app.

Layer Technology Role
Windows AppKit Borderless, transparent, always-on-top pet window
Pet rendering SpriteKit Sprite animation from hand-coded frame matrices
Panels SwiftUI Vault palette, capture panel, notice bubble, plugin picker
Sources/Chestnut/
  main.swift, AppDelegate.swift
  Pet/        # window, scene, state machine, geometry, frames, themes
  Vaults/     # VaultRegistry, VaultWatcher
  Actions/    # ObsidianBridge, Courier, Capture
  Panels/     # SwiftUI palettes and panels hosted in NSPanel
  Plugins/    # manifest, registry, runner, dispatch, drop routing, save, picker
  Support/    # config, state, hotkeys, journal, trash, CLI lookup, logging

ARCHITECTURE.md covers the design decisions that span files.

Design principles

  • Chestnut never modifies Obsidian's files or its .obsidian/ settings. It writes into a vault only when you ask it to: a capture, a delivery, or a plugin save.
  • Vaults are identified by path, never by name, because names collide.
  • No network calls and no telemetry.
  • The obsidian CLI is optional. Every CLI call has a filesystem fallback.
  • No image assets. Sprites are text matrices mapped through a color palette.

Contributing

See CONTRIBUTING.md for build instructions and ground rules.

License

MIT © @gapmiss

About

A tiny always-on-top pixel companion for Obsidian users on macOS. Hop between vaults, deliver notes, capture thoughts. No network, no telemetry.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Contributors

Languages