# 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`. ## Recommended Approach 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 * !.gitignore ``` 2. Add `check-jq` to `Justfile`. 3. 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. 4. Add `check-install: check-craftos check-jq check-luacheck`. 5. Change `install` from `install-git-hooks check-craftos` to `install-git-hooks check-install`. 6. 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. 7. 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 "/={{justfile_directory()}}/"` 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: ```just # 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}}`. 8. Add `repl` as an interactive human-only wrapper. ```just # Human-only interactive REPL. LLM agents must not execute this command. repl: @just craftos --cli ``` 9. Add the same `just repl` restriction to `CLAUDE.md` under constraints or boot/install guidance. 10. Update `DEVELOPMENT.md` requirements to include `jq`. 11. 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: ```sh craftos -d --headless --exec 'print("__READY__"); os.shutdown()' ``` 2. CCEmuX computer-data override: ```sh craftos -d -C --headless --exec 'print("__READY__"); os.shutdown()' ``` 3. Explicit mounts: ```sh craftos -d \ --mount-ro /trapos= \ --mount-ro /apis=/apis \ --mount-ro /programs=/programs \ --mount-ro /servers=/servers \ --mount-ro /startup=/startup \ --headless \ --exec 'print(fs.exists("/apis/net.lua")); print(fs.exists("/trapos/manifest.json")); os.shutdown()' ``` 4. macOS `-d` + `--rom` combo (the recipe's actual shape): ```sh craftos -d \ --rom /Applications/CraftOS-PC.app/Contents/Resources \ --mount-ro /trapos= \ --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.