core types + interfaces: error taxonomy, RequestSpec, Formatter, Resolver, Provider (PRINFRA-120)#1
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
32f2730 to
bb0b001
Compare
| // the framework executor converts them to HTTP requests. | ||
| // | ||
| // All fields are defined even if not all are used yet — the codegen pipeline | ||
| // and framework features (pagination, polling, upload) depend on this shape. |
There was a problem hiding this comment.
The full struct is defined upfront (including Pollable, PollConfig, FilePath, BodyEncoding) even though only a subset is used by video list. This avoids reshaping the core type when codegen, pagination, polling, and multipart upload are added.
|
|
||
| // Formatter is the interface for CLI output rendering. | ||
| // JSONFormatter outputs structured JSON. TUIFormatter (future) adds tables and progress bars. | ||
| type Formatter interface { |
There was a problem hiding this comment.
Only JSONFormatter exists today. A TUIFormatter implementing this same interface will add --human mode with lipgloss tables and bubbletea spinners — no changes to commands needed.
|
|
||
| // Resolver resolves API credentials. | ||
| // EnvResolver reads from the HEYGEN_API_KEY env var. | ||
| // Future: OS keyring and file-based credential storage with priority chain. |
There was a problem hiding this comment.
Currently only EnvResolver exists. The plan is to add KeyringResolver (OS keychain via go-keyring) and FileResolver (~/.heygen/credentials) with a priority chain: env > keyring > file.
There was a problem hiding this comment.
this is actualy how I solved it before as well
wondering though what do other CLIs do? do we need the keyring resolver immediately or get away with just looking at the file
There was a problem hiding this comment.
Good question. Here's how others handle it:
- Stripe CLI: env var (STRIPE_API_KEY) → config file (~/.config/stripe/config.toml) → interactive stripe login (device auth flow). No keyring.
- gh CLI: env var (GH_TOKEN) → config file (~/.config/gh/hosts.yml). No keyring.
- AWS CLI: env var → ~/.aws/credentials file → instance metadata. No keyring.
None of the major CLIs use OS keyring.
File-based storage is the industry standard. We can skip keyring entirely and go: env var → config file (~/.heygen/credentials).
For this PR, env-only is intentional. File-based storage comes in future PRs (auth/config milestone).
| // EnvProvider reads from env vars. FileProvider (future) reads ~/.heygen/config.toml. | ||
| type Provider interface { | ||
| APIKey() string | ||
| BaseURL() string |
There was a problem hiding this comment.
Provider intentionally does NOT include APIKey() — credentials are auth.Resolver's job. This prevents two sources of truth for API keys when keyring/file storage is added.
740533d to
0b271fa
Compare
…Resolver, Provider Stable types and interfaces that codegen and framework features build against: - CLIError with exit codes (0/1/2/3), ToErrorEnvelope for JSON output - APIError matching HeyGen's standard error envelope - FromAPIError mapping HTTP status → exit code - RequestSpec: []QueryParam, []FieldSpec, BodyEncoding, FilePath, *PollConfig - output.Formatter interface (Data + Error) - auth.CredentialResolver interface (priority chain for API keys) - config.Provider interface (non-secret settings, BaseURL only) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0b271fa to
5059ec9
Compare
Industry research shows none of the major CLIs (Stripe, gh, AWS, gcloud) use OS keyring. File-based storage with 0600 permissions is the standard. Removes keyring mentions from auth resolver comment and git conventions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PRINFRA-120 CLI M0: Repo scaffold + core types
Set up the Go repository and define the core type contracts that M1 and M2 depend on. Scope
Why firstThese types are the shared contracts consumed by M1 (codegen, framework) and M2 (auth, config). They must be stable before parallel work begins. Acceptance Criteria
|
Merge activity
|
…-122) (#2) ## Description Implements the interfaces from PR #1 — this is where the actual behavior lives. **Components:** - **EnvCredentialResolver** — reads `HEYGEN_API_KEY` from the environment. If missing, returns an auth error (exit 3) with a hint telling the user what to set. - **EnvProvider** — reads `HEYGEN_API_BASE` from the environment, defaults to `https://api.heygen.com`. Used to override the API URL in tests and staging. - **Client** — wraps Go's `net/http` with automatic `x-api-key` and `User-Agent` header injection on every request. The `WithHTTPClient` option is how tests swap in a mock server. - **Executor** — takes a RequestSpec and turns it into a real HTTP call. Handles URL construction, path parameter substitution (e.g. `/v3/videos/{video_id}`), query param encoding, JSON body marshaling, and error parsing. Returns raw JSON on success or a CLIError on failure, with the API's request ID attached when available. - **JSONFormatter** — the default output renderer. Pretty-prints successful JSON to stdout, renders errors as `{"error": {...}}` envelopes to stderr. Rejects non-JSON responses as errors (the CLI's contract is structured output). **How they connect:** The root command's startup hook creates an EnvProvider, resolves credentials via EnvCredentialResolver, then builds a Client configured with the provider's base URL. Commands use the Client's Executor to run their RequestSpecs, and the Formatter to write the result. ## Testing All tests use Go's `httptest.Server` — no real API calls. Coverage includes header injection, retry scenarios, query param encoding with repeated keys, path param substitution, POST body marshaling, error envelope parsing for 400/401/500, network errors, and invalid JSON rejection. Linear: [PRINFRA-122](https://linear.app/heygen/issue/PRINFRA-122)
…RA-142) (#3) ## Description Wires everything from PRs #1 and #2 into a working CLI. After this, `heygen video list` works end-to-end against the real HeyGen API. **What happens when you run `heygen video list --limit 5`:** 1. `main.go` creates a JSON formatter before anything else — so even early failures (bad auth, bad flags) get structured JSON error output, never plain text. 2. Cobra parses the command and flags. Unknown commands and bad flags are caught and returned as usage errors (exit 2). 3. The root command's startup hook runs: creates a config provider, resolves the API key, and builds an HTTP client. 4. The `video list` command validates `--limit` is 1-100, builds a RequestSpec, and hands it to the executor. 5. The executor makes the HTTP call and returns raw JSON. The formatter pretty-prints it to stdout. 6. If anything fails at any step, the error flows back to `main.go` where it's classified (auth → exit 3, usage → exit 2, everything else → exit 1) and rendered as a JSON envelope to stderr. **Test harness:** `runCommand()` in the test helper mirrors this exact flow — fresh Cobra tree, buffer-backed formatter, same error classification. Tests assert on exit codes and the JSON envelope shape, not just whether an error occurred. ## Testing 10 command tests using httptest mocks: - Success with valid JSON response - Missing auth (exit 3 with hint) - API 401 and 400 responses (correct exit codes, request ID preserved) - Flags passed correctly as query params - Limit validation (exit 2 for 0, -5, 999) - Unknown flag and unknown command (exit 2) - Server error (exit 1) Manual smoke test: `HEYGEN_API_KEY=<key> ./bin/heygen video list --limit 2` returns real video JSON. Linear: [PRINFRA-142](https://linear.app/heygen/issue/PRINFRA-142)

Description
Defines the core types and interfaces that the rest of the CLI is built on. Every command, the HTTP executor, the output system, and the auth/config layers all depend on these.
What's introduced:
Data()writes successful responses to stdout,Error()writes failures to stderr. Different implementations (JSON today, TUI tables later) can slot in without changing any command code.Testing
Error type tests cover exit code mapping for all HTTP statuses, the JSON envelope shape, field omission for empty values, and all constructor functions.
Linear: PRINFRA-120