30 Apr 2026 · 9 min read
Fixing Up My Markdown
If you write online, the chances are, you have heard of Markdown, the syntax created by John Gruber as a simplified way of marking up plain text with simple formatting that can then be processed to other formats, such as HTML. The premise is based around maintaining simplicity and portability of content. Once the domain of bloggers “in-the-know”, Markdown has become much more commonplace and is supported to some extent by many online systems that accept content. Not just content management systems, but also generic forms, case management tools, e-mail clients, and even many desktop applications too. It is almost a lingua franca for simple text formatting.
I have been writing in Markdown for about 20 years now and I have definitely developed my own quirks and nuances in how I use it. Usually these are compatible with tools, but occasionally I get caught out in some tools by things like a lack of spacing. As such I had never subscribed to beautification or formatting tools because of the prescriptive nature they often employed. However, I have now been converted. Well, kind of.
Yet Another Terpstra Tool
Brett Terpstra is one of my favourite developers in the Mac ecosystem. The number of times I look to see if someone has created a tool to do what I want and find Brett has is eerie. His efforts have saved me many hours of developing my own solutions, and I dare say has probably done a better job than I would have had time to.
Earlier this year he released Markdown Fixup, “An Opinionated Markdown Linter”. That immediately put me off looking into it as I decided that this was probably yet another Markdown formatting tool that would be too constrained for me in how I could utilise it and align it to my own personal style. I mean saying it is opinionated does set the expectation quite strongly.
I set it aside for a while and then finally had a bit of time where I could look at something and it resurfaced on my list of things to take a look at … just in case.
The good news was that while Markdown Fixup was certainly opinionated, it also came with lots of settings I could tweak. That unlocked an opportunity.
Taming the Settings
When I had a little more time available, I began to experiment with the tool. It is a command line utility where you can pass it a file or “pipe” content in, and it will output the reformatted content based on a set of configuration criteria.
It took me a while to get the configuration working as I wanted. There was a convenient command to set up a default configuration file, but I didn’t find it all that helpful other than to see the full list of options (explanations however are on GitHub), and after paring things down I eventually figured out that wasn’t the right way to do it.
What worked best for me was keeping all the configuration options in the file and simply moving the rule-based configuration options between two sections to be absolutely explicit about what rule to apply, and what rules not to apply. If you use this tool, then this is certainly something you will want to take your time over and decide for yourself.
As an example, in my ~/.config/md-fixup/config.yml file, I have a section called rules: within this section are two sub-sections, skip: (for rules that are not to be used), and include: (for rules that are to be used). They break down as follows.
skip |
include |
|---|---|
|
|
That configuration gives me a near perfect setup. But near isn’t what I want. It is so close at this point, but also where I bump into the opinionated nature of the tool. It enforces bold and italic formatting using specific characters, which are an imperfect match to what I want. In addition, it cleverly uses different list markers at different levels, also something I don’t want. There didn’t seem to be a way to override these opinions.
The natural thing many people might resort to at this point is dropping an F-bomb. I’m not a fan of the profane, so I resort to what I believe is an F-build….
Fork It
Because it is a close match and it is adding a whole lot of value as is, I needed to find a way to tweak the output. One option was to apply a remedial action to apply my own markup requirements after Markdown Fixup runs. This isn’t necessarily all that easy to do, and it is typically better to fix issues at the source wherever possible. Option two was to modify the code to allow me to do exactly what I wanted.
Fortunately, because Brett had posted the code to GitHub the option to modify it was available. I could fork it, and modify it to suit my own needs.
While I could have put this forward as a pull request, I feel that this is probably beyond that threshold of the opinionation that the tool currently promotes. It is written in Rust, and that isn’t a language I have any experience with, so I also made judicious use of an LLM, and the changes were more extensive than I had anticipated - but all quite doable. The result is that at this time, I plan to just leave this as a fork. It meets my needs and provides a few options for people to override the defaults in the same way I wanted to, but I would expect Brett would develop this functionality in a different way should this be something he rolls into the utility in the future.
In this forked version, I applied some additional configuration options to the configuration file.
unordered_list_markerspecifies the character to use for bulleted lists.italic_markerspecifies the character to use for italics.bold_markerspecifies the character to use for bold (this will be doubled).
In my config.yml, I have the following entries:
unordered_list_marker: "-"
italic_marker: "*"
bold_marker: "*"
Installing the Fork
I have updated the help and the version to strongly indicate that this is a forked version of the original. Unless you specifically want the same type adjustments I have applied, I would strongly suggest you install the original version. However, if you do want to install the fork, it is about as easy.
First of all, make sure you have the Homebrew package manager installed. This opens up a whole world of easy install utilities like this.
Then from the command line, enter the following command:
brew tap sylumer/tap
This lets Homebrew know about the utilities I have in my formula list in my Homebrew repo on GitHub.
Next, you can install my forked version of Markdown Fixup using the command to install its formula (based entirely on the formula for the original by the way).
brew install sylumer/tap/md-fixup
To confirm the installation, you can run this command:
which md-fixup && md-fixup --version
This should give you some output like this:
/opt/homebrew/bin/md-fixup
md-fixup v0.1.36-sylumer.1
Fork of original md-fixup by Brett Terpstra
The first line tells you where md-fixup is installed. The second is the version (so v1 of my fork of version 0.1.36), and the third is an additional version note referencing the original.
Easy Access Away From the Command Line
With everything in place, the next question becomes how to use it. It is easy to use from the command line if you know how to pipe content around, but I do most of my writing in visual apps like Nova and Drafts. While I could query and get Drafts content on the command line, it would be much easier to access if I could set it up in the graphical user interface.
It would be relatively easy to set this up in Automator or even Apple Shortcuts as a Quick Action/Service. However, I tend to access all of my Markdown automations via a conflict palette in Keyboard Maestro. It made sense for me to roll this into that setup.
The first step is to get the text from the application I am using. I might not always want to modify all text so I decided to go with selected text - like I would if I was setting it as a Quick Action. Fortunately, this is something I do quite regularly and I had already split this out into a subroutine macro.

What I really wanted to do was to copy the selected text, and if no text was selected, copy all of the text. Unfortunately this is complicated by the fact that many text editing apps copy the current line if no selection is made. So my original idea of checking if a copy of the current selection contained anything wasn’t going to fly and a bit of research suggested there was no clever way around this that would work for all apps.
With the current selection passed to Keyboard Maestro and ready for overwriting, it is a relatively simple matter to pass that text in and paste the formatted text back.

For my own use I then have a separate group where I call this macro and assign it to a common keyboard shortcut - to trigger the Keyboard Maestro conflict palette. It is kept in another group alongside other macros that are not necessarily in the conflict palette. It may seem like duplication through a stub macro, but it helps me with organisation and tracking of my many macros.
Conclusion
In the case I described, it was firmly in that most of the way there category. Markdown Fixup was close. Close enough that it was worth the effort to push it the rest of the way. Forking it gave me exactly the behaviour I wanted, without fighting against its defaults.
That’s the real takeaway. If something almost works, don’t dismiss it. You might be one small adjustment away from a perfect fit—whether that’s configuration, automation, or even modifying the code.
In my case, that meant leaning on an LLM to help, writing some Rust code, and just iterating for a bit until I got everything working as I wanted. Is the code perfect? I wouldn’t imagine so. But it is fast, it works, and it solves that point of friction I’d had previously.
And that, in my humble opinion, is enough to justify it.
If you run into friction in your own processes and tools, it is worth asking yourself: is this a dead end—or an opportunity to take control of the outcome? You might be surprised at just how close you are to that ideal solution.