Contributing and reference1 of 12
A development environment
On this page
Kotoba is two programs in one repository: a Python package under api/, and a Next.js frontend at the
root. You need Python for the first. You need Node only if you are changing the second.
Most of the commands here were run in the repository before this was written. Three were not — the backend server, the setup sandbox and the frontend build — and each says so where it appears.
What you need
| Version | Where it is written down | |
|---|---|---|
| Python | 3.11 or newer | api/pyproject.toml, requires-python = ">=3.11" |
| Node | 22 | .nvmrc |
Node is optional. An install of the wheel carries the frontend already compiled and never needs it. A clone needs it only to run the dev server or to rebuild the packaged UI.
Clone and install
git clone https://github.com/rodhnin/kotoba-companion
cd kotoba-companion
python -m venv .venv && source .venv/bin/activate
pip install -e "api/[dev]"
pip install -e "api/[dev]" is the whole install: the package plus every optional extra plus the test
tools. A --dry-run of exactly that command resolved cleanly and reported it would install
kotoba-companion-0.10.0.
Installing
api/requirements.txtinstead is not equivalent. That file says so at the top: it is a convenience mirror for tooling that wants a flat list, it carries the backend set only (nocli, nodev), and a package installed from this file alone is not importable askotobaat all — souvicorn kotoba.server:appstops withModuleNotFoundError.It cannot silently drift:
api/tests/test_requirements_mirror_pyproject.pyfails if any line disagrees withpyproject.toml. Change a floor there and copy it down; never edit one only in the mirror. Measured: 3 passed.
The extras, read out of api/pyproject.toml
| Extra | What it pulls in | Why |
|---|---|---|
server | fastapi, pydantic, uvicorn[standard] | only kotoba.server imports these, so the terminal installs without them |
voice | websockets | the local voice socket |
cli | prompt_toolkit, rich, pillow | the interactive terminal; without pillow it falls back to kaomoji |
web | readability-lxml, lxml | the local page extractor |
mcp | mcp >=1.27,<2 | MCP servers; the ceiling is deliberate, 2.0 renames Tool.inputSchema |
discord | discord.py[voice], numpy | the Discord bot |
dev | all six above, plus pytest and pytest-timeout | what a contributor installs |
There is no lint extra and no linter in CI. Ruff is not a gate here.
The README's clone instructions now say
pip install -e "api/[dev]"; the short set survives indocs/self-hosting.md. That is the runtime set withoutdiscordand without pytest, so the tests will not run under it. Use[dev]if you intend to contribute.
The two processes
The backend and the frontend run separately, in two terminals.
# terminal 1 — the backend
uvicorn kotoba.server:app --reload --port 8000
# terminal 2 — the frontend
npm install
npm run dev # http://localhost:3000
Port 8000 is not arbitrary. next.config.ts proxies /api/* to KOTOBA_BACKEND_URL, which defaults
to http://127.0.0.1:8000. Point the frontend somewhere else by setting that variable before
npm run dev. The voice WebSocket does not go through that proxy — Next does not forward upgrades
— so it dials the backend directly.
/ redirects to /app; that is where the companion lives. Measured on a running dev server: /app
answers 200, / answers 307. /setup is the first-run wizard, and with no keys configured / sends
you there instead.
The dev server used to write this page was started on a spare port to avoid a collision, not on 3000.
3000 is next dev's own default and what the scripts and the README use.
The backend starts with no API keys at all — the loop answers with a canned offline reply
(core/loop.py, _offline_reply) — so the UI, the event pipeline and the avatar can be worked on
before any provider is wired up. Real keys go in api/.env, copied from api/.env.example.
For a backend change, GET /health must answer {"status": "ok"}. That is what the route returns in
api/src/kotoba/server.py, and several tests call it; it is the one route that is never behind the
gate. The server itself was not started while writing this page, to avoid opening the database sitting
in the tree.
The packaged UI, and how it relates to the dev server
The Python package can carry the compiled frontend inside itself, at
api/src/kotoba/web/. That directory is gitignored, so a fresh clone has none of it, and nothing
about a clone depends on it.
python scripts/build_web.py # needs Node; writes api/src/kotoba/web/
Not run while writing this page — it rewrites that directory. Its individual checks were exercised against the build already present; see the build pipeline.
Which one you get:
- A clone with Node keeps the dev server. Your edits have to show, and a compiled bundle would not show them.
- An install from a wheel gets the packaged build, and
kotoba serveserves it from the backend.
The build script refuses to run while a .env, .env.local, .env.production, .env.development or
either .local variant sits at the repository root, because Next would inline those values into the
bundle that then ships to everyone. It also refuses if a Live2D model is sitting under public/. See
the build pipeline.
Trying the first-run screens without touching your own install
./try-setup.sh
It runs the real kotoba setup against a throwaway directory under /tmp: KOTOBA_HOME and a dozen
sibling variables are redirected, and OPENAI_API_KEY, XAI_API_KEY and ELEVENLABS_API_KEY are
blanked, so the wizard asks from the top the way a stranger sees it. It prints the sandbox path and,
on every exit including Ctrl+C, the command to delete it.
Not exercised while writing this page — it launches an interactive wizard. The script itself was read line by line.
Line endings
.gitattributes stores and checks out everything as LF, including on Windows. A CRLF checkout breaks
the shell scripts and makes every file look modified.
