From 280146b5d023b43c4f19dc4afbd4508e9adec8e4 Mon Sep 17 00:00:00 2001 From: digitallysavvy Date: Thu, 20 Aug 2026 18:13:17 -0400 Subject: [PATCH 1/3] fix: align python quickstart environment setup Use server/.env consistently across setup, runtime, and doctor checks. Remove the separate web env file and update documentation and ignore rules. --- .dockerignore | 3 +- .gitignore | 2 -- AGENTS.md | 2 +- README.md | 10 +++--- docs/ai/L0_repo_card.md | 2 +- docs/ai/L1/01_setup.md | 13 ++------ docs/ai/L1/02_architecture.md | 2 +- docs/ai/L1/05_workflows.md | 2 +- docs/ai/L1/07_gotchas.md | 2 +- docs/ai/L1/08_security.md | 4 +-- docs/ai/L1/L2/from_scratch_bootstrap.md | 4 +-- docs/ai/L1/L2/managed_agent_config.md | 2 +- package.json | 6 ++-- server/.gitignore | 3 +- server/README.md | 16 +++++----- server/src/server.py | 3 +- server/tests/conftest.py | 5 ++- web/.env.local.example | 2 -- web/scripts/doctor.ts | 41 +------------------------ 19 files changed, 35 insertions(+), 89 deletions(-) delete mode 100644 web/.env.local.example diff --git a/.dockerignore b/.dockerignore index 8fa99a6..04e4c4f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,7 @@ **/venv **/node_modules **/__pycache__ -*.env.local -**/.env.local +**/.env **/tests docs/ .github/ diff --git a/.gitignore b/.gitignore index bb0325d..1376f37 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,6 @@ dist/ # Environment .env -.env.local -.env.*.local # Agora CLI local project binding .agora/ diff --git a/AGENTS.md b/AGENTS.md index bd35ced..d4942bf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,7 +72,7 @@ The sections below (Start Here, Patterns, Anti-Patterns, etc.) remain the canoni - Prefer the smallest change that keeps local mode and deployed mode aligned. - Keep Python-specific agent lifecycle changes in `server`. - Keep browser state and RTC/RTM lifecycle changes in `web`. -- Treat `server/.env.local` as CLI-managed by default. +- Treat `server/.env` as CLI-managed by default. - If you change request or response contracts, update the web client, backend, contract checks, and README together. ## Commands diff --git a/README.md b/README.md index 284121b..4f3e483 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ cd agent-quickstart-python agora login agora project use bun run setup -agora project env write server/.env.local +agora quickstart env write . bun run doctor:local bun run dev ``` @@ -79,8 +79,8 @@ To export local env values from the Agora CLI-bound project: ```bash agora project use -agora project env write server/.env.local -rg "^(AGORA_APP_ID|AGORA_APP_CERTIFICATE)=" server/.env.local +agora quickstart env write . +rg "^(AGORA_APP_ID|AGORA_APP_CERTIFICATE)=" server/.env ``` ## Environment variables @@ -153,8 +153,8 @@ The browser talks to Next.js `/api/*` routes. In local mode, Next rewrites those ## Troubleshooting - **Agent does not join or transcripts are missing:** run `agora project doctor --deep`. -- **Missing credentials:** run `agora project env write server/.env.local`. -- **Auth errors from backend:** confirm `AGORA_APP_ID` and `AGORA_APP_CERTIFICATE` are set in `server/.env.local`. +- **Missing credentials:** run `agora quickstart env write .`. +- **Auth errors from backend:** confirm `AGORA_APP_ID` and `AGORA_APP_CERTIFICATE` are set in `server/.env`. - **Frontend cannot reach backend:** confirm `AGENT_BACKEND_URL=http://localhost:8000` in local frontend scripts. - **Unsure who owns `/api/*`:** Next owns browser-facing `/api/*`; FastAPI owns `/get_config`, `/startAgent`, `/stopAgent`. diff --git a/docs/ai/L0_repo_card.md b/docs/ai/L0_repo_card.md index d4c6ba6..a160818 100644 --- a/docs/ai/L0_repo_card.md +++ b/docs/ai/L0_repo_card.md @@ -11,7 +11,7 @@ | Language | Python 3.10+ (FastAPI + uvicorn) backend + Next.js 16 / React 19 web | | Deploy Target | `web/` as Next.js app, `server/` as a reachable FastAPI service | | Owner | Agora Conversational AI DevEx | -| Last Reviewed | 2026-06-11 | +| Last Reviewed | 2026-08-20 | | Recipe Role | `base` | | Recipe Version | `1.0.0` | | Recipe Status | `experimental` | diff --git a/docs/ai/L1/01_setup.md b/docs/ai/L1/01_setup.md index abf5e1a..4577742 100644 --- a/docs/ai/L1/01_setup.md +++ b/docs/ai/L1/01_setup.md @@ -26,7 +26,7 @@ bun run setup # runs: setup:env → setup:backend → setup:frontend → setup:done ``` -`setup:env` copies `server/.env.example` → `server/.env.local` if missing. `setup:backend` recreates `server/venv`, upgrades pip, and installs `requirements.txt`. `setup:frontend` runs `bun install`. `setup:deps` exists for `bun run dev:check`, not for `bun run setup`. +`setup:env` copies `server/.env.example` → `server/.env` if missing. It preserves an existing `server/.env` written by `agora init` or `agora quickstart env write`. `setup:backend` recreates `server/venv`, upgrades pip, and installs `requirements.txt`. `setup:frontend` runs `bun install`. `setup:deps` exists for `bun run dev:check`, not for `bun run setup`. > The package.json scripts use `server/venv/` (no leading dot). `bun run dev:backend` activates `server/venv` and runs `python src/server.py` from inside `server/`. If you create the venv under a different name you'll need to adjust the scripts or symlink. @@ -41,13 +41,6 @@ AGENT_GREETING=Hi there! I'm Ada, your virtual assistant from Agora. How can I h PORT=8000 ``` -`web/.env.local.example`: - -``` -# Required: Next rewrites /api/* requests to the Python backend. -AGENT_BACKEND_URL=http://localhost:8000 -``` - | Variable | Process | Required | Notes | | ------------------------ | -------------------- | -------- | --------------------------------------------------------------------- | | `AGORA_APP_ID` | Python (server) | Yes | Loaded by `Agent.__init__` via `os.environ`. | @@ -78,7 +71,7 @@ bun run dev # setup:env → setup:deps → concurrently {back bun run dev:backend # python3 server/src/server.py bun run dev:frontend # cd web && AGENT_BACKEND_URL=http://localhost:8000 bun run dev bun run doctor # bun + node_modules sanity -bun run doctor:local # adds python3 + .env.local + AGORA_* presence +bun run doctor:local # adds python3 + server/.env + AGORA_* presence bun run build # bun --filter web build bun run verify # doctor + verify:web:api + verify:web:build bun run verify:local # doctor:local + verify:backend + verify:local:fastapi + verify:web:proxy + verify:web:build @@ -107,7 +100,7 @@ bun run clean # remove backend venv, node_modules, .next, web/d ## Common Setup Failures - `bun run doctor:local` fails on **"python3 not found"** → install Python ≥ 3.10. -- Doctor fails on missing `server/.env.local` → run `bun run setup:env` or copy from `server/.env.example`. +- Doctor fails on missing `server/.env` → run `agora quickstart env write .` or `bun run setup:env`. - `cd web && bun run doctor` rejects empty/invalid `AGENT_BACKEND_URL` → ensure the URL is `http://` or `https://`. - `verify:web:api` fails on a new route → extend `web/scripts/verify-api-contracts.ts` to cover it. diff --git a/docs/ai/L1/02_architecture.md b/docs/ai/L1/02_architecture.md index a9b93b5..fba4ff2 100644 --- a/docs/ai/L1/02_architecture.md +++ b/docs/ai/L1/02_architecture.md @@ -57,7 +57,7 @@ If `AGENT_BACKEND_URL` is unset/empty, **no rewrites register** — the client c - `FastAPI(title="...", version="2.0.0")`. - `CORSMiddleware` with `allow_origins=["*"]`, `allow_credentials=True`. -- Reads `server/.env.local` then `server/.env` via `python-dotenv` at startup, resolved relative to `server/src/server.py`. +- Reads `server/.env` via `python-dotenv` at startup, resolved relative to `server/src/server.py`. - Constructs a single `Agent` instance at import time (`agent = Agent()`). - Routes registered on an `APIRouter`: `GET /get_config`, `POST /startAgent`, `POST /stopAgent`. - All responses use the envelope `{ "code": 0, "msg": "success", "data": ... }`. diff --git a/docs/ai/L1/05_workflows.md b/docs/ai/L1/05_workflows.md index ba535b0..2da0725 100644 --- a/docs/ai/L1/05_workflows.md +++ b/docs/ai/L1/05_workflows.md @@ -20,7 +20,7 @@ Edit `server/src/agent.py`: - **Prompt:** modify the `ADA_PROMPT` constant. -- **Greeting:** set `AGENT_GREETING` in `server/.env.local`, or change the default in the constructor. +- **Greeting:** set `AGENT_GREETING` in `server/.env`, or change the default in the constructor. - **VAD:** edit `turn_detection` dict (start/end mode, speech threshold, silence/interrupt durations). - **LLM:** change the `OpenAI(...)` constructor (model, history, BYOK key, base URL). - **STT:** change the `DeepgramSTT(...)` constructor. diff --git a/docs/ai/L1/07_gotchas.md b/docs/ai/L1/07_gotchas.md index 74f998a..c798b8b 100644 --- a/docs/ai/L1/07_gotchas.md +++ b/docs/ai/L1/07_gotchas.md @@ -64,7 +64,7 @@ The quickstart deliberately uses `generate_convo_ai_token` for both RTC and RTM ## Env Loading Is File-Relative -`server.py` derives the `server/` directory from `__file__` and loads `server/.env.local` then `server/.env`. Running from the repo root still finds those files; missing `AGORA_APP_ID` or `AGORA_APP_CERTIFICATE` leaves `agent = None` and routes return `500`. +`server.py` derives the `server/` directory from `__file__` and loads `server/.env`. Running from the repo root still finds that file; missing `AGORA_APP_ID` or `AGORA_APP_CERTIFICATE` leaves `agent = None` and routes return `500`. ## `server/scripts/run_fake_server.py` Is for Tests Only diff --git a/docs/ai/L1/08_security.md b/docs/ai/L1/08_security.md index d97277d..6b6fee4 100644 --- a/docs/ai/L1/08_security.md +++ b/docs/ai/L1/08_security.md @@ -62,9 +62,9 @@ If you need real auth, add a FastAPI dependency that validates a header on each ## Secret Handling Rules -- `server/.env.local` is the developer's secret store; do not commit it. +- `server/.env` is the developer's secret store; do not commit it. - `server/.env.example` documents shape only — never put real values there. -- `load_dotenv` reads `server/.env.local` then `server/.env` using a path derived from `server/src/server.py`; missing credentials fail startup initialization and leave routes returning `500`. +- `load_dotenv` reads `server/.env` using a path derived from `server/src/server.py`; missing credentials fail startup initialization and leave routes returning `500`. - Do not log full env. `logger.error("failed: %s", err)` is fine; `logger.error(os.environ)` is not. ## CSP / Security Headers diff --git a/docs/ai/L1/L2/from_scratch_bootstrap.md b/docs/ai/L1/L2/from_scratch_bootstrap.md index 08bfe76..44da6cd 100644 --- a/docs/ai/L1/L2/from_scratch_bootstrap.md +++ b/docs/ai/L1/L2/from_scratch_bootstrap.md @@ -12,7 +12,7 @@ Why: provider schemas, SDK builder fields, token behavior, and RTM event details | Need | Read First | Deep Detail | Source Reference | | --- | --- | --- | --- | -| Project setup, commands, env vars | [../01_setup.md](../01_setup.md) | none | `package.json`, `server/.env.example`, `web/.env.local.example` | +| Project setup, commands, env vars | [../01_setup.md](../01_setup.md) | none | `package.json`, `server/.env.example` | | End-to-end architecture and data flow | [../02_architecture.md](../02_architecture.md) | [session_lifecycle.md](session_lifecycle.md) | `web/src/components/LandingPage.tsx`, `web/src/components/ConversationComponent.tsx`, `server/src/server.py` | | File/module responsibilities | [../03_code_map.md](../03_code_map.md) | none | `web/`, `server/`, `web/scripts/` | | API payloads and response shapes | [../06_interfaces.md](../06_interfaces.md) | [verification_scripts.md](verification_scripts.md) | `server/src/server.py`, `web/src/services/api.ts`, `web/next.config.ts` | @@ -29,7 +29,7 @@ Implement these pieces in order: 2. Create `server/` with FastAPI, uvicorn, python-dotenv, and `agora-agents>=2.0.0` in `server/requirements.txt`. 3. Add `server/.env.example` with `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE`, optional `AGENT_GREETING`, and optional `PORT`. 4. Implement `server/src/agent.py` with an `Agent` class that reads env once, constructs `AsyncAgora`, builds `AgoraAgent` with managed `DeepgramSTT`, `OpenAI`, `MiniMaxTTS`, starts async sessions, stores sessions by `agent_id`, and stops by active session or `client.stop_agent`. -5. Implement `server/src/server.py` with `GET /get_config`, `POST /startAgent`, and `POST /stopAgent`; load env file-relative from `server/.env.local` then `server/.env`. +5. Implement `server/src/server.py` with `GET /get_config`, `POST /startAgent`, and `POST /stopAgent`; load `server/.env` relative to the source file. 6. In `GET /get_config`, replace missing, zero, or negative UIDs with a generated non-zero UID, generate a one-hour RTC+RTM token with `generate_convo_ai_token`, and return `{ app_id, token, uid, channel_name, agent_uid }`. 7. Create a Next.js App Router web app under `web/` with React, TypeScript, Tailwind, `agora-rtc-react`, `agora-rtm`, `agora-agent-client-toolkit`, and `agora-agent-uikit`. 8. Implement `web/next.config.ts` rewrites for `/api/get_config`, `/api/startAgent`, and `/api/stopAgent` to `${AGENT_BACKEND_URL}/...`; return no rewrites when the env var is missing. diff --git a/docs/ai/L1/L2/managed_agent_config.md b/docs/ai/L1/L2/managed_agent_config.md index 758403a..ae0c076 100644 --- a/docs/ai/L1/L2/managed_agent_config.md +++ b/docs/ai/L1/L2/managed_agent_config.md @@ -77,7 +77,7 @@ Edit the `ADA_PROMPT` string constant at the top of `agent.py`. Keep it concise ### Change the greeting -Set `AGENT_GREETING` in `server/.env.local`, or change the inline fallback string in `Agent.__init__`. +Set `AGENT_GREETING` in `server/.env`, or change the inline fallback string in `Agent.__init__`. ### Change VAD diff --git a/package.json b/package.json index 5f5a086..dabd5c1 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,13 @@ "backend": "cd server && source venv/bin/activate && python src/server.py", "frontend": "cd web && AGENT_BACKEND_URL=http://localhost:8000 bun run dev", "setup": "bun run setup:env && bun run setup:backend && bun run setup:frontend && bun run setup:done", - "setup:env": "test -f server/.env.local || (cp server/.env.example server/.env.local && echo '\nCreated server/.env.local. Add Agora credentials before running the app.')", + "setup:env": "test -f server/.env || (cp server/.env.example server/.env && echo '\nCreated server/.env. Add Agora credentials before running the app.')", "setup:deps": "test -d node_modules || (echo 'Installing workspace dependencies...' && bun install)", "setup:backend": "cd server && rm -rf venv && python3 -m venv venv && source venv/bin/activate && python -m pip install --upgrade pip && PIP_INDEX_URL=https://pypi.org/simple python -m pip install -r requirements.txt", "setup:frontend": "bun install", - "setup:done": "echo '\n✅ Setup complete! Next steps:' && echo ' 1. Run: agora project env write server/.env.local' && echo ' 2. Run: bun run dev\n'", + "setup:done": "echo '\n✅ Setup complete! Next steps:' && echo ' 1. Run: agora quickstart env write .' && echo ' 2. Run: bun run dev\n'", "doctor": "bash -c 'set -e; echo \"Checking shared repo prerequisites...\"; command -v bun >/dev/null && echo \"- bun available\" || { echo \"- bun not found\"; exit 1; }; test -d node_modules && echo \"- workspace dependencies installed\" || { echo \"- root node_modules missing; run bun install\"; exit 1; }'", - "doctor:local": "bash -c 'set -e; bun run doctor; command -v python3 >/dev/null && echo \"- python3 available\" || { echo \"- python3 not found\"; exit 1; }; test -f server/.env.local && echo \"- server/.env.local present\" || { echo \"- missing server/.env.local\"; exit 1; }; grep -Eq \"^AGORA_APP_ID=.+$\" server/.env.local && echo \"- AGORA_APP_ID configured\" || { echo \"- AGORA_APP_ID missing in server/.env.local\"; exit 1; }; grep -Eq \"^AGORA_APP_CERTIFICATE=.+$\" server/.env.local && echo \"- AGORA_APP_CERTIFICATE configured\" || { echo \"- AGORA_APP_CERTIFICATE missing in server/.env.local\"; exit 1; }'", + "doctor:local": "bash -c 'set -e; bun run doctor; command -v python3 >/dev/null && echo \"- python3 available\" || { echo \"- python3 not found\"; exit 1; }; test -f server/.env && echo \"- server/.env present\" || { echo \"- missing server/.env\"; exit 1; }; grep -Eq \"^AGORA_APP_ID=.+$\" server/.env && echo \"- AGORA_APP_ID configured\" || { echo \"- AGORA_APP_ID missing in server/.env\"; exit 1; }; grep -Eq \"^AGORA_APP_CERTIFICATE=.+$\" server/.env && echo \"- AGORA_APP_CERTIFICATE configured\" || { echo \"- AGORA_APP_CERTIFICATE missing in server/.env\"; exit 1; }'", "build": "cd web && bun run build", "verify": "bun run verify:web", "verify:local": "bun run doctor:local && bun run verify:backend && bun run verify:local:fastapi && bun run verify:web:proxy && bun run verify:web:build", diff --git a/server/.gitignore b/server/.gitignore index 53616f0..d98b9c7 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -10,7 +10,6 @@ ENV/ .venv # Environment variables -.env.local .env # IDE @@ -22,4 +21,4 @@ ENV/ # OS .DS_Store -Thumbs.db \ No newline at end of file +Thumbs.db diff --git a/server/README.md b/server/README.md index fadebd4..03f2c25 100644 --- a/server/README.md +++ b/server/README.md @@ -17,7 +17,7 @@ bun run setup Agora credentials: ```bash -agora project env write server/.env.local +agora quickstart env write . ``` Run the app: @@ -31,7 +31,7 @@ This assumes the Agora CLI is installed and logged in. The command uses the proj If you are not using the Agora CLI, create the env file manually and fill in your project values: ```bash -cp server/.env.example server/.env.local +cp server/.env.example server/.env ``` From `server/`: @@ -41,16 +41,16 @@ From `server/`: Backend-only Agora CLI env write: ```bash -agora project env write .env.local +agora quickstart env write .. ``` Manual fallback: ```bash -cp .env.example .env.local +cp .env.example .env ``` -`.env.example` is the reference template. If you are not using the Agora CLI, edit `.env.local` and fill in your Agora credentials: +`.env.example` is the reference template. If you are not using the Agora CLI, edit `.env` and fill in your Agora credentials: - `AGORA_APP_ID` - Your Agora App ID (Required) - `AGORA_APP_CERTIFICATE` - Your Agora App Certificate (Required) - Agora managed provider access should be enabled for this project @@ -65,7 +65,7 @@ To select a specific existing project before writing env values: ```bash agora project use -agora project env write .env.local +agora quickstart env write .. ``` To create a new project instead of using your default project: @@ -73,7 +73,7 @@ To create a new project instead of using your default project: ```bash agora project create my-first-voice-agent --feature rtc --feature convoai agora project use my-first-voice-agent -agora project env write .env.local +agora quickstart env write .. ``` **Note**: The service uses Token007 authentication generated from `AGORA_APP_ID` and `AGORA_APP_CERTIFICATE`. Third-party vendor keys are not required in this default managed setup. The current default chain matches the Next.js quickstart: `DeepgramSTT` (`nova-3`) + `OpenAI` (`gpt-4o-mini`) + `MiniMaxTTS` (`speech_2_6_turbo` / `English_captivating_female1`). The FastAPI sample now uses `AsyncAgora` so the request path matches the local Agora guidance for async frameworks. @@ -99,7 +99,7 @@ pip install -r requirements.txt python src/server.py ``` -The service will start on port 8000 (or the port specified in `.env.local`). +The service will start on port 8000 (or the port specified in `.env`). ## How This Fits The Repo diff --git a/server/src/server.py b/server/src/server.py index 427100a..11b6ffd 100644 --- a/server/src/server.py +++ b/server/src/server.py @@ -14,9 +14,8 @@ from typing import Any, Dict, Optional from dotenv import load_dotenv -# Load environment variables from .env.local or .env +# The Agora CLI writes the Python quickstart environment to server/.env. _base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -load_dotenv(os.path.join(_base_dir, '.env.local'), override=True) load_dotenv(os.path.join(_base_dir, '.env'), override=True) from fastapi import APIRouter, FastAPI, HTTPException, Query diff --git a/server/tests/conftest.py b/server/tests/conftest.py index d248d58..37a0563 100644 --- a/server/tests/conftest.py +++ b/server/tests/conftest.py @@ -1,9 +1,8 @@ """Shared fixtures for the server test suite. Standalone: no Agora cloud, no real credentials. A deterministic fake env is -injected, and python-dotenv is neutralized so a developer's real -`server/.env.local` cannot override the test env (server.py loads it with -override=True). +injected, and python-dotenv is neutralized so a developer's real `server/.env` +cannot override the test env (server.py loads it with override=True). """ import importlib import os diff --git a/web/.env.local.example b/web/.env.local.example deleted file mode 100644 index 50633cf..0000000 --- a/web/.env.local.example +++ /dev/null @@ -1,2 +0,0 @@ -# Required: Next rewrites /api/* requests to the Python backend. -AGENT_BACKEND_URL=http://localhost:8000 diff --git a/web/scripts/doctor.ts b/web/scripts/doctor.ts index 13dc817..9740865 100644 --- a/web/scripts/doctor.ts +++ b/web/scripts/doctor.ts @@ -1,48 +1,9 @@ -import { existsSync, readFileSync } from 'node:fs' -import path from 'node:path' - function fail(message: string): never { console.error(message) process.exit(1) } -function loadEnvFile(filePath: string): Record { - if (!existsSync(filePath)) { - return {} - } - - const contents = readFileSync(filePath, 'utf8') - const result: Record = {} - for (const rawLine of contents.split('\n')) { - const line = rawLine.trim() - if (!line || line.startsWith('#')) continue - - const separatorIndex = line.indexOf('=') - if (separatorIndex <= 0) continue - - const key = line.slice(0, separatorIndex).trim() - const value = line.slice(separatorIndex + 1).trim() - result[key] = value - } - - return result -} - -const cwd = process.cwd() -const envPath = path.join(cwd, '.env.local') -const examplePath = path.join(cwd, '.env.local.example') - -if (!existsSync(examplePath)) { - fail('Missing .env.local.example. Restore the tracked template before continuing.') -} - -const fileEnv = loadEnvFile(envPath) -const mergedEnv = { - ...fileEnv, - ...Object.fromEntries(Object.entries(process.env).filter(([, value]) => typeof value === 'string')), -} - -const backendUrl = mergedEnv.AGENT_BACKEND_URL +const backendUrl = process.env.AGENT_BACKEND_URL if (!backendUrl?.trim()) { fail( 'Missing AGENT_BACKEND_URL. The web app proxies /api/* requests to the Python backend and cannot serve them in-process.', From 80409e48da0d5b8c919c6621b1e47a7fded95125 Mon Sep 17 00:00:00 2001 From: digitallysavvy Date: Thu, 20 Aug 2026 18:54:42 -0400 Subject: [PATCH 2/3] fix: keep agent greeting in Python quickstart code --- README.md | 2 -- docs/ai/L1/01_setup.md | 2 -- docs/ai/L1/05_workflows.md | 4 ++-- docs/ai/L1/06_interfaces.md | 2 +- docs/ai/L1/08_security.md | 2 +- docs/ai/L1/L2/from_scratch_bootstrap.md | 2 +- docs/ai/L1/L2/managed_agent_config.md | 2 +- docs/ai/RECIPE.md | 4 ++-- server/.env.example | 1 - server/src/agent.py | 7 +++---- 10 files changed, 11 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 4f3e483..e900bb5 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,6 @@ Set backend env values: ```bash AGORA_APP_ID=your_agora_app_id AGORA_APP_CERTIFICATE=your_agora_app_certificate -AGENT_GREETING=optional_custom_greeting ``` To export local env values from the Agora CLI-bound project: @@ -91,7 +90,6 @@ Primary backend env file: [`server/.env.example`](server/.env.example). | --- | :---: | :---: | --- | | `AGORA_APP_ID` | ✅ | — | Agora Console -> Project -> App ID | | `AGORA_APP_CERTIFICATE` | ✅ | — | Agora Console -> Project -> App Certificate (server only) | -| `AGENT_GREETING` | | built-in greeting | Optional opening line override | | `PORT` | | `8000` | FastAPI server port | | `AGENT_BACKEND_URL` (web deploy) | ✅ | — | Required in deployed `web` app when proxying to external FastAPI | diff --git a/docs/ai/L1/01_setup.md b/docs/ai/L1/01_setup.md index 4577742..6bbd657 100644 --- a/docs/ai/L1/01_setup.md +++ b/docs/ai/L1/01_setup.md @@ -37,7 +37,6 @@ bun run setup ``` AGORA_APP_ID=your_agora_app_id AGORA_APP_CERTIFICATE=your_agora_app_certificate -AGENT_GREETING=Hi there! I'm Ada, your virtual assistant from Agora. How can I help? PORT=8000 ``` @@ -45,7 +44,6 @@ PORT=8000 | ------------------------ | -------------------- | -------- | --------------------------------------------------------------------- | | `AGORA_APP_ID` | Python (server) | Yes | Loaded by `Agent.__init__` via `os.environ`. | | `AGORA_APP_CERTIFICATE` | Python (server) | Yes | Server-only. | -| `AGENT_GREETING` | Python (server) | No | Optional first utterance. | | `PORT` | Python (server) | No | Default `8000` (`server.py`). | | `AGENT_BACKEND_URL` | Next build (web) | Yes for rewrites | Empty/missing → no `/api/*` rewrites registered. Required by `web/scripts/doctor.ts`. | | `NEXT_PUBLIC_AGENT_UID` | Browser (web) | No | Optional UID override read in `ConversationComponent.tsx`. | diff --git a/docs/ai/L1/05_workflows.md b/docs/ai/L1/05_workflows.md index 2da0725..ce93beb 100644 --- a/docs/ai/L1/05_workflows.md +++ b/docs/ai/L1/05_workflows.md @@ -20,7 +20,7 @@ Edit `server/src/agent.py`: - **Prompt:** modify the `ADA_PROMPT` constant. -- **Greeting:** set `AGENT_GREETING` in `server/.env`, or change the default in the constructor. +- **Greeting:** change `DEFAULT_GREETING` in `server/src/agent.py`. - **VAD:** edit `turn_detection` dict (start/end mode, speech threshold, silence/interrupt durations). - **LLM:** change the `OpenAI(...)` constructor (model, history, BYOK key, base URL). - **STT:** change the `DeepgramSTT(...)` constructor. @@ -33,7 +33,7 @@ After editing, run `bun run verify:backend && bun run verify:web:api`. ## Deploy the Web and Backend Separately - **Web (Next.js):** build via `cd web && bun run build`. Configure `AGENT_BACKEND_URL` on the deploy target to the public URL of your FastAPI service. Serve with `bun run start` or any Node hosting platform. -- **Backend (FastAPI):** install deps from `server/requirements.txt`, set `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE`, optional `AGENT_GREETING`/`PORT`, and run `python3 server/src/server.py` or `uvicorn server.src.server:app --host 0.0.0.0 --port $PORT`. +- **Backend (FastAPI):** install deps from `server/requirements.txt`, set `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE`, and optionally `PORT`, then run `python3 server/src/server.py` or `uvicorn server.src.server:app --host 0.0.0.0 --port $PORT`. - The two deploys never share env vars. The browser only ever needs `/api/*` to resolve via the rewrite layer. ## Verify Locally diff --git a/docs/ai/L1/06_interfaces.md b/docs/ai/L1/06_interfaces.md index 116defa..c853ce8 100644 --- a/docs/ai/L1/06_interfaces.md +++ b/docs/ai/L1/06_interfaces.md @@ -35,7 +35,7 @@ CORS middleware: `allow_origins=["*"]`, `allow_credentials=True`. | Scope | Variable | | ---------------------- | ----------------------------------------- | | Python server (required) | `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE` | -| Python server (optional) | `AGENT_GREETING`, `PORT` | +| Python server (optional) | `PORT` | | Next build | `AGENT_BACKEND_URL` | | Browser | `NEXT_PUBLIC_AGENT_UID` (optional) | diff --git a/docs/ai/L1/08_security.md b/docs/ai/L1/08_security.md index 6b6fee4..15b2832 100644 --- a/docs/ai/L1/08_security.md +++ b/docs/ai/L1/08_security.md @@ -15,7 +15,7 @@ | -------------- | ---------------------------------------------------------------------- | | Browser | `NEXT_PUBLIC_AGENT_UID` (optional) | | Next build/run | `AGENT_BACKEND_URL` | -| FastAPI | `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE`, `AGENT_GREETING`, `PORT` | +| FastAPI | `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE`, `PORT` | Mark `AGORA_APP_CERTIFICATE` as a sensitive secret in whichever host runs the Python service. The certificate value never appears in `web/`. diff --git a/docs/ai/L1/L2/from_scratch_bootstrap.md b/docs/ai/L1/L2/from_scratch_bootstrap.md index 44da6cd..c98f9f1 100644 --- a/docs/ai/L1/L2/from_scratch_bootstrap.md +++ b/docs/ai/L1/L2/from_scratch_bootstrap.md @@ -27,7 +27,7 @@ Implement these pieces in order: 1. Create a bun workspace with `web` as a workspace member and root scripts that orchestrate backend, frontend, setup, doctor, verify, and clean tasks. 2. Create `server/` with FastAPI, uvicorn, python-dotenv, and `agora-agents>=2.0.0` in `server/requirements.txt`. -3. Add `server/.env.example` with `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE`, optional `AGENT_GREETING`, and optional `PORT`. +3. Add `server/.env.example` with `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE`, and optional `PORT`. 4. Implement `server/src/agent.py` with an `Agent` class that reads env once, constructs `AsyncAgora`, builds `AgoraAgent` with managed `DeepgramSTT`, `OpenAI`, `MiniMaxTTS`, starts async sessions, stores sessions by `agent_id`, and stops by active session or `client.stop_agent`. 5. Implement `server/src/server.py` with `GET /get_config`, `POST /startAgent`, and `POST /stopAgent`; load `server/.env` relative to the source file. 6. In `GET /get_config`, replace missing, zero, or negative UIDs with a generated non-zero UID, generate a one-hour RTC+RTM token with `generate_convo_ai_token`, and return `{ app_id, token, uid, channel_name, agent_uid }`. diff --git a/docs/ai/L1/L2/managed_agent_config.md b/docs/ai/L1/L2/managed_agent_config.md index ae0c076..237814e 100644 --- a/docs/ai/L1/L2/managed_agent_config.md +++ b/docs/ai/L1/L2/managed_agent_config.md @@ -77,7 +77,7 @@ Edit the `ADA_PROMPT` string constant at the top of `agent.py`. Keep it concise ### Change the greeting -Set `AGENT_GREETING` in `server/.env`, or change the inline fallback string in `Agent.__init__`. +Change `DEFAULT_GREETING` in `server/src/agent.py`. ### Change VAD diff --git a/docs/ai/RECIPE.md b/docs/ai/RECIPE.md index 6854772..f56abbb 100644 --- a/docs/ai/RECIPE.md +++ b/docs/ai/RECIPE.md @@ -57,7 +57,7 @@ Do not recreate Agora ConvoAI integration from memory. Provider schemas, SDK bui | ID | Surface | How to extend | Required follow-up | | -- | ------- | ------------- | ------------------ | | `api.routes` | `server/src/server.py`, `web/next.config.ts`, `web/src/services/api.ts` | Add FastAPI route, add rewrite, add browser fetch helper. | Extend `web/scripts/verify-api-contracts.ts`; add smoke coverage if the route belongs in local verification. | -| `agent.managed-config` | `server/src/agent.py` | Change `ADA_PROMPT`, `AGENT_GREETING`, `turn_detection`, `OpenAI`, `DeepgramSTT`, `MiniMaxTTS`, `parameters`, or session options. | Run backend compile and local FastAPI smoke checks; document new env vars in `server/.env.example`. | +| `agent.managed-config` | `server/src/agent.py` | Change `ADA_PROMPT`, `DEFAULT_GREETING`, `turn_detection`, `OpenAI`, `DeepgramSTT`, `MiniMaxTTS`, `parameters`, or session options. | Run backend compile and local FastAPI smoke checks; document new env vars in `server/.env.example`. | | `web.conversation-ui` | `web/src/components/*`, `web/src/lib/conversation.ts` | Customize pre-call, transcript, metrics, connection status, microphone, or visualizer UI. | Preserve RTC/RTM lifecycle ownership and transcript UID normalization. | | `verification.contracts` | `web/scripts/*.ts`, root `package.json` | Add contract checks for new browser/backend boundaries. | Keep checks runnable without live Agora credentials where possible. | @@ -76,7 +76,7 @@ Do not recreate Agora ConvoAI integration from memory. Provider schemas, SDK bui | Contract | Stable shape | | -------- | ------------ | | Required backend env | `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE` | -| Optional backend env | `AGENT_GREETING`, `PORT` | +| Optional backend env | `PORT` | | Required web deploy env | `AGENT_BACKEND_URL` | | Optional browser env | `NEXT_PUBLIC_AGENT_UID` | | `GET /api/get_config` | Query `channel?`, `uid?`; returns `data.app_id`, `data.token`, `data.uid`, `data.channel_name`, `data.agent_uid`. | diff --git a/server/.env.example b/server/.env.example index 7360cd5..27e79ed 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,4 +1,3 @@ AGORA_APP_ID=your_agora_app_id AGORA_APP_CERTIFICATE=your_agora_app_certificate -AGENT_GREETING=Hi there! I'm Ada, your virtual assistant from Agora. How can I help? PORT=8000 diff --git a/server/src/agent.py b/server/src/agent.py index ad897df..96fa603 100644 --- a/server/src/agent.py +++ b/server/src/agent.py @@ -21,6 +21,8 @@ If you do not know a specific fact about Agora, say so plainly and suggest checking docs.agora.io. Keep most replies to one or two sentences unless the user explicitly asks for more detail. """ +DEFAULT_GREETING = "Hi there! I'm Ada, your virtual assistant from Agora. How can I help?" + class Agent: """ @@ -33,10 +35,7 @@ class Agent: def __init__(self): self.app_id = os.getenv("AGORA_APP_ID") self.app_certificate = os.getenv("AGORA_APP_CERTIFICATE") - self.greeting = os.getenv( - "AGENT_GREETING", - "Hi there! I'm Ada, your virtual assistant from Agora. How can I help?", - ) + self.greeting = DEFAULT_GREETING if not self.app_id or not self.app_certificate: raise ValueError("AGORA_APP_ID and AGORA_APP_CERTIFICATE are required") From 5e7a49c41f32005c097332cfe98cde9d028731ae Mon Sep 17 00:00:00 2001 From: digitallysavvy Date: Fri, 21 Aug 2026 13:11:17 -0400 Subject: [PATCH 3/3] fix: harden quickstart environment setup --- .dockerignore | 2 + .gitignore | 2 + README.md | 2 + docs/ai/L0_repo_card.md | 2 +- docs/ai/L1/01_setup.md | 4 +- package.json | 6 +- scripts/setup-server-env.sh | 115 ++++++++++++++++++++++++++++++++++++ server/.gitignore | 1 + server/README.md | 2 + 9 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 scripts/setup-server-env.sh diff --git a/.dockerignore b/.dockerignore index 04e4c4f..4b15fef 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,6 +2,8 @@ **/node_modules **/__pycache__ **/.env +**/.env.local +*.env.local **/tests docs/ .github/ diff --git a/.gitignore b/.gitignore index 1376f37..bb0325d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ dist/ # Environment .env +.env.local +.env.*.local # Agora CLI local project binding .agora/ diff --git a/README.md b/README.md index e900bb5..6bc4a29 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ bun run doctor:local bun run dev ``` +`bun run setup` preserves a configured `server/.env`, copies a legacy `server/.env.local` when needed, and prints the credential-writing step when the resulting file lacks real Agora values. Setup and `doctor:local` replace an untouched example file with configured legacy credentials. This supports CLI versions that wrote `.env.local`. + Services: - Frontend: `http://localhost:3000` diff --git a/docs/ai/L0_repo_card.md b/docs/ai/L0_repo_card.md index a160818..f5a709d 100644 --- a/docs/ai/L0_repo_card.md +++ b/docs/ai/L0_repo_card.md @@ -11,7 +11,7 @@ | Language | Python 3.10+ (FastAPI + uvicorn) backend + Next.js 16 / React 19 web | | Deploy Target | `web/` as Next.js app, `server/` as a reachable FastAPI service | | Owner | Agora Conversational AI DevEx | -| Last Reviewed | 2026-08-20 | +| Last Reviewed | 2026-08-21 | | Recipe Role | `base` | | Recipe Version | `1.0.0` | | Recipe Status | `experimental` | diff --git a/docs/ai/L1/01_setup.md b/docs/ai/L1/01_setup.md index 6bbd657..7cd5206 100644 --- a/docs/ai/L1/01_setup.md +++ b/docs/ai/L1/01_setup.md @@ -26,7 +26,7 @@ bun run setup # runs: setup:env → setup:backend → setup:frontend → setup:done ``` -`setup:env` copies `server/.env.example` → `server/.env` if missing. It preserves an existing `server/.env` written by `agora init` or `agora quickstart env write`. `setup:backend` recreates `server/venv`, upgrades pip, and installs `requirements.txt`. `setup:frontend` runs `bun install`. `setup:deps` exists for `bun run dev:check`, not for `bun run setup`. +`setup:env` preserves a configured `server/.env`, copies a legacy `server/.env.local` to `server/.env` when needed, or seeds `server/.env` from `server/.env.example`. If the seeded file has both example values and a CLI version writes valid credentials to the legacy path, `setup:env` and `doctor:local` copy those credentials into `server/.env`. The completion message omits the credential-writing command when the resulting file has non-placeholder Agora credentials. `setup:backend` recreates `server/venv`, upgrades pip, and installs `requirements.txt`. `setup:frontend` runs `bun install`. `setup:deps` exists for `bun run dev:check`, not for `bun run setup`. > The package.json scripts use `server/venv/` (no leading dot). `bun run dev:backend` activates `server/venv` and runs `python src/server.py` from inside `server/`. If you create the venv under a different name you'll need to adjust the scripts or symlink. @@ -98,7 +98,7 @@ bun run clean # remove backend venv, node_modules, .next, web/d ## Common Setup Failures - `bun run doctor:local` fails on **"python3 not found"** → install Python ≥ 3.10. -- Doctor fails on missing `server/.env` → run `agora quickstart env write .` or `bun run setup:env`. +- Doctor fails on missing or placeholder credentials in `server/.env` → run `agora quickstart env write .`. - `cd web && bun run doctor` rejects empty/invalid `AGENT_BACKEND_URL` → ensure the URL is `http://` or `https://`. - `verify:web:api` fails on a new route → extend `web/scripts/verify-api-contracts.ts` to cover it. diff --git a/package.json b/package.json index dabd5c1..bb4cc95 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,13 @@ "backend": "cd server && source venv/bin/activate && python src/server.py", "frontend": "cd web && AGENT_BACKEND_URL=http://localhost:8000 bun run dev", "setup": "bun run setup:env && bun run setup:backend && bun run setup:frontend && bun run setup:done", - "setup:env": "test -f server/.env || (cp server/.env.example server/.env && echo '\nCreated server/.env. Add Agora credentials before running the app.')", + "setup:env": "bash scripts/setup-server-env.sh prepare", "setup:deps": "test -d node_modules || (echo 'Installing workspace dependencies...' && bun install)", "setup:backend": "cd server && rm -rf venv && python3 -m venv venv && source venv/bin/activate && python -m pip install --upgrade pip && PIP_INDEX_URL=https://pypi.org/simple python -m pip install -r requirements.txt", "setup:frontend": "bun install", - "setup:done": "echo '\n✅ Setup complete! Next steps:' && echo ' 1. Run: agora quickstart env write .' && echo ' 2. Run: bun run dev\n'", + "setup:done": "bash scripts/setup-server-env.sh next-steps 'bun run doctor:local' 'bun run dev'", "doctor": "bash -c 'set -e; echo \"Checking shared repo prerequisites...\"; command -v bun >/dev/null && echo \"- bun available\" || { echo \"- bun not found\"; exit 1; }; test -d node_modules && echo \"- workspace dependencies installed\" || { echo \"- root node_modules missing; run bun install\"; exit 1; }'", - "doctor:local": "bash -c 'set -e; bun run doctor; command -v python3 >/dev/null && echo \"- python3 available\" || { echo \"- python3 not found\"; exit 1; }; test -f server/.env && echo \"- server/.env present\" || { echo \"- missing server/.env\"; exit 1; }; grep -Eq \"^AGORA_APP_ID=.+$\" server/.env && echo \"- AGORA_APP_ID configured\" || { echo \"- AGORA_APP_ID missing in server/.env\"; exit 1; }; grep -Eq \"^AGORA_APP_CERTIFICATE=.+$\" server/.env && echo \"- AGORA_APP_CERTIFICATE configured\" || { echo \"- AGORA_APP_CERTIFICATE missing in server/.env\"; exit 1; }'", + "doctor:local": "bash -c 'set -e; bun run doctor; command -v python3 >/dev/null && echo \"- python3 available\" || { echo \"- python3 not found\"; exit 1; }; bash scripts/setup-server-env.sh prepare; bash scripts/setup-server-env.sh check'", "build": "cd web && bun run build", "verify": "bun run verify:web", "verify:local": "bun run doctor:local && bun run verify:backend && bun run verify:local:fastapi && bun run verify:web:proxy && bun run verify:web:build", diff --git a/scripts/setup-server-env.sh b/scripts/setup-server-env.sh new file mode 100644 index 0000000..f6d63b9 --- /dev/null +++ b/scripts/setup-server-env.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +env_file="$repo_root/server/.env" +legacy_env_file="$repo_root/server/.env.local" +example_env_file="$repo_root/server/.env.example" + +credential_is_configured() { + local target_file="$1" + local key="$2" + local placeholder="$3" + + [[ -f "$target_file" ]] && + grep -Eq "^${key}=[^[:space:]]+([[:space:]]*)?$" "$target_file" && + ! grep -Eq "^${key}=${placeholder}([[:space:]]*)?$" "$target_file" +} + +credentials_are_configured() { + local target_file="$1" + + credential_is_configured "$target_file" AGORA_APP_ID your_agora_app_id && + credential_is_configured "$target_file" AGORA_APP_CERTIFICATE your_agora_app_certificate +} + +uses_example_credentials() { + local target_file="$1" + + grep -Eq '^AGORA_APP_ID=your_agora_app_id([[:space:]]*)?$' "$target_file" && + grep -Eq '^AGORA_APP_CERTIFICATE=your_agora_app_certificate([[:space:]]*)?$' "$target_file" +} + +prepare_env() { + if [[ -f "$env_file" ]]; then + if uses_example_credentials "$env_file" && credentials_are_configured "$legacy_env_file"; then + cp "$legacy_env_file" "$env_file" + printf '\nCopied configured server/.env.local to server/.env.\n' + fi + return + fi + + if [[ -f "$legacy_env_file" ]]; then + cp "$legacy_env_file" "$env_file" + printf '\nCopied existing server/.env.local to server/.env.\n' + return + fi + + cp "$example_env_file" "$env_file" + printf '\nCreated server/.env. Add Agora credentials before running the app.\n' +} + +check_credential() { + local key="$1" + local placeholder="$2" + + if ! credential_is_configured "$env_file" "$key" "$placeholder"; then + if grep -Eq "^${key}=${placeholder}([[:space:]]*)?$" "$env_file"; then + printf -- '- %s still has the example value in server/.env\n' "$key" >&2 + return 1 + fi + printf -- '- %s missing in server/.env\n' "$key" >&2 + return 1 + fi + + printf -- '- %s configured\n' "$key" +} + +check_env() { + local status=0 + + if [[ ! -f "$env_file" ]]; then + printf -- '- missing server/.env\n' >&2 + return 1 + fi + + printf -- '- server/.env present\n' + check_credential AGORA_APP_ID your_agora_app_id || status=1 + check_credential AGORA_APP_CERTIFICATE your_agora_app_certificate || status=1 + return "$status" +} + +print_next_steps() { + local doctor_command="$1" + local dev_command="$2" + + printf '\nSetup complete.\n' + if check_env >/dev/null 2>&1; then + printf 'Agora credentials are configured.\n' + printf 'Next steps:\n' + printf ' 1. Run: %s\n' "$doctor_command" + printf ' 2. Run: %s\n' "$dev_command" + else + printf 'Next steps:\n' + printf ' 1. Run: agora quickstart env write .\n' + printf ' 2. Run: %s\n' "$doctor_command" + printf ' 3. Run: %s\n' "$dev_command" + fi +} + +case "${1:-}" in + prepare) + prepare_env + ;; + check) + check_env + ;; + next-steps) + print_next_steps "${2:?doctor command is required}" "${3:?dev command is required}" + ;; + *) + printf 'Usage: %s {prepare|check|next-steps}\n' "$0" >&2 + exit 2 + ;; +esac diff --git a/server/.gitignore b/server/.gitignore index d98b9c7..6268d1e 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -10,6 +10,7 @@ ENV/ .venv # Environment variables +.env.local .env # IDE diff --git a/server/README.md b/server/README.md index 03f2c25..ba45819 100644 --- a/server/README.md +++ b/server/README.md @@ -14,6 +14,8 @@ Repo setup: bun run setup ``` +Setup preserves a configured `server/.env` and copies a legacy `server/.env.local` when needed. The completion message reflects the credential state. Setup and `doctor:local` replace an untouched example file with configured legacy credentials. + Agora credentials: ```bash