claude-scriptorium 0.1.3

Render Claude Code sessions as self-contained HTML
Documentation
#!/usr/bin/env just --justfile

set dotenv-load
set ignore-comments

pre-commit-args := ""
cargo-test-args := ""
watch-paths := "src Cargo.toml justfile"

fonts-dir := "src/fonts"
junicode-ref := "v2.226"
firacode-version := "6.2"
unifrakturcook-ref := "e98889587d96e21ad9d93030fe37ccd9f6c50995"

[default]
[doc("List available recipes")]
list:
    @just --list

[doc("Initial repo setup after cloning")]
setup:
    rustup toolchain install nightly --component rustfmt
    uvx pre-commit install
    uvx --from playwright playwright install chromium

[doc("Run tests")]
[group("rust")]
test *args:
    cargo test --all-features {{ cargo-test-args }} {{ args }}

alias t := test

# rustfmt.toml uses unstable options, which only nightly rustfmt honours.

[doc("Format Rust code")]
[group("rust")]
format:
    cargo +nightly fmt

[doc("Lint Rust code, fixing what clippy can fix on its own")]
[group("rust")]
lint:
    cargo clippy --all-targets --all-features --fix --allow-dirty --allow-staged
    cargo clippy --all-targets --all-features -- -D warnings

[doc("Render a session to an HTML file")]
[group("rust")]
render *args:
    cargo run -- render {{ args }}

alias r := render

[doc("Serve a session with live reload, rebuilding and restarting on source changes")]
[group("rust")]
serve *args:
    #!/usr/bin/env bash
    set -uo pipefail
    if ! command -v fswatch >/dev/null 2>&1; then
      echo "just serve needs fswatch (apt install fswatch / brew install fswatch)" >&2
      exit 1
    fi
    bin="target/debug/claude-scriptorium"
    trap 'kill "${pid:-}" 2>/dev/null || true' EXIT
    while true; do
      pid=""
      if cargo build -q; then
        "$bin" serve {{ args }} &
        pid=$!
      else
        echo "build failed; waiting for changes" >&2
      fi
      fswatch -1 {{ watch-paths }} >/dev/null
      [[ -n "$pid" ]] && kill "$pid" 2>/dev/null
      [[ -n "$pid" ]] && wait "$pid" 2>/dev/null
    done

alias s := serve

[doc("Rerun a recipe when sources change, e.g. `just watch render <session>`")]
[group("rust")]
watch recipe *args:
    #!/usr/bin/env bash
    set -euo pipefail
    if ! command -v fswatch >/dev/null 2>&1; then
      echo "just watch needs fswatch (apt install fswatch / brew install fswatch)" >&2
      exit 1
    fi
    run() { just {{ recipe }} {{ args }} || true; }
    run
    fswatch -o {{ watch-paths }} | while read -r _; do run; done

alias w := watch

[doc("Vendor the embedded web fonts and their licenses from upstream")]
[group("rust")]
fonts:
    #!/usr/bin/env bash
    set -euo pipefail
    for tool in curl unzip uvx; do
      command -v "$tool" >/dev/null 2>&1 || { echo "just fonts needs $tool" >&2; exit 1; }
    done
    dir="{{ fonts-dir }}"
    lic="$dir/licenses"
    mkdir -p "$lic"
    tmp="$(mktemp -d)"
    trap 'rm -rf "$tmp"' EXIT
    get() { echo "  ${2#"$dir"/}" >&2; curl -sSfL "$1" -o "$2"; }

    # Junicode 2 (SIL OFL): humanist medievalist serif for body text. One
    # variable file per posture spans every weight; italic is a separate face.
    juni="https://raw.githubusercontent.com/psb1558/Junicode-font/{{ junicode-ref }}"
    get "$juni/webfiles/JunicodeVF-Roman.woff2" "$dir/JunicodeVF-Roman.woff2"
    get "$juni/webfiles/JunicodeVF-Italic.woff2" "$dir/JunicodeVF-Italic.woff2"
    get "$juni/OFL.txt" "$lic/Junicode-OFL.txt"

    # Fira Code (SIL OFL): monospace for code and tool output. The variable
    # woff2 ships only inside the release zip; its license lives in the repo.
    fira="https://github.com/tonsky/FiraCode"
    get "$fira/releases/download/{{ firacode-version }}/Fira_Code_v{{ firacode-version }}.zip" "$tmp/fira.zip"
    unzip -p "$tmp/fira.zip" "woff2/FiraCode-VF.woff2" > "$dir/FiraCode-VF.woff2"
    echo "  FiraCode-VF.woff2" >&2
    get "https://raw.githubusercontent.com/tonsky/FiraCode/{{ firacode-version }}/LICENSE" "$lic/FiraCode-OFL.txt"

    # UnifrakturCook (SIL OFL): single-weight blackletter for headings. Cyreal's
    # repo is the upstream and carries the license beside the font, but ships a
    # TTF, so compress it to woff2 here.
    uc="https://raw.githubusercontent.com/cyrealtype/UnifracturCook/{{ unifrakturcook-ref }}"
    get "$uc/UnifrakturCook-Regular-nohints.ttf" "$tmp/UnifrakturCook.ttf"
    get "$uc/OFL.txt" "$lic/UnifrakturCook-OFL.txt"
    echo "  UnifrakturCook.woff2" >&2
    uvx --with brotli --from fonttools fonttools ttLib.woff2 compress \
      -o "$dir/UnifrakturCook.woff2" "$tmp/UnifrakturCook.ttf"

[doc("Clean build artifacts")]
[group("rust")]
clean:
    cargo clean

[doc("Upgrade Rust dependencies")]
[group("rust")]
upgrade:
    cargo update

# Sequential on purpose: `cargo fmt` and `cargo clippy --fix` rewrite the same
# files, so running them concurrently races.

[private]
fix-code: format lint

[doc("Run pre-commit checks")]
[group("checks")]
fix:
    git add --update
    uvx pre-commit run {{ pre-commit-args }}

alias f := fix

[doc("Run all checks (formatting, linting, testing)")]
[group("checks")]
check: fix test

alias c := check

[doc("Upgrade pre-commit hooks")]
[group("checks")]
pre-commit-upgrade:
    uvx pre-commit autoupdate