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

uv run cremind serve opens a second listener on :1515 to serve the bundled SPA from app/static/ui/ — but only if that directory contains an index.html. In a fresh checkout it's empty, so the listener stays silent and leaves :1515 for Vite.

The trap: if you've ever run bash scripts/build_ui.sh (for example, for a release smoke test), app/static/ui/ is populated. The backend will then fight Vite for :1515, and you'll be looking at a stale build with no HMR.

Pick one of these before starting Terminal A:

# Option A (simplest): wipe the build artifact. It's gitignored;
# rebuild anytime with bash scripts/build_ui.sh.
Remove-Item -Recurse -Force app\static\ui

# Option B: keep the build, but disable the SPA listener this session.
$env:CREMIND_UI_PORT = "0"

# Option C: keep the build, point the listener at a nonexistent path.
$env:CREMIND_UI_DIR = "C:\nonexistent"

If you skip this and the backend wins :1515, Terminal A logs SPA listener: http://127.0.0.1:1515 (serving …\app\static\ui). That line is the warning sign. Apply one of the options above and restart Terminal A.

Terminal A — backend

uv run cremind serve

The API listens on :1112. With the prerequisite applied, Terminal A logs SPA not present at …\app\static\ui; UI listener disabled and leaves :1515 for Vite.

Terminal B — Vite dev server

cd ui
npm run web:dev

Open http://localhost:1515. The port-swap heuristic in ui/src/services/runtimeConfig.ts detects the :1515 host and auto-resolves the backend at :1112 — you don't need to set VITE_AGENT_URL.

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's SPA listener won the port; 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, no UIuv run cremind serve. The UI listener on :1515 stays silent.
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. 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

  • Stale UI on :1515 after a previous build_ui.sh. See the prerequisite above. Symptom in Terminal A: the SPA listener: … log line. Symptom in the browser: edits to ui/src/** don't show up.
  • 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 port-swap heuristic only fires when the SPA is served at :1515. Running Vite on a different port (for example via $env:PORT) skips it; then you need VITE_AGENT_URL.
  • 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