diff --git a/.gitignore b/.gitignore index 419ef53..ec7ed9a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ # build info tsconfig.build.tsbuildinfo + +# local secrets +.env diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..064279c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,25 @@ +# MeshBot — Workflow Rules + +## Change process (mandatory) + +1. **Small, incremental changes** — work is done one change at a time, in smaller parts; each part must be tested thoroughly with unit tests covering it before moving on. Connected issues/tasks that reference the same part of code MAY be done together on one branch/PR with a single version bump (e.g. two tweaks to the same codec function). +2. **Feature branch + PR** — every change lands on a feature branch off `main` (no direct pushes to main). The branch carries the commits below; open a pull request to `main`. +3. **Changelog entry** — every such change is listed in `CHANGELOG.md` with date, time and version (format: `## v — YYYY-MM-DD HH:MM`, then bullet points of what changed). +4. **Version bump** — the version is updated in `package.json`. +5. **Verification must pass** — there is no CI pipeline yet; before opening a PR both local checks must be green: `npm run build` and `npm test` (vitest unit suite). +6. **Tag + manual deploy after merge** — once merged, tag the version on `main` (e.g. `v0.1.0`). The service (`http://bot.dev.kkhost.pl`) is updated manually afterwards: `docker compose up -d --build` on the host. + +## Verification + +1. **Local checks** — `npm run build` (nest build) and `npm test` (vitest; no e2e suite yet). Both must pass before a PR is opened. +2. **Docker** — `docker compose build`, then after `docker compose up -d`: `curl -s -H "Host: bot.dev.kkhost.pl" http://localhost/status` returns JSON (`{ connected, myNodeNum, host, port }`). `"connected":false` is normal while the bridge/device is down — the service retries every 5 s. + +## API + +1. **Live base URL** — `http://bot.dev.kkhost.pl` (Traefik → docker-compose stack on this host; container port 3000 via external network dev_net, no published ports). +2. **Endpoints** — `GET /status` (`{ connected, myNodeNum, host, port }`) and `POST /send` (body `{ "text": "...", "to"?: }`, returns `{ id }`). No machine-readable spec yet; keep this list in sync with the controller. + +## Other + +1. For longer terminal tasks you should use pty_* tools. +2. Host npm (10.9.4) has an arborist crash bug ("edgesOut") — if plain `npm` misbehaves on installs, use `npx npm@latest ...`. The lockfile must stay in sync with package.json: the Docker build runs `npm ci`. diff --git a/ORCHESTRATOR.md b/ORCHESTRATOR.md new file mode 100644 index 0000000..6290635 --- /dev/null +++ b/ORCHESTRATOR.md @@ -0,0 +1,65 @@ +# ORCHESTRATOR.md — Operating Manual for Successor Agents + +You are taking over the autonomous development loop on **MeshBot** (NestJS bot that TCP-connects to a Meshtastic serial bridge, decodes protobuf frames, logs inbound text messages and exposes HTTP endpoints to send them). This document captures operational knowledge that is NOT written down in AGENTS.md. Read AGENTS.md first — it defines the mandatory change process and this file complements it. + +Credentials are deliberately NOT stored in this file — see §1 for where they live. Never paste token values into any tracked file. + +## 1. Project identity + +- Repo: Gitea at https://git.kkhost.pl → org **OpenCode**, repo **MeshBot** (full name `OpenCode/MeshBot`). Every Gitea API/MCP call uses owner=`OpenCode`, repo=`MeshBot`. +- Local workdir + deploy dir: `/home/krzyw/projects/sandbox/mesh-bot` (git checkout; `docker compose up -d --build` deploys from here). +- Service URL: http://bot.dev.kkhost.pl (Traefik → docker-compose stack on this host, container port 3000 via external network dev_net; no published ports — TLS is terminated upstream of the local dev_traefik and *.dev.kkhost.pl DNS is network-wide). `GET /status` reports bridge connectivity. +- Gitea API access for git.kkhost.pl: prefer the gitea MCP tools — they are already configured with a token. For raw REST/curl, read the current token from the local .env file at repo root (variable `GITEA_TOKEN`; create it if missing — .env is gitignored and must never be committed): `source .env && curl -s -H "Authorization: token $GITEA_TOKEN" https://git.kkhost.pl/api/v1/user`. +- Current user: kamilkrzywda (id 1). + +## 2. Remote repo workflow + +- origin = https://git.kkhost.pl/OpenCode/MeshBot.git, branch `main`. Work happens against the remote: pull before starting work (`git fetch origin && git reset --hard origin/main`), implement issues added to the remote repo (Gitea at git.kkhost.pl, org OpenCode / repo MeshBot), commit + push per completed issue. + +## 3. The loop (per issue) + +1. Small incremental change on a feature branch off `main`; connected issues may share one branch/PR with a single version bump. +2. Bump `package.json` version + add CHANGELOG.md entry at top: `## v — YYYY-MM-DD HH:MM` then bullet points of what changed (docs-only changes get patch bumps too). +3. Local verification ONLY (there is no CI): `npm run build`, `npm test` (the whole vitest unit suite must pass — the test count grows over time, so compare against the previous green run, never a hardcoded number), and for container-affecting changes `docker compose build`. +4. Push → open PR to main → merge manually once local checks are green (no CI gate, no auto-merge configured); verify via `GET /pulls/{n}` that state=closed and merged_at is set. +5. Tag the version on main: `git tag v origin/main && git push origin v` (after `git fetch origin main`). +6. Deploy in a PTY (long task): `cd /home/krzyw/projects/sandbox/mesh-bot && git checkout main && git reset --hard origin/main && docker compose up -d --build`; then verify `curl -s -H "Host: bot.dev.kkhost.pl" http://localhost/status` returns JSON with the expected host/port (`"connected":false` is normal while the bridge/device is down — the service retries every 5 s). +7. Close each issue with a comment: what changed, version, PR number, merge commit SHA, deployed URL. +8. Repeat for the next open issue: `GET /issues?state=open&type=issues` — note Gitea lists pull requests in this endpoint too; filter out entries with a `pull_request` key. + +## 4. Gitea API + MCP quirks (learned the hard way) + +- The gitea MCP tools work for issues/PRs/files/tags IF you pass owner=`OpenCode`, repo=`MeshBot`. A wrong owner/repo makes EVERY call return `{"message":"not found","url":"https://git.kkhost.pl/api/swagger"}` — that swagger URL inside a 404 is the signature of "wrong path", not "Gitea is down". +- MCP has no working merge method (the endpoint 405s) → merge via REST: `POST /repos/OpenCode/MeshBot/pulls/{n}/merge` with body `{"do":"merge","merge_style":"merge"}`. + +## 5. Docker & deploy facts + +- Multi-stage `node:22-alpine` Dockerfile: build stage runs `npm ci` + `nest build`, final stage copies pruned node_modules + dist; entrypoint `node dist/main.js`. +- docker-compose.yml has Traefik labels ONLY — no published ports, no websecure/certs. Router rule Host(`bot.dev.kkhost.pl`) on the `web` entrypoint → container port 3000, attached to external network dev_net. From the host you verify with a Host header: `curl -s -H "Host: bot.dev.kkhost.pl" http://localhost/status`. +- The build runs `npm ci` → package-lock.json must stay in sync with package.json (see AGENTS.md → Other for the host npm quirk). + +## 6. Mesh protocol facts (learned the hard way) + +- Every message in BOTH directions is framed `[0x94][0xC3][len MSB][len LSB][protobuf]` — big-endian uint16 length, max payload 1024 bytes; payloads are protobuf `FromRadio`/`ToRadio` from `@meshtastic/protobufs`. +- `@meshtastic/protobufs` is ESM-only → it is accessed via a cached dynamic import from the CJS app (works on Node ≥ 22.12 require(ESM)). `"type":"module"` was deliberately removed from package.json — do not re-add it; the whole app is CommonJS. +- TEXT_MESSAGE_APP portnum = **1** (NOT 3 — 3 is a different app). +- `FromRadio` oneof cases are `myInfo` / `configCompleteId` / `packet`; on a `MeshPacket`, decoded data lives under `payloadVariant.value` (case `'decoded'`). +- The serial bridge accepts only ONE client at a time — a second connection is refused until the first drops. If the bot keeps failing to connect while the port is open, another app is probably holding the bridge. + +## 7. App architecture quick map + +- `src/mesh/codec.ts` — framing (`extractFrames`, `frame`) + protobuf encode/decode (`decodeFromRadio`, `buildTextToRadio`); unit tests in codec.spec.ts. +- `src/mesh/mesh.service.ts` — TCP connection to the bridge (MESH_HOST/MESH_PORT env, defaults 192.168.1.4:4003), buffer + frame extraction on data, FromRadio dispatch (`myInfo` → myNodeNum, `configCompleteId` → ready log, `packet` → text-message logging), fixed 5 s reconnect. +- `src/mesh/mesh.controller.ts` — `GET /status`, `POST /send`. + +## 8. Finding current state (this file keeps no snapshots) + +This manual is meant to stay valid long-term, so it deliberately contains NO version or issue snapshots — they rot. To orient yourself when you take over: `git log --oneline -10` (+ CHANGELOG.md once it exists) for what has shipped; `GET /issues?state=open&type=issues` (filter out entries with a `pull_request` key) for what is left to do. + +## 9. Standing user instruction & etiquette + +- The user's standing order for this project: work through the issue list autonomously — implement → PR → merge → tag → deploy → close with comment → next — until told to stop. +- Small incremental changes; each part fully tested (unit) before moving on. +- Long terminal tasks → pty_* tools (deploys, builds). Background watchers → run_background_process with notify=true. +- Never push directly to main; never deploy from a feature branch; production deploys are manual only (`docker compose up -d --build` after tagging). +- **Never commit credentials.** Tokens/secrets live in the MCP server config or with the user, never in tracked files (see §1).