diff --git a/.github/assets/screenshot-chat.png b/.github/assets/screenshot-chat.png new file mode 100644 index 0000000..2aad658 Binary files /dev/null and b/.github/assets/screenshot-chat.png differ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4a71d07 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +# Builds and publishes the temetro Docker images and cuts a GitHub Release. +# +# Trigger: push a semver tag, e.g. +# git tag v0.1.0 && git push origin v0.1.0 +# +# The frontend image bakes NO API URL — it resolves the backend from the host +# the browser uses at runtime — so one published image works for every clinic. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# DOCKERHUB_USERNAME — a Docker Hub account with push access to `temetro` +# DOCKERHUB_TOKEN — a Docker Hub access token for that account +# Without them the build/login step fails; the workflow is otherwise inert. +name: release + +on: + push: + tags: + - "v*.*.*" + +permissions: + contents: write # create the GitHub Release (the update check reads this) + +env: + REGISTRY_NAMESPACE: temetro + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Derive version from tag + id: meta + run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build & push backend + uses: docker/build-push-action@v6 + with: + context: ./backend + push: true + tags: | + ${{ env.REGISTRY_NAMESPACE }}/temetro-backend:${{ steps.meta.outputs.version }} + ${{ env.REGISTRY_NAMESPACE }}/temetro-backend:latest + + - name: Build & push frontend + uses: docker/build-push-action@v6 + with: + context: ./frontend + push: true + tags: | + ${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:${{ steps.meta.outputs.version }} + ${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:latest + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f0f9f4f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +All notable changes to temetro are recorded here. The format is loosely based on +[Keep a Changelog](https://keepachangelog.com/), and the project follows +[Semantic Versioning](https://semver.org/). See [RELEASING.md](./RELEASING.md) +for how releases are cut and published. + +## [Unreleased] + +### Added +- **Version & update awareness.** `GET /api/version` reports the running version + and checks GitHub Releases for a newer one; Settings → **About & updates** shows + the current/latest version, and an optional, dismissible banner appears when an + update is available. +- **LAN access.** The frontend now resolves the backend URL from the host the + browser is using, so other departments can reach temetro at + `http://:3000` with no rebuild. A Settings panel surfaces the + shareable network address. `GET /api/network` reports detected LAN addresses. +- **Prebuilt Docker images** published to Docker Hub (`temetro/temetro-backend`, + `temetro/temetro-frontend`) via a tag-triggered GitHub Actions release workflow. +- **Voice dictation** on the AI chat input (Web Speech API), with graceful + fallback where the browser doesn't support it. + +### Changed +- `docker-compose.yml` references the published images (with a build fallback) and + no longer bakes a fixed API URL into the frontend. + +## [0.1.0] — 2026-06-26 + +Initial baseline: clinician AI-chat UI wired to the TypeScript/Express/Postgres +API (Better Auth, multi-tenant clinics, org-scoped patient records), the patient +wallet encrypted-share flow, and Dockerised local run. diff --git a/README.md b/README.md index b49776e..76b4e6f 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,141 @@ +
+ # temetro -**temetro** is an **open-source** clinical tool that acts as an **AI middleman** between clinicians -and patient data. Clinicians use a natural-language AI chat to retrieve and organize patient -information, displayed as rich record cards. +**An open-source AI middleman between clinicians and patient data.** -Its distinguishing idea is a **patient-owned data model**: instead of (or alongside) living in a -doctor's own database, a patient's record can be stored on the **patient's own device**. When a -clinician adds or changes data, they **sign** it (blockchain-style); the change is written to the -patient's record and **cannot be modified until the patient approves it** through a companion app. -temetro can also **read existing patient databases** and present them in the same organized card UI. +Clinicians ask in plain language; temetro retrieves and organizes patient +information as rich record cards — backed by a **patient-owned data model**. -> **Status.** The **backend is built** — a TypeScript + Express + Postgres API (Drizzle ORM) with -> authentication and multi-tenant clinics via [Better Auth](https://better-auth.com), an org-scoped -> patient records API, plus appointments, prescriptions, tasks, doctor's notes, analytics, an -> activity audit log, real-time staff messaging (Socket.io), and notifications. The **frontend chat -> is wired to it** (real auth, route protection, clinic switching, live patient data). -> -> **Still vision, not built:** the patient companion app and the blockchain-style -> **signing / patient-owned storage / approval** flow, and the AI chat replies themselves (currently -> mock — no LLM call yet; a `/chat` endpoint is the next planned step). +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) +[![Docker images](https://img.shields.io/badge/Docker%20Hub-temetro-2496ED?logo=docker&logoColor=white)](https://hub.docker.com/u/temetro) +[![Changelog](https://img.shields.io/badge/changelog-0.1.0-success)](./CHANGELOG.md) -## Monorepo layout +![temetro AI chat](./.github/assets/screenshot-chat.png) -This repository is a **monorepo** containing two independent apps that live side by side (each with -its own `package.json` / `node_modules` and its own `CLAUDE.md`): +
-- **[`frontend/`](./frontend)** — the Next.js 16 product app (the clinician-facing AI chat UI). - See [`frontend/CLAUDE.md`](./frontend/CLAUDE.md). -- **[`backend/`](./backend)** — the Express 5 + Postgres API (Drizzle ORM + Better Auth). - See [`backend/CLAUDE.md`](./backend/CLAUDE.md) and [`backend/README.md`](./backend/README.md). +## What is temetro? -The marketing **landing page lives in its own separate repository** (`temetro-landing`), not here. +temetro is a clinical tool that puts a **natural-language AI chat** between +clinicians and patient records. Instead of clicking through tabs, a clinician +asks for what they need and temetro returns organized **record cards** — +vitals, labs, medications, problems, encounters — with trend sparklines and +detail views. -## Run locally with Docker (recommended) +Its distinguishing idea is a **patient-owned data model**. Instead of (or +alongside) living only in a doctor's database, a patient's record can live on +the **patient's own device**. When a clinician adds or changes data they +**sign** it (blockchain-style); the change is written to the patient's record +and **cannot be modified until the patient approves it** through a companion +wallet app. temetro can also **read existing patient databases** and present +them in the same card UI. -Docker Compose builds and runs Postgres, the backend, and the frontend together. From the -**`backend/`** directory (the Compose file builds the sibling `../frontend`): +> "Decentralization" here means keys and data live on the patient's device and +> the relay only ever forwards ciphertext — it is **not** a literal blockchain. +> Records are off-chain, which is what lets a temporary share be deleted. + +## Features + +- 🗂️ **AI chat over patient records** — `/patient ` (or natural language) + renders the record as cards with sparklines and detail dialogs. +- 🔐 **Auth & multi-tenant clinics** — email/password + staff usernames, + organizations, and role-based access (owner / admin / doctor / reception / + pharmacy / lab) via [Better Auth](https://better-auth.com). +- 🩺 **Full clinical surface** — patients, appointments, prescriptions, tasks, + doctor's notes, pharmacy inventory, analytics, an activity audit log, + real-time staff messaging, and notifications. +- 📲 **Patient wallet & encrypted share** — a companion app holds the record + encrypted on-device; clinics import records over an end-to-end encrypted, + patient-approved relay (with optional auto-deleting temporary shares). +- 🌐 **Works across the clinic LAN** — open it on the server or from any + department's computer at `http://:3000`; no per-machine config. +- 🔄 **Self-update awareness** — the app tells admins when a newer release is + out and how to update. + +## Quick start (Docker) + +The fastest path uses the **prebuilt images** published to Docker Hub. From the +[`backend/`](./backend) directory (it holds the Compose file): ```bash cd backend -cp .env.example .env - -# generate a strong auth secret and paste it into BETTER_AUTH_SECRET in .env: -openssl rand -base64 32 - -docker compose up --build +docker compose pull # fetch the latest published images +docker compose up -d # start Postgres + backend + frontend ``` -Then open: +No `.env` or secret setup is required — the backend generates and persists any +missing secrets on first start. Then open: -- Frontend → http://localhost:3000 -- Backend → http://localhost:4000 (health check: `GET /health`) -- Postgres → localhost:5432 +- **Frontend** → http://localhost:3000 +- **Backend** → http://localhost:4000 (health: `GET /health`) -Database migrations are applied automatically on backend container start. +Prefer to **build from source** (for development)? Use `docker compose up +--build` instead. Migrations apply automatically on backend start. -**Port conflict?** If another Postgres already holds host port `5432`, set `POSTGRES_PORT` (e.g. -`5433`) in `backend/.env`. The app still talks to Postgres internally on `db:5432`; only the -published host port changes. +> **Port conflict?** If another Postgres holds host port `5432`, set +> `POSTGRES_PORT` (e.g. `5433`) in `backend/.env`. The app still talks to +> Postgres internally on `db:5432`; only the published host port changes. -Run just the API + database with `docker compose up db backend`. To browse the database with -Adminer: `docker compose --profile tools up adminer` → http://localhost:8080. +### Access from other computers (hospital LAN) -## Run locally without Docker +temetro figures out the backend address from the host you open it on, so other +departments can simply visit **`http://:3000`** — no rebuild +needed. Settings → **About & updates** shows the exact shareable address. The +server's firewall must allow ports `3000`/`4000`. -Run each app in its own terminal (you'll need a local Postgres reachable from `DATABASE_URL`): +### Updating + +```bash +docker compose pull && docker compose up -d +``` + +The app surfaces a notification when a newer release exists. See +[`CHANGELOG.md`](./CHANGELOG.md) for what changed and [`RELEASING.md`](./RELEASING.md) +for how releases are built and published. + +## Run without Docker + +Each app runs independently (you'll need a local Postgres in `DATABASE_URL`): ```bash # backend -cd backend -npm install -cp .env.example .env # point DATABASE_URL at your local Postgres -npm run db:migrate # apply migrations -npm run dev # http://localhost:4000 +cd backend && npm install && cp .env.example .env +npm run db:migrate && npm run dev # http://localhost:4000 # frontend (second terminal) -cd frontend -npm install -npm run dev # http://localhost:3000 +cd frontend && npm install && npm run dev # http://localhost:3000 ``` -The frontend reads `NEXT_PUBLIC_API_URL` (default `http://localhost:4000`) to reach the backend. +> With `SMTP_HOST` unset, verification / reset / invitation emails are **printed +> to the backend console** — no email setup needed for local development. -> If `SMTP_HOST` is unset, verification / reset / invitation emails are **printed to the backend -> console** instead of being sent — no email setup is needed for local development. +## Monorepo layout + +Two independent apps live side by side, each with its own `package.json` and +`CLAUDE.md`: + +- **[`frontend/`](./frontend)** — Next.js 16 product app (the AI chat UI). +- **[`backend/`](./backend)** — Express 5 + Postgres API (Drizzle ORM + Better + Auth). See [`backend/README.md`](./backend/README.md) for the API reference. + +The **patient wallet app** and the **marketing landing page** live in their own +separate repositories. ## Tech stack -- **Frontend:** Next.js 16 (App Router) · React 19 · TypeScript · Tailwind CSS v4 · i18next · - Socket.io client. -- **Backend:** Node ≥ 20 · TypeScript (ESM) · Express 5 · Postgres · Drizzle ORM · Better Auth - (email/password + organizations/RBAC) · Socket.io. +- **Frontend:** Next.js 16 (App Router) · React 19 · TypeScript · Tailwind CSS v4 + · i18next · Socket.io client. +- **Backend:** Node ≥ 20 · TypeScript (ESM) · Express 5 · Postgres · Drizzle ORM + · Better Auth (email/password + organizations/RBAC) · Socket.io · AI SDK. + +## Contributing + +Issues and pull requests are welcome. Please keep each PR focused on one logical +change and run the type checks (`npm run typecheck` in `backend/`, `npx tsc +--noEmit` in `frontend/`) before opening it. Bug reports and feature requests +have [issue templates](./.github/ISSUE_TEMPLATE). ## License -MIT. +[MIT](./LICENSE). diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..0d141ca --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,56 @@ +# Releasing temetro + +temetro is self-hosted: clinics run it with Docker and update by pulling new +images. Each release publishes prebuilt images to Docker Hub and cuts a GitHub +Release; the running app checks that release feed to tell admins an update is +available. This document is the checklist for cutting one. + +## Versioning + +We follow [Semantic Versioning](https://semver.org/) — `MAJOR.MINOR.PATCH`, +starting at **0.1.0**. The canonical version lives in the root +[`package.json`](./package.json); `backend/package.json` and +`frontend/package.json` track the same number. `GET /api/version` reports it. + +## One-time setup + +Add these repository secrets (GitHub → Settings → Secrets and variables → +Actions) so the release workflow can publish: + +| Secret | What | +| --- | --- | +| `DOCKERHUB_USERNAME` | Docker Hub account with push access to the `temetro` namespace | +| `DOCKERHUB_TOKEN` | A Docker Hub access token for that account | + +Images are published as `temetro/temetro-backend` and `temetro/temetro-frontend`. + +## Cutting a release + +1. **Bump the version** in `package.json`, `backend/package.json`, and + `frontend/package.json` to the new `X.Y.Z`. +2. **Update [`CHANGELOG.md`](./CHANGELOG.md)**: move the `Unreleased` notes under a + new `## [X.Y.Z] — ` heading. +3. **Commit** the bump (`chore(release): vX.Y.Z`). +4. **Tag and push**: + ```bash + git tag vX.Y.Z + git push origin main --tags + ``` +5. The **`release` workflow** (`.github/workflows/release.yml`) then: + - builds and pushes both images tagged `X.Y.Z` **and** `latest`, and + - creates a **GitHub Release** with auto-generated notes. + +That GitHub Release is what the in-app update check compares against, so update +notifications light up for everyone on an older version once it's published. + +## How clinics update + +On the server machine, from the directory holding `docker-compose.yml`: + +```bash +docker compose pull && docker compose up -d +``` + +This pulls the new `latest` images and restarts. Pin a specific version with +`TEMETRO_VERSION=X.Y.Z docker compose up -d`. Updating is optional — the in-app +banner just makes it discoverable. diff --git a/backend/README.md b/backend/README.md index ffe01ab..4c4f3d8 100644 --- a/backend/README.md +++ b/backend/README.md @@ -9,9 +9,16 @@ shape exactly. ## Quick start (Docker) ```bash -docker compose up # db + backend + frontend — no setup needed +docker compose pull # fetch prebuilt images from Docker Hub (fast) +docker compose up -d # db + backend + frontend — no setup needed ``` +`docker compose up --build` instead builds from source (for development). The +Compose file references the published `temetro/temetro-backend` and +`temetro/temetro-frontend` images with a build fallback, so the same file serves +both clinics and developers. Update with `docker compose pull && docker compose +up -d` (see [`../RELEASING.md`](../RELEASING.md)). + No `.env` or manual secret generation is required: on first start the backend generates any missing secrets (`BETTER_AUTH_SECRET`, `AI_CREDENTIALS_KEY`) and persists them to a Docker volume, so they stay stable across restarts. Create a @@ -72,6 +79,8 @@ Other org-scoped resources follow the same pattern (CRUD, role-gated): | Analytics | `GET /api/analytics` | — (any member) | computed clinic aggregates | | Conversations | `/api/conversations` | — (participant-scoped) | staff messaging; real-time over Socket.io | | Notifications | `/api/notifications` | — (per-recipient) | auto-generated; `read-all` + per-id read | +| Version | `GET /api/version` | — (public) | running version + GitHub-release update check | +| Network | `GET /api/network` | — (public) | detected LAN addresses for sharing the app | Real-time messaging and live notifications are delivered over **Socket.io**, attached to the same HTTP server; the handshake is authenticated with the Better Auth session cookie. diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index e1bc10f..9b36331 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,15 +1,25 @@ # Full-stack dev/run orchestration for temetro. # -# docker compose up # db + backend + frontend — that's it. +# docker compose up -d # run prebuilt images (clinics — fast) +# docker compose up --build # build from source (developers) +# docker compose pull && docker compose up -d # update to the latest release +# +# Each service has both `image:` (published on Docker Hub) and `build:` (the +# source), so the same file serves clinics pulling releases and developers +# building locally. `TEMETRO_VERSION` (default `latest`) pins the image tag. # # No .env or secret setup is required: the backend generates any missing # secrets on first start and persists them (see docker-entrypoint.sh). Create a # .env only if you want to override something (SMTP, a real auth secret, etc.). # -# Frontend -> http://localhost:3000 +# Frontend -> http://localhost:3000 (also reachable at http://:3000) # Backend -> http://localhost:4000 # Postgres -> localhost:5432 # +# LAN access: the frontend finds the backend from the address you open it on, so +# other departments can just visit http://:3000 — no rebuild. The +# in-app Settings → "About & updates" page shows the shareable network address. +# # The frontend service builds the sibling ../frontend app. Run just the API # with: docker compose up db backend # @@ -36,6 +46,7 @@ services: retries: 10 backend: + image: temetro/temetro-backend:${TEMETRO_VERSION:-latest} build: context: . restart: unless-stopped @@ -68,15 +79,16 @@ services: - "4000:4000" frontend: + image: temetro/temetro-frontend:${TEMETRO_VERSION:-latest} build: context: ../frontend args: - NEXT_PUBLIC_API_URL: http://localhost:4000 + # Empty by default -> the app derives the backend URL from the host the + # browser uses (localhost or a LAN IP). Set to pin a fixed/proxied URL. + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-} restart: unless-stopped depends_on: - backend - environment: - NEXT_PUBLIC_API_URL: http://localhost:4000 ports: - "3000:3000" diff --git a/frontend/README.md b/frontend/README.md index e215bc4..28a2f21 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,36 +1,53 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# temetro frontend -## Getting Started +The clinician-facing **AI chat UI** for [temetro](../) — a Next.js 16 app where +clinicians retrieve and organize patient data in natural language, rendered as +rich record cards. It's wired to the [`../backend`](../backend) for real auth, +multi-tenant clinics, and live patient data. -First, run the development server: +![temetro AI chat](../.github/assets/screenshot-chat.png) + +## Stack + +Next.js 16 (App Router) · React 19 · TypeScript · Tailwind CSS v4 · +[COSS](https://coss.dev) UI components (Base UI) · i18next · Socket.io client. + +> This app runs a **customized Next.js 16** whose conventions differ from the +> public docs (e.g. route protection lives in `proxy.ts`, not `middleware.ts`). +> See [`CLAUDE.md`](./CLAUDE.md) and `node_modules/next/dist/docs/` before +> writing Next.js code. + +## Develop ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +npm install +npm run dev # Next dev server (Turbopack) on http://localhost:3000 ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Other scripts: `npm run build` (production build), `npm run start` (serve the +build), `npm run lint`. There is no test runner — verify changes by running the +dev server. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +### Talking to the backend -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +The frontend needs the API running (see [`../backend`](../backend)). It resolves +the backend URL **from the host you open the app on** — so it works on +`localhost` and across the clinic LAN (`http://:3000`) without a +rebuild. Set `NEXT_PUBLIC_API_URL` only to pin a fixed or reverse-proxied URL; +see [`lib/backend-url.ts`](./lib/backend-url.ts). -## Learn More +## Architecture -To learn more about Next.js, take a look at the following resources: +- **`app/`** — App Router. `app/(app)/` is the authenticated product shell; + `app/(auth)/` holds the login / signup / onboarding pages. +- **`components/chat/`** — the chat UI (input, message state, patient cards). +- **`components/settings/`** — settings panels, including **About & updates** + (version + LAN access). +- **`components/ui/`** — COSS primitives (Base UI, added via the shadcn CLI). +- **`lib/`** — API + auth clients, i18n, and data helpers. -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +See [`CLAUDE.md`](./CLAUDE.md) for the full architecture, theming, and gotchas. -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +## License -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +[MIT](./LICENSE). diff --git a/package.json b/package.json index e9b7721..37d6f0c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,11 @@ { + "name": "temetro", + "version": "0.1.0", + "private": true, "devDependencies": { "shadcn": "^4.11.0" + }, + "dependencies": { + "xcodebuildmcp": "^2.6.2" } }