# ADR 0010: ccpm Package Manager ## Status Accepted ## Date 2026-06-08 ## Context The previous install flow (a `LIST_FILES` table inside `install.lua` and a single flat `manifest.json` read by `wget`) was all-or-nothing. There was no way to install just networking or just the UI, and no way to add or remove pieces of the OS after the initial install. The project also outgrew the "Trap's ComputerCraft APIs" framing into a small in-game OS — TrapOS — with a name, a version, a boot banner, and a persisted beta channel; that motion is preserved here even though the original install mechanism is fully replaced. We want a package manager, `ccpm` ("ComputerCraft Package Manager"), installed first as a standalone user-facing step. After that, a machine can `ccpm update`, `ccpm install trapos`, `ccpm install trapos-net`, `ccpm uninstall trapos-ui`, and manage where packages come from. TrapOS itself is installed through a `trapos` meta-package; the `wget run .../install-ccpm.lua` bootstrap exists only to install `ccpm`. ## Decision ### Packages are descriptors over the existing tree Source files stay where they are (`apis/`, `programs/`, `servers/`, `startup/`); their install targets remain the same absolute CC paths, so `require` paths and the dev mounts are unchanged. A package is a descriptor that *references* those files: `packages//ccpm.json` with `{ name, version, description, dependencies, files, autostart }`. `packages/index.json` lists the packages a registry offers (for `ccpm search`). There is no `ccpm.json` at the repo root. The split is finer-grained than the install examples imply: | package | contents | deps | |----------|-----------------------------------------------------------------|----------| | trapos-core | ccpm, libccpm, eventloop, upgrade, events | — | | trapos-test | libtest, runtest | trapos-core | | trapos-boot | motd, servers (startup) | trapos-core | | trapos-net | net, router, ping, ping-server | trapos-core | | trapos-ui | libtui, tuidemo | trapos-core | | trapos-ai | AI client for opencode serve | trapos-core | | trapos | full TrapOS meta-package | trapos-boot, trapos-net, trapos-ui, trapos-test, trapos-ai | ### Two files for ccpm, "manifest" reserved for the OS To avoid colliding with the OS `manifest.json`, ccpm never uses the word "manifest". Local state lives under `/trapos`: - `ccpm.json` — ordered registry list `{ registries = { { name, type, branch } } }`. `type` is `gitea` (resolves to `git.trapcloud.fr//raw/branch//`, the default seeded by the bootstrap), `github` (resolves to `raw.githubusercontent.com///`, deprecated but still supported), or `http`/`https` (the `name` is a base URL). - `ccpm.lock.json` — installed packages `{ packages = { = { version, registry, files, dependencies, autostart } } }`, used by `ls`, `uninstall`, and `reinstall`. - `ccpm.cache.json` — packages advertised by configured registries, written by `ccpm update` from each registry's `packages/index.json`, used by `ccpm search`, `ccpm available`, and `ccpm upgrade`. `apis/libccpm.lua` is the testable core (a factory; `http`/`stateDir`/`installRoot` are injectable for tests). `programs/ccpm.lua` is a thin CLI over it. ### The bootstrap installs only ccpm `install-ccpm.lua` resolves only the `trapos-core` package descriptor (pulling any future dependencies), downloads its files, and writes: - `/trapos/manifest.json` — the aggregated `{ name, version, branch, files, autostart }` still consumed by `startup/motd.lua` and `startup/servers.lua` after boot packages are installed. This is the surviving piece of the previous manifest-driven install: it is no longer the install source of truth (each package's `ccpm.json` is), but it is still the local system state used at boot for the colored `TrapOS v` banner and to read the `autostart` list. `branch` is the persisted beta opt-in (a single confirmed `--beta` install switches subsequent `ccpm upgrade` runs to `next` with no flag needed; `--stable` is the symmetric opt-out); - `/trapos/ccpm.lock.json` — so right after a fresh install `ccpm install trapos-core` correctly reports "already installed"; - `/trapos/ccpm.json` — seeding/refreshing the default `guillaumearm/cc-libs` registry to track the install branch. The install path is: - `wget run .../install-ccpm.lua` — install `ccpm` (`trapos-core`) and seed the default registry. - `ccpm update` — refresh the local package cache. - `ccpm install trapos` — install the full OS. During beta, `trapos` includes `trapos-test` by default. On a subsequent `upgrade`, `programs/upgrade.lua` delegates to `ccpm upgrade`, which upgrades installed packages using `/trapos/ccpm.cache.json`. Users run `ccpm update` first to refresh available versions. ## Consequences - The repo gains a `packages/` descriptor tree; the flat source layout is untouched. - `just trapos` (formerly `just craftos`; see [ADR-0005](adr-0005-craftos-pc-harness-and-probes.md)) no longer derives mounts from `manifest.json .files` (it is now `.packages`); it mounts a fixed list of top-level dirs instead. `just test` was already on fixed mounts and is unaffected. - ccpm logic is covered by `tests/ccpm.lua` (URL resolution, dependency ordering, cycle/missing detection, already-installed, registry CRUD, cache update, available status, upgrade, uninstall dependency guard) with an injected `http` stub — no network in tests. ## Future Work - Version ranges (today a single pinned version per package). - http/https registries beyond a plain base URL (auth, caching).