cc-libs/.plans/craftos-just-plan.md

6.8 KiB

CraftOS Just Recipes Plan

Goal

Add repository-local CraftOS-PC launch recipes:

  • just craftos launches CraftOS-PC with repo-local save data under .craftos.
  • just repl launches the same environment with --cli for human interactive use only.
  • just install delegates tool validation to a new just check-install recipe.

Findings

CraftOS-PC docs confirm -d / --directory changes the save data root. With -d .craftos, generated files should be under .craftos/config/global.json, .craftos/config/0.json, and .craftos/computer/0/, not directly as .craftos/global.json.

CraftOS-PC docs describe -C as a CCEmuX compatibility flag for the computer-data directory. Do not make -C . the default unless an isolated probe proves it gives the desired repo layout without polluting the repository root.

just runs recipes from the Justfile directory by default, and justfile_directory() is available. Use it to anchor .craftos and mount paths to the repository root even when just is invoked from a subdirectory.

manifest.json already lists the shipped files. Its top-level directories are currently startup, servers, programs, and apis.

Use --directory for persistent CraftOS-PC state and command-line mounts for repository files.

Do not use -C . as the normal path to expose this repository. It controls saved computer contents, while --mount-ro directly models the intended behavior: make repo files available in the ComputerCraft filesystem without copying them into a VM save.

Mount the repository root at /trapos so existing code can read /trapos/manifest.json. Also mount each manifest top-level directory at its ComputerCraft root path, for example /apis, /programs, /servers, and /startup.

Prefer read-only mounts by default. Use explicit caller-provided args for unusual cases.

Implementation Steps

  1. Add .craftos/.gitignore so the directory exists in the repo while generated CraftOS-PC state stays untracked.
*
!.gitignore
  1. Add check-jq to Justfile.

  2. Add check-luacheck to Justfile so install checks every host-side CLI requirement explicitly.

    check-jq and check-luacheck are tool-presence checks only (verify the binary is on $PATH), mirroring the shape of the existing check-craftos. They do not run lint.

  3. Add check-install: check-craftos check-jq check-luacheck.

  4. Change install from install-git-hooks check-craftos to install-git-hooks check-install.

  5. Change check to depend on check-luacheck and keep luacheck . as the command. Because check-luacheck only verifies presence (step 3), no lint runs twice.

    Leave ci: check-craftos check unchanged. ci deliberately does not pull in check-jq: the pre-commit path does not use jq, so adding it would inflate the hook's required tooling for no benefit. The two dep chains (install → check-install, ci → check-craftos + check → check-luacheck) are intentional, not an oversight to unify later.

  6. Add craftos *args: check-install.

Recommended behavior:

  • Use --directory "{{justfile_directory()}}/.craftos".
  • Keep the existing macOS --rom /Applications/CraftOS-PC.app/Contents/Resources workaround.
  • Add --mount-ro "/trapos={{justfile_directory()}}".
  • Use jq over manifest.json to derive unique top-level directories and add one --mount-ro "/<dir>={{justfile_directory()}}/<dir>" per directory.
  • Forward user args after the recipe defaults. just forwards flags to variadic recipes normally, so callers should use commands like just craftos --headless and just craftos --exec 'print("__READY__"); os.shutdown()' without an extra -- separator.

Build the recipe-generated argv with quoted arguments and invoke craftos with forwarded user args as "$@" so paths and --exec code containing spaces survive. Do not use unquoted $mount_args word splitting, and do not use {{args}} for forwarding because it does not preserve shell quoting.

Sketch:

# Launch CraftOS-PC with repo-local data and read-only repo mounts.
# Pass args through to `craftos`, for example:
#   just craftos --headless --exec 'print("__READY__"); os.shutdown()'
[positional-arguments]
craftos *args: check-install
    #!/usr/bin/env bash
    set -euo pipefail
    repo='{{justfile_directory()}}'
    argv=(--directory "$repo/.craftos")
    if [ "$(uname -s)" = "Darwin" ]; then
        argv+=(--rom /Applications/CraftOS-PC.app/Contents/Resources)
    fi
    argv+=(--mount-ro "/trapos=$repo")
    while IFS= read -r dir; do
        argv+=(--mount-ro "/$dir=$repo/$dir")
    done < <(jq -r '.files[] | split("/")[0]' manifest.json | sort -u)
    exec craftos "${argv[@]}" "$@"

Verify the shebang recipe receives variadic args as "$@" before committing. If it does not, use a linewise [positional-arguments] recipe that delegates to bash -c '...' craftos "$@"; do not fall back to {{args}}.

  1. Add repl as an interactive human-only wrapper.
# Human-only interactive REPL. LLM agents must not execute this command.
repl:
    @just craftos --cli
  1. Add the same just repl restriction to CLAUDE.md under constraints or boot/install guidance.

  2. Update DEVELOPMENT.md requirements to include jq.

  3. If the final mount behavior changes the local harness contract, update docs/install-craftos-pc.md or ADR-0005 accordingly.

Probe Before Finalizing Mounts

Run probes in /var/folders/l7/c5f9hw8j52zg5rx0vwz7_4d40000gn/T/opencode, not in the repository first.

Compare these cases:

  1. Plain repo-local data directory:
craftos -d <temp-data> --headless --exec 'print("__READY__"); os.shutdown()'
  1. CCEmuX computer-data override:
craftos -d <temp-data> -C <temp-computers> --headless --exec 'print("__READY__"); os.shutdown()'
  1. Explicit mounts:
craftos -d <temp-data> \
  --mount-ro /trapos=<repo> \
  --mount-ro /apis=<repo>/apis \
  --mount-ro /programs=<repo>/programs \
  --mount-ro /servers=<repo>/servers \
  --mount-ro /startup=<repo>/startup \
  --headless \
  --exec 'print(fs.exists("/apis/net.lua")); print(fs.exists("/trapos/manifest.json")); os.shutdown()'
  1. macOS -d + --rom combo (the recipe's actual shape):
craftos -d <temp-data> \
  --rom /Applications/CraftOS-PC.app/Contents/Resources \
  --mount-ro /trapos=<repo> \
  --headless \
  --exec 'print(fs.exists("/trapos/manifest.json")); os.shutdown()'

This must succeed before committing the macOS branch of the recipe — -d changes data-root resolution, and the existing test recipe only proves --rom works without -d.

Choose mounts unless -C demonstrably gives the desired repo-root ComputerCraft layout without repository pollution.

Verification

Run:

  • just --list
  • just check-install
  • just craftos --help
  • just craftos --headless --exec 'print("__READY__"); os.shutdown()'
  • just test
  • git status --short

Do not run just repl as an LLM agent.