A tiny script that adds my internal links

A 40-line Python script adds internal links to my Ghost drafts via a shell alias. It is crude, fast, and only saves minutes, which is exactly why it works.
A tiny script that adds my internal links

The whole workflow fits in one shell alias

My smallest useful AI-ish workflow is a shell alias called ghostlink.

I type:

ghostlink my-post-slug

That one command opens the draft with that slug in Ghost, scans the HTML, checks my 100 most recent published posts, and inserts internal links where the draft mentions topics I have already written about.

The result is the same HTML, now with a few extra internal links, written back to the Ghost draft via the API.

The whole thing is a 40-line Python script that lives in ~/.hermes/scripts, plus a shell alias that passes the slug. No UI, no dashboard, no cron jobs. Just a tiny tool I run when I am almost done writing.

What the script actually does

The logic is intentionally crude.

The script pulls two things from Ghost:

  • The draft HTML for the slug I pass in
  • The titles and URLs of my 100 most recent published posts

Then it runs a keyword match with a relevance threshold. There is no semantic similarity, no embeddings, no model calls. It checks whether the draft contains a word like Tailwind or Make.com and whether I have a published post with that word in the title.

If the confidence is above a fuzzy threshold, it wraps the first mention of that word in an <a> tag that points to the matching post.

I wrote about this earlier in A publishing system is not a writing system.

On my Mac Studio, it takes about four seconds to run. It talks to the local Ghost API, so there is no per-run cost and no external dependency beyond Ghost itself.

Why this counts as “AI” at all

On paper, this barely qualifies as AI. There is no model call in the hot path. The “intelligence” is a keyword match plus a relevance threshold.

The useful part is that I do not have to remember my own archives while I am editing. I do not need to think, “I wrote about Make.com last month, I should link that here.” The script does that lookup for me. It is not smart, but it is attentive in a way that I am not when I am trying to finish a draft.

That is the pattern I like: use simple, almost dumb automation to handle the mechanical parts of a decision I would have made anyway.

Where it fails: context

The main limitation is obvious: the script has no idea what I actually mean.

Once, I was writing about a Withings scale and mentioned “health” in the context of tracking metrics at home. The script linked that word to a client article about workplace health insurance, because the titles both contained “health” and the keyword threshold was satisfied.

The keyword matched. The context was wrong.

This is why the workflow does not eliminate my judgment. It just accelerates the mechanical part of adding links I would have added anyway. I still open the Ghost editor, skim the draft, and remove anything that looks off.

More on this in my article Make.com + Ghost: Auto‑Archiving Blog Posts With 0 Traffic.

Why it replaced my Make.com scenario

Before this script, I used a Make.com module to do roughly the same thing. That scenario tried to parse Ghost HTML with regular expressions, scan for keywords, and write back the modified HTML.

It kept breaking on HTML entities. &nbsp; characters and inline images would confuse the regex, and Ghost would respond with 422 validation errors.

The Python script is not smarter in terms of logic, but it is less fragile because it uses a real HTML parser. It runs BeautifulSoup over the draft, walks the DOM, and inserts links where they belong.

It also helps that it runs locally. I do not have to wait for a cloud automation module to spin up or wonder whether a visual scenario editor silently changed something. It is a script I can open, read, and fix.

The manual trigger is part of the design

A useful side effect of this workflow is that it forces a pause before publishing.

Because I run ghostlink manually, I always end up back in the Ghost editor to check what it did. While I am there, I often catch other problems: a title that does not match the content, a missing excerpt, a broken featured image.

Read also Three things I delete from almost every AI draft.

If I wired all of this into a fully automated pipeline that ran on publish, I would lose that human checkpoint.

How small it actually is

If I had to delete this script tomorrow, I would lose about 15 minutes per week.

That is the definition of a small workflow. It does not transform how I write. It just removes a tiny annoyance that was big enough to delay publishing: the feeling that I should go back and add links to old posts, but I do not really want to spend the time.

Now, I write the draft, run ghostlink, clean up any bad suggestions, and move on. The script has become part of my routine precisely because it does so little. It is specific, it is fast, and it does not ask for attention.

Subscribe to my newsletter

Subscribe to my newsletter to get the latest updates and news

Member discussion