Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,49 @@ Run the full CI check suite:
make ci
```

This runs ruff lint, ruff format check, and pytest. All three must pass.
This runs ruff lint, ruff format check, prettier (frontend), `vue-tsc`
(frontend type-check), and pytest. All must pass.

To auto-fix formatting and safe lint issues before checking:
To auto-fix Python formatting and safe lint issues before checking:

```
ruff format .
ruff check --fix .
make format
make ci
```

## Running tests

```
pytest # all tests
pytest tests/ # same, explicit path
make test # pytest under the project's venv
```

Tests live in `tests/` and cover pure utility functions that need no Flask
app context. Keep new tests free of Flask/database dependencies where possible.
app context. Keep new tests free of Flask/database dependencies where
possible.

## Frontend (when working in `frontend/`)

The frontend is a Vite + Vue 3 SPA written in **TypeScript** (strict mode).
Single-file components use `<script setup lang="ts">`.

```
make frontend-lint # prettier check over src/**/*.{js,vue}
make frontend-lint # prettier check over src/**/*.{ts,vue}
make frontend-type-check # vue-tsc --noEmit
```

The frontend is a Vite + Vue 3 SPA. `npm --prefix frontend install` installs
its deps. Prettier is the formatter; no separate ESLint config exists yet.
Both are part of `make ci`. Prettier is the formatter; there is no separate
ESLint config. State lives in a Pinia store at `src/stores/game.ts` —
prefer adding getters there over duplicating fallback expressions in
components.

## README

`README.md` has two top-level paths the project commits to:

- **Running Ceopardy (operators)** — pipx install of a release wheel, then
`ceopardy init` + `ceopardy serve`.
- **Hacking on Ceopardy (developers)** — clone + `make venv` + `make run`.

When you change the install, run, or dev workflow, update the matching
section. When you change a command name, port, or default behavior the
README documents, update the README in the same change.
128 changes: 55 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,106 +28,88 @@ Starting with v0.5, Ceopardy is split in two parts:

- A Python/Flask back-end that exposes a small REST API (`/api/v1/...`) and
broadcasts state changes over a single Socket.IO namespace (`/game`).
- A Vite + Vue 3 front-end (in `frontend/`) that powers the crowd-facing
viewer, the host UI, and the start screen.
- A Vite + Vue 3 + TypeScript front-end (in `frontend/`) that powers the
crowd-facing viewer, the host UI, and the start screen.

Ceopardy is designed for single-operator local-network use: the server binds
to `127.0.0.1` and there is no authentication on the host UI. If you need to
expose it on a LAN, put your own reverse proxy (and auth) in front.

## First time deployment

You need Python, pip, virtualenv and Node.js (LTS). The tl;dr:
## Running Ceopardy (operators)

make venv
source .venv/bin/activate # bash/zsh
source .venv/bin/activate.fish # fish
make init # seed data/ and game-media/ from templates
npm install --prefix frontend
npm run build --prefix frontend
python run.py
For people who just want to host a game.

`make venv` creates `.venv/` and installs both runtime and dev requirements.
`make init` is a one-time step that copies starter `data/1st.round` and
`data/Questions.cp` into the repo and creates `game-media/`. Edit those files
to set up your game.
Install [pipx](https://pipx.pypa.io/), then install the latest release wheel
(requires `curl` and `jq`):

### Optional: direnv

If you use [direnv](https://direnv.net/), the repo ships an `.envrc` that puts
`.venv/bin` on your `PATH` automatically when you `cd` into the directory —
works in bash, zsh, and fish. Install direnv (see
[upstream docs](https://direnv.net/docs/installation.html) for shell hook setup),
then from the repo root:

make venv # create the venv first; direnv won't do this for you
direnv allow # trust the .envrc
pipx install "$(curl -fsSL https://api.github.com/repos/obilodeau/ceopardy/releases/latest | jq -r '.assets[] | select(.name | endswith(".whl")) | .browser_download_url')"

After that, entering the directory activates the venv and leaving deactivates
it — no manual `source` needed.
Or pin a specific version from the
[releases page](https://github.com/obilodeau/ceopardy/releases):

Then open [the host view](http://127.0.0.1:5000/host) to set up the game.
[The players' view](http://127.0.0.1:5000/) (also known as the viewer) can be
opened at any time.
pipx install https://github.com/obilodeau/ceopardy/releases/download/v0.6.0/ceopardy-0.6.0-py3-none-any.whl

`python run.py` runs the built-in dev server (debug + reloader). For
production, run it under gunicorn with the eventlet worker. Socket.IO requires
a single worker process unless you also configure a Redis message queue:
Then scaffold a per-game directory and start the server:

pip install gunicorn
gunicorn -k eventlet -w 1 -b 127.0.0.1:5000 'run:app'
mkdir my-game && cd my-game
ceopardy init # writes data/ + game-media/ starter content
# edit data/Questions.cp and data/1st.round to set up your game
ceopardy serve # starts the server on http://127.0.0.1:5000/
ceopardy serve --debug # add verbose logging + auto-reload

Put nginx (or similar) in front for TLS and to expose it on the network — the
app itself binds to localhost because the host interface has no auth.
Open the two URLs `ceopardy serve` prints:

- Viewer: <http://localhost:5000/> — what the crowd sees on the projector.
- Host: <http://localhost:5000/host> — what you (the operator) drive.

## Development
`ceopardy init` never overwrites existing files; it's safe to re-run. The
SQLite database, round files, and uploaded media all resolve relative to the
directory you run `ceopardy` from, so **keep one directory per game**.

Run Flask and Vite side by side. Vite hot-reloads the UI and proxies
`/api` and `/socket.io` to Flask.
> **Note:** Ceopardy persists transactions to a SQLite database as the host
> submits points, so a crash doesn't lose the game state. The flipside is
> that games must be finalized (click "Game over") before a new one can be
> started in the same directory.

# terminal 1 - Flask
python run.py

# terminal 2 - Vite dev server
npm run dev --prefix frontend
## Hacking on Ceopardy (developers)

Then open http://localhost:5173/.
You need Python 3.11+, pip, virtualenv, and Node.js (LTS).

git clone https://github.com/obilodeau/ceopardy.git
cd ceopardy
make venv # creates .venv/ + installs deps
source .venv/bin/activate # bash/zsh
source .venv/bin/activate.fish # fish
make init # seeds data/ + game-media/
make run # starts Flask (:5000) + Vite (:5173)

## Install as a CLI (pipx)
Then open <http://localhost:5173/> — Vite hot-reloads the UI and proxies
`/api` and `/socket.io` to Flask on `:5000`. **In dev, always use the Vite
URL** (`:5173`); the Flask port serves the *built* SPA which gets stale.

Ceopardy is distributed as a wheel attached to each
[GitHub release](https://github.com/obilodeau/ceopardy/releases). Install the
latest one with pipx (requires `curl` and `jq`):
### Optional: direnv

pipx install "$(curl -fsSL https://api.github.com/repos/obilodeau/ceopardy/releases/latest | jq -r '.assets[] | select(.name | endswith(".whl")) | .browser_download_url')"
If you use [direnv](https://direnv.net/), the repo ships an `.envrc` that
auto-activates `.venv` on `cd`. Run `make venv` first (direnv won't), then
`direnv allow`.

Or pick a specific version by pointing pipx at a wheel URL from the
[releases page](https://github.com/obilodeau/ceopardy/releases), e.g.:
### Before committing

pipx install https://github.com/obilodeau/ceopardy/releases/download/v0.6.0/ceopardy-0.6.0-py3-none-any.whl
Run the full CI suite — same checks GitHub Actions runs:

Then scaffold a game directory and start the server:
make ci # ruff lint + format check + prettier + vue-tsc + pytest

mkdir my-game && cd my-game
ceopardy init # scaffolds data/ and game-media/ in CWD
# edit data/Questions.cp and data/1st.round
ceopardy # or `ceopardy serve` — starts the server
To auto-fix Python formatting first:

`ceopardy init` never overwrites existing files; it's safe to re-run. The
server, the SQLite database, and the question files all resolve relative to
the directory you run `ceopardy` from, so keep one directory per game.
make format

See `AGENTS.md` for the conventions the codebase follows.

## Prepare a game
### Building a wheel locally

Game data goes in `data/`. There you should add round files (create a `.round`
file) and questions in `Questions.cp`. The format is pretty self explanatory.
Run `ceopardy init` (or `make init` from the repo) to get a working starter
set; `data/1st.round` and `data/Questions.cp` are the minimal example.
User-supplied media referenced by questions (e.g. `[img:photo.png]`) goes in
`game-media/` next to `data/`.
`make build` reproduces the release path (frontend bundle + sdist + wheel):

> **Note:** In order to avoid dataloss due to a crash, Ceopardy is backed by a
> database where transactions are pushed when the hosts submit the points. This
> has the flipside requiring games to be finalized before a new one can be
> started. Make sure that you always push the "Game over" button before
> reloading to start a new game.
make build
pipx install --force dist/ceopardy-*.whl # test the wheel end-to-end
Loading