diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..405f7ed2b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,5 @@ +# AGENTS.md + +This repository's guidelines for AI coding agents live in [CLAUDE.md](CLAUDE.md), the single source of truth. + +Read **@CLAUDE.md** for the project overview and working conventions. Detailed material lives under [`references/`](references/), linked from there. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..938287912 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,147 @@ +# AI Agent Guidelines for auth0-cli + +This document provides context and guidelines for AI coding assistants working with the auth0-cli codebase. + +## Your Role + +You are a Go CLI engineer maintaining the Auth0 CLI — a Cobra-based tool (`internal/cli` over the `go-auth0` Management API) where, because it stores tenant secrets on users' machines and generates its command docs, secure credential handling and doc regeneration are first-class concerns on every change. + +--- + +## Working Principles + +Apply these on every task in this repo — they keep changes correct, small, and reviewable. + +- **Think before coding.** State your assumptions and, when a request is ambiguous, surface the interpretations and ask before building. Recommend a simpler approach when you see one. A clarifying question up front beats a wrong implementation. +- **Simplicity first.** Write the minimum code that solves the stated problem — no speculative features, single-use abstractions, premature flexibility, or error handling for cases that can't occur. +- **Surgical changes.** Touch only what the request requires. Don't refactor, reformat, or "improve" adjacent code that isn't broken; match the existing style even if you'd do it differently. Every changed line should trace directly to the request. Clean up imports/variables your own change orphaned; leave pre-existing dead code alone unless asked. +- **Goal-driven execution.** Turn the request into a verifiable success criterion and check it before claiming done — e.g. "add a flag" becomes "add the flag, wire it through, add a table-driven test, and regenerate docs." Don't report success you haven't verified. + +--- + +## Project Overview + +**auth0-cli** is the official command-line interface for Auth0 — build, manage, and test Auth0 integrations from the terminal. + +- **Language:** Go 1.25.8 +- **Tech Stack:** Cobra (commands) + pflag, go-auth0 Management SDK (v1 `management` and v2), Sentry crash reporting, zalando/go-keyring for secret storage, terraform-exec (Terraform export), charmbracelet/glamour (markdown rendering) +- **Package Manager:** Go modules — **vendored** (`vendor/` is committed; run `go mod tidy && go mod vendor` after dependency changes) +- **Minimum Platform Version:** Go 1.25.8 (from `go.mod`) +- **Dependencies:** go-auth0 v1.44.0 + v2.14.0, spf13/cobra 1.10.2, getsentry/sentry-go 0.47.0, zalando/go-keyring 0.2.8 · test: stretchr/testify 1.11.1, golang/mock (gomock) 1.6.0 + +--- + +## Project Structure + +``` +auth0-cli/ +├── cmd/ +│ ├── auth0/ # Main entrypoint — calls cli.Execute() +│ └── doc-gen/ # Generates docs/*.md from Cobra commands +├── internal/ +│ ├── cli/ # All CLI commands (Cobra) — the bulk of the code +│ ├── auth/ # Device-code authentication flow against Auth0 +│ ├── auth0/ # go-auth0 Management API wrappers + generated mocks +│ ├── keyring/ # System keyring storage for tokens & client secrets +│ ├── analytics/ # Segment usage tracking (opt-out via env var) +│ ├── instrumentation/ # Sentry crash reporting +│ ├── config/ # On-disk CLI config (tenants, default tenant) +│ ├── display/ # Output rendering (tables, JSON, colors) +│ ├── prompt/ # Interactive prompts (survey/promptui) +│ └── iostream/ # TTY / pipe detection +├── docs/ # GENERATED command reference (make docs) — do not hand-edit +├── test/integration/ # YAML-driven integration tests (commander) +└── Makefile # Canonical build/test/lint/docs targets +``` + +### Key Files + +| File | Purpose | +|------|---------| +| `cmd/auth0/main.go` | Entry point — thin wrapper over `cli.Execute()` | +| `internal/cli/root.go` | Root command, DI wiring (`cli` struct, renderer, tracker) | +| `internal/cli/cli.go` | `cli` struct, tenant/config setup, API client init | +| `internal/auth/auth.go` | Device-code OAuth flow, token exchange | +| `internal/keyring/keyring.go` | Secret storage abstraction over go-keyring | +| `Makefile` | All build/test/lint/docs commands | + +--- + +## Boundaries + +### ✅ Always Do + +- Run `make lint` and `make test-unit` before committing. +- Follow the existing Cobra command patterns and naming (see [references/code-style.md](references/code-style.md)). +- Add table-driven unit tests for new functionality; regenerate mocks with `make test-mocks` when an interface changes. +- **Regenerate command docs with `make docs` whenever you add/change a command, flag, or help text.** CI runs `make check-docs` and fails if `docs/` is out of sync. +- Update `README.md` in the same PR when a change touches what it documents — installation, config/auth, the top-level command list, deprecations, or supported workflows (per-flag and per-command detail lives in the generated `docs/`, via `make docs`, not the README). Update `CUSTOMIZATION_GUIDE.md` for Universal Login/branding changes and `MIGRATION_GUIDE.md` for breaking changes. +- After changing dependencies, run `go mod tidy && go mod vendor` — the `vendor/` directory is committed and must stay in sync. +- Route new usage tracking through the existing `analytics.Tracker` (`internal/analytics`) and preserve the `AUTH0_CLI_ANALYTICS=false` opt-out; do not hand-roll a new tracking client. + +### ⚠️ Ask First + +- **Any breaking change to a command, flag, or output format — always ask first.** Never break backward compatibility on your own initiative. +- Adding new dependencies (also requires `go mod vendor`). +- Modifying authentication, token exchange, or keyring storage code (`internal/auth`, `internal/keyring`). +- Changes to CI/CD configuration (`.github/workflows/`, `.goreleaser.yml`). +- Running integration tests (`make test-integration`) — they hit a **live Auth0 tenant**, are slow, and can mutate real resources (see [references/testing.md](references/testing.md)). + +### 🚫 Never Do + +- Commit secrets, API keys, tokens, or a populated `.env`. +- Log or print access tokens, refresh tokens, or client secrets. +- Hand-edit generated files: `docs/*.md` (regenerate via `make docs`) or `internal/auth0/mock/*` (regenerate via `make test-mocks`). +- Hand-edit the `vendor/` directory. +- Remove or skip failing tests without fixing them. +- Break backward compatibility without asking first and getting explicit approval. + +--- + +## Security Considerations + +- **Credential storage:** Client secrets, access tokens, and legacy refresh tokens are stored in the OS keyring via `zalando/go-keyring` (`internal/keyring`). Access tokens are chunked (2048-byte segments) because some keyrings cap value size. Never move secrets to plaintext config or logs. +- **Authentication:** Uses the OAuth device-authorization flow (`internal/auth`) for interactive login, and client-credentials (secret or private-key JWT) for machine auth. Do not weaken or bypass these flows. +- **Crash reporting:** `internal/instrumentation` ships a **public, write-only** Sentry DSN (safe to embed). Crash reporting is disabled for `dev`/empty-version builds — do not enable it for local builds. +- **Analytics:** `internal/analytics` sends usage events; honor the `AUTH0_CLI_ANALYTICS=false` opt-out and the debug-build skip. +- **Never commit secrets, API keys, or tokens.** + +--- + +> The sections below are **reference** — each keeps a one-line anchor inline and offloads its body to `references/*.md`. Read a file only when the task needs it. + +## Commands + +Core loop: `make build` (binary to `./out/auth0`), `make test-unit` (safe, no creds), `make lint`, `make docs` (regenerate command reference). + +See [references/commands.md](references/commands.md) for the full command list. Read it when you need to build, test, lint, generate docs/mocks, or check vulnerabilities. + +## Testing + +Framework is Go's `testing` + `testify` assertions + `gomock`; tests are table-driven and colocated as `*_test.go`. The default `make test-unit` suite is unit-only and needs no credentials; `make test-integration` hits a live tenant and requires `AUTH0_DOMAIN`/`AUTH0_CLIENT_ID`/`AUTH0_CLIENT_SECRET` (Ask First). + +See [references/testing.md](references/testing.md) for conventions, mocking, running a single test, and the integration tier. Read it when writing or running tests. + +## Code Style + +Go standard style enforced by `golangci-lint` (v2): `gofmt -s` + `goimports` with local prefix `github.com/auth0/auth0-cli`, plus `errcheck`, `revive`, `staticcheck`, `gocritic`, `godot` (comments end with a capitalized sentence + period). Commands follow a consistent Cobra constructor pattern with declarative `Flag` structs. + +See [references/code-style.md](references/code-style.md) for naming, the command pattern, and good/bad examples. Read it when adding or editing a command. + +## Git Workflow + +Branch names are ticket-scoped (e.g. `DXCDT-1234/short-description`) or `docs/…`, `fix-…`. PRs use `.github/PULL_REQUEST_TEMPLATE.md` (Changes / References / Testing sections). + +See [references/git-workflow.md](references/git-workflow.md) for branch, commit, and PR conventions. Read it before committing or opening a PR. + +## Common Pitfalls + +The top one: forgetting `make docs` after a command/flag change fails CI (`make check-docs`). Others involve vendoring, mock regeneration, and the v1/v2 go-auth0 split. + +See [references/pitfalls.md](references/pitfalls.md) for the full list. Read it when a build/CI step fails unexpectedly. + +## Docs Update Rules + +The `docs/` command reference is **generated** — never hand-edit it; run `make docs`. Prose docs (`README.md`, guides) are hand-maintained. + +See [references/docs-update.md](references/docs-update.md) for the tracked-docs inventory and the code-to-docs mapping. Read it when your change touches user-facing behavior. diff --git a/references/code-style.md b/references/code-style.md new file mode 100644 index 000000000..395885fce --- /dev/null +++ b/references/code-style.md @@ -0,0 +1,79 @@ +# Code Style + +## Enforced tooling + +`golangci-lint` v2 (`.golangci.yml`) is the gate. Enabled linters: `errcheck`, `gocritic`, `godot`, `revive`, `staticcheck` (all checks), `unconvert`, `unused`, `whitespace`. Formatters: `gofmt` with `simplify`, and `goimports` with local prefix `github.com/auth0/auth0-cli` (local imports grouped last). + +- `godot`: comments must be full sentences — capitalized, ending in a period. +- `errcheck`: check returned errors (the config exempts some via exclusion rules, but prefer handling them). + +## Naming conventions + +- Standard Go: `PascalCase` for exported identifiers, `camelCase` for unexported, short receiver names. +- Command files are named after the resource: `apps.go`, `apis.go`, `custom_domains.go`; their tests are `_test.go`. +- Flags are declared as package-level `Flag` structs (see below), named `` (e.g. `loginClientID`). + +## The command pattern + +Commands are Cobra constructors that take the shared `*cli` struct and return a `*cobra.Command`. Flags are declared declaratively: + +**✅ Good** — declarative flag, wired into a Cobra command: + +```go +var loginClientID = Flag{ + Name: "Client ID", + LongForm: "client-id", + Help: "Client ID of the application when authenticating via client credentials.", + IsRequired: false, +} + +func loginCmd(cli *cli) *cobra.Command { + var inputs LoginInputs + cmd := &cobra.Command{ + Use: "login", + Short: "Authenticate to your tenant", + RunE: func(cmd *cobra.Command, args []string) error { + return runLogin(cmd.Context(), cli, &inputs) + }, + } + loginClientID.RegisterString(cmd, &inputs.ClientID, "") + return cmd +} +``` + +**❌ Bad** — hardcoded flag strings, ignored error, no help text: + +```go +func loginCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{Use: "login", Run: func(cmd *cobra.Command, args []string) { + id, _ := cmd.Flags().GetString("client-id") // errcheck: unchecked error + doLogin(id) // no context, no error return + }} + cmd.Flags().String("client-id", "", "") // no help; not a Flag struct + return cmd +} +``` + +## Dominant patterns + +- **Dependency injection via the `cli` struct** (`internal/cli/cli.go`) — carries the renderer, analytics tracker, config, and API client; passed to every command constructor. +- **`RunE` returning errors** rather than `Run` + `os.Exit`; errors bubble to the root command. +- **Rendering through `internal/display`** — never `fmt.Println` results directly; use the renderer so JSON/table/format flags work. + +## Machine-readable output + +Every result-producing command supports mutually-exclusive output flags (`--json`, `--json-compact`, `--csv`) via the shared `cli` struct — wire them the same way existing commands do (see `roles.go`, `users.go`): + +```go +cmd.Flags().BoolVar(&cli.jsonCompact, "json-compact", false, "Output in compact json format.") +cmd.MarkFlagsMutuallyExclusive("json", "json-compact", "csv") +``` + +`--json-compact` emits single-line JSON, ideal for piping to `jq` in scripts and command examples: + +```bash +auth0 apps list --json-compact | jq '.[] | {client_id, name}' +auth0 users show --json-compact | jq '{id: .user_id, email: .email}' +``` + +Prefer `--json-compact | jq ...` over hand-parsing table output when documenting or scripting against the CLI. diff --git a/references/commands.md b/references/commands.md new file mode 100644 index 000000000..6874ac08f --- /dev/null +++ b/references/commands.md @@ -0,0 +1,59 @@ +# Commands + +All commands are Makefile targets (run `make help` to list them). These mirror what CI runs in `.github/workflows/main.yml`. + +```bash +# Build the CLI binary for the native platform -> ./out/auth0 +make build + +# Install the binary into $GOPATH/bin +make install + +# Build for all supported platforms (CI "Build" job) +make build-all-platforms + +# Run unit tests (safe — no credentials required) +make test-unit + +# Run all tests (unit + integration; integration needs a live tenant) +make test + +# Run integration tests only (requires AUTH0_DOMAIN/CLIENT_ID/CLIENT_SECRET) +make test-integration +# Filter to a subset: +make test-integration FILTER="attack protection" + +# Regenerate gomock mocks (after changing a mocked interface) +make test-mocks + +# Lint (golangci-lint v2, config .golangci.yml) +make lint + +# Check for known vulnerabilities (govulncheck) +make check-vuln + +# Regenerate the docs/ command reference from Cobra commands +make docs + +# Verify docs are in sync (CI gate — fails if `make docs` produces a diff) +make check-docs + +# Download dependencies +make deps + +# Clean the docs output +make docs-clean +``` + +## CI jobs (`.github/workflows/main.yml`) + +1. **Checks** — `make check-docs` + `golangci-lint` with `-c .golangci.yml`. +2. **Unit Tests** — `make test-unit`, uploads coverage. +3. **Integration Tests** — `make test-integration` (skipped for forks/dependabot; only on `main`-targeted PRs). Uses `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET` secrets. +4. **Build** — `make build-all-platforms`. + +## Running without building + +```bash +go run ./cmd/auth0 +``` diff --git a/references/docs-update.md b/references/docs-update.md new file mode 100644 index 000000000..cd1a02445 --- /dev/null +++ b/references/docs-update.md @@ -0,0 +1,28 @@ +# Docs Update Rules + +This is a **CLI** repo, so the code-to-docs mapping is command/flag-oriented. + +## Tracked docs + +| Doc | Covers | Present | +|-----|--------|---------| +| `README.md` | Install, quickstart, top-level command overview | present | +| `docs/*.md` | **Generated** per-command reference (`make docs`) — one file per command | present | +| `CUSTOMIZATION_GUIDE.md` | Universal Login / branding customization workflow | present | +| `MIGRATION_GUIDE.md` | Migration notes for breaking changes | present | +| `CONTRIBUTING.md` | Dev setup, adding a command, adding a dependency, releasing | present | + +> `EXAMPLES.md` is not tracked in this repo — usage examples live in `README.md` and the generated `docs/`. + +## When you change code, update these docs + +| Change | Update | +|--------|--------| +| Add/rename/remove a command | `make docs` (regenerates `docs/`), plus `README.md` if it's a top-level command | +| Add/change a flag or its help text | `make docs` | +| Change command output/behavior | `make docs` if help text changed | +| Breaking change (flag/output/command removed or renamed) | Ask first; then `MIGRATION_GUIDE.md` + `make docs` | +| Change to Universal Login / branding flow | `CUSTOMIZATION_GUIDE.md` | +| Change dev setup, build, or release steps | `CONTRIBUTING.md` | + +> The generated `docs/` reference must never be hand-edited — always regenerate via `make docs`. CI's `make check-docs` enforces this. Update the mapped hand-written doc **in the same PR** as the code change. diff --git a/references/git-workflow.md b/references/git-workflow.md new file mode 100644 index 000000000..0a179b437 --- /dev/null +++ b/references/git-workflow.md @@ -0,0 +1,34 @@ +# Git Workflow + +## Branch naming + +Observed conventions in this repo: + +- Ticket-scoped: `DXCDT-1234/short-description` (Jira key + slug). +- Type-scoped: `docs/…`, `fix-…`, `issue--…`. +- Automated: `dependabot/…`. + +Match the pattern that fits your change; prefer the ticket-scoped form when a Jira ticket exists. + +## Commit messages + +Conventional-commit style prefixes are used across history: `docs:`, `chore(deps):`, `fix:`, `feat:`. Keep the subject imperative and concise; scope in parentheses where useful (e.g. `chore(deps): bump ...`). + +## Pull requests + +Use `.github/PULL_REQUEST_TEMPLATE.md`, which has three sections: + +- **🔧 Changes** — what changed and why; types/methods added, deleted, deprecated, or changed; usage summary for new/changed public surface. +- **📚 References** — GitHub issue/PR links, Community posts, related PRs. +- **🔬 Testing** — how the change was tested. + +## Before opening a PR + +1. `make lint` +2. `make test-unit` +3. `make docs` (if you touched commands/flags/help) — CI's `make check-docs` will fail otherwise. +4. `go mod tidy && go mod vendor` (if you touched dependencies). + +## Releases + +Releases are cut by maintainers via a tag-triggered GitHub workflow + Goreleaser — not by agents editing files. The `CHANGELOG.md` is written as part of that release flow (see the `Add changelog for vX.Y.Z` PRs), not by feature PRs. Do not bump versions or add changelog entries by hand as part of a feature change. diff --git a/references/pitfalls.md b/references/pitfalls.md new file mode 100644 index 000000000..dbbd4cc63 --- /dev/null +++ b/references/pitfalls.md @@ -0,0 +1,17 @@ +# Common Pitfalls + +1. **Forgetting to regenerate docs.** Any change to a command, flag, or help string must be followed by `make docs`. CI runs `make check-docs`, which regenerates and fails if `git status` is dirty. This is the most common CI failure. + +2. **Vendoring drift.** Dependencies are vendored (`vendor/` is committed). After `go get`/`go mod` changes you must run `go mod tidy && go mod vendor`, or the build/CI breaks. Do not hand-edit `vendor/`. + +3. **Stale mocks.** Mocks in `internal/auth0/mock` are generated by `mockgen`. After changing a mocked interface, run `make test-mocks` — do not hand-edit the generated files. + +4. **Two go-auth0 major versions coexist.** The repo imports both `github.com/auth0/go-auth0` (v1, `management`) and `github.com/auth0/go-auth0/v2`. Check which version a given command already uses before adding calls; don't mix types across the two. + +5. **Printing results directly.** Use the `internal/display` renderer instead of `fmt.Println` so `--json` and format flags keep working, and so nothing accidentally prints a secret. + +6. **Leaking secrets in output/logs.** Client secrets and tokens live in the OS keyring (`internal/keyring`). Never log them; secret-revealing output (e.g. `--reveal-secrets`) must be explicit and opt-in. + +7. **Enabling telemetry/crash reporting for dev builds.** Both analytics and Sentry intentionally no-op when `buildinfo.Version` is empty or `dev`. Don't remove those guards. + +8. **`godot` lint failures.** Comments must be complete sentences ending in a period — an easy lint miss on new code. diff --git a/references/testing.md b/references/testing.md new file mode 100644 index 000000000..0fd7f1075 --- /dev/null +++ b/references/testing.md @@ -0,0 +1,48 @@ +# Testing + +## Frameworks & layout + +- **Unit tests:** Go's standard `testing`, with `github.com/stretchr/testify` (`assert`) for assertions and `github.com/golang/mock/gomock` for mocks. +- **Location:** colocated `*_test.go` files next to the code (e.g. `internal/cli/apps_test.go`), same package. +- **Integration tests:** YAML-driven test cases under `test/integration/*-test-cases.yaml`, executed by `commander` against a live Auth0 tenant. +- **Coverage:** produced by `make test-unit` (`coverage-unit-tests.out`) and uploaded to Codecov in CI. `codecov.yml` holds the config; there is no hard local threshold gate. + +## Running tests + +```bash +# All unit tests (safe — no credentials) +make test-unit + +# A single package +go test -race ./internal/cli/... + +# A single test by name +go test -race ./internal/cli/ -run TestAppsListCmd + +# Integration subset by filter +make test-integration FILTER="apps" +``` + +## Unit test conventions + +- **Table-driven:** a `tests := []struct{ name string; args []string; assertOutput func(...) }` slice iterated with `t.Run(tt.name, ...)`. +- **Mocking the API:** use the generated mocks in `internal/auth0/mock` with a `gomock.Controller`; set expectations via `EXPECT()`. Regenerate with `make test-mocks` after changing a mocked interface. +- **Output assertions:** command output is captured and checked with helpers like `expectTable(t, out, headers, rows)`. +- Name cases descriptively ("happy path", "reveal secrets") rather than by index. + +## Integration / Acceptance tests + +> ⚠️ These hit a **live Auth0 tenant**, are slow, and can create/modify/delete real resources. **Ask before running them** (see Boundaries in CLAUDE.md). + +```bash +# Requires: AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET +# (set in a .env at repo root or exported) +make test-integration +``` + +**First time?** These credentials come from a Machine-to-Machine (M2M) application in your Auth0 tenant: + +- `AUTH0_DOMAIN` — your tenant domain (e.g. `travel0.us.auth0.com`). +- `AUTH0_CLIENT_ID` / `AUTH0_CLIENT_SECRET` — the M2M app's credentials, authorized for the Management API. + +See [`CONTRIBUTING.md`](../CONTRIBUTING.md) for the full step-by-step setup, or Auth0's [Machine-to-Machine apps guide](https://auth0.com/docs/get-started/auth0-overview/create-applications/machine-to-machine-apps).