The Hatchery Field Guide

Install

One line, and nothing hidden

Every pet on hatchery.world installs into Codex with one line in a terminal. Before you run it, here is everything it does, and the whole script, so you can check for yourself.

What it does

  1. Downloads the pet's checksum list, then its two files, pet.json and its sprite sheet, from hatchery.world over HTTPS, into a temporary folder.
  2. Checks every file against its SHA-256 checksum, and stops before installing anything if one differs.
  3. Moves the pet into Codex's pets folder, ${CODEX_HOME:-$HOME/.codex}/pets/<pet>/, the folder OpenAI's pet format names.

What it never does

  • Ask for a password or use sudo. It refuses to run as root.
  • Run anything it downloads. A pet is a picture and a short description, nothing more.
  • Touch anything outside that one pet's folder, or replace a pet you already have unless you add --force.

Three ways to install

One line

curl -fsSL https://hatchery.world/install.sh | sh -s -- bookworm

Read it first

curl -fsSLO https://hatchery.world/install.sh
less install.sh
sh install.sh bookworm

By hand

Download a pet's zip from its page and unzip it into ~/.codex/pets/. Each zip's checksum is on its page.

The installer works on macOS and Linux. On Windows, use the zip.

The script

This is the exact file served at https://hatchery.world/install.sh: 5 KB, SHA-256 3a7e7bfb2748f5b686043ffa1e538678dfc8fc3315fbe333b4ac2f442893e144. To check a copy you downloaded: shasum -a 256 install.sh. Each pet's checksums are at https://hatchery.world/<pet>/install.sha256, and all of them are in pets.json.

#!/bin/sh
# The Hatchery installer. https://hatchery.world/install/
#
# Puts one Hatchery pet into Codex. It is short on purpose, so you can read all of it before you run it.
#
#   curl -fsSL https://hatchery.world/install.sh | sh -s -- bookworm
#   sh install.sh bookworm            (after downloading this file and reading it)
#   sh install.sh bookworm --force    (replace a copy that is already installed)
#
# What it does, and nothing else:
#   1. Downloads the pet's checksum list, then its two files (pet.json and its sprite sheet) from
#      hatchery.world over HTTPS, into a temporary folder.
#   2. Checks every file against its SHA-256 checksum, and stops before installing anything if one differs.
#   3. Moves the pet into Codex's pets folder: ${CODEX_HOME:-$HOME/.codex}/pets/<pet>/
#
# It never asks for a password, never uses sudo, never runs anything it downloads, and never touches
# anything outside that one pet's folder. A pet that is already installed is left alone unless you add --force.

set -eu

SITE="${HATCHERY_URL:-https://hatchery.world}"   # change only to test against a local copy of the site
USAGE="usage: sh install.sh <pet> [--force]   for example: sh install.sh bookworm"

say() { printf '%s\n' "$*"; }
fail() { printf 'hatchery: %s\n' "$*" >&2; exit 1; }

PET=""
FORCE=0
for arg in "$@"; do
  case "$arg" in
    --force) FORCE=1 ;;
    -h | --help) say "$USAGE"; exit 0 ;;
    -*) fail "unknown option $arg. $USAGE" ;;
    *) [ -z "$PET" ] || fail "one pet at a time. $USAGE"; PET="$arg" ;;
  esac
done

# A pet's name is lower-case letters, digits and hyphens, as in its address on hatchery.world.
case "$PET" in
  "" | -* | *[!a-z0-9-]*) fail "give the pet's name as it appears in its hatchery.world address. $USAGE" ;;
esac

[ "$(id -u)" -ne 0 ] || fail "run this as yourself, not with sudo or as root: Codex's pets live in your own folder"

command -v curl >/dev/null 2>&1 || fail "curl is needed to download the pet, and it isn't installed"
if command -v sha256sum >/dev/null 2>&1; then
  digest() { sha256sum "$1" | cut -d ' ' -f 1; }
elif command -v shasum >/dev/null 2>&1; then
  digest() { shasum -a 256 "$1" | cut -d ' ' -f 1; }
elif command -v openssl >/dev/null 2>&1; then
  digest() { openssl dgst -sha256 -r "$1" | cut -d ' ' -f 1; }
else
  fail "no SHA-256 tool found (sha256sum, shasum or openssl), so the download can't be checked"
fi

case "$SITE" in
  https://*) SECURE="--proto =https --tlsv1.2" ;;   # HTTPS only, including redirects
  *) SECURE="" ;;
esac
fetch() {   # fetch <path on the site> <file>
  # shellcheck disable=SC2086  # $SECURE is two options, or none
  curl -fsSL --retry 2 $SECURE -o "$2" "$SITE/$1"
}

PETS="${CODEX_HOME:-$HOME/.codex}/pets"
DEST="$PETS/$PET"
if [ -e "$DEST" ] && [ "$FORCE" -ne 1 ]; then
  say "$PET is already installed, in $DEST. Left as it is."
  say "To replace it with the copy on hatchery.world, run the same command with --force at the end."
  exit 0
fi

TMP=$(mktemp -d "${TMPDIR:-/tmp}/hatchery.XXXXXX") || fail "could not make a temporary folder"
STAGE=""
trap 'rm -rf "$TMP" ${STAGE:+"$STAGE"}' EXIT
trap 'exit 1' INT TERM HUP

say "Installing $PET from $SITE"
fetch "$PET/install.sha256" "$TMP/install.sha256" || fail "there is no pet called $PET on $SITE (or the site can't be reached)"

mkdir "$TMP/$PET"
COUNT=0
while read -r WANT NAME; do
  case "$NAME" in "" | .* | *[!A-Za-z0-9._-]*) fail "the checksum list names an unexpected file; nothing was installed" ;; esac
  case "$WANT" in *[!0-9a-f]*) fail "the checksum list is not well formed; nothing was installed" ;; esac
  [ "${#WANT}" -eq 64 ] || fail "the checksum list is not well formed; nothing was installed"
  fetch "$PET/$NAME" "$TMP/$PET/$NAME" || fail "could not download $NAME; nothing was installed"
  [ "$(digest "$TMP/$PET/$NAME")" = "$WANT" ] || fail "$NAME does not match its checksum; nothing was installed"
  say "  $NAME  sha256 $WANT  checked"
  COUNT=$((COUNT + 1))
done < "$TMP/install.sha256"

[ "$COUNT" -ge 2 ] && [ -f "$TMP/$PET/pet.json" ] || fail "the download is incomplete; nothing was installed"
grep -q "\"id\"[[:space:]]*:[[:space:]]*\"$PET\"" "$TMP/$PET/pet.json" || fail "pet.json is for a different pet; nothing was installed"

mkdir -p "$PETS"
STAGE="$PETS/.hatchery-$PET-$$"   # assembled beside its final place, then renamed in one step
cp -R "$TMP/$PET" "$STAGE"
if [ -e "$DEST" ]; then rm -rf "$DEST"; fi   # reached only with --force
mv "$STAGE" "$DEST"
STAGE=""

say "Installed $PET in $DEST"
say "In Codex: Settings > Appearance > Pets. If $PET isn't listed yet, quit Codex and open it again."