Skip to content

Commit acc3b33

Browse files
Merge pull request #53 from AgoraIO/fix/quickstart-env
fix(quickstart): align Python and Go env wiring
2 parents 3be3390 + 0f9ea7c commit acc3b33

9 files changed

Lines changed: 413 additions & 123 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ Earlier entries pre-date this convention and only carry their version's compare
2424
- **BREAKING**: Stop persisting CLI API/OAuth integration values in `config.json`. `apiBaseUrl`, `oauthBaseUrl`, `oauthClientId`, and `oauthScope` are now derived from the selected login region or from explicit environment variable overrides (`AGORA_API_BASE_URL`, `AGORA_OAUTH_BASE_URL`, `AGORA_OAUTH_CLIENT_ID`, `AGORA_OAUTH_SCOPE`). Existing configs auto-migrate to schema version `4` and drop those legacy keys on first load; users who previously pinned custom endpoints in `config.json` should move those values to environment variables.
2525
- Add `PROJECT_REGION_MISMATCH` when a repo-local `.agora/project.json` binding points to a different region than the active login region.
2626

27+
### Fixed
28+
29+
- Align new Python and Go quickstart env writing with the upstream repositories by targeting `server/.env.local` and `AGORA_APP_ID` / `AGORA_APP_CERTIFICATE`, avoid detecting Go quickstarts as Python quickstarts, and retain compatibility with legacy quickstart env layouts.
30+
- Correct the Go skills and README quickstart wording from a token-service recipe to the actual Go ConvoAI voice-agent quickstart.
31+
2732
## [0.2.5] - 2026-06-05
2833

2934
Installer migration, quickstart scaffold cleanup, and onboarding doc refresh.

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Command examples use `agora` for the installed CLI. Local source builds use `./a
9494
|------|---------|--------------|
9595
| Next.js video app | `agora init my-nextjs-demo --template nextjs` | A cloned Next.js quickstart, project binding, and `.env.local` |
9696
| Python voice agent | `agora init my-python-demo --template python` | A Python quickstart with Agora credentials written for the backend |
97-
| Go token service | `agora init my-go-demo --template go` | A Go server quickstart with project metadata and env wiring |
97+
| Go voice agent | `agora init my-go-demo --template go` | A Go quickstart with Agora credentials written for the backend |
9898

9999
Run `agora quickstart list` to see all available templates.
100100

@@ -206,16 +206,17 @@ Prints build metadata. Release binaries include version, commit, and build date.
206206

207207
| Command | Env path | Key names |
208208
|---------|----------|-----------|
209-
| `agora init` / `quickstart env write` | Template-defined (`.env.local`, `server/.env`, etc.) | Template-specific (`NEXT_PUBLIC_*`, `APP_ID`, …) |
209+
| `agora init` / `quickstart env write` | Template-defined (`.env.local`, `server/.env.local`, etc.) | Template-specific (`NEXT_PUBLIC_*`, `AGORA_*`, …) |
210210
| `agora project env write <path>` | User-supplied path | `AGORA_*` or `NEXT_*` only |
211211

212212
Quickstart template behavior:
213213

214214
- Next.js quickstarts write `.env.local` with `NEXT_PUBLIC_AGORA_APP_ID` plus `NEXT_AGORA_APP_CERTIFICATE`
215-
- Python quickstarts copy `server/env.example` to `server/.env`, then use `APP_ID` plus `APP_CERTIFICATE`
216-
- Go quickstarts copy `server-go/env.example` to `server-go/.env`, then use `APP_ID` plus `APP_CERTIFICATE`
215+
- Python quickstarts copy `server/.env.example` to `server/.env.local`, then use `AGORA_APP_ID` plus `AGORA_APP_CERTIFICATE`
216+
- Go quickstarts copy `server/.env.example` to `server/.env.local`, then use `AGORA_APP_ID` plus `AGORA_APP_CERTIFICATE`
217+
- Existing Python and Go quickstarts keep their recorded env path and legacy `APP_ID` / `APP_CERTIFICATE` keys when reconfigured.
217218

218-
`project env write` auto-detects Next.js workspaces (or accepts `--template nextjs|standard`) and writes `AGORA_APP_ID` / `AGORA_APP_CERTIFICATE` or the Next.js equivalents. It does not use `APP_ID` / `APP_CERTIFICATE`; use `quickstart env write` for Python and Go quickstart layouts.
219+
`project env write` auto-detects Next.js workspaces (or accepts `--template nextjs|standard`) and writes `AGORA_APP_ID` / `AGORA_APP_CERTIFICATE` or the Next.js equivalents. Use `quickstart env write` when you want the CLI to choose the official quickstart's env path.
219220

220221
Existing `.env` and `.env.local` files are preserved: the CLI appends missing credentials, updates existing credential keys, and comments out duplicate or stale Agora credential aliases for the selected runtime.
221222

docs/automation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,8 @@ Required `data` fields:
681681
Env write behavior:
682682
- quickstart env files contain only the App ID and App Certificate variable names required by the template
683683
- Next.js uses `NEXT_PUBLIC_AGORA_APP_ID` and `NEXT_AGORA_APP_CERTIFICATE`
684-
- Python and Go use `APP_ID` and `APP_CERTIFICATE`
684+
- Python and Go use `AGORA_APP_ID` and `AGORA_APP_CERTIFICATE`
685+
- existing Python and Go quickstarts retain their recorded env path and legacy `APP_ID` / `APP_CERTIFICATE` keys when reconfigured
685686
- project metadata such as project ID, project name, region, template, projectType, and env path is stored in `.agora/project.json`
686687
- existing quickstart env files are preserved; missing credential keys are appended and existing credential keys are updated
687688
- stale Agora credential aliases for another runtime are commented out to avoid ambiguous dotenv resolution; for example, a Next.js quickstart prefers `NEXT_PUBLIC_AGORA_APP_ID` and comments out old `AGORA_APP_ID` / `APP_ID` entries when replacing them

internal/cli/doctor.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,6 @@ func summarizeCategoryStatus(items []doctorCheckItem) string {
4646
return "skipped"
4747
}
4848

49-
func quickstartAppIDKey(templateID string) string {
50-
switch templateID {
51-
case "nextjs":
52-
return "NEXT_PUBLIC_AGORA_APP_ID"
53-
case "python", "go":
54-
return "APP_ID"
55-
default:
56-
return ""
57-
}
58-
}
59-
6049
func lookupDotenvValue(content, key string) (string, bool) {
6150
for _, line := range strings.Split(content, "\n") {
6251
trimmed := strings.TrimSpace(line)
@@ -196,8 +185,21 @@ func buildWorkspaceDoctorDetails(target projectTarget) (doctorCheckCategory, map
196185

197186
template, found := findQuickstartTemplate(templateID)
198187
envRel := strings.TrimSpace(binding.EnvPath)
199-
if envRel == "" && found {
200-
envRel = template.EnvTargetPath
188+
layout := quickstartEnvLayout{}
189+
if found {
190+
if envRel != "" {
191+
layout, _ = quickstartEnvLayoutForEnvPath(*template, envRel)
192+
}
193+
if layout.EnvTargetPath == "" {
194+
if detected, ok := quickstartEnvLayoutForPath(root, *template); ok {
195+
layout = detected
196+
} else if fallback, ok := template.defaultEnvLayout(); ok {
197+
layout = fallback
198+
}
199+
}
200+
if envRel == "" {
201+
envRel = layout.EnvTargetPath
202+
}
201203
}
202204
if envRel == "" {
203205
items = append(items, doctorCheckItem{Name: "workspace_env_path", Message: "Could not determine quickstart env target path", Status: "warn"})
@@ -270,7 +272,7 @@ func buildWorkspaceDoctorDetails(target projectTarget) (doctorCheckCategory, map
270272
warnings = append(warnings, doctorIssue{Code: "WORKSPACE_ENV_METADATA_MISSING", Message: "Quickstart env file is missing Agora-managed project metadata comments"})
271273
}
272274

273-
appIDKey := quickstartAppIDKey(templateID)
275+
appIDKey := layout.AppIDKey
274276
if appIDKey != "" {
275277
if envAppID, ok := lookupDotenvValue(envContent, appIDKey); !ok {
276278
items = append(items, doctorCheckItem{

internal/cli/integration_quickstart_test.go

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,17 @@ func TestCLIQuickstartListAndCreate(t *testing.T) {
2323
})
2424
pythonRepo := createLocalGitRepo(t, map[string]string{
2525
"README.md": "# Python Quickstart\n",
26-
"server/env.example": "APP_ID=\nAPP_CERTIFICATE=\nPORT=8000\n",
26+
"server/.env.example": "AGORA_APP_ID=\nAGORA_APP_CERTIFICATE=\nPORT=8000\n",
2727
"server/main.py": "print('hello')\n",
28-
"web-client/package.json": `{"name":"python-quickstart-web"}`,
28+
"server/requirements.txt": "",
29+
"web/package.json": `{"name":"python-quickstart-web"}`,
2930
})
3031
goRepo := createLocalGitRepo(t, map[string]string{
31-
"README.md": "# Go Quickstart\n",
32-
"server-go/env.example": "APP_ID=\nAPP_CERTIFICATE=\nPORT=8080\n",
33-
"server-go/main.go": "package main\nfunc main() {}\n",
34-
"web-client/package.json": `{"name":"go-quickstart-web"}`,
32+
"README.md": "# Go Quickstart\n",
33+
"server/.env.example": "AGORA_APP_ID=\nAGORA_APP_CERTIFICATE=\nPORT=8080\n",
34+
"server/go.mod": "module agent-quickstart-go/server\n",
35+
"server/main.go": "package main\nfunc main() {}\n",
36+
"client/package.json": `{"name":"go-quickstart-web"}`,
3537
})
3638

3739
project := buildFakeProject("Project Alpha", "prj_123456", "app_123456", "global")
@@ -96,12 +98,12 @@ func TestCLIQuickstartListAndCreate(t *testing.T) {
9698
if createBound.exitCode != 0 || !strings.Contains(createBound.stdout, `"envStatus":"configured"`) || !strings.Contains(createBound.stdout, `"projectId":"prj_123456"`) {
9799
t.Fatalf("unexpected bound quickstart create result: %+v", createBound)
98100
}
99-
localEnv, err := os.ReadFile(filepath.Join(boundTarget, "server", ".env"))
101+
localEnv, err := os.ReadFile(filepath.Join(boundTarget, "server", ".env.local"))
100102
if err != nil {
101-
t.Fatalf("expected .env in bound scaffold: %v", err)
103+
t.Fatalf("expected .env.local in bound scaffold: %v", err)
102104
}
103-
if !strings.Contains(string(localEnv), "APP_ID=app_123456") || !strings.Contains(string(localEnv), "APP_CERTIFICATE=") || !strings.Contains(string(localEnv), "PORT=8000") || strings.Contains(string(localEnv), "# Project ID:") || strings.Contains(string(localEnv), "# Project Name:") || strings.Contains(string(localEnv), "BEGIN AGORA CLI QUICKSTART") {
104-
t.Fatalf("unexpected .env contents: %s", string(localEnv))
105+
if !strings.Contains(string(localEnv), "AGORA_APP_ID=app_123456") || !strings.Contains(string(localEnv), "AGORA_APP_CERTIFICATE=") || !strings.Contains(string(localEnv), "PORT=8000") || strings.Contains(string(localEnv), "# Project ID:") || strings.Contains(string(localEnv), "# Project Name:") || strings.Contains(string(localEnv), "BEGIN AGORA CLI QUICKSTART") {
106+
t.Fatalf("unexpected .env.local contents: %s", string(localEnv))
105107
}
106108
metadataRaw, err := os.ReadFile(filepath.Join(boundTarget, ".agora", "project.json"))
107109
if err != nil {
@@ -196,12 +198,23 @@ func TestCLIQuickstartListAndCreate(t *testing.T) {
196198
if createGoBound.exitCode != 0 || !strings.Contains(createGoBound.stdout, `"envStatus":"configured"`) {
197199
t.Fatalf("unexpected bound go quickstart create result: %+v", createGoBound)
198200
}
199-
goEnv, err := os.ReadFile(filepath.Join(goBoundTarget, "server-go", ".env"))
201+
goEnv, err := os.ReadFile(filepath.Join(goBoundTarget, "server", ".env.local"))
200202
if err != nil {
201-
t.Fatalf("expected .env in bound go scaffold: %v", err)
203+
t.Fatalf("expected .env.local in bound go scaffold: %v", err)
202204
}
203-
if !strings.Contains(string(goEnv), "APP_ID=app_123456") || !strings.Contains(string(goEnv), "APP_CERTIFICATE=") || !strings.Contains(string(goEnv), "PORT=8080") {
204-
t.Fatalf("unexpected go .env contents: %s", string(goEnv))
205+
if !strings.Contains(string(goEnv), "AGORA_APP_ID=app_123456") || !strings.Contains(string(goEnv), "AGORA_APP_CERTIFICATE=") || !strings.Contains(string(goEnv), "PORT=8080") {
206+
t.Fatalf("unexpected go .env.local contents: %s", string(goEnv))
207+
}
208+
writeGoEnv := runCLI(t, []string{"quickstart", "env", "write", goBoundTarget, "--json"}, cliRunOptions{
209+
env: map[string]string{
210+
"XDG_CONFIG_HOME": configHome,
211+
"AGORA_API_BASE_URL": api.baseURL,
212+
"AGORA_LOG_LEVEL": "error",
213+
},
214+
workdir: rootDir,
215+
})
216+
if writeGoEnv.exitCode != 0 || !strings.Contains(writeGoEnv.stdout, `"template":"go"`) || strings.Contains(writeGoEnv.stdout, `"template":"python"`) {
217+
t.Fatalf("unexpected go quickstart env write result: %+v", writeGoEnv)
205218
}
206219

207220
noCertProject := buildFakeProject("No Cert", "prj_nocert", "app_nocert", "global")
@@ -284,18 +297,24 @@ func TestCLIQuickstartEnvWriteUsesTargetRepoBindingPrecedence(t *testing.T) {
284297
if !strings.Contains(string(envRaw), "APP_ID=app_alpha") || !strings.Contains(string(envRaw), "PORT=8080") || strings.Contains(string(envRaw), "APP_ID=app_beta") {
285298
t.Fatalf("expected target repo binding project app id in env, got %s", string(envRaw))
286299
}
300+
if _, err := os.Stat(filepath.Join(targetDir, "server", ".env.local")); !errors.Is(err, os.ErrNotExist) {
301+
t.Fatalf("did not expect a current env file in a legacy scaffold, got %v", err)
302+
}
287303
}
288304

289305
func TestCLIQuickstartEnvWriteMissingBindingEvenWhenEnvExists(t *testing.T) {
290306
configHome := t.TempDir()
291307
targetDir := filepath.Join(t.TempDir(), "demo-go")
292-
if err := os.MkdirAll(filepath.Join(targetDir, "server-go"), 0o755); err != nil {
308+
if err := os.MkdirAll(filepath.Join(targetDir, "server"), 0o755); err != nil {
309+
t.Fatal(err)
310+
}
311+
if err := os.WriteFile(filepath.Join(targetDir, "server", ".env.example"), []byte("AGORA_APP_ID=\nAGORA_APP_CERTIFICATE=\n"), 0o644); err != nil {
293312
t.Fatal(err)
294313
}
295-
if err := os.WriteFile(filepath.Join(targetDir, "server-go", "env.example"), []byte("APP_ID=\nAPP_CERTIFICATE=\n"), 0o644); err != nil {
314+
if err := os.WriteFile(filepath.Join(targetDir, "server", "go.mod"), []byte("module agent-quickstart-go/server\n"), 0o644); err != nil {
296315
t.Fatal(err)
297316
}
298-
if err := os.WriteFile(filepath.Join(targetDir, "server-go", ".env"), []byte("APP_ID=stale\nAPP_CERTIFICATE=stale\n"), 0o644); err != nil {
317+
if err := os.WriteFile(filepath.Join(targetDir, "server", ".env.local"), []byte("AGORA_APP_ID=stale\nAGORA_APP_CERTIFICATE=stale\n"), 0o644); err != nil {
299318
t.Fatal(err)
300319
}
301320

internal/cli/integration_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func TestMain(m *testing.M) {
6767
os.Exit(executeCLI(cliArgs))
6868
return
6969
}
70+
7071
os.Exit(m.Run())
7172
}
7273

@@ -314,6 +315,8 @@ func helperEnv(base []string, overrides map[string]string) []string {
314315
// quickstart repos so quickstart-clone tests do not hit the network.
315316
func createLocalGitRepo(t *testing.T, files map[string]string) string {
316317
t.Helper()
318+
t.Setenv("GIT_ALLOW_PROTOCOL", "file")
319+
317320
repoDir := t.TempDir()
318321
for path, content := range files {
319322
filePath := filepath.Join(repoDir, filepath.FromSlash(path))

0 commit comments

Comments
 (0)