A minimalist yet powerful markdown editor your agent can drive.
$ brew tap malinborn/mdmini && brew trust malinborn/mdmini && brew install --cask couplet
Everything a markdown file needs, nothing else.
Markdown renders inline as you type. No split pane, no preview toggle.
**fast**
fast
Create, edit, reorder, and delete table rows and columns. Drag to reorder.
Click any heading to fold the section below it. Keeps long docs navigable.
Fenced code blocks render with language-aware syntax highlighting.
Flowcharts, sequence, class, Gantt, ER, pie, mindmap — all render inline as SVG. Pinch to zoom, swipe to pan around the big ones.
Secret values in .env and shell configs (.zshrc, .bashrc) are masked by default. Cursor or Cmd+E to reveal.
External changes to open files are detected and reloaded automatically.
Open files instantly from the terminal with couplet file.md — or the short alias, coup file.md.
Drag any file onto the Dock icon to open it. Works with multiple files at once.
Reopen the windows you had last time with ⇧⌘T — files, positions, scroll, and unsaved drafts.
Six theme families, each with a light and a dark half: Classic, Aurora, Blueprint, Phosphor, Paper and Ink. Follow the system or pick a half yourself — and the Dock icon takes on the theme’s colours.
Changes are saved continuously. Crash recovery restores your last session.
Install with Homebrew, open from anywhere, and register the MCP server once — your agent gets a local socket into the running app. No daemon, no cloud.
couplet was called md-mini. Already have it? brew upgrade --cask mdmini moves you over and your data comes along. couplet, coup and the old mdmini all work in the terminal; to switch your agent over, run the prompt from AI → Teach Your AI couplet.
After an upgrade, macOS asks for folder access again. couplet is ad-hoc signed — no Apple Team ID — so macOS identifies it by a code hash that changes with every release, and the permission you granted belongs to what it now considers a different app. Signing and notarizing properly costs $99/year, which a free open-source tool is not going to carry. Grant it again and carry on.
|---| line under it|, but couplet’s cell reader ignored whatever sat outside them: | R-091 drew an empty row, and a | b | lost a. Both ends are read now, the same way the markdown parser reads them.csv and .tsv are tables now — open one and the window holds the same table widget markdown gets: edit cells, add and drag rows and columns, and the file is saved back as CSV. The delimiter (, ; or Tab), a BOM and the line endings are read from the file on every save, so an Excel export stays an Excel export. couplet also shows up in Finder’s “Open With” for both. couplet edit refuses CSV files for now; show works:nth-child(even of …), which WebKit recomputes from scratch for every row: quadratic, 4.6 s of style work on 20,000 rows, now 0.5 s. A cell edit rebuilt the whole table; it now patches the cells that changed, and a commit in a 10,000-row table went from 1.7 s to under 0.3 s. Markdown tables render up to 1,000 rows (was 500)overflow: hidden, which quietly made it the box a sticky header sticks to.py (or any code file) opened inside a grey box that vanished on the first click: its style hung on a class the editor rewrites on every focus change. The box is gone for good. And Enter after def f(): indented by two spaces, because nothing ever set an indent unit. couplet now reads it from the file (four spaces, two, or tabs), or uses the language’s convention when the file has none!important rule used to paint them identically. A test holds every theme to a 4.5:1 contrast for match text3 / 17 right in the field, toggles for case, regexp and whole word, a replace row on demand, and a red field when nothing matches or the pattern is broken⌘G / ⌘⇧G — Find Next and Find Previous are in the Edit menu and printed next to the arrows in the panel. They always worked; nothing said so. With the tab drawer open, ⌘G is still “To a window…”.md file in ~/couplet/, named by date, titled by its first line. Close it with ⌘W, the red button or ⌘Q and it is put away in the stash, not thrown away; a tab you never typed in just disappears. Drafts from earlier versions are imported as notes on first launch — read back and compared before the old copy moves aside⌃S opens it beside the tab drawer, ⌃T puts the current tab away, and a file you tag or put away is kept as a reference — the file itself stays where it was, untouched. Drag cards between the two drawers, sort by changed, opened or kind, filter by #tag; a project window opens the stash already filtered to its repository. A narrow window widens itself so the drawers never overlap, and shrinks back when the stash closesтайник finds тайнике and мент finds документ, case-insensitive in any alphabet. Titles weigh more than text, the matched spot is highlighted and the card shows the text around it, not the first line~/couplet/.trash/; Trash at the bottom of the stash restores it with its tags or deletes it for good. A note open in another window is released by that window first, after its last keystroke is on disk, so the next autosave cannot bring it back. Removing a file reference never touches the filecouplet stash search | list | get | add | tag and the same five MCP tools. The default scope is the repository the agent works in; search returns short snippets and get one note at a time, so an agent never pulls the whole stash into its context. Run AI → Teach Your AI couplet again to tell it⌘' / ⌘⇧' walk through AI marks — the caret jumps to the next or previous edit your agent highlighted, comment anchor or open question, wrapping around and unfolding whatever hides it. They are menu shortcuts, so they work with focus in a comment box> levels, each with its own bar, muted towards the quote grey. Tables and mermaid inside a quote stay source: editing them would rewrite the lines without their >** along with it, and the repair filter, seeing one marker of a pair touched, added a stray **. The selection is now split at hidden markers it crosses, and spans of the same kind mergeView → Tabs → Show Dates hides itView → Tabs → Show Next Tab worked when clicked and did nothing when pressed. The culprit was the kind of shortcut, not the key: a Control-only menu shortcut never reaches the menu while the window is active, the page swallows it as an ordinary keypress, and ⌃[ failed the same way in a trial build. ⌃Tab / ⌃⇧Tab are now caught by the page itself, the way ⌃1…⌃9 always were⌘⇧] / ⌘⇧[ — the same keys as Safari, Chrome, Terminal and Finder, and a Command shortcut does reach the menu. Both pairs do the same thing; a test now fails the build if a Control-only shortcut ever lands in the native menu againbrew upgrade --cask mdmini still works and moves you onto the couplet caskmdmini is an alias for couplet, and coup is the short one, so scripts, aliases and $EDITOR need no edits. An MCP server registered as mdmini still answers with the same tools. Restart agent sessions opened before the upgrade: they still talk to md-mini’s socketAI → Teach Your AI couplet hands over a prompt that now also cleans up the md-mini setup: it registers couplet instead of mdmini, replaces the old skill (and keeps it if you edited it by hand), rewrites the note in CLAUDE.md, and tells you what it changed. Installed from the DMG? Delete md-mini.app — opened by accident, it would start empty⌘T, ⌘W, ⌘1…⌘9, ⌃Tab. A file lives in one tab across the whole app, so opening it again focuses it instead of making a copy. Windows are numbered #N; the notch on the left edge opens the tab drawer with ⌘J and glints while a tab your agent opened is unread. ⌘⇧T brings back the last closed tab, caret included⌘Z stays in its own document — after ⌘O or a Recent file, undo could write the previous document’s text into the new one: the inverse of a deletion survived the swap. History is rebuilt on every switch now, and the last few hundred milliseconds of typing are saved before it rather than dropped/theme in the slash menu switches themes live as you arrow through the list⌘+/⌘− set a font size the editor overrode, so the only thing that ever grew was the slash-menu tooltip. It is page zoom now: text, tables, diagrams and toasts scale and rewrapView → Editor Engine, now spelled Raw, Preview and Live Render: the word “Live” in “Live Preview” only ever existed to contrast with the beta, and there is nothing left to contrast with. Someone sitting in Raw stays in Raw — that is a decision to look at the source, not a rendering preference to overruleAI → Teach Your AI mdmini. “Getting Started”, “Connect via CLI”, “Connect via MCP” and “Teach your AI” each explained their own part and left the assembly to you, so you ended up stitching a setup out of four texts. Now the document hands over a prompt: your agent registers md-mini over MCP, writes itself a skill on using it well, and adds a short note to its own config about when to reach for it. A second prompt in the same document does the skill only, if you would rather your main config stayed untouched✓ is not a symmetrical glyph, so it never looks centred anywhere. If that hair is going to follow you around all day, View → OCD Alignment replaces it with a cross drawn as two strokes rotated around one point, symmetrical by construction. We like the tick. We also know how it is⌘Plus, and accelerators are parsed by key code, where Plus is not one. The item simply had no shortcut, which you could only notice by comparing it with Zoom Out. It is ⌘= now, next to ⌘−. And in Live Render's link inspector, Open on a link that points inside the document jumps to that heading instead of doing nothing at all**bold word** into **bold word.; Enter inside it left two unpaired ** on separate lines; a space after it produced **bold **, which CommonMark refuses to close, so the asterisks stopped being markup and became four literal characters on screen. A repair layer now sits in front of the document and tells “the user is deleting this whole span” from “the pair just got torn” by the shape of the change, and writes the marker back before it ever reaches diskEsc or the format shortcut ends it. Option+arrows and Option+Backspace step over the hidden markers instead of stopping on them, so a “word” is a word you can seeTab / ⇧Tab across columns, wrapping within the row; ↵ to the next row, and on the last row it leaves the table rather than dead-ending; ⇧↵ breaks a line inside the cell; ⌘↵ commits; ⌘⇧↵ adds a row. The ⓘ beside the wrap toggle has the whole map, because a keyboard grid nobody is told about is a keyboard grid nobody usesindexOf(quote), the first occurrence from the top of the file, so a one-word quote or a heading that also appears in a table of contents put the card on a duplicate, usually far above, and reported a confident match while doing it. Threads are now anchored by the text around them. Measured over 20,962 cases generated from this repo's own markdown and then edited the way an agent edits a file: first occurrence 61.5%, context anchoring 100%rename, and rename keeps the directory entry while dropping everything that lives on the inode. Mode, owner, group, ACLs and extended attributes — quarantine included — are now copied onto the temp before it moves into place; editing through a symlink writes to what the link points at instead of replacing the link; and a save the filesystem refuses raises a toast rather than failing in silence. The atomicity itself is still there and now proven rather than asserted: a test really does SIGKILL the process mid-write and checks the file came back whole. Comment sidecars go through the same path, since they sit in the same protected, cloud-synced foldersJSON.stringify(JSON.parse(s)) is not a formatter, it is a re-serializer: every literal was destroyed and rebuilt from a JavaScript double. 12345678901234567890 came back as 12345678901234567000, 1e5 as 100000, 3.0 as 3. It now re-indents the text it was given, so number literals, string escapes, key order and duplicate keys survive byte for byte — and the result lands in a ```json fence when the file is markdownTab always inserted two spaces, and CommonMark nests a sub-item only once its marker reaches the parent's content column, which is 3 for 1. and 4 for 10., so an ordered sub-list never nested at all. Tab now indents every selected item, not just the first; folded sections stop hiding text in Raw mode; an AI edit highlights the regions that changed rather than everything between them; toolbar buttons name their shortcut; and [1] in prose keeps its brackets instead of being read as a link reference⇧⌘M, or use the 💬 in Live Render's selection toolbar. The quoted words stay highlighted in the document, the card header carries an excerpt of them, and putting the caret in a commented fragment makes its cards shimmer — and the other way around. With several comments stacked under one paragraph, that is the difference between finding the right card and hunting for itAI → Connect Agent to Doc Questions copies a prompt that arms a watcher over your project. With Claude Code's Monitor tool, a new comment interrupts the session that already has your context, the moment you send it. That is the whole design: nothing here spawns an agent or hands your question to one that knows nothing about the workcouplet answer plan.md --id c-4f2a1b. If the comment asked for a change rather than an answer, insert into text folds it into the document — highlighted like any other AI edit, Escape to dismiss, ⌘Z to take it back. If it was just a question, resolve closes the thread.mdmini_comments_<file>.md, plain markdown you can read, edit by hand and diff in git. md-mini never rewrites that file wholesale, only appends to it. So the feature works with the app closed, works for agents with no MCP, and needs no store of its own — md-mini is deliberately not the source of truth herebrew install --cask mdmini simply refused on an Intel machine, and never said why. The culprit was a single arch-dependent type: Objective-C's BOOL is a bool on Apple Silicon and an i8 everywhere else, so one true literal compiled on one machine and broke the othercouplet show file.md --line 42 scrolls a window to a spot and pulses it; cat new.md | couplet edit file.md pushes content straight into the live buffer and highlights exactly what changed. No writing to disk and praying the watcher noticescouplet ask plants a real question with option buttons inside the document and blocks until you click. Checkboxes for multi-select, a free-text field when none of the options fitcouplet mcp runs a stdio MCP server exposing the same three verbs as tools, so agents that prefer MCP need no shell glue. couplet agent prints a ready-to-paste block for your CLAUDE.mdView → Editor Engine → (beta) Live Rendermd-mini.com; the old GitHub Pages address redirects⇧⌘T brings them back, or use File → Reopen Windows from Last Sessionbrew upgrade --cask mdmini closes every window, and now you can get them backCmd+scroll does the same for a mouse⤢ button or a double-click refits; drag the bottom edge to resize the frame[text](url) inside a cell now renders as a clickable link instead of raw brackets and parens[Back to top](#heading) jumps within the document; re-clicking the same anchor no longer leaks out to the system browser.zshrc, .bashrc, .bash_profile, .profile and friends now render as Shell code instead of plain textexport API_KEY=…, TOKEN=… are masked to a sk-…jkl pill with a Copy button, while the key and the rest of the line stay highlightedCmd+E, to see the raw value; shell expansions ($VAR, $(…)) are left untouched[Section](#section) now scrolls to the matching heading instead of doing nothing#section-2)1.| and line breaks inside cells survive a save/reload via \| and <br># H1–###### H6```mermaid blocks render as inline SVG diagramsbrew update prefix.env values blurred by defaultcouplet file.md via Homebrew tap