diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 800c951..575804a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v7 - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true @@ -365,6 +365,24 @@ jobs: Remove-Item Env:VERSION, Env:AGORA_INSTALL_SOURCE, Env:RELEASES_DOWNLOAD_BASE_URL, Env:RELEASES_PAGE_URL -ErrorAction SilentlyContinue } + - name: Reject Windows PowerShell 5.1 installer runs + if: runner.os == 'Windows' + shell: pwsh + run: | + $previousNativePreference = $PSNativeCommandUseErrorActionPreference + $PSNativeCommandUseErrorActionPreference = $false + $output = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File ./install.ps1 2>&1 | Out-String + $exitCode = $LASTEXITCODE + $PSNativeCommandUseErrorActionPreference = $previousNativePreference + $global:LASTEXITCODE = 0 + + if ($exitCode -eq 0) { + throw 'Expected install.ps1 to reject Windows PowerShell 5.1.' + } + if ($output -notmatch 'requires PowerShell 7 or newer') { + throw "Expected an actionable PowerShell 7 requirement, got: $output" + } + - name: Smoke test PowerShell installer S3 fallback if: runner.os == 'Windows' shell: pwsh diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 17b1d83..e8668dc 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v7 - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index cebe716..467d690 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@v7 - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 534c265..152a0b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: fetch-depth: 0 # GoReleaser needs full history for changelog - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@v7 - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: "24" registry-url: "https://registry.npmjs.org" diff --git a/CHANGELOG.md b/CHANGELOG.md index 6297c9b..d0dae0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,31 @@ Earlier entries pre-date this convention and only carry their version's compare ## [Unreleased] +## [0.2.9] - 2026-08-21 + +Quickstart project and environment consistency, Android onboarding, Windows installer compatibility, and release workflow maintenance. + ### Added - Add the Android conversational AI client/server quickstart to `agora init` and `agora quickstart`, writing credentials only to the included Python server and returning setup steps for the server, HTTPS tunnel, and Android client ([#55](https://github.com/AgoraIO/cli/pull/55)). +### Changed + +- Expand installation guidance with the canonical Agora CDN, GitHub-hosted fallbacks, the S3 mirror option, and a PowerShell 7 guard with an actionable process-scoped execution-policy workaround ([03f46af](https://github.com/AgoraIO/cli/commit/03f46af), [#61](https://github.com/AgoraIO/cli/pull/61)). +- Update GitHub Actions dependencies to `actions/setup-node@v7` and `actions/setup-go@v7` ([#57](https://github.com/AgoraIO/cli/pull/57), [#58](https://github.com/AgoraIO/cli/pull/58)). +- Align the Python quickstart `nextSteps` install command with the upstream template (`bun run setup` instead of `bun install`) ([#60](https://github.com/AgoraIO/cli/pull/60)). +- Make clone-only quickstart creation explicit with `--template-only`; interactive runs prompt for an existing project when none resolves, while JSON, CI, and non-TTY runs fail before cloning with `QUICKSTART_PROJECT_REQUIRED` ([91cb8f7](https://github.com/AgoraIO/cli/commit/91cb8f7)). + +### Fixed + +- Bump the pinned Go toolchain to 1.26.6 to address reachable standard-library vulnerabilities reported by `govulncheck` in Go 1.26.5 ([09fcba8](https://github.com/AgoraIO/cli/commit/09fcba8)). +- Ensure `agora init` and quickstart env writes create `server/.env` with `AGORA_APP_ID` and `AGORA_APP_CERTIFICATE` for Python and Go, including older directory layouts that previously emitted unsupported unprefixed names; align with the upstream Python and Go env-file migrations ([91cb8f7](https://github.com/AgoraIO/cli/commit/91cb8f7), [Python #26](https://github.com/AgoraIO-Conversational-AI/agent-quickstart-python/pull/26), [Go #8](https://github.com/AgoraIO-Conversational-AI/agent-quickstart-go/pull/8)). + +### Documentation + +- Add `docs/env-local.md` explaining that Quickstart env files are created by the CLI from the template example plus the selected project's App ID and App Certificate (not downloaded from Console); link it from `README.md`, `docs/llms.txt`, and `docs/sitemap.xml` ([#59](https://github.com/AgoraIO/cli/pull/59)). +- Clarify the README Quick Start around `agora init` project selection (including `Default Project` reuse vs interactive picker), `--project` / `--new-project`, `.agora/project.json` vs `project use`, and that env files are written by the CLI rather than downloaded from Console ([#60](https://github.com/AgoraIO/cli/pull/60)). + ## [0.2.8] - 2026-07-28 Region-aware authentication, OAuth UX, quickstart compatibility, and installer and documentation delivery improvements. @@ -363,7 +384,8 @@ Set `AGORA_ALLOW_UPGRADE_IN_CI=1` only when a CI job intentionally needs to muta - Support machine-readable JSON output for automation and agent workflows. - Ship automated release packaging through GoReleaser, including cross-platform archives, Linux packages, Homebrew, Scoop, npm wrapper packages, Docker images, and install scripts. -[Unreleased]: https://github.com/AgoraIO/cli/compare/v0.2.8...HEAD +[Unreleased]: https://github.com/AgoraIO/cli/compare/v0.2.9...HEAD +[0.2.9]: https://github.com/AgoraIO/cli/compare/v0.2.8...v0.2.9 [0.2.8]: https://github.com/AgoraIO/cli/compare/v0.2.7...v0.2.8 [0.2.7]: https://github.com/AgoraIO/cli/compare/v0.2.6...v0.2.7 [0.2.6]: https://github.com/AgoraIO/cli/compare/v0.2.5...v0.2.6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91d81ad..20a227e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ For end-user behavior and machine-readable contracts, see: Requirements: -- **Go** 1.26.2+ (see `go.mod`). Release builds intentionally track the current stable Go toolchain; this distributed CLI does not target older Go compiler support. +- **Go** 1.26.6+ (see `go.mod`). Release builds intentionally track the current stable Go toolchain; this distributed CLI does not target older Go compiler support. - **Git**. - (Optional) `golangci-lint` v1.64.8 — install matches CI; instructions in the next section. diff --git a/README.md b/README.md index f93aed4..fe5faff 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Native Agora CLI for authentication, project management, quickstart setup, and d - macOS 12+, Linux (glibc 2.31+ or musl), or Windows 10+ for the prebuilt binaries. - `git` on `PATH` for `agora init` and `agora quickstart create` (they shell out to `git clone`). +- PowerShell 7+ (`pwsh`) for the native Windows installer. - For the source build, the Go toolchain pinned in [`go.mod`](go.mod). ### Install the CLI @@ -26,19 +27,21 @@ agora --help The script is served from the Agora CDN (`dl.agora.io`, CloudFront). Binaries download from GitHub by default and automatically fall back to the CDN mirror if GitHub is unreachable; downloads are verified against `checksums.txt` regardless of source. -Windows PowerShell: +Windows PowerShell 7+: ```powershell irm https://dl.agora.io/cli/install.ps1 | iex ``` -If your PowerShell execution policy blocks inline scripts (the default on most Windows clients), download the installer to disk and run it with `-ExecutionPolicy Bypass`. The `Invoke-WebRequest` form works on both Windows PowerShell 5.1 and PowerShell 7+: +If execution policy blocks the installer, download it to disk and launch it in a new PowerShell 7 process with a process-scoped bypass: ```powershell Invoke-WebRequest -Uri https://dl.agora.io/cli/install.ps1 -OutFile .\install.ps1 -powershell -ExecutionPolicy Bypass -File .\install.ps1 +pwsh -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 ``` +An organization-level `MachinePolicy` or `UserPolicy` can override the process setting. In that case, ask an administrator to allow the script or use a verified release archive instead. + Alternative install paths (GitHub-hosted; use `install.ps1` for PowerShell): ```bash @@ -83,26 +86,61 @@ Requires the Go toolchain pinned in [go.mod](go.mod). For direct installer optio ## Quick Start +Recommended path: install the CLI, log in, then run `agora init`. The CLI binds a project, clones an official quickstart, writes the runtime env file (App ID and App Certificate), and creates `.agora/project.json`. You do **not** need to download an env file from Agora Console for this flow. + ```bash +# 1) Log in agora login + +# 2) Create a local demo bound to a project +# Interactive (TTY): reuses "Default Project" if present; otherwise prompts to pick or create agora init my-nextjs-demo --template nextjs +# For deterministic non-interactive / --json / CI runs, select or create explicitly +# agora init my-nextjs-demo --template nextjs --project +# agora init my-nextjs-demo --template nextjs --new-project + +# 3) Install deps and start the app (follow nextSteps from init; Next.js example:) +cd my-nextjs-demo +pnpm install +# Newer pnpm may block dependency build scripts (ERR_PNPM_IGNORED_BUILDS). If so: +# pnpm approve-builds --all +# pnpm rebuild +pnpm dev +# Other templates print their own commands, e.g. python: bun run setup && bun run dev +# go: make setup && make dev + +# 4) Open the app in a browser (Next.js default: http://localhost:3000) +open http://localhost:3000 +# Linux: xdg-open http://localhost:3000 +# Windows: start http://localhost:3000 + +# 5) Optional: check project/workspace readiness agora project doctor --json ``` +`init` also prints template-specific next steps in its output. Refresh credentials or rebind the repo later with: + +```bash +cd my-nextjs-demo +agora quickstart env write . --project +``` + +If an env or project command reports `No project selected`, pass `--project`, run `agora project use `, or work inside a directory that already has `.agora/project.json`. That is expected when none of those contexts exist—not a missing Console env download. `agora init` uses the onboarding selection flow described above instead. + Command examples use `agora` for the installed CLI. Local source builds use `./agora` from the repo root. ## What You Can Build Quickly | Goal | Command | What You Get | |------|---------|--------------| -| Next.js video app | `agora init my-nextjs-demo --template nextjs` | A cloned Next.js quickstart, project binding, and `.env.local` | -| Python voice agent | `agora init my-python-demo --template python` | A Python quickstart with Agora credentials written for the backend | -| Go voice agent | `agora init my-go-demo --template go` | A Go quickstart with Agora credentials written for the backend | +| Next.js video app | `agora init my-nextjs-demo --template nextjs` | A cloned Next.js quickstart, `.agora` binding, and `.env.local` | +| Python voice agent | `agora init my-python-demo --template python` | A Python quickstart with `server/.env` credentials | +| Go voice agent | `agora init my-go-demo --template go` | A Go quickstart with `server/.env` credentials | | Android voice AI app | `agora init my-android-demo --template android` | An Android client with credentials written only to the included Python server | Android follows the same project binding and env-writing flow as the web quickstarts. Its `nextSteps` additionally cover starting the Python server, opening a temporary HTTPS tunnel, writing that public URL to `local.properties`, and assembling the Android client. The App Certificate remains only in `server/.env.local`. -Run `agora quickstart list` to see all available templates. +By default `init` reuses a project named `Default Project` when present. In an interactive TTY without that project, it prompts you to pick an existing project or create a new one. Non-interactive/`--json`/CI runs fall back to the most recent project (or create one when none exist). Pass `--project ` or `--new-project` to control selection explicitly. Run `agora quickstart list` to see all available templates. ## Command Model @@ -126,13 +164,14 @@ The command model is intentionally layered: | Goal | Command | |------|---------| -| New user, one shot | `agora init --template ` | +| New user, one shot | `agora init --template ` (reuses `Default Project` / interactive picker; or `--project` / `--new-project`) | | List available templates | `agora quickstart list` | -| Clone a starter only | `agora quickstart create ...` | -| Re-sync env in a cloned quickstart | `agora quickstart env write [dir]` | +| Clone a starter only | `agora quickstart create ... --template-only` | +| Re-sync / rebind env in a cloned quickstart | `agora quickstart env write [dir]` (optional `--project` to rebind) | | Write env to an arbitrary path / non-quickstart repo | `agora project env write ` | +| Set global CLI project context | `agora project use ` | | Install self-test | `agora doctor --json` | -| Project/workspace readiness | `agora project doctor --json` | +| Project/workspace readiness | `agora project doctor --json` (add `--deep` in a bound repo) | | Manage feature webhooks | `agora project webhook ... --json` | ### Env-related commands @@ -156,7 +195,7 @@ agora introspect --json ### `init` -Recommended onboarding command. It creates or binds a project, clones a quickstart, writes env, persists context, and prints next steps. +Recommended onboarding command. By default it reuses a project named `Default Project` when present. In an interactive TTY without that project, it prompts you to pick or create one. Non-interactive/`--json`/CI runs fall back to the most recent project (or create one when none exist). Prefer `--project ` or `--new-project` for explicit selection. It clones a quickstart, writes the template env file from the project API, writes `.agora/project.json`, updates global context, and prints next steps. ### `quickstart` @@ -208,7 +247,9 @@ Prints build metadata. Release binaries include version, commit, and build date. ## Env Files and Project Binding -`quickstart env write` and `project env write` both keep dotenv files limited to runtime credentials, but they target different workflows: +Env files hold runtime credentials. Project selection is separate: use `--project`, `.agora/project.json`, or `agora project use`. The CLI writes App ID and App Certificate from the selected project's API response into the template env file. It does **not** download a ready-made dotenv from Agora Console. + +Prefer `agora quickstart env write` inside official quickstarts. Use `agora project env write ` only when you need a specific dotenv path outside that layout. | Command | Env path | Key names | |---------|----------|-----------| @@ -218,21 +259,23 @@ Prints build metadata. Release binaries include version, commit, and build date. Quickstart template behavior: - Next.js quickstarts write `.env.local` with `NEXT_PUBLIC_AGORA_APP_ID` plus `NEXT_AGORA_APP_CERTIFICATE` -- Python quickstarts copy `server/.env.example` to `server/.env.local`, then use `AGORA_APP_ID` plus `AGORA_APP_CERTIFICATE` -- Go quickstarts copy `server/.env.example` to `server/.env.local`, then use `AGORA_APP_ID` plus `AGORA_APP_CERTIFICATE` +- Python quickstarts copy `server/.env.example` to `server/.env`, then use `AGORA_APP_ID` plus `AGORA_APP_CERTIFICATE` +- Go quickstarts copy `server/.env.example` to `server/.env`, then use `AGORA_APP_ID` plus `AGORA_APP_CERTIFICATE` - Existing Python and Go quickstarts keep their recorded env path and legacy `APP_ID` / `APP_CERTIFICATE` keys when reconfigured. -`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. +`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. 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. +See [Using `.env.local`](docs/env-local.md) for how the CLI creates and updates env files from Quickstart examples and the selected project's credentials. + See [docs/automation.md](docs/automation.md) for JSON fields and the full credential matrix. ### Repo-local binding -The CLI writes repo-local project metadata to `.agora/project.json` so it can detect which Agora project a cloned demo is bound to even when you work inside the repo later. +`.agora/project.json` is the **repo-local** project binding (not the env file). It lets the CLI know which Agora project a cloned demo uses when you work inside that repo later. `agora project use` only sets the **global CLI context** and does not rewrite `.agora/project.json`. -Project resolution precedence is consistent across commands: +Commands that resolve an existing project context, including env-write commands, use this precedence: 1. explicit `--project` or positional project argument 2. repo-local `.agora/project.json` resolved from the target repo path @@ -341,7 +384,8 @@ The most common issues: - **OAuth browser does not open**: `agora login --no-browser` prints the URL so you can open it elsewhere; or `agora config update --browser-auto-open=false`. - **`git` is missing**: `agora init` and `agora quickstart create` shell out to `git clone`. Install `git` and retry. - **Project has no app certificate**: `quickstart env write`, `init`, and `project env --with-secrets` need a project with an App Certificate. Pick another project or enable one in [Agora Console](https://console.agora.io). -- **No project selected**: pass `--project `, run `agora project use `, or run from a repo that already has `.agora/project.json`. +- **No project selected**: the command has no project context. Pass `--project `, run `agora project use `, or work inside a repo with `.agora/project.json` (created by `init` / `quickstart env write`). Do not expect the CLI to infer a project from `.env.local` alone. +- **Wrong or stale credentials in a quickstart**: re-run `agora quickstart env write . --project ` from the demo directory instead of pasting a Console-downloaded env file. Full guide with debug logging, CI tips, completion troubleshooting, and the `--debug` flag: [docs/troubleshooting.md](docs/troubleshooting.md). diff --git a/cmd/gendocs/main.go b/cmd/gendocs/main.go index 2b8437b..bd63766 100644 --- a/cmd/gendocs/main.go +++ b/cmd/gendocs/main.go @@ -15,47 +15,64 @@ import ( "bytes" "flag" "fmt" + "io" "os" "github.com/AgoraIO/cli/internal/cli" ) +var ( + newRootForDocs = cli.NewRootForDocs + renderCommandReference = cli.RenderCommandReference +) + func main() { - out := flag.String("o", "docs/commands.md", "destination markdown file") - check := flag.Bool("check", false, "exit non-zero if the destination file would change (used in CI to detect drift)") - flag.Parse() + if exitCode := run(os.Args[1:], os.Stderr); exitCode != 0 { + os.Exit(exitCode) + } +} + +func run(args []string, stderr io.Writer) int { + flags := flag.NewFlagSet("gendocs", flag.ContinueOnError) + flags.SetOutput(stderr) + out := flags.String("o", "docs/commands.md", "destination markdown file") + check := flags.Bool("check", false, "exit non-zero if the destination file would change (used in CI to detect drift)") + if err := flags.Parse(args); err != nil { + return 2 + } - root, err := cli.NewRootForDocs() + root, err := newRootForDocs() if err != nil { - fmt.Fprintf(os.Stderr, "gendocs: failed to build root command: %v\n", err) - os.Exit(1) + fmt.Fprintf(stderr, "gendocs: failed to build root command: %v\n", err) + return 1 } var buffer bytes.Buffer - if err := cli.RenderCommandReference(&buffer, root); err != nil { - fmt.Fprintf(os.Stderr, "gendocs: render failed: %v\n", err) - os.Exit(1) + if err := renderCommandReference(&buffer, root); err != nil { + fmt.Fprintf(stderr, "gendocs: render failed: %v\n", err) + return 1 } if *check { existing, err := os.ReadFile(*out) if err != nil { - fmt.Fprintf(os.Stderr, "gendocs: cannot read %s for drift check: %v\n", *out, err) - fmt.Fprintln(os.Stderr, "Hint: run `make docs-commands` to generate it.") - os.Exit(2) + fmt.Fprintf(stderr, "gendocs: cannot read %s for drift check: %v\n", *out, err) + fmt.Fprintln(stderr, "Hint: run `make docs-commands` to generate it.") + return 2 } if !bytes.Equal(existing, buffer.Bytes()) { - fmt.Fprintf(os.Stderr, "gendocs: %s is out of date.\n", *out) - fmt.Fprintln(os.Stderr, "Run `make docs-commands` and commit the result.") - os.Exit(1) + fmt.Fprintf(stderr, "gendocs: %s is out of date.\n", *out) + fmt.Fprintln(stderr, "Run `make docs-commands` and commit the result.") + return 1 } - fmt.Fprintf(os.Stderr, "gendocs: %s is up to date.\n", *out) - return + fmt.Fprintf(stderr, "gendocs: %s is up to date.\n", *out) + return 0 } if err := os.WriteFile(*out, buffer.Bytes(), 0o644); err != nil { - fmt.Fprintf(os.Stderr, "gendocs: failed to write %s: %v\n", *out, err) - os.Exit(1) + fmt.Fprintf(stderr, "gendocs: failed to write %s: %v\n", *out, err) + return 1 } - fmt.Fprintf(os.Stderr, "gendocs: wrote %s (%d bytes)\n", *out, buffer.Len()) + fmt.Fprintf(stderr, "gendocs: wrote %s (%d bytes)\n", *out, buffer.Len()) + return 0 } diff --git a/cmd/gendocs/main_test.go b/cmd/gendocs/main_test.go new file mode 100644 index 0000000..ffae24d --- /dev/null +++ b/cmd/gendocs/main_test.go @@ -0,0 +1,97 @@ +package main + +import ( + "bytes" + "errors" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/spf13/cobra" +) + +func TestRunWritesAndChecksCommandReference(t *testing.T) { + outputPath := filepath.Join(t.TempDir(), "commands.md") + var stderr bytes.Buffer + if exitCode := run([]string{"-o", outputPath}, &stderr); exitCode != 0 { + t.Fatalf("run(write) exit = %d, stderr = %s", exitCode, stderr.String()) + } + generated, err := os.ReadFile(outputPath) + if err != nil { + t.Fatalf("ReadFile() error = %v", err) + } + if !bytes.Contains(generated, []byte("agora quickstart create")) { + t.Fatalf("generated reference does not contain quickstart create") + } + if !strings.Contains(stderr.String(), "gendocs: wrote") { + t.Fatalf("write stderr = %q", stderr.String()) + } + + stderr.Reset() + if exitCode := run([]string{"-check", "-o", outputPath}, &stderr); exitCode != 0 { + t.Fatalf("run(check) exit = %d, stderr = %s", exitCode, stderr.String()) + } + if !strings.Contains(stderr.String(), "is up to date") { + t.Fatalf("check stderr = %q", stderr.String()) + } +} + +func TestRunReportsGeneratorFailures(t *testing.T) { + originalRoot := newRootForDocs + originalRender := renderCommandReference + t.Cleanup(func() { + newRootForDocs = originalRoot + renderCommandReference = originalRender + }) + + newRootForDocs = func() (*cobra.Command, error) { + return nil, errors.New("root failed") + } + var stderr bytes.Buffer + if exitCode := run(nil, &stderr); exitCode != 1 || !strings.Contains(stderr.String(), "failed to build root command") { + t.Fatalf("root failure = exit %d, stderr %s", exitCode, stderr.String()) + } + + newRootForDocs = originalRoot + renderCommandReference = func(io.Writer, *cobra.Command) error { + return errors.New("render failed") + } + stderr.Reset() + if exitCode := run(nil, &stderr); exitCode != 1 || !strings.Contains(stderr.String(), "render failed") { + t.Fatalf("render failure = exit %d, stderr %s", exitCode, stderr.String()) + } +} + +func TestRunReportsDriftAndFileFailures(t *testing.T) { + directory := t.TempDir() + stalePath := filepath.Join(directory, "stale.md") + if err := os.WriteFile(stalePath, []byte("stale"), 0o600); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + + tests := []struct { + name string + args []string + code int + want string + }{ + {name: "drift", args: []string{"-check", "-o", stalePath}, code: 1, want: "out of date"}, + {name: "missing check file", args: []string{"-check", "-o", filepath.Join(directory, "missing.md")}, code: 2, want: "cannot read"}, + {name: "write failure", args: []string{"-o", directory}, code: 1, want: "failed to write"}, + {name: "invalid flag", args: []string{"-unknown"}, code: 2, want: "flag provided but not defined"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var stderr bytes.Buffer + if exitCode := run(tt.args, &stderr); exitCode != tt.code { + t.Fatalf("run() exit = %d, want %d; stderr = %s", exitCode, tt.code, stderr.String()) + } + if !strings.Contains(stderr.String(), tt.want) { + t.Fatalf("stderr does not contain %q: %s", tt.want, stderr.String()) + } + }) + } +} diff --git a/docs/automation.md b/docs/automation.md index 531486b..0745c94 100644 --- a/docs/automation.md +++ b/docs/automation.md @@ -42,6 +42,7 @@ Use this guide for: - Use `--yes` (or `-y`) / `AGORA_NO_INPUT=1` to assume the default answer to confirmation prompts. Following industry convention for `-y` (apt-style), the flag never starts brand-new interactive flows: in JSON, CI, or non-TTY contexts the CLI still fails fast with the same `AUTH_UNAUTHENTICATED` error you would have seen without `--yes`, instead of silently launching an OAuth browser flow. - Interactive login prompts only appear in interactive pretty-mode TTY runs. Automation should authenticate up front with `agora login`; `--json`, `AGORA_OUTPUT=json`, detected CI environments, and non-TTY stdin all skip the prompt and fail with `AUTH_UNAUTHENTICATED`. - In non-interactive runs (`--yes`, JSON, CI, non-TTY), pass `--template` explicitly to `agora init`. The CLI now fails fast with `QUICKSTART_TEMPLATE_REQUIRED` instead of silently selecting a template. +- In non-interactive `quickstart create` runs, pass `--project ` (or establish global project context) to configure the scaffold, or pass `--template-only` to explicitly skip project and credential resolution. Otherwise the CLI fails before cloning with `QUICKSTART_PROJECT_REQUIRED`. - Output mode precedence is: explicit CLI flag (`--json` or `--output`) first, user-set `AGORA_OUTPUT` second, then user-customized config file value, then **CI auto-detect → JSON** (see below), then pretty. - Set `AGORA_AGENT=` in automated environments to explicitly label agent traffic in the API `User-Agent`. When unset, the CLI may infer a coarse label such as `cursor`, `claude-code`, `cline`, `windsurf`, `codex`, or `aider` from known agent environment markers. Set `AGORA_AGENT_DISABLE_INFER=1` to disable inference. - Use `agora mcp serve` to expose local Agora CLI tools to MCP-capable agents. The full surface is exposed: `agora.version`, `agora.introspect`, `agora.auth.{status,logout}`, `agora.config.{path,get}`, `agora.telemetry.status`, `agora.upgrade.check`, `agora.project.{list,show,use,create,doctor,env,env_write}`, `agora.project.feature.{list,status,enable}`, `agora.project.webhook.{events,list,show,create,update,delete}`, `agora.quickstart.{list,create,env_write}`, and `agora.init`. Authentication is intentionally **not** exposed via MCP because OAuth requires an interactive browser; run `agora login` once on the host first. @@ -614,6 +615,7 @@ Display-oriented fields: Automation notes: - `--ref ` pins the cloned quickstart source for workshops and reproducible demos. +- `--template-only` explicitly skips project lookup and env-file creation. Without a resolved project or this flag, non-interactive runs fail with `QUICKSTART_PROJECT_REQUIRED` before cloning. Example: diff --git a/docs/commands.md b/docs/commands.md index 8a49931..7f286e9 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -333,6 +333,7 @@ Clone an official Agora quickstart into a new directory | `--project` | `string` | — | project ID or exact project name to use for env seeding | | `--ref` | `string` | — | git branch, tag, or ref to clone for pinned workshops | | `--template` | `string` | — | quickstart template ID from `agora quickstart list` | +| `--template-only` | `bool` | — | clone without resolving a project or writing credentials | ### `agora quickstart env` diff --git a/docs/env-local.md b/docs/env-local.md new file mode 100644 index 0000000..6cacf61 --- /dev/null +++ b/docs/env-local.md @@ -0,0 +1,78 @@ +--- +title: Using .env.local +--- + +# Using `.env.local` + +For official Next.js, Python, and Go Quickstarts, Agora CLI creates or updates +the runtime-specific env file with the Agora App ID and App Certificate for the +selected project. It does not download a ready-made dotenv file from Console. +The CLI starts with the example env file from the cloned repository, then +writes the credential keys required by that runtime. + +## How the file is created + +| Command | Behavior | +|---------|----------| +| `agora init --template ` | Clones the Quickstart, selects or creates a project, and writes its env file. | +| `agora quickstart create ...` | Writes the env file when a project is resolved; use `--template-only` to explicitly clone without credentials. Interactive runs prompt when no project resolves. | +| `agora quickstart env write [dir]` | Creates or updates the runtime-specific env file in an existing Quickstart. | +| `agora project env write [path]` | Creates or updates a dotenv file at the selected path without cloning a Quickstart. | + +Quickstart env layouts: + +| Quickstart | Example source | Target path | Credential keys | +|------------|----------------|-------------|-----------------| +| Next.js | `env.local.example` | `.env.local` | `NEXT_PUBLIC_AGORA_APP_ID`, `NEXT_AGORA_APP_CERTIFICATE` | +| Python | `server/.env.example` | `server/.env` | `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE` | +| Go | `server/.env.example` | `server/.env` | `AGORA_APP_ID`, `AGORA_APP_CERTIFICATE` | + +If the target env file already exists, the CLI uses it as the starting content +and updates the Agora credential keys while preserving unrelated entries. If +the target does not exist, the CLI starts from the Quickstart's example file. +If neither file exists, it creates a new file containing the credential entries. + +To refresh credentials or switch the Quickstart to another project, run the env +write command again with the target project. The CLI updates the same env file +in place: + +```bash +cd +agora quickstart env write . --project +``` + +Prefer `agora quickstart env write` for official Quickstarts. Use +`agora project env write ` when you want to write credentials to a +specific dotenv path outside the official Quickstart layout. + +## Where the credentials come from + +After authentication, the CLI fetches the selected project's details from the +Agora CLI project API. The App ID and App Certificate returned for that project +are written to the local env file. + +Commands that resolve an existing project context, including `quickstart create`, +`quickstart env write`, and `project env write`, use this precedence: + +1. Explicit `--project ` +2. Repo-local `.agora/project.json` +3. Global project context set by `agora project use` + +`agora init` has a separate onboarding flow. Use `--project` to select an +existing project or `--new-project` to force creation. Without either flag, it +prefers a project named `Default Project`, prompts in an interactive terminal, +uses the most recently created project in non-interactive runs, or creates a +project when none exist. + +The selected project must have an App Certificate. If it does not, enable one +in Agora Console or select a different project before writing the env file. + +Restart the development server after updating the env file so it reloads the +new values. + +## Keep credentials private + +The env file can contain an App Certificate. Do not commit it to version +control, paste its values into issues or logs, or share it outside the intended +development environment. Confirm that the file is covered by the repository's +`.gitignore` rules. diff --git a/docs/error-codes.md b/docs/error-codes.md index c622205..d176137 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -39,6 +39,8 @@ This catalog is the source of truth for stable codes. CI runs `make snapshot-err | `QUICKSTART_TEMPLATE_UNKNOWN` | 1 | The template ID is not known to this CLI. | Run `agora quickstart list`. | | `QUICKSTART_TEMPLATE_UNAVAILABLE` | 1 | The template exists but is not currently available. | Choose an available template. | | `QUICKSTART_TEMPLATE_ENV_UNSUPPORTED` | 1 | The selected template does not define an env target path. | Choose a template with env support or configure the env file manually. | +| `QUICKSTART_PROJECT_REQUIRED` | 1 | `quickstart create` could not resolve a project in a non-interactive run, or the account has no projects to select interactively. | Pass `--project`, set global context with `agora project use`, use `agora init`, or explicitly pass `--template-only`. | +| `QUICKSTART_CREATE_ABORTED` | 1 | The interactive quickstart project picker was canceled. | Re-run and select a project, or pass `--template-only`. | | `QUICKSTART_TARGET_EXISTS` | 1 | The clone target already exists. | Choose a new directory. | | `QUICKSTART_REF_INVALID` | 1 | `--ref` is empty after trimming, starts with `-`, or contains whitespace/control characters. | Pass a valid git branch, tag, or commit (no leading `-`). | | `QUICKSTART_REPO_OVERRIDE_INVALID` | 1 | The `AGORA_QUICKSTART_