Boris Mann Tech Blog

Building GhostOff and our networked, personalized software futures

In which I agentic code Ghost to Offprint script and fall into the future of networked code sharing with Vit

July 04, 2026

I made a Ghost blog export to offprint script that I call "GhostOff" and it's available on Tangled:

bmann.ca/ghostoff
Migrating content using the Ghost API to Offprint / Standard Site on atproto
https://tangled.org/bmann.ca/ghostoff

This also happens to be one of my first experiments with agentic coding1.

I spent an hour or so in plan mode2, explaining how it should work, detailing the .env file and credentials that would be needed, requiring the use of the atcute library by , attaching the Lexicon Garden MCP server3, and preparing a test account and test publication. The Ghost Content API is pretty standardized. I walked through some of the things in Ghost Cards that might not have offprint block equivalents4.

And it worked pretty great first try in build mode. It built everything and then ran the script to test it with the credentials in the env file, downloaded the Ghost posts, and wrote them out as atproto native posts. It verified that the records validated. There are only 26 posts in on the atprotocol.dev Ghost blog, and it ported them all to Offprint! Amazing!5

Screenshot of the ported posts in Skyreader

There was some confusion about standard.site wrappers then containing app.offprint.content containers and the block structure. At one point it decided to write everything as leaflet lexicon items instead. Yes, the standard site wrapper vs content types is confusing for everyone.

I tweaked a few things and ran the script a couple of more times. I'm...done???

OAuth for the CLI

One of the suggestions on what to do next was moving to OAuth. OAuth??? For a CLI tool???

This is in fact totally possible, and there are lots of very nice auth / CLI / website flows. But maybe tricky? I vaguely remembered that atproto needs 127.0.0.1 rather than just "localhost". Made a plan, told it to go ahead, asked it to write some docs about how to test it manually.

And now we don't need an app password, and we don't need to include a PDS endpoint because it looks it up from the handle.

GhostOff OAuth for CLI Flow
Just a quick couple of screenshots with URLs. npm run dev to start the flow, you're going to need a couple of things filled into .env.
Depending on your settings, it will pretty much just pop open a browser to the login page of your PDS:

GhostOff OAuth for CLI Flow

Just a quick couple of screenshots with URLs. npm run dev to start the flow, you're going to need a couple of things filled into .env.

Depending on your settings, it will pretty much just pop open a browser to the login page of your PDS:

Screenshot of the selfhosted.social login page, with the atprotocol.dev account. Yes, I should make a different throwaway account, because I'm going to hand this one to Brittany

Yes, it is very much using that old classic, transition:generic, because I told it to0. I will tackle OAuth Scopes at some point.

Screenshot of authorizing OAuth scopes, which IYKYK is transition:generic and will make everyone mad. But is in fact the same set of permissions as app passwords.

Authentication successful. You may return to the natural environment of your terminal and watch log messages of atproto-ization being visited on your Ghost posts.

I realized in running this again that… the session sticks around. So going to need to work on cleaning that up.

And that's it. It works and we can start mass exporting Ghost blogs to Offprint: GhostOff indeed!

What's Next

Of course, a CLI script that no one knows about that has to be downloaded from a git forge doesn't solve much of anything in bring people to the Atmosphere. Even with the magic of OAuth, and maybe expanding that localhost trick to gather env variables - enter in your Ghost API key, select or create the publication you want to import to, add support for .

Something that is a desktop app would probably be more accessible. Yeah, it could be a website too.

Substack is a much juicier target, at least for those that aren't actively running monetized newsletters.

I have an Instagram export sitting around somewhere. And a really giant Flickr export…

Networked, Personalized Futures

All of the above sounds like a lot of work. And people might send me PRs, or give me user feedback, or report bugs. Or advocate that I should add supporting their flavour of Markdown to my roadmap which if I build or accept a PR for, I am cursed to maintain forever in "my" codebase.

Nah. I ain't doing all that. Just fork it and tinker with it on your own.

But I actually think the future is way more amazing, and is a glimpse in that direction.

a codebase is not a distribution artifact. a codebase is a living organism that can adapt to each install, and it deserves a living ecosystem.

the future is not “one repo, one roadmap.” the future is 
millions of personalized codebases—each one evolving continuously, shaped by its caretakers’s needs, values, constraints, and taste.

vit is the mechanism for that future.

Go read the whole doctrine. And yes, it's built on atproto by .6

What do I think this means? Well, I've started using the phrase "infinite forks". And it scares me. I got my start in open source in Drupal. "Don't hack core". Forking core is bad and hard. We go farther when we collect in a repo and work on one codebase together.

So we get our current picture. Not decentralized git, but centralized git forges where we collect issues and discussions and of course the little images of avatars that are people that sometimes come into the real world so you can hug them. And it's been good. It's been great.

But only for coders. For Open Sourcerers. And forks are bad because software is hard.

I've been writing about Networked Orgs for 4 years. Vit feels a lot like this shape of things. Not a git forge single repo hub, with a couple of branches and forks as spokes, downstream/upstream. It is, as described by vit, searching your trusted network for capabilities you can integrate. Evolving constantly.

diagram of source code movement today, and with vit an interconnected network

So, uh, I made a beacon for the ghostoff project and published some caps. I think this is important and we need more people to come try things out.7

bmann% vit vet ghost-content-export
=== v̇it cap review ===
Review this cap carefully before trusting it.

  Ref:     ghost-content-export
  Title:   Ghost Content API Export
  Author:  did:plc:2cxgdrgtsmrbqnjkwyplmp43

  Fetches public Ghost CMS posts via the Content API 
  and caches them locally for further processing.

--- Text ---
This cap authenticates to a Ghost CMS instance 
using a Content API key, paginates through published posts, 
and writes the raw post data to a local directory. 
It handles pagination, rate limits, and featured image URLs 
so downstream steps can work with clean, cached JSON.
---

To trust this cap, run:

  vit vet ghost-content-export --trust

I've got caps published to the network. If you want to add OAuth login for your CLI apps, this might work for you.8

A screenshot of pdsls a vit cap record https://pdsls.dev/at://did:plc:2cxgdrgtsmrbqnjkwyplmp43/org.v-it.cap/3mpsohtszms2x#info ref: "atproto-oauth-cli" text: "This cap integrates @atproto/oauth-client-node into a Node.js CLI. It starts an ephemeral loopback server on 127.0.0.1 for the OAuth callback, stores session state in a local file with restricted permissions, restores the session on later runs, and uses DPoP-signed request handlers to read from and write to the user's AT Protocol PDS." $type: "org.v-it.cap" title: "ATProto OAuth for CLI Apps" beacon: "vit:knot.commonscomputer.com//did:plc:mfquhie7kthb4ig453glwgdk" createdAt: "2026-07-04T08:48:40.093Z" description: "Adds OAuth login with a local loopback callback to Node.js CLI tools using AT Protocol."

Infinite forks. Hug your people. We can get through this together. Let's get busy internecting.


1.
Literally my second thing, and the first one from scratch, from an empty directory. Some more about this and why I made the move now in a recent blog post.
2.
Currently using OpenCode, and as mentioned in the blog post, Umans with open model GLM 5.2. I'll be doing more with our Z-Space Local AI cluster (or cocore!) soon.
3.
Before I connected Lexicon Garden, it was making up app.test.whatever lexicons, so I interrupted it.
4.
There's a table showing the mapping in the GhostOff docs folder.
5.
Screenshot is of posts listed in Skyreader.app - will need to re-run this to make it into the actual Atmosphere Community Blog, which is also waiting on some Offprint atproto sync updates.
6.
@jeremie.com is the creator of Jabber/XMPP and also happens to be a director of Bluesky PBC. More at sol pbc. And he'll be at Local-First Conf in Berlin with a few new items in his bag of tracks.
7.
I got stuck because I'm using opencode and vit doesn't detect it. Next up contributing to the vit upstream, which is still on boring old github.
8.
Will it? This is, uh, highly alpha software on a distributed network with code hosted on a "knot" that lives on a NixOS minipc at my coworking space. Ping me if this does anything.
300baud modem or first broadband?

atproto
vit
offprint
standard.site
agentic development
ghost-blog

Boris Mann Tech Blog

Journals, asides, notes