# AGENTS.md ## Cursor Cloud specific instructions This section is for Cloud Agents running in an environment where the startup **update script** has already installed dependencies. It captures the non-obvious bits of running Massing here; standard commands and product detail live in the README, `docs/getting-started.md`, and the `.claude/skills/`. ### What the update script already did - Installed the web workspace deps (`npm install`, which also runs `apps/web` `predev` WASM copy). - Ensured **Node 24** is installed via `nvm` (the repo requires `node >=24`; Node 18 breaks the web build). - Installed the Python API deps into the **user site** (`~/.local`): `services/api/requirements.lock` (runtime, hash-pinned, py3.12/Linux — matches prod) and `services/api/requirements-dev.txt` (test/lint: httpx, ruff, bandit, pytest, coverage, DracoPy). `services/data`'s deps are a strict subset of the lock, so there is no separate install for it. ### Services (all run from source in dev) | Service | Path | Dev run command | Port | | --- | --- | --- | --- | | Web (Vite + TS viewer/authoring UI) | `apps/web` | `npm run dev` | 5173 | | API (FastAPI; bundles the `services/data` ifcopenshell library) | `services/api` | `uvicorn aec_api.main:app --reload` | 8000 | Postgres + MinIO are **not** needed for dev: the API defaults to **SQLite** (`sqlite:///./aec.db`) and **local filesystem storage**. Docker/`docker compose --profile full` is the self-host path, not required here. The `converter` (Node IFC→Fragments) is job-style/optional — the API reconverts in-process on publish. ### Node 24 gotcha (important) `/exec-daemon/node` (v22) sits first on `PATH` and **shadows** nvm's Node 24 even after `nvm use 24`. Before any web command, put Node 24 first explicitly: ```bash export NVM_DIR="$HOME/.nvm"; . "$NVM_DIR/nvm.sh"; export PATH="$(nvm which 24 | xargs dirname):$PATH" node -v # must print v24.x ``` ### Running the two dev servers Start each in its own long-lived terminal (e.g. tmux). The web app in dev calls the API at `http://localhost:8000` (see `apps/web/src/api/httpCore.ts`); CORS default already allows the Vite origin. API — must set a **writable local `IFC_DIR`/`STORAGE_DIR`**, otherwise authoring endpoints 500 with `PermissionError: /app` (the defaults are the in-container paths `/app/ifc`, `./storage`): ```bash cd services/api export PYTHONPATH="src:../data/src" # both are required; ':' separator on Linux export PATH="$HOME/.local/bin:$PATH" # uvicorn/ruff console scripts live in ~/.local/bin export IFC_DIR="$PWD/.devdata/ifc" STORAGE_DIR="$PWD/.devdata/storage" mkdir -p "$IFC_DIR" "$STORAGE_DIR" python -m uvicorn aec_api.main:app --host 0.0.0.0 --port 8000 --reload ``` Web (Node 24 activated as above): `cd apps/web && npm run dev`. `AEC_RBAC` defaults to `0` in dev, so writes work without auth (the web app also sends no user). Health check: `curl http://localhost:8000/health` → `{"status":"ok"}`. ### Lint / test / build - Web (Node 24): `cd apps/web && npm run typecheck && npm run lint && npx vitest run` (build: `npm run build`). - API lint (exactly as CI): `cd services/api && python -m ruff check src/ ../data/src/` — **not** `ruff check .` (the `test_*.py` files carry intentional late imports and would report isort noise). - API tests: there is **no pytest entry point**. The runner is `run_tests.py` and must run **from `services/api`** (`PYTHONPATH="src:../data/src" PYTHONUTF8=1 python run_tests.py`). Run a single test the same way: `PYTHONPATH="src:../data/src" python test_.py`. See `.claude/skills/backend-tests`. ### Hello-world (verified working) New model → 3D authoring: welcome screen → **Start a project / New model from scratch** → pick a template (e.g. *Office bay*) → the API authors a real IFC, reconverts to Fragments, and the viewer loads it; clicking an element shows its IFC `GlobalId` + property sets. Backend equivalent: `POST /projects` then `POST /projects/{id}/model/blank`, then poll `GET /projects/{id}/publish/status`.