Cremind
Contributing & Maintenance

Development setup

First-time setup and the everyday two-terminal dev loop — backend on :1112, Vite on :1515, each half hot-reloading.

This page takes you from a clean checkout to a running dev environment with hot reload on both halves of the app. The everyday loop is two terminals: the Python backend on :1112 and the Vite dev server on :1515, each side reloading its own code.

First-time setup

Run these once. They install dependencies for both halves and write your local config.

# Python deps (reads pyproject.toml + uv.lock)
uv sync

# Node deps
cd ui
npm install
cd ..

# Initial config: profile, LLM keys, ports, etc.
uv run cremind setup

uv run cremind setup is interactive and writes ~/.cremind/ with your profile and provider credentials. You only need to run it again if you delete ~/.cremind/.

ui/node_modules is large

The UI's node_modules is roughly 600 MB. The first npm install takes a while — that's expected, not a hang.

The two-terminal loop

The daily loop runs the backend and the Vite dev server side by side. The backend serves the API on :1112; Vite serves the UI on :1515 with hot module replacement (HMR).

Prerequisite: free port :1515 for Vite

The merged backend always binds the single public port (CREMIND_UI_PORT, default :1515) — whether or not a SPA is built — so by default it fights Vite for :1515. Free that port for Vite by running the backend on loopback only:

# Bind only the internal API (loopback :1112); open no public :1515 bind.
$env:CREMIND_UI_PORT = "0"

If you skip this, the backend binds :1515 and Vite can't — set CREMIND_UI_PORT=0 and restart Terminal A.

Terminal A — backend

$env:CREMIND_UI_PORT = "0"               # bind the internal API on :1112 only; free :1515 for Vite
$env:APP_URL = "http://localhost:1112"   # agent card + OAuth redirects target the backend, not Vite
uv run cremind serve

The log shows CREMIND_UI_PORT=0 — serving loopback-only on http://127.0.0.1:1112: the backend binds only the internal API (loopback) and leaves :1515 free for Vite.

APP_URL is required for account-linking in dev

APP_URL is the backend's public origin, which the OAuth redirect (and the A2A agent card) derive from. In production it's :1515, but in dev the backend lives on :1112 while :1515 is Vite — so leave it at the production default and a Gmail/Atlassian consent redirect lands on Vite's SPA ("Select a profile…") instead of the backend's /api/oauth/.../callback. Setting APP_URL=http://localhost:1112 points the redirect at the backend so linking completes.

Terminal B — Vite dev server

cd ui
$env:VITE_AGENT_URL = "http://localhost:1112"   # point the SPA at the backend's internal API
npm run web:dev

Open http://localhost:1515 (Vite). VITE_AGENT_URL points the SPA at the backend's internal API — the merged app is same-origin in production, so without it runtimeConfig.ts would resolve the API at Vite's own :1515, which doesn't serve it. (Tip: put VITE_AGENT_URL=http://localhost:1112 in ui/.env.local so you don't set it each session.)

To confirm you're hitting Vite and not a stale backend bundle, open browser DevTools and look at the Sources panel. You should see /@vite/client — that's HMR live. If you only see a hashed file like assets/index-Cabc123.js, the backend bound :1515 (you didn't set CREMIND_UI_PORT=0); revisit the prerequisite above.

What hot-reloads

ChangeHow to pick it up
ui/src/** (Vue, TS, CSS)Vite HMR — instant in the browser.
ui/vite.config*.ts, ui/package.jsonRestart Terminal B (Ctrl+C, then npm run web:dev).
app/** (Python)Restart Terminal A. There is no --reload flag on cremind serve today.
pyproject.toml depsuv sync --all-extras, then restart Terminal A.
app/__version__.pyThe pre-commit hook syncs ui/package.json automatically. Manual: python scripts/sync_ui_version.py.

Optional: auto-restart on Python edits

cremind serve doesn't expose --reload, but you can wrap it with watchfiles from the outside:

uv run --with watchfiles watchfiles "uv run cremind serve" app

This watches app/ and restarts the whole process on any change. It's slower than an in-process reload — roughly a couple of seconds for a cold restart — but it always works.

Variations

You want…Run
Backend only (API on loopback)$env:CREMIND_UI_PORT=0; uv run cremind serve — binds :1112 only, no public :1515.
UI pointed at a remote backendcd ui ; npm run web:dev. Set the agent URL via the setup wizard, or $env:VITE_AGENT_URL = "https://..." before npm run web:dev.
Single-port end-to-end smoke (no HMR)bash scripts/build_ui.sh ; uv run cremind serve (Windows: .\scripts\build_ui.ps1 ; uv run cremind serve). The SPA is bundled into app/static/ui/ and served on :1515 by the backend. Use this for pre-release verification, not active dev.
Electron desktop devcd ui ; npm run dev. Wraps the SPA in an Electron window; it talks to the backend URL from ~/.cremind-ui/cremind-config.json.

Gotchas

  • Backend grabbed :1515. If you forget CREMIND_UI_PORT=0, the merged backend binds :1515 (Terminal A logs Serving on public http://0.0.0.0:1515) and Vite can't take it — or you end up viewing the bundled build instead of HMR. Set the env var and restart Terminal A.
  • First page load after setup. The SPA on :1515 may show the setup wizard until the agent URL is configured. After that it sticks.
  • Backend not picking up code changes. Kill and restart Terminal A. Don't restart Vite — it's stateful for HMR.
  • The dev SPA needs VITE_AGENT_URL. The old :1515:1112 port-swap was removed (the SPA is same-origin in production), so in dev you must point it at the backend with VITE_AGENT_URL=http://localhost:1112 (or ui/.env.local).
  • Don't edit ui/package.json's version field. It's regenerated from app/__version__.py by scripts/sync_ui_version.py, wired into the prebuild / preweb:build npm hooks. See Versioning.

Next

On this page