No description
Find a file
Naomi Persephone Amethyst d87e7f564b
Some checks failed
ci / test (push) Has been cancelled
ci / nix (push) Has been cancelled
ci / binaries (push) Has been cancelled
ci / packages (amd64) (push) Has been cancelled
ci / packages (arm64) (push) Has been cancelled
ci / freebsd-packages (push) Has been cancelled
ci / images (girlc) (push) Has been cancelled
ci / images (girlpages) (push) Has been cancelled
ci / images (girlserve) (push) Has been cancelled
ci / release (push) Has been cancelled
site: close menus as soon as anything else is touched
They closed on a click, but a finger that moves a little, or lands while the
page is still moving, is a scroll to the browser, with no click, so a phone's
tap off a menu could leave it open.
2026-11-06 22:55:53 +00:00
.github/workflows girl on FreeBSD: rendering without man-db, and FreeBSD packages 2026-11-02 01:54:47 +00:00
cmd girlrepo inspect: look around a repository, local or remote 2026-11-06 22:55:43 +00:00
dist freebsd: packages without the ports tree's keywords 2026-11-02 02:23:09 +00:00
doc repo: a page's latest is its main package's 2026-11-06 03:27:35 +00:00
internal site: close menus as soon as anything else is touched 2026-11-06 22:55:53 +00:00
man girlrepo inspect: look around a repository, local or remote 2026-11-06 22:55:43 +00:00
test/e2e site: close menus as soon as anything else is touched 2026-11-06 22:55:53 +00:00
.dockerignore CI: packages and images, of girlpages and of girlc alone 2026-10-19 05:37:05 +00:00
.gitignore girl on FreeBSD: rendering without man-db, and FreeBSD packages 2026-11-02 01:54:47 +00:00
flake.lock Nix: a flake, with packages and a NixOS module 2026-11-02 01:54:36 +00:00
flake.nix Nix: a flake, with packages and a NixOS module 2026-11-02 01:54:36 +00:00
go.mod crawl: Nix channels (crawl nix) 2026-11-02 01:54:19 +00:00
go.sum crawl: Nix channels (crawl nix) 2026-11-02 01:54:19 +00:00
LICENSE girlpages: man, but make it girly 2026-10-10 17:44:15 +00:00
Makefile girl on FreeBSD: rendering without man-db, and FreeBSD packages 2026-11-02 01:54:47 +00:00
README.md girlrepo inspect: look around a repository, local or remote 2026-11-06 22:55:43 +00:00

girlpages 💅

man, but she's ✨ that girl ✨.

girl is a drop-in for man(1) that shows every manual page rewritten in the voice of a Gen Z teenage girl, with colour and emoji, in your terminal. The vibe changes; the facts don't. Every option, argument, default, file, example and caveat from the original is kept, and code and flags are copied exactly.

$ girl ls
ls(1)                         ✨ girl manual ✨                         ls(1)

💖✨ ls(1) ✨💖

       bestie this one lives in the General Commands Manual 🎀 aka the
       everyday-icons section

🌸 NAME 🌸

       ls - List directory contents. Ignore files and directories starting
       with a '.' by default 🙈

       okay so ls is literally the first command you ever learn and she is
       still the main character, no cap 👑 …

(Picture that in pink, purple and mint.)

girlpages.dev

Every page, girlified, on the web: girlpages.dev. Browse by section, read any version from Debian, Ubuntu, Fedora, Arch or upstream (and RFCs, too), and search across all of them, typos and all. It installs as an app and works offline.

It's also the official girlpages repository, at girlpages.dev/repo: girl looks there for a page before asking a model, girlc reads from it, and girlrepo import can mirror it, in a few big packs. See Repositories.

How it works

  1. girl finds the page with man -w, so names, sections and search order are exactly man's.
  2. If it's cached, it's shown right away.
  3. Otherwise the page is rendered to plain text, sent to an LLM (long pages go in parts, split at sections), and the reply is cached. If the model drops any command-line options, that part is retried once.
  4. The result is laid out like a man page and shown in your pager.

The LLM can be the Claude Code CLI (claude -p) or anything that speaks the OpenAI chat completions API: OpenAI, OpenRouter, Ollama, llama.cpp, vLLM, LM Studio and friends.

Install

You need Go 1.25+, man-db, and either the claude CLI or an OpenAI-compatible endpoint.

Just for you

make install-user

This puts girl, girlrepo and girlc in ~/.local/bin, their man pages in ~/.local/share/man, a config file in ~/.config/girlpages/config.toml, and (with systemd) a daily user timer that refreshes pages you've converted. Pages are cached in ~/.cache/girlpages.

Or just go install github.com/NaomiAmethyst/girlpages/cmd/girl@latest.

System-wide

make
sudo make install

This installs girl, girlrepo and girlc to /usr/local (change with PREFIX=), with the config in /etc/girlpages/config.toml and a shared cache in /var/cache/girlpages that every user reads from. It also installs:

  • girlpages-update.service, which runs girl --bulk --update --prune;
  • a hook for each package manager it finds that starts that service when man pages change: an apt DPkg::Post-Invoke hook, a pacman hook, a dnf post-transaction action, and an apk commit hook, with a daily run from crond in /etc/periodic/daily as Alpine has no systemd (HOOKS="apt pacman dnf apk" or HOOKS=none to choose);
  • girlpages-update.timer, a daily run for pages that arrive any other way.

The hooks don't convert anything new until you opt in by girlifying the whole system once:

sudo girl --bulk --dry-run   # how many pages is that, exactly
sudo girl --bulk -j 8        # go off, queen

After that, new and updated pages are converted automatically. Heads up: a typical system has thousands of man pages, so a full run is a lot of LLM calls. You can do it a section at a time (sudo girl --bulk 1 8), and since it's incremental you can interrupt it and pick up where you left off.

make uninstall / make uninstall-user undo it all (config and caches are left alone). Packagers: see the top of the Makefile for DESTDIR and friends.

Packages and images

Each release has deb, rpm, apk and Arch packages for amd64 and arm64: of girlpages, which is everything above with each package manager's hook and the daily timer, and of girlc alone, one static binary with no dependencies. There are images too, ghcr.io/naomiamethyst/girlpages (everything, on Debian) and ghcr.io/naomiamethyst/girlc (girlc on its own, from scratch):

docker run --rm ghcr.io/naomiamethyst/girlc ls

Nix

The repository is a flake, with girlpages (everything) and girlc, for Linux and macOS:

nix run github:NaomiAmethyst/girlpages#girlc -- ls      # read a page
nix profile install github:NaomiAmethyst/girlpages      # girl, girlrepo, girlc, girlserve

On NixOS, its module installs girlpages with its config and girlifies what's new daily (Nix has no hooks for when packages change):

{
  inputs.girlpages.url = "github:NaomiAmethyst/girlpages";
  # … then, in a configuration's modules: girlpages.nixosModules.default, and
  services.girlpages = {
    enable = true;
    settings.openai = { base_url = "https://api.example.org/v1"; model = "some-model"; api_key_file = "/run/secrets/girlpages"; };
  };
}

make packages builds the packages here (it needs nfpm; ARCH=arm64 for the other one).

Each release has binaries for using without a package manager, too: girlc on its own for Linux (amd64, arm64, ARMv6, 386, riscv64), macOS, Windows and FreeBSD (girlc-VERSION-OS-ARCH, one static binary to copy anywhere), and everything, girl, girlrepo, girlc and girlserve with their man pages and an example config, in a tarball per Linux architecture (girlpages-VERSION-linux-ARCH.tar.gz), and the same for FreeBSD amd64 and arm64. make binaries builds them here.

FreeBSD packages are in each release too, of girlpages (with a pkg trigger and a daily periodic script to keep it fresh) and girlc alone, for FreeBSD 14 and 15 on amd64 and aarch64: pkg add girlpages-VERSION-freebsd15-amd64.pkg. Without man-db, as on FreeBSD, girl renders pages with mandoc (or groff).

In your browser

girl --serve                  # http://localhost:1989/
girl --html ls > ls.html      # one page, as a self-contained web page

--serve lets you browse pages by section, search names and descriptions, and read them with a table of contents, clickable references to other pages, and a link to the boring original. It's pink and white in light mode and purple and black in dark mode, following your browser (or the 🌗 button).

Open a page that isn't girlified yet and it's converted on the spot, with a waiting page that refreshes itself until she's ready. Everyone asking for the same page shares one conversion, and no more than jobs requests hit the model at once.

There's no login, so anyone who can reach it can spend your LLM budget: keep it on localhost (the default), or use --cached-only to only serve what's already converted (sudo girl --bulk first, say).

Repositories

Someone already girlified the page you want? A girlpages repository is a pile of ready-made pages on a static web host, and girl reads from one before bothering a model: girlpages.dev's, unless you say otherwise.

[repo]
url = "https://girlpages.dev/repo"   # the default; another, or "none" for none

Only the hash of a page's source is sent (to fetch its conversion), so the repo learns which pages you read, and nothing else; url = "none" (or GIRLPAGES_REPO=none) asks nothing of anyone but your model.

Pages are matched by the hash of their roff source, so you only ever get a conversion of exactly the version you have installed; anything else is converted as usual. With a repo set, girl --bulk works even with no model at all, caching everything the repo has. girl --import URL-or-dir does the same for a repo you name, and girl --export DIR writes your own cache out as a repo, with the package each page came from, for other machines to import or for girlrepo to publish.

A repo has more than what's installed here, too: girl -r reads any page in it, in any version and variant, and searches it.

girl -r open                                  # open(1), installed or not
girl -r --versions ls                         # the versions it has, and how to pick them
girl -r --page-version debian/bookworm ls     # one of them (or 9.4, or a hash)
girl -r --variant man ls                      # the original
girl -r -k "list directory"                   # search it (--ngrams for words inside words)

girlc

Just want to read? girlc is the same thing on its own: a little man that only reads from a repo. It's one static binary (about 7 MB) with no config file and no libraries, and brings its own root certificates for systems that have none. So it's a light way to keep the manual in containers that leave man pages out for their size: copy it into any image, even scratch, and it reads from girlpages.dev (or another repo, with GIRLC_REPO or --repo; packagers can build in another with make girlc GIRLC_REPO=https://…/repo). It takes the same options as girl -r (make install and make install-user put it in too, or make girlc && sudo make install-girlc installs just it; documented in girlc(1)).

COPY girlc /usr/local/bin/girlc
girlc ls                # ls(1), girlified
girlc -a printf         # printf(1), (3) and (3p)
girlc -k compress       # search
girlc rfc 9110          # an RFC
girlc --markdown https://girlpages.dev/girl/1/ls/   # a page by its address, as Markdown

Running one

girlrepo (installed with girl, documented in girlrepo(1)) builds them:

girlrepo ingest --distro debian --release trixie pool/*.deb   # or .pkg.tar.zst, .rpm, .apk, dirs
girlrepo ingest --system                                      # or this machine, with its packages
girlrepo ingest git+https://github.com/tmux/tmux#3.5a          # or a git repository
girlrepo crawl apt https://deb.debian.org/debian trixie        # or a whole package repository
girlrepo crawl rpm https://dl.rockylinux.org/pub/rocky/9/BaseOS/x86_64/os
girlrepo crawl pacman https://geo.mirror.pkgbuild.com/core/os/x86_64
girlrepo crawl apk https://dl-cdn.alpinelinux.org/alpine/v3.22/main/x86_64
girlrepo crawl freebsd https://download.freebsd.org/releases/amd64/15.1-RELEASE
girlrepo crawl nix https://channels.nixos.org/nixos-25.05
girlrepo girlify -n --price 0.10/0.50                         # what would it cost?
girlrepo import https://girlpages.dev                          # merge in another repo (by its packs)
girlrepo export /srv/packs                                    # or write yours as just packs, for mirroring
girlrepo girlify -j 8                                         # convert what isn't yet
girlrepo girlify --variant pirate fork                        # or in another voice (--list-variants)
girlrepo girlify --variant pirate --names-from common.txt      # or for a list of pages
girlrepo publish --url https://example.org --gc               # index it and build the site
girlrepo serve                                                # preview at localhost:1990
girlrepo inspect describe 'ls(1)'                             # and look around one (below)

publish also writes .zst, .br and .gz copies of everything for the web server to send as they are (dist/hosting/ has configurations for static-web-server and nginx, with the right cache lifetimes), and girl asks for them. That leaves a directory to upload anywhere static: the repo in repo/, and a website around it. The static part is what search engines see: a page for the latest version (by version number) of every man page, girlified, with OpenGraph, JSON-LD and a sitemap, plus section listings (in pages of 200, which scroll on into each other with JavaScript). Everything else runs in the browser straight from the repo: originals, older versions, browsing just Debian, just Debian trixie, just Arch or upstream git, and search over every page and version from every source (typos and all: grpe finds grep). Pages made in other voices too (girlrepo girlify --variant pirate) offer each, and any of them can be made your default, kept in your browser. The site installs as an app (a PWA) and works offline: every repo file fetched is kept (in the browser's Cache API), so pages you've read and searches you've done work without a network, and offline the app renders any page from them. Each section listing has a "save for offline" button that fetches all its pages, and the search page has ones that fetch the search index (and, separately, its typo matching), and a switch to show only pages that are saved. When it notices a newly published repository it clears out the search index files that aren't used any more, in the background. Each page version is stored once however many packages, distros or aliases share it, and everything but a few pointer files is content-addressed, so it caches forever and syncs cheaply. The format is specified in doc/repo-format.md, for anyone building tools against it.

A repo is a couple of million small files, though, which is a lot of requests to mirror. So publish also writes them as packs: a log of zstd-compressed tar files of about 256 MiB (the whole girlpages repo's 8 GiB is 670 MiB of them), with each publish's changes in a small tail pack. girlrepo import syncs a repo by its packs when it has them, fetching just the new ones next time; girlrepo export DIR writes a pack-only copy to host anywhere, adding to it each time; and girlrepo unpack merges packs you've copied over by hand.

Serving millions of small files off a network file system is slow too, so a site can also live in an archive: a few big files (segments of every file's bytes, stored in each form it's sent in, and an index of where they are) that girlrepo publish --archive DIR adds each new version to, writing only what changed; publish --from-packs PACKS --archive DIR goes straight from packs to an archive, with no repository (or millions of files) between. girlserve, a small static server (and image) of its own, serves an archive, sending each file in the smallest form the client accepts, and switches to new versions as they're written, with health checks, Prometheus metrics and access logs for running it in production. dist/hosting/kubernetes.yaml has a CronJob that publishes packs dropped into a directory into an archive and a Deployment serving it; doc/archive-format.md has the format.

Looking around a repository

girlrepo inspect shows a repository on its own terms, without parsing its JSON yourself, whether it's yours or someone else's: a remote repository has no directory listings, so everything's reached by following what refers to it, from manifest.json down. Use --repo for anywhere but -d's.

girlrepo inspect ls                                  # chapters, sections, pages, sources, packs, index
girlrepo inspect ls chapters/3                       # its pointer, sections and shards
girlrepo inspect ls chapters/3 --follow              # its pages
girlrepo inspect describe 'ls(1)'                    # its versions, and why the current one's current
girlrepo inspect ls 'ls(1)/girl'                     # its headings and older generations
girlrepo inspect cat 'ls(1)@9.7/man'                 # a variant of a version
girlrepo inspect cat index/words/grep                # the pages a word's in, by name
girlrepo inspect get index/packages/coreutils        # the JSON
girlrepo inspect get page/page-53/53a4….json --raw   # or a file by its path, as it's stored
girlrepo inspect describe packs/2 --repo https://girlpages.dev  # a pack's header
girlrepo inspect check 'ls(1)' -r                    # is it right, and all that's under it?
girlrepo inspect check / -r --sample 20 --repo https://girlpages.dev
girlrepo inspect check stray.json --as page          # a file of yours, as a listing

ls shows the addresses it can take. check looks at each thing's hash, shape and what follows from it (is current what its versions make it?), and exits non-zero if anything's wrong.

Configure

# ~/.config/girlpages/config.toml (or /etc/girlpages/config.toml)
backend = "auto"   # auto, claude or openai

[claude]
model = "haiku"   # the default

[openai]
base_url = "http://localhost:11434/v1"   # Ollama, for example
model = "qwen3:32b"

[repo]
url = ""   # a girlpages repository to try first

Converting thousands of pages? Batch APIs are about half the price, and girl --bulk (and girlrepo girlify) can use OpenRouter's or Anthropic's:

batch = "anthropic"            # or "openrouter", which uses [openai]

[anthropic]
api_key_file = "/etc/girlpages/anthropic-key"   # or ANTHROPIC_API_KEY
model = "claude-haiku-5-5"

Results come back within a day. Runs save what they sent, so Ctrl-C is fine and --no-wait just sends them off; the next run collects them.

Use more than one service? Name each in a [providers.NAME] section (any of [openai]'s settings, or [claude]'s with backend = "claude", and its own batch) and switch with --provider instead of editing the file:

provider = "sail"   # the default; must come before any [section]

[providers.sail]
base_url = "https://api.sailresearch.com/v1"
model = "deepseek-ai/DeepSeek-V4.1-Flash"
api_key_file = "/home/me/.config/girlpages/sail-key"
api = "responses"
completion_window = "flex"
batch = "sail"

[providers.haiku]
backend = "claude"
model = "claude-haiku-5-5"
girlrepo girlify --provider haiku -j 8

girl --doctor shows what it's actually using. Everything is documented in dist/config.toml and in girl girl. Want a different vibe? Set extra_instructions.

Usage

All the man you know:

girl ls                 # a page
girl 3 printf           # from a section
girl -a printf          # every match
girl -k compress        # apropos (straight to man -k)
girl -l ./thing.1       # a local file

Plus:

girl --refresh tar      # convert again
girl --model opus tar   # with a different model
girl --no-cache tar     # ask the LLM fresh, without saving it
girl --cached-only ls   # never call the LLM
girl --repo URL ls      # try a girlpages repository first
girl -r ls              # read it from the repository instead
girl --raw ls           # the stored markup
girl --doctor           # config, backend and cache status
girl --html ls          # a standalone HTML page
girl --serve            # the web app
girl --bulk [sections]  # convert everything

Hacking

make check   # tests, vet, gofmt
make e2e     # the website's navigation in a real browser (needs Python playwright)
make lint    # type-check the website's JavaScript (needs npx)

Pages are stored as a small Markdown dialect with colour tags (<pink>…</pink> and friends, plus <rainbow>), and <bold>, <italic> and <kbd>, which models write anyway; internal/render turns that into ANSI or HTML, and internal/web is the server, templates and stylesheet. The prompt lives in internal/llm/prompt.go. Repositories are internal/repo (format, store, client, search index), internal/ingest and internal/site; the browser search in internal/site/static/search.js must match internal/repo/search.go, which a test checks with node.

License

Copyright © 2026 Naomi Persephone Amethyst naomi@amethyst.name

girlpages is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See LICENSE.