Therapy Deliberate Practice Studio helps psychotherapy trainees practise specific communication skills, record or type a response, and review structured feedback. The repository also contains an optional desktop Local Runtime Suite for running speech and language models on the learner's computer.
apps/
web/ React learner and administration interface
api/ Shared Hono API, provider, and persistence code
worker/ Cloudflare Worker, D1 migrations, and R2 integration
packages/
shared/ Shared application types and utilities
services/
local-runtime-suite/ FastAPI gateway, model adapters, and Tauri desktop app
infra/ Local development resources
The production web path uses a Cloudflare Worker with D1 and R2. The desktop app starts a loopback-only FastAPI gateway. When local AI is selected, the browser calls that gateway directly; a hosted Worker cannot reach a learner's 127.0.0.1.
uv for Local Runtime developmentThe root package.json pins the supported Node.js and npm ranges.
Install the JavaScript dependencies:
npm ci
Copy the environment example:
cp .env.example .env
Set the values required by the surface you are running. Keep all secret values out of source control.
For the web client, create apps/web/.env:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
The Vite variables are public browser configuration. Never put service-role keys or other secrets in a VITE_ variable.
For the Worker-backed application:
npm run dev
This starts the Worker development server and the Vite web server. You can also start them separately:
npm run dev:worker
npm run dev:web
The Node and SQLite API adapter remains available for focused local development:
npm run dev:api
The supported learner flow uses one local gateway for speech recognition and language evaluation.
Start the Local Runtime Suite from its desktop application or run the development gateway:
npm run dev:local
Open the desktop application and wait until the gateway is ready.
Copy the local URL and pairing key shown by the desktop application.
Open Settings in the Therapy web application.
Select local AI, paste both values, and test the connection.
Load compatible speech and language models before starting practice.
The pairing key is stored in the browser for that exact loopback origin. The browser sends it only to the local gateway as a bearer credential. It is not sent to the hosted API, included in a URL, or written to ordinary application logs.
The default gateway origin is http://127.0.0.1:8484. Protected gateway routes require the pairing key. The public health route reveals only basic product status.
See services/local-runtime-suite/README.md for model support, gateway development, and packaging details.
The generated catalog in apps/web/public/local-suite/models.json is the source shown by the web and desktop interfaces.
| Model | Purpose | Packaged platforms |
|---|---|---|
| Qwen3 4B Instruct MLX | Language evaluation | Apple-silicon macOS |
| Qwen3 4B Instruct through Transformers | Language evaluation | Apple-silicon macOS, Windows x64, Linux x64 |
| Parakeet MLX | Speech recognition | Apple-silicon macOS |
| Faster Whisper | Speech recognition | Apple-silicon macOS, Intel macOS, Windows x64, Linux x64 |
Intel macOS does not advertise a packaged language model because PyTorch 2.13 has no Python 3.12 Intel-macOS wheel. The desktop interface filters the catalog by the detected platform instead of offering a model that cannot load.
In Supabase:
http://localhost:5173/login for local development./login URL for the deployed application.SUPABASE_URL, SUPABASE_ANON_KEY, and SUPABASE_JWT_SECRET for the relevant runtime.The anonymous key is browser-visible by design. The JWT secret is sensitive and must remain a server-side secret.
Real Time Mode generates or reuses patient audio and stores it in R2.
The Worker uses the deliberate_practice_audio R2 binding declared in apps/worker/wrangler.jsonc. Set R2_BUCKET to the bound bucket name. The Worker does not need S3-compatible R2 access keys.
The Node adapter uses the S3-compatible variables in .env:
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=deliberate-practice-audio
R2_PUBLIC_BASE_URL=
R2_S3_ENDPOINT=
Set OPENAI_API_KEY when hosted text-to-speech or hosted evaluation is required. The Worker value must be a Wrangler secret.
Administrator batch parsing and patient-audio generation use Cloudflare Queues so long-running work does not block the Worker request. Create the production queues once:
npx wrangler queues create admin-batch-parse
npx wrangler queues create patient-audio
The ADMIN_BATCH_PARSE_QUEUE and PATIENT_AUDIO_QUEUE producer and consumer bindings are declared in apps/worker/wrangler.jsonc. Keep those names aligned with the queues in the intended Cloudflare account. Queue creation and remote binding changes are production mutations and require explicit deployment authorization.
Cloudflare tracks D1 migrations in apps/worker/migrations.
Apply local D1 migrations:
npm run migrate:local -w apps/worker
Apply remote D1 migrations only when you are deliberately changing the configured production database:
npm run migrate:remote -w apps/worker
Initialize the local SQLite database from the same schema:
sqlite3 apps/api/infra/local.db < apps/worker/migrations/0001_init_v2.sql
sqlite3 apps/api/infra/local.db < apps/api/infra/seed.sql
Apply the text-to-speech asset migration when the local database does not yet contain it:
sqlite3 apps/api/infra/local.db < apps/worker/migrations/0002_add_tts_assets.sql
Local administration can use the development bypass only when ENV=development:
ENV=development
BYPASS_ADMIN_AUTH=true
DEV_ADMIN_TOKEN=choose-a-local-token
Store the token in browser local storage for local testing:
localStorage.setItem("devAdminToken", "<DEV_ADMIN_TOKEN>");
Production administration uses Cloudflare Access plus ADMIN_EMAILS, ADMIN_GROUPS, CF_ACCESS_AUD, and the exact Access team origin in CF_ACCESS_ISSUER (for example, https://your-team.cloudflareaccess.com, without a trailing slash). Keep the development bypass disabled in production. The API fails closed when the audience or issuer is missing or invalid.
Authorized administrators can curate authors, criteria, patient examples, interaction examples, translations, publication state, and bulk selections. Batch parsing and patient-audio generation expose queued progress instead of holding the request open.
Run the bounded repository checks:
npm run lint
npm test
npm run build
npm run audit:production
The continuous-integration workflow also checks Python formatting and tests, the generated model catalog, Rust formatting and tests, workflow syntax, and desktop release metadata.
Desktop package builds are manual and build-only. They do not create or update a GitHub Release. See services/local-runtime-suite/desktop/README.md for the release boundary.
Building and testing this repository does not deploy the Worker or publish a desktop release.
The Worker deployment command is:
npm run deploy:prod
Run it only with explicit deployment authorization and the intended Cloudflare account selected.
Desktop publication uses a separate manual workflow that validates the selected version tag and matching native build, signatures, notarization, and an explicit PUBLISH confirmation before it can create a draft release. GitHub environment protection is a repository setting, not something workflow YAML can establish. Treat publication as disabled until administrators independently verify required reviewers, no-bypass rules, version-tag restrictions, and environment-scoped credentials for both desktop release environments. Even after that setup, a human must inspect and publish any draft separately.
Common server-side variables are:
OPENAI_API_KEYOPENAI_KEY_ENCRYPTION_SECRETSUPABASE_URLSUPABASE_ANON_KEYSUPABASE_JWT_SECRETR2_BUCKETR2_PUBLIC_BASE_URLADMIN_EMAILSADMIN_GROUPSCF_ACCESS_AUDCF_ACCESS_ISSUERENVBYPASS_ADMIN_AUTHDEV_ADMIN_TOKENAI_MODE, LOCAL_STT_URL, LOCAL_LLM_URL, and LOCAL_LLM_MODEL remain for the Node adapter and compatibility tests. The production learner flow does not expect a hosted Worker to call those loopback URLs. Users configure the browser-to-gateway connection in Therapy Settings.
386 commits
TypeScript
78.4%
Python
16.8%
Rust
3.0%
JavaScript
1.0%
Therapy Deliberate Practice Studio helps psychotherapy trainees practise specific communication skills, record or type a response, and review structured feedback. The repository also contains an optional desktop Local Runtime Suite for running speech and language models on the learner's computer.
apps/
web/ React learner and administration interface
api/ Shared Hono API, provider, and persistence code
worker/ Cloudflare Worker, D1 migrations, and R2 integration
packages/
shared/ Shared application types and utilities
services/
local-runtime-suite/ FastAPI gateway, model adapters, and Tauri desktop app
infra/ Local development resources
The production web path uses a Cloudflare Worker with D1 and R2. The desktop app starts a loopback-only FastAPI gateway. When local AI is selected, the browser calls that gateway directly; a hosted Worker cannot reach a learner's 127.0.0.1.
uv for Local Runtime developmentThe root package.json pins the supported Node.js and npm ranges.
Install the JavaScript dependencies:
npm ci
Copy the environment example:
cp .env.example .env
Set the values required by the surface you are running. Keep all secret values out of source control.
For the web client, create apps/web/.env:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
The Vite variables are public browser configuration. Never put service-role keys or other secrets in a VITE_ variable.
For the Worker-backed application:
npm run dev
This starts the Worker development server and the Vite web server. You can also start them separately:
npm run dev:worker
npm run dev:web
The Node and SQLite API adapter remains available for focused local development:
npm run dev:api
The supported learner flow uses one local gateway for speech recognition and language evaluation.
Start the Local Runtime Suite from its desktop application or run the development gateway:
npm run dev:local
Open the desktop application and wait until the gateway is ready.
Copy the local URL and pairing key shown by the desktop application.
Open Settings in the Therapy web application.
Select local AI, paste both values, and test the connection.
Load compatible speech and language models before starting practice.
The pairing key is stored in the browser for that exact loopback origin. The browser sends it only to the local gateway as a bearer credential. It is not sent to the hosted API, included in a URL, or written to ordinary application logs.
The default gateway origin is http://127.0.0.1:8484. Protected gateway routes require the pairing key. The public health route reveals only basic product status.
See services/local-runtime-suite/README.md for model support, gateway development, and packaging details.
The generated catalog in apps/web/public/local-suite/models.json is the source shown by the web and desktop interfaces.
| Model | Purpose | Packaged platforms |
|---|---|---|
| Qwen3 4B Instruct MLX | Language evaluation | Apple-silicon macOS |
| Qwen3 4B Instruct through Transformers | Language evaluation | Apple-silicon macOS, Windows x64, Linux x64 |
| Parakeet MLX | Speech recognition | Apple-silicon macOS |
| Faster Whisper | Speech recognition | Apple-silicon macOS, Intel macOS, Windows x64, Linux x64 |
Intel macOS does not advertise a packaged language model because PyTorch 2.13 has no Python 3.12 Intel-macOS wheel. The desktop interface filters the catalog by the detected platform instead of offering a model that cannot load.
In Supabase:
http://localhost:5173/login for local development./login URL for the deployed application.SUPABASE_URL, SUPABASE_ANON_KEY, and SUPABASE_JWT_SECRET for the relevant runtime.The anonymous key is browser-visible by design. The JWT secret is sensitive and must remain a server-side secret.
Real Time Mode generates or reuses patient audio and stores it in R2.
The Worker uses the deliberate_practice_audio R2 binding declared in apps/worker/wrangler.jsonc. Set R2_BUCKET to the bound bucket name. The Worker does not need S3-compatible R2 access keys.
The Node adapter uses the S3-compatible variables in .env:
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=deliberate-practice-audio
R2_PUBLIC_BASE_URL=
R2_S3_ENDPOINT=
Set OPENAI_API_KEY when hosted text-to-speech or hosted evaluation is required. The Worker value must be a Wrangler secret.
Administrator batch parsing and patient-audio generation use Cloudflare Queues so long-running work does not block the Worker request. Create the production queues once:
npx wrangler queues create admin-batch-parse
npx wrangler queues create patient-audio
The ADMIN_BATCH_PARSE_QUEUE and PATIENT_AUDIO_QUEUE producer and consumer bindings are declared in apps/worker/wrangler.jsonc. Keep those names aligned with the queues in the intended Cloudflare account. Queue creation and remote binding changes are production mutations and require explicit deployment authorization.
Cloudflare tracks D1 migrations in apps/worker/migrations.
Apply local D1 migrations:
npm run migrate:local -w apps/worker
Apply remote D1 migrations only when you are deliberately changing the configured production database:
npm run migrate:remote -w apps/worker
Initialize the local SQLite database from the same schema:
sqlite3 apps/api/infra/local.db < apps/worker/migrations/0001_init_v2.sql
sqlite3 apps/api/infra/local.db < apps/api/infra/seed.sql
Apply the text-to-speech asset migration when the local database does not yet contain it:
sqlite3 apps/api/infra/local.db < apps/worker/migrations/0002_add_tts_assets.sql
Local administration can use the development bypass only when ENV=development:
ENV=development
BYPASS_ADMIN_AUTH=true
DEV_ADMIN_TOKEN=choose-a-local-token
Store the token in browser local storage for local testing:
localStorage.setItem("devAdminToken", "<DEV_ADMIN_TOKEN>");
Production administration uses Cloudflare Access plus ADMIN_EMAILS, ADMIN_GROUPS, CF_ACCESS_AUD, and the exact Access team origin in CF_ACCESS_ISSUER (for example, https://your-team.cloudflareaccess.com, without a trailing slash). Keep the development bypass disabled in production. The API fails closed when the audience or issuer is missing or invalid.
Authorized administrators can curate authors, criteria, patient examples, interaction examples, translations, publication state, and bulk selections. Batch parsing and patient-audio generation expose queued progress instead of holding the request open.
Run the bounded repository checks:
npm run lint
npm test
npm run build
npm run audit:production
The continuous-integration workflow also checks Python formatting and tests, the generated model catalog, Rust formatting and tests, workflow syntax, and desktop release metadata.
Desktop package builds are manual and build-only. They do not create or update a GitHub Release. See services/local-runtime-suite/desktop/README.md for the release boundary.
Building and testing this repository does not deploy the Worker or publish a desktop release.
The Worker deployment command is:
npm run deploy:prod
Run it only with explicit deployment authorization and the intended Cloudflare account selected.
Desktop publication uses a separate manual workflow that validates the selected version tag and matching native build, signatures, notarization, and an explicit PUBLISH confirmation before it can create a draft release. GitHub environment protection is a repository setting, not something workflow YAML can establish. Treat publication as disabled until administrators independently verify required reviewers, no-bypass rules, version-tag restrictions, and environment-scoped credentials for both desktop release environments. Even after that setup, a human must inspect and publish any draft separately.
Common server-side variables are:
OPENAI_API_KEYOPENAI_KEY_ENCRYPTION_SECRETSUPABASE_URLSUPABASE_ANON_KEYSUPABASE_JWT_SECRETR2_BUCKETR2_PUBLIC_BASE_URLADMIN_EMAILSADMIN_GROUPSCF_ACCESS_AUDCF_ACCESS_ISSUERENVBYPASS_ADMIN_AUTHDEV_ADMIN_TOKENAI_MODE, LOCAL_STT_URL, LOCAL_LLM_URL, and LOCAL_LLM_MODEL remain for the Node adapter and compatibility tests. The production learner flow does not expect a hosted Worker to call those loopback URLs. Users configure the browser-to-gateway connection in Therapy Settings.
386 commits
TypeScript
78.4%
Python
16.8%
Rust
3.0%
JavaScript
1.0%