catcher 0.9.0

A minimal, local-first markdown notes TUI over plain files
catcher-0.9.0 is not a library.

catcher

https://github.com/user-attachments/assets/5a060469-40c5-4fd8-a17c-cab2e60b6f96

A minimal note-taking app for the terminal. Local-first, no accounts, no sync — your notes are just a flat folder of .md files in ~/catcher, so grep, git, and Obsidian all work on them too.

Open a terminal pane, jot something down, close it.

catcher was called tinynote until 0.9. The binary, the crate, the formula, and the ~/.config/catcher and ~/catcher folders all changed name; an existing ~/.config/tinynote or ~/tinynote is still picked up, and TINYNOTE_DIR still works, so nothing needs moving.

Install

brew install tinycomputer-io/tap/catcher

Or with cargo:

cargo install catcher

Then run catcher.

Using it

A bare catcher reopens the note you had open when you closed it, wherever it lives. Naming something — catcher spec.md, catcher ~/vault — asks for that instead.

One note on screen at a time. ^K opens the palette — every note listed by filename with the folder it lives in, fuzzy-searched by filename first, then title, then body, plus commands (new, open, delete, rename file, preview, help, settings, quit).

^O opens a note. It is the palette's twin, and the difference is what it ranks by: notes you opened most recently first, then the most recently edited, and it walks subfolders — so pointing catcher at an Obsidian vault still lets you jump straight to applications/log.md from wherever you are. Both the list and the tree show each note by its filename, the name a [[wikilink]] reaches it by. Type to fuzzy-search filenames, with titles and folder paths as a second chance.

Tab flips ^O between the ranked list and a folder tree — the same index, the other way of looking at it. The tree opens on the note you have open, with the folders above it unfolded, so the first thing it tells you is where you are; unfolds a folder or opens a note, folds one or steps out to the folder it lives in, and a closed folder says how many notes are beneath it. Whatever you have typed comes along either way, so log and then tab shows you where the log notes live. quick_open_mode: browse in the settings makes the tree what ^O opens on.

It reaches past your notes dir three ways. A note you have opened before is always offered, wherever it lives — that is what the recents list is for, and it survives restarts. Folders listed in quick_open_dirs are searched every time. And typing a path~/vault/spec.md, or ~/vault/spec — opens that file directly, which is the escape hatch for a note catcher has never been shown. Opening a note from another folder pulls it into the session; it saves back where it lives, and is never renamed.

The editor is a live preview, Obsidian-style: headings, emphasis, ==highlight==, code, links, quotes, bullets and / checkboxes are styled as you type, and the line the cursor is on shows its raw markdown so it's always editable. Code fences, tables, rules and images are drawn whole and flip back to source when the cursor lands inside. Long lines soft-wrap; nothing scrolls sideways. ^P flips to the full rendered page, with images drawn inline in terminals that support graphics (Ghostty, kitty, iTerm2). The view sticks across notes: follow a link, pick a linked mention or open another note while reading and it opens as a page too; a new note always opens in the editor. Quotes keep their rail down every row, blank and wrapped ones included, and an Obsidian callout (> [!summary] TL;DR, or [!note], [!tip], [!warning]…) is drawn as a boxed card with its type and title across the top.

[[wikilinks]] work the way an Obsidian vault expects. [[spec]] finds the note by filename, title, or the tail of its path — [[stories/story-matrix]] — and [[spec|the spec]] draws the label instead. ⌥⏎ on one follows it, and so does ^-click or -click; a link that names no note is drawn in the danger colour, and following it offers to create the note, in the folder the target named. [[#heading]], ![[image.png]] and \[[escaped]] are left alone, and so is a link inside a code fence. wikilinks: no leaves them all as the literal text a reader without Obsidian sees.

A --- front matter block is metadata rather than prose: the reading view never shows it, and front_matter in the settings says what the editor does with it — dim it, show it styled like any other markdown, or hide it until the cursor moves in. Adding properties to status_bar_items puts a count of the keys it declares in the bottom line, on the notes that have any.

At the foot of the reading view is linked mentions: the notes that link to the one you are reading, each with the sentence the link sits in, most recently edited first. A note that links here several times is one row with a ×3 beside it, and every row is itself a link — follow it to go there. A note nothing points at gets no footer at all, not even a rule. It is a scan and not a stored graph — nothing is kept beside your notes — so it runs on a thread and the footer appears a moment after the page does. linked_mentions: no turns it off, and so does turning wikilinks off, since there would be nothing to count.

In the reading view a wide table is not squeezed into columns two characters across. Its columns keep a readable width and wrap inside it, and the table itself pans sideways — and , or a sideways scroll — with a on the header row where it carries on. Nothing is cut. table_style in the settings picks the rule: auto (leave a table that fits alone, scroll one that doesn't), scroll, fit, wrap, or cards for one labelled block per row.

Notes autosave half a second after you stop typing. Mouse works as expected: click to place the cursor, drag to select (copies on release), scroll, click a palette row. In the reading view a click no longer drops you into the editor — drag to select and it copies on release, so you can lift a quote out of a rendered page. ^V pastes — a clipboard image becomes a PNG in your attachments folder with the markdown link inserted for you.

^N creates a note. Its filename follows the first line (# Groceriesgroceries.md) until you rename the file yourself, after which the title and filename go their own ways.

CLI

catcher                  open the TUI on the note you last had open
catcher groceries        open the note whose title best matches; create it if none does
catcher add "buy milk"   write a new note and print its path — no TUI
cat x | catcher add      same, from stdin
catcher path             print the notes directory
catcher ~/vault/spec.md  open that file, with the session rooted at its folder
catcher ~/vault          open the TUI rooted at that folder
catcher --keys           print the key events your terminal sends (esc quits)
catcher --help           usage

Pointing catcher at a file or folder roots that one session there without touching your config. Outside your configured notes dir, filenames are never auto-renamed (Obsidian links depend on them) and image paste is refused.

Keys

Key Action
^K Palette: search notes, run commands
^O Open a note: every folder, recent first
^/ or F1 Help card: every key
^N New note
^, Settings
^P Toggle markdown preview
^S Save now
^Z / ^Y Undo / redo
^C ^X ^V Copy / cut / paste
⌥⏎ Follow the [[wikilink]] under the cursor
⌥P Peek at the [[wikilink]] under the cursor; in preview, hovering a link does the same
^B / ^F Back / forward through the notes you have opened, browser-style
Tab In ^O: flip between the ranked list and the folder tree
Esc Close palette, cancel, leave preview, clear selection
^Q Quit

Every key in that table but Tab is settable — tab means “the other view of this” and is not a command. The settings note has a ## Keys section with one line per action — key_palette: ^K, key_open: ^O — and takes ^K, cmd+k, alt+k, f5, or none to unbind — or several at once, as ^/ f1. ^K answers to either ctrl or cmd, so the same file works on a Mac and on Linux; spell out cmd+ or ctrl+ when you want one exactly. Delete note and Rename file ship unbound and are yours to claim. The palette shows each command's current key beside it, and so does the ^/ help card (F1 opens it too, for terminals that swallow ^/) — which is itself searchable: type save on it and only the saving rows stay. Esc closes it. The palette itself is monochrome — it is chrome over the note, and a hue there would compete with the one the note spends on its headings.

The palette's search box takes the Mac editing keys too: ⌘⌫ clears it, ⌥⌫ deletes a word.

Editing is macOS-style — ⌘←/⌘→ for line start/end, ⌘↑/⌘↓ for top/bottom, ⌥←/⌥→ by word, ⌘⌫ and ⌥⌫ to delete, ⌘A to select all. Add to any movement to extend the selection. ^-click or -click follows a link.

If a shortcut misbehaves, run catcher --keys to see what your terminal actually sends. Ghostty binds most Mac editing keys to legacy control codes, which catcher handles — except ⌘↑/⌘↓, which Ghostty keeps for itself. Use ^Home/^End, or unbind them:

keybind = cmd+up=unbind
keybind = cmd+down=unbind

-click on a link needs macos-option-as-alt set; ^-click always works.

Settings

Every note you land on — from ^O, the tree, a [[wikilink]], a linked mention, ^N — goes on a history, and ^B / ^F walk it the way a browser does: going back and then opening something else drops what was ahead, and a note deleted in the meantime is skipped.

Settings are a note. ^, opens ~/.config/catcher/settings.md in catcher itself — same editor, same preview, no $EDITOR and no TOML — and ^S applies it at once. Colours, page width and everything else but notes_dir change on the next frame.

Every setting is a - key: value line with a one-line hint after it. The file is written on first run with all of them in it, and rewritten when a new setting appears — your values are kept, since the file is generated from the settings it was just read into.

notes_dir, attachments_dir where notes and pasted images live (~/ expands; CATCHER_DIR overrides the first)
theme auto (the default: catcher asks the terminal which way its background runs at startup), dark, or light
accent, bright, grey, dim, link, code_bg, code_fg, border, danger, ground the ten colours, as #rrggbb, #rgb, an ANSI name, default, or theme to leave it to the theme
page_width widest the note column is drawn, in columns, or full
borders rounded, square, none
bold_headings, status_bar, key_hints chrome, on or off
window_title the terminal window's title follows the open note's name, on or off
status_bar_items what the bottom line shows, in order: path, name, mode, properties, keys, message
autosave_ms, tab_width how soon a note saves, how far tab goes
rename_files whether a filename follows its note's title
front_matter dim, show, hide — what the editor does with a --- block (the reading view never shows one)
table_style auto, scroll, fit, wrap, cards — what happens to a table wider than the page
preview_click select or edit — what a click in the reading view does
wikilinks whether [[links]] open notes, or stay the literal text
linked_mentions the notes that link here, at the foot of the reading view
quick_open recursive or folder — how far ^O looks
quick_open_mode search or browse — which half of ^O it opens on
quick_open_dirs extra folders ^O searches; repeat the line, or separate with commas
key_palette, key_open, key_new, key_settings, key_preview, key_save, key_help (key_shortcuts still read), key_quit, key_copy, key_cut, key_paste, key_undo, key_redo, key_delete, key_rename, key_follow, key_back, key_forward, key_peek one key each — ^K, cmd+k, alt+k, f5, or none

An existing config.toml is read once, to seed settings.md, and then left alone.

Development

cargo run      # against ~/catcher (set CATCHER_DIR to test elsewhere)
cargo test
cargo clippy

Rust, ratatui + crossterm. src/editor.rs is the text buffer, src/md.rs the live-preview styling and click mapping, src/render.rs the full-page preview, src/images.rs inline images, src/cli.rs argument parsing, src/config.rs the settings note, src/keys.rs the bindings, src/index.rs the quick-open index and recents, src/tree.rs the folder tree behind ^O's browse mode, src/mentions.rs the linked-mentions scan, src/clipboard.rs copy and paste. Every colour in the app lives in one place: the theme module at the top of src/md.rs.

License

MIT