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/AGENTS.md b/AGENTS.md index ecc9264..c152951 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,8 @@ internal/cli/ auth.go login / logout / whoami / auth status projects.go project create / use / show / env / env write / doctor quickstart.go quickstart create / env write / list - init.go init — one-step: project + quickstart + env + recipes.go official recipe API client, discovery, and recipe scaffold setup + init.go init — one-step: project + quickstart/recipe + env doctor.go project doctor — readiness checks, workspace mode install_doctor.go Top-level agora doctor — install self-test (PATH, network, auth, MCP host) env_help.go agora env-help — authoritative env-var catalog @@ -72,6 +73,7 @@ The surface is deliberately layered. Use the highest-level command that covers t ``` agora ├── init Recommended path: reuses existing project (or creates if none); add --new-project to force creation +├── recipes Official recipe discovery (list / show) ├── version Build version, commit, and date ├── introspect Machine-readable command metadata for agents ├── doctor Install self-test (PATH, version, network, auth, MCP host); use project doctor for project readiness @@ -105,7 +107,8 @@ agora **Design rules — do not break these:** - `project` = remote Agora control-plane resource; it never scaffolds local files - `quickstart` = local repo clone; requires `git` on the PATH; upstream `.git` is removed after clone so demos start without template history -- `init` = the only command that composes both +- `recipes` = read-only discovery from the official recipes API; it never clones or mutates projects +- `init` = the only command that composes a project with a quickstart or recipe scaffold - The `add` namespace is reserved; keep it hidden and return a command-not-found error if invoked ## Project Resolution Precedence diff --git a/CHANGELOG.md b/CHANGELOG.md index 6297c9b..358656a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,31 @@ Earlier entries pre-date this convention and only carry their version's compare ## [Unreleased] +## [0.2.9] - 2026-08-24 + +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)). +- Add official recipe discovery with `agora recipes list/show` and recipe-backed initialization with `agora init --recipe `. Recipe metadata is resolved from the versioned `recipes.agora.io` API before project creation, and the API-provided env contract drives credential writing without framework guessing. + +### 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`, `quickstart env write`, and `project env write` share credential-file semantics: Python and Go use `server/.env` with `AGORA_APP_ID` and `AGORA_APP_CERTIFICATE`, template example values are preserved, and unsupported legacy credential names are normalized consistently; align with the upstream Python and Go env-file migrations ([91cb8f7](https://github.com/AgoraIO/cli/commit/91cb8f7), [b285093](https://github.com/AgoraIO/cli/commit/b285093), [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 @@ -363,7 +385,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..922f656 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,64 @@ 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 or recipe, 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 +# Or initialize an official recipe from recipes.agora.io +# agora init my-agent --recipe tool-calling +# 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 | +| Official recipe | `agora init my-agent --recipe tool-calling` | The recipe repository, API-defined env file, and `.agora` binding | 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 @@ -110,6 +151,7 @@ The command model is intentionally layered: - `init` for the recommended onboarding path (project + clone + env) - `quickstart` for local cloned starter repos (requires `git`) +- `recipes` for read-only discovery of official recipes from `recipes.agora.io` - `project` for remote Agora control-plane resources (does not clone scaffolds) - `auth` for login and session inspection - `config` for local CLI defaults @@ -126,13 +168,15 @@ The command model is intentionally layered: | Goal | Command | |------|---------| -| New user, one shot | `agora init --template ` | +| New user, one shot | `agora init --template ` or `--recipe ` (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]` | +| List official recipes | `agora recipes list` | +| 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 +200,14 @@ 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 either a built-in quickstart (`--template`) or an official catalog recipe (`--recipe`), writes credentials using that source's env contract, writes `.agora/project.json`, updates global context, and prints next steps. + +### `recipes` + +Lists and inspects the official recipes published by `recipes.agora.io`. Use +`recipes list --type all|ai|rtc` to discover slugs and `recipes show ` to +inspect the repository, recipe document, and optional CLI initialization +metadata. Recipe discovery is read-only; `init --recipe` performs the clone. ### `quickstart` @@ -208,31 +259,35 @@ 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 | |---------|----------|-----------| -| `agora init` / `quickstart env write` | Template-defined (`.env.local`, `server/.env.local`, etc.) | Template-specific (`NEXT_PUBLIC_*`, `AGORA_*`, …) | +| `agora init` / `quickstart env write` | Template-defined (`.env.local`, `server/.env`, or `server/.env.local`) | Template-specific (`NEXT_PUBLIC_*`, `AGORA_*`, …) | | `agora project env write ` | User-supplied path | `AGORA_*` or `NEXT_*` only | 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` -- Existing Python and Go quickstarts keep their recorded env path and legacy `APP_ID` / `APP_CERTIFICATE` keys when reconfigured. +- 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 when reconfigured, while legacy `APP_ID` / `APP_CERTIFICATE` assignments are commented out and replaced with `AGORA_APP_ID` / `AGORA_APP_CERTIFICATE`. -`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 +396,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/RELEASING.md b/RELEASING.md index 96cc63b..b487acd 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -38,6 +38,26 @@ The release workflow (`.github/workflows/release.yml`) then: Before tagging, ensure [CHANGELOG.md](CHANGELOG.md) has the version section finalized (empty `[Unreleased]`, dated release heading, updated compare links), including any migration or upgrade notes. GoReleaser publishes auto-generated release notes from commits; paste highlights from the CHANGELOG section into the GitHub release description if you want a curated summary. +## Recipe-backed init dependency (v0.2.9+) + +Recipe-backed init depends on the versioned API at +`https://recipes.agora.io/api/v1`. Deploy and smoke-test the recipes release +before tagging a CLI release that advertises `agora init --recipe`: + +- `GET /recipes?type=all`, `?type=ai`, and `?type=rtc` return schema version 1 + and only official recipes. +- `GET /recipes/` returns the documented detail wrapper. Recipes intended + for CLI initialization include `cli.projectType` and all four `cli.env` + fields; catalog-only recipes may omit `cli`. +- A missing or non-official slug returns `RECIPE_NOT_FOUND` without exposing + its metadata. +- Run a CLI smoke test against production using `agora recipes list --json`, + `agora recipes show --json`, and an init-compatible recipe in a + disposable directory before pushing the CLI tag. + +Do not tag the CLI first: existing binaries fail safely when the API is +unavailable, but the advertised onboarding path would not be usable. + ## Local Verification Before cutting a tag: 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..59aca96 100644 --- a/docs/automation.md +++ b/docs/automation.md @@ -41,10 +41,11 @@ Use this guide for: - Use `--debug` (equivalent to `AGORA_DEBUG=1`) to echo structured log records to stderr. The flag does not change exit codes, JSON envelope shape, or NDJSON progress events; it only mirrors the entries that would normally be written to the log file. Pair with `--json` for fully machine-parseable runs that also surface internal events to your CI logs. v0.2.0 dropped the legacy `--verbose` / `-v` alias and the `AGORA_VERBOSE` env var; persisted configs that contain a `verbose` key are auto-promoted to `debug` on first load. - 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 runs (`--yes`, JSON, CI, non-TTY), pass exactly one of `--template` or `--recipe` to `agora init`. The CLI fails fast with `INIT_SOURCE_REQUIRED` when neither source is provided. +- 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. +- 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}`, `agora.recipes.{list,show}`, and `agora.init`. Authentication is intentionally **not** exposed via MCP because OAuth requires an interactive browser; run `agora login` once on the host first. - Use `agora open --target docs` for the human GitHub Pages docs and `agora open --target docs-md` for the agent-facing raw Markdown index. In CI/non-TTY runs the command defaults to URL-only output unless `--browser` is set. The Markdown tree is published under predictable `/md/` URLs, for example `/md/commands.md`, `/md/automation.md`, and `/md/error-codes.md`. - Docs publishing reads `internal-docs/pages/site.env` for `CLI_DOCS_*` and `CLI_INSTALL_*` URL defaults; staging Pages builds can override those environment variables at workflow time without changing docs content. The resolved values are published as `/docs.env` for transparency. - The CLI maintains a short-lived on-disk completion cache for `agora project use ` under `/cache/projects.json`. The cache is only used for completions when a **local unexpired session exists** (`session.json` with a non-empty access token and a future `expiresAt`, when present), so Tab does not suggest stale project names after logout or local session expiry. The cache TTL is 5 minutes by default; override with `AGORA_PROJECT_CACHE_TTL_SECONDS=` (set to `0` to disable). Cache files older than 24 h are pruned at every CLI startup. Set `AGORA_DISABLE_CACHE=1` to drop the cache on the next startup. The cache is invalidated automatically by `agora logout` and `agora project create` (the latter clears the file; it does not embed the new project until the next successful list fetch). To **force-refresh** the cached completion page, run `agora project list --refresh-cache` while authenticated; that command fetches the unfiltered first page used by completion and rewrites `projects.json` when it succeeds. @@ -77,6 +78,7 @@ You can always override: Primary command groups: - `init` +- `recipes` - `quickstart` - `project` - `auth` @@ -342,6 +344,7 @@ Example: ```bash ./agora init my-nextjs-demo --template nextjs --json ./agora init my-nextjs-demo --template nextjs --new-project --json +./agora init my-agent --recipe tool-calling --new-project --json ``` By default `init` reuses an existing project — preferring one named exactly `"Default Project"`. If no default exists, interactive sessions show existing projects with a create-new option and default to the most recently created project; JSON, CI, and non-TTY runs select the most recent project automatically. Pass `--new-project` to force creation. Use `--project ` to bind to a specific project. @@ -350,8 +353,10 @@ For deterministic automation, always pass `--project ` or `--new-projec Required `data` fields: - `action` Always `init`. -- `template` - Template ID such as `nextjs`, `python`, or `go`. +- `sourceType` + `quickstart` or `recipe`. +- `sourceId` + The template ID or recipe slug. - `projectAction` `created` or `existing`. - `reusedExistingProject` @@ -364,17 +369,24 @@ Required `data` fields: - `path` Absolute path to the cloned quickstart. - `envPath` - Path of the env file relative to the cloned quickstart root. + Path of the env file relative to the cloned scaffold root. +- `envStatus` + Credential env write status from the selected source contract. - `metadataPath` Repo-local project binding file path, currently `.agora/project.json`. - `enabledFeatures` Array of features enabled during this run. Defaults to `rtc`, `rtm`, and `convoai` for newly created projects unless overridden with `--feature`. Empty for existing projects since the CLI did not create them in this run. - `nextSteps` - Ordered list of suggested follow-up commands for the selected template. + Ordered list of suggested follow-up commands for the selected source. - `status` Currently `ready`. Optional fields: +- `template` + Present for built-in quickstart initialization. +- `recipe`, `recipeUrl`, `recipeRawUrl`, `primaryPrompt`, `cloneUrl` + Present for recipe-backed initialization. The CLI resolves this metadata from + the official recipes API before it selects or creates a project. - `rtmDataCenter` RTM data center configured on the new project when RTM was enabled. Defaults to `NA` when `--rtm-data-center` is omitted. @@ -382,7 +394,8 @@ Display-oriented fields: - `title` Safe branch fields: -- `template` +- `sourceType` +- `sourceId` - `projectAction` - `projectId` - `path` @@ -506,6 +519,11 @@ Example: ./agora project env write apps/web/.env.local --json ``` +Use this command for an explicit dotenv path in a custom repository. For an +official Agora Quickstart, prefer `agora quickstart env write [dir]` so the CLI +selects the template-specific path, seeds the template example, and updates +Quickstart binding metadata. + Optional `data` fields: - `credentialLayout` Either `standard` (AGORA_* keys) or `nextjs` (`NEXT_PUBLIC_AGORA_APP_ID` and `NEXT_AGORA_APP_CERTIFICATE`) when the workspace is detected or overridden as Next.js. @@ -522,7 +540,7 @@ Required `data` fields: - `status` One of `created`, `updated`, `appended`, or `overwritten`. - `keysWritten` - Ordered list of credential keys that were written. By default `project env write` uses `AGORA_APP_ID` and `AGORA_APP_CERTIFICATE`. Next.js workspaces (detected via `package.json` / `next.config.*` / `env.local.example` / repo `.agora` `projectType` / `template: nextjs`, or forced with `--template nextjs`) use `NEXT_PUBLIC_AGORA_APP_ID` and `NEXT_AGORA_APP_CERTIFICATE` instead. Non-secret project metadata stays in `.agora/project.json`. + Ordered list of credential keys that were written. By default `project env write` uses `AGORA_APP_ID` and `AGORA_APP_CERTIFICATE`. Next.js workspaces (detected via `package.json` / `next.config.*` / `env.local.example` / repo `.agora` `projectType` / `template: nextjs`, or forced with `--template nextjs`) use `NEXT_PUBLIC_AGORA_APP_ID` and `NEXT_AGORA_APP_CERTIFICATE` instead. Unsupported credential aliases such as `APP_ID` and `APP_CERTIFICATE` are commented out when the canonical keys are written. Non-secret project metadata stays in `.agora/project.json`. Optional `data` fields (present when the CLI updates or creates repo metadata): - `metadataUpdated` @@ -572,6 +590,53 @@ Safe branch fields: - `region` - `values` +### `recipes list` + +Example: + +```bash +./agora recipes list --type ai --json +``` + +Required `data` fields: +- `action` + Always `list`. +- `type` + `all`, `ai`, or `rtc`. +- `total` +- `items` + Official recipes returned by the versioned recipes API. Each summary includes + `slug`, `title`, `mainRepoUrl`, `recipeUrl`, `platforms`, `type`, and + `official` (always `true`). + +Safe branch fields: +- `type` +- `items[].slug` +- `items[].type` +- `items[].official` + +### `recipes show` + +Example: + +```bash +./agora recipes show tool-calling --json +``` + +Required `data` fields: +- `action` + Always `show`. +- `recipe` + The official recipe detail, including `recipeRawUrl` and `primaryPrompt`. + `recipe.cli` is optional in the API. `init --recipe` requires it and returns + `RECIPE_INIT_UNSUPPORTED` when it is absent. + +Safe branch fields: +- `recipe.slug` +- `recipe.type` +- `recipe.official` +- `recipe.mainRepoUrl` + ### `quickstart list` Example: @@ -614,6 +679,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: @@ -662,6 +728,11 @@ Example: ./agora quickstart env write /abs/path/to/my-python-demo --json ``` +This is the canonical env-write workflow for official Agora Quickstarts. It +uses the same credential merge and legacy-key cleanup semantics as +`project env write`, while additionally selecting the template-specific target, +seeding its example file, and updating `.agora/project.json`. + Required `data` fields: - `action` Always `env-write`. diff --git a/docs/commands.md b/docs/commands.md index 8a49931..90d0cbc 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -102,7 +102,7 @@ _No local flags. Inherited parent and global flags still apply; run `agora --help` for the full flag set._ + +### `agora recipes list` + +List recipes from the Agora catalog + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--type` | `string` | `all` | recipe type: all, ai, or rtc | + +### `agora recipes show` + +Show one Agora recipe + +_No local flags. Inherited parent and global flags still apply; run `agora --help` for the full flag set._ + ### `agora skills` Browse curated Agora workflows for humans and AI agents @@ -438,3 +460,5 @@ Show the current auth status **`outputModes`**: `pretty`, `json` **`doctorStatus`**: `healthy`, `warning`, `not_ready`, `auth_error` + +**`recipeTypes`**: `all`, `ai`, `rtc` diff --git a/docs/env-local.md b/docs/env-local.md new file mode 100644 index 0000000..88bf4ca --- /dev/null +++ b/docs/env-local.md @@ -0,0 +1,81 @@ +--- +title: Using Quickstart env files +--- + +# Using Quickstart env files + +For official Next.js, Python, Go, and Android 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` | +| Android | `server/.env.example` | `server/.env.local` | `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. Both commands use +the same credential merge rules: unrelated entries are preserved, current keys +are updated, and unsupported legacy credential names are commented out. + +## 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..3b31465 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -35,17 +35,27 @@ This catalog is the source of truth for stable codes. CI runs `make snapshot-err | Code | Exit | Meaning | Recovery | |------|------|---------|----------| -| `QUICKSTART_TEMPLATE_REQUIRED` | 1 | `init` needs a template in JSON, CI, or non-TTY mode. | Pass `--template` or run `agora quickstart list`. | | `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_