Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d3bfd20
fix: a fresh clone can actually follow the manual quickstart
mahimairaja Aug 8, 2026
95be7c1
docs: say only what ShipVoice Pro actually ships, and credit the vend…
mahimairaja Aug 8, 2026
e401b79
fix(backend): close registration and make the admin gate a dependency
mahimairaja Aug 8, 2026
a5705f9
chore(backend): drop the uv init scaffolding
mahimairaja Aug 8, 2026
7f03fbe
feat(backend): describe the configured agent, without pretending to o…
mahimairaja Aug 8, 2026
5ce578f
feat(frontend): the ShipVoice console, with four surfaces marked Pro
mahimairaja Aug 8, 2026
c602cbc
feat(frontend): no sign-in, one Upgrade button, the real mark
mahimairaja Aug 8, 2026
d07c042
feat: formatted
mahimairaja Aug 8, 2026
e2db35d
refactor(frontend): Settings becomes Deployment, and stops copying th…
mahimairaja Aug 8, 2026
3cec5cf
refactor(backend): delete the auth slice nothing was using
mahimairaja Aug 8, 2026
6d47452
feat: LiveKit credentials live in the database, editable from the con…
mahimairaja Aug 8, 2026
beb89c2
feat: the worker follows the LiveKit project, instead of being warned…
mahimairaja Aug 8, 2026
2a5a67b
feat: formateed agent
mahimairaja Aug 9, 2026
28b2332
feat: formatted backend
mahimairaja Aug 9, 2026
0efe8c8
feat: formatted frontend
mahimairaja Aug 9, 2026
b7ab559
feat: a fresh clone talks, and the providers move to Cerebras and Inw…
mahimairaja Aug 9, 2026
dd9aca4
refactor: six env values, and a first screen that works
mahimairaja Aug 9, 2026
0095265
feat: a doctor that names the cause, and a repo a coding agent can read
mahimairaja Aug 9, 2026
124d0b4
fix: the image build no longer needs LiveKit credentials
mahimairaja Aug 9, 2026
c1cbbe2
fix: close the review's three blockers and twelve other findings
mahimairaja Aug 9, 2026
55e0c4c
feat: the reference console, with Overview and Calls actually backed
mahimairaja Aug 9, 2026
d094964
feat(frontend): Calls and Call detail match the reference design
mahimairaja Aug 9, 2026
c298f3c
fix(frontend): call the in-flight state what the reference calls it
mahimairaja Aug 9, 2026
b6c6f7e
feat(console): build the Agents tab against the reference
mahimairaja Aug 9, 2026
217e1f6
feat(console): edit the agent system prompt from the Agents page
mahimairaja Aug 9, 2026
bb9275c
refactor(console): say what a dash means once, not on every page
mahimairaja Aug 9, 2026
4b00b1b
feat(console): carry the whole reference Overview, Pro parts greyed
mahimairaja Aug 9, 2026
26d637d
refactor(console): mark sample figures with a dot, not a worded chip
mahimairaja Aug 9, 2026
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
57 changes: 57 additions & 0 deletions .claude/commands/setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
description: Set up this repo and get a voice agent talking
---

Get this clone from nothing to a spoken reply. Keep it short; `scripts/doctor.py`
does the diagnosing, so you do not have to.

## 1. Ask one question

"In one sentence, what should this agent do? (Enter for a general assistant.)"

Ask nothing else. There is no sign-in, LiveKit credentials go in `.env`, and
everything else has a default.

## 2. Get `.env` filled in

If `.env` is missing, `cp .env.example .env`.

Then run `cd agent && uv run python ../scripts/doctor.py` and read it. If it
names anything missing, tell the human exactly which variables to paste into
`.env`, and where each comes from:

- `LIVEKIT_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET` from https://cloud.livekit.io
- `DEEPGRAM_API_KEY` from https://console.deepgram.com
- `CEREBRAS_API_KEY` from https://cloud.cerebras.ai
- `INWORLD_API_KEY` from https://platform.inworld.ai

Then wait for them to say it is done.

**Do not ask for a key, do not read one back, do not echo one, and never write
one into a file yourself.** Everything in this conversation is saved to the
Claude Code transcript on disk. The human pastes their own keys; you never see
them. If the doctor says a key is rejected, say which one and let them fix it.

If `AGENT_SERVICE_TOKEN` is empty, you may generate that one yourself, since it
is not anyone's credential: `openssl rand -hex 32`, written to both
`AGENT_SERVICE_TOKEN` and `BACKEND_API_TOKEN`.

## 3. Write the persona

Unless they pressed Enter, rewrite `agent/prompts/instructions.md` for what they
described. Keep the `{agent_name}` placeholder, keep the output rules (plain
text, one to three sentences, one question at a time), and keep it short: it is
a voice prompt, not a manual.

## 4. Start it and check

```bash
docker compose up -d --build
cd agent && uv run python ../scripts/doctor.py --live
```

Report what the doctor says. If everything passes, tell them to open
http://localhost:5173, go to Agents, and hit **Start test call**.

If something fails, the doctor already named the cause and the fix. Do that,
run it again, and do not start guessing at logs.
73 changes: 46 additions & 27 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,35 +1,54 @@
# Root env for the docker compose stack (cp to .env).
# All three services read from this file; keys are grouped by who uses them.
# Copy to .env, fill in the values below, then: docker compose up --build
#
# This is the ONLY env file docker compose reads. The per-service .env files
# (agent/, backend/, frontend/) are for running those services by hand.
#
# Everything else has a working default in code. Add a variable here only when
# you need to override one.

# ===== LiveKit (shared by backend + agent; the browser also connects here) =====
# Use a LiveKit Cloud project (free tier) or your self-hosted server.
LIVEKIT_URL=wss://your-project.livekit.cloud
# ===== LiveKit =====
# A free LiveKit Cloud project works: https://cloud.livekit.io
# The URL is the wss:// address of your project.
LIVEKIT_URL=
LIVEKIT_API_KEY=
LIVEKIT_API_SECRET=

# ===== Agent: voice providers =====
OPENAI_API_KEY=
# ===== Voice providers =====
# Speech to text: https://console.deepgram.com
DEEPGRAM_API_KEY=
CARTESIA_API_KEY=
AGENT_NAME=assistant

# ===== Backend =====
ENV=dev
# Generate a strong value for anything you deploy (>= 32 chars).
JWT_SECRET_KEY=change-me-in-prod-change-me-in-prod-32chars-min
# Allow the frontend origin so the browser can call /token.
CORS_ORIGINS_STR=http://localhost:5173
# Postgres (the compose 'db' service). Only needed for the auth/User endpoints.
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=db
DB_PORT=5432
DB_NAME=app
DB_SSL=disable

# ===== Frontend (baked into the static build) =====
VITE_TOKEN_ENDPOINT=http://localhost:8000/api/v1/token
VITE_AGENT_NAME=assistant
# The LLM: https://cloud.cerebras.ai
CEREBRAS_API_KEY=
# Text to speech: https://platform.inworld.ai
INWORLD_API_KEY=

# ===== Optional =====
# What the agent is called. It MUST match on both sides or LiveKit never
# dispatches the worker and the call connects to silence with no error, so
# change both or neither.
# AGENT_NAME=assistant
# VITE_AGENT_NAME=assistant

# Lets the worker take its LiveKit project from the backend and follow changes
# made in the console. Both values must be the SAME long random string, and an
# empty value disables the endpoint that serves them rather than opening it.
# Generate one with: openssl rand -hex 32
AGENT_SERVICE_TOKEN=
BACKEND_API_TOKEN=

# Whether the worker records its calls. It posts each call's start, its turns
# and its end to the backend, which is what fills the console's Calls page.
# Needs the two tokens above, so it stays off until you set them. Compose turns
# this on; set it to false to keep the worker silent. Console mode never
# reports, whatever this says.
BACKEND_REPORTING_ENABLED=true

# How the console says this deployment runs a call: "sequential" is one agent
# from hello to goodbye, "supervisor" is a router that hands one turn at a time
# to a specialist. Those two only; anything else falls back to sequential.
# AGENT_PATTERN=sequential

# What the agent says it works for, shown next to a call in the console.
# BUSINESS_NAME=

# Error tracking, off when unset.
# SENTRY_DSN=
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@ docs/superpowers
# Root env (copy .env.example -> .env)
.env
.env.local
.env.*
!.env.example

# Written by '/setup': holds the non-secret setup answers. The command deletes
# it on a successful run; this is the backstop.
setup.json
.gstack/

# Design source: the reference console export. Internal, and half a megabyte
# of bundled markup nobody cloning this repo needs.
reference/
109 changes: 109 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Working in this repo

For coding agents. Six facts, then how to run it. Everything here is verified
against the code, not aspirational.

## What this is

A voice AI starter. Three services, each independently runnable:

- `agent/` a LiveKit Agents worker. Deepgram nova-3 speech to text, Cerebras
`gemma-4-31b`, Inworld `inworld-tts-2` speech, Silero VAD, LiveKit's
multilingual turn detector.
- `backend/` FastAPI. Mints LiveKit room tokens, stores the LiveKit project,
describes the agent. Five routes, no authentication.
- `frontend/` the console. React, Vite, one page that matters (Agents) plus a
Deployment page.

## The six things that will cost you an hour if you do not know them

1. **`docker compose` reads the root `.env` and nothing else.** There are also
`agent/.env`, `backend/.env` and `frontend/.env`; those are for running a
service by hand. Writing a value into the wrong one produces a config that
works one way and not the other.

2. **`AGENT_NAME` and `VITE_AGENT_NAME` must be byte-identical.** LiveKit
dispatches the worker by exact string match. When they differ the room
opens, the token is valid, the browser connects, and nothing ever speaks.
No error is logged anywhere. `scripts/doctor.py` checks this first for a
reason.

3. **`VITE_*` values are baked into the frontend at image build time.** After
changing one, `docker compose up -d --build`. A plain `restart` silently
keeps the old value, which produces failure 2.

4. **Console mode needs no LiveKit, no backend and no database.**
`cd agent && uv run python main.py console` runs the whole speech to model
to speech loop in the terminal with just the three provider keys. It is the
fastest way to prove the agent works before anything else is up.

5. **The backend has no authentication at all.** Every route is open. That is
deliberate for a tool you run on your own machine and wrong on a public
address. Never expose it without putting something in front of it.

6. **Never write, echo, `cat` or read back an API key.** Anything you put in a
tool call is persisted to the coding agent's transcript on disk, where the
user does not know to look for it. Name the variable and the file, ask the
human to paste it themselves, then run the doctor to confirm it worked. This
is the one rule in this file with no exceptions.

## Running it

```bash
cp .env.example .env # LiveKit and three provider keys
docker compose up --build # postgres, backend, agent, console
```

Then http://localhost:5173, Agents, **Start test call**.

The backend brings the schema to head on startup, so there is no migration
step. If something does not work, do not guess:

```bash
cd agent && uv run python ../scripts/doctor.py --live
```

It names the cause and the fix. Every failure in this stack is quiet, so
reading its output is faster than reading logs.

## Changing the agent

The persona is `agent/prompts/instructions.md`, a plain file with one
`{agent_name}` placeholder. Editing it is the whole process, and there is no
restart: LiveKit runs `entrypoint()` per job, `Assistant.__init__` calls
`load_instructions()`, and that reads the file every time. So a save lands on
the next call. A call already in progress keeps the prompt it started with.

Two ways to edit it, same file:

- The file itself, in your editor.
- The console, on the Agents page, over `GET` and
`PUT /api/v1/agents/{slug}/prompt`. `{slug}` is `AGENT_NAME`; anything else
is a 404. The write needs `CONSOLE_WRITES_ENABLED` (compose sets it) and the
backend's read-write mount of `./agent/prompts` (compose sets that too). It
is atomic, a temp file in the same directory renamed over the target, so a
save never leaves the worker reading half a persona. Without the mount the
editor still loads and every save answers 409 naming the path.

Do not edit `agent/src/prompts/instructions.py`: that holds the packaged
fallback for a clone that has no prompt file. When the file is missing the
console shows an empty editor and says the worker is running that fallback.

## Conventions

- Python: `uv`, `ruff`, `mypy` on `src`. Never `pip`.
- Frontend: `pnpm` only, pinned. The build is `tsc -b && vite build` with
`noUnusedLocals` and `noUnusedParameters`, so an unused import fails the
build, not the lint.
- Frontend imports come from `react-router`, never `react-router-dom`.
- No `: JSX.Element` return annotations; that namespace is gone in React 19.
- No em dashes in code, comments, docs or UI copy.
- Tests: `uv run pytest -q` in `agent/` and `backend/`, `pnpm test` in
`frontend/`.

## What is deliberately not here

Call logging, campaigns, customer records, evaluations, billing and metering
belong to ShipVoice Pro. The console shows them greyed out so the shape of the
full product is visible. Do not build them here: an empty table is worse than
an honest absence.
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# CLAUDE.md

Read `AGENTS.md` in this directory. It is the contract for this repo and it is
the only one: this file exists because Claude Code looks for this name, and
duplicating the content here would guarantee the two drift apart.

@AGENTS.md

## Setup

`/setup` walks a new clone to a talking agent. It asks what the agent should
be, writes the persona, brings the stack up, and runs the doctor.

You will be asked to paste three API keys into `.env` yourself. That is not
friction for its own sake: anything typed into this conversation is written to
the Claude Code transcript on disk, so keys must not pass through it.
15 changes: 15 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,18 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

---

Scope of the grant above.

Parts of this repository are third-party code vendored from public component
registries. Those files are licensed by their upstream authors, not by the
notice above. They are listed in THIRD_PARTY_NOTICES.md.

Images under assets/ that depict ShipVoice Pro are included for comparison and
are not covered by the grant above. They remain the property of the author.

The same applies to the ShipVoice name and the mark in frontend/public. The
code is yours to use under the terms above; the brand is not. Ship what you
build under your own name.
Loading
Loading