ghostoff

Export Ghost CMS posts to Offprint / Standard.site over AT Protocol

  • ESM
License
MIT
Install Size
194.3 kB(42.7 MB)
Vulns
1
Published

Run

$npx ghostoff
$pnpm dlx ghostoff
$yarn dlx ghostoff
$bunx ghostoff
$deno run npm:ghostoff
$vlx ghostoff
$vp dlx ghostoff
$nubx ghostoff

Readme

GhostOff

Export public posts from a Ghost CMS and upload them to Offprint via the AT Protocol / Standard.site lexicons.

The live test publication is at https://ghostoff.offprint.app.

View the package on npm: npmx.dev/package/ghostoff.

Installation

Install globally from npm:

npm install -g ghostoff

Or run directly with npx:

npx ghostoff -- --help

What it does

  • Paginates through Ghost's public Content API for posts.
  • Downloads each post's images, resizes them to fit atproto's blob limits, and uploads them as blobs to a PDS.
  • Converts Ghost HTML bodies into Offprint's block-based format (app.offprint.content).
  • Writes a site.standard.document record per post and an app.offprint.document.article record that points back to it.
  • Tracks progress in a local state file so reruns update existing records instead of duplicating them.

Quick start

Copy .env.example to .env and fill in the secrets:

cp .env.example .env
GHOST_URL=https://your-ghost-site.com
GHOST_API_KEY=your-ghost-content-api-key
ATPUBLICATION_AT_URI=at://did:plc:.../site.standard.publication/...

# Auth: OAuth is the default. Set only if you want password auth.
ATP_IDENTIFIER=your-handle-or-did
# ATP_APP_PASSWORD=your-app-password

Run a dry first pass to inspect what would be uploaded:

npm install
npm run dev -- --dry-run

When you're ready, run the real migration:

npm run dev

If you haven't set ATP_APP_PASSWORD, the CLI will ask:

No app password found in .env or --atproto-app-password. Would you like to authenticate via OAuth [Y/n]?

Answer Y and it will open a browser so you can authenticate with your PDS. If you set ATP_APP_PASSWORD, it uses password auth instead.

Rerunning the same command will putRecord existing records instead of creating duplicates.

CLI options

Flag Environment variable Description
--ghost-url GHOST_URL Ghost publication URL
--ghost-api-key GHOST_API_KEY Ghost Content API key
--atproto-identifier ATP_IDENTIFIER atproto handle or DID
--atproto-app-password ATP_APP_PASSWORD atproto app password
--atproto-service ATP_SERVICE PDS/service URL (password auth only)
--publication-at-uri ATPUBLICATION_AT_URI Existing site.standard.publication AT-URI
--oauth-client-id GHOSTOFF_OAUTH_CLIENT_ID OAuth client_id URL (dev default: http://localhost)
--oauth-scope GHOSTOFF_OAUTH_SCOPE OAuth scope (default: atproto transition:generic)
--oauth-session-file OAuth session cache (default: ghostoff-oauth-session.json)
--oauth-state-file OAuth transient state cache (default: ghostoff-oauth-state.json)
--dry-run Build records without uploading
--export-dir Local asset cache directory (default: ghostoff-export)
--state-file Idempotency state file (default: ghostoff-state.json)
--session-file Password session cache (default: ghostoff-session.json)
--verbose Verbose logging

Scripts

npm run dev          # run the migration
npm run build        # compile TypeScript to dist/
npm run typecheck    # type-check without emitting

Project layout

src/
├── index.ts              # CLI orchestration
├── config.ts             # env + argv loading
├── ghost.ts              # Ghost Content API client
├── assets.ts             # image download, cache, resize, blob upload
├── html-to-offprint.ts   # Ghost HTML -> Offprint blocks
├── facets.ts             # inline formatting -> richtext facets
├── atproto.ts            # auth adapters (OAuth + password), record ops, validation
├── oauth.ts              # loopback OAuth server and file-backed stores
├── state.ts              # idempotency state file
├── rate-limit.ts         # retry + rate-limit helpers
└── types.ts              # shared type definitions

Learning more

  • PLAN.md — the original migration plan.
  • docs/session-summary.md — a narrative of how the first version was built.
  • docs/development.md — how the codebase is organized.
  • docs/next-steps.md — ideas for what to add or improve next.