diff --git a/fern/products/cli-generator/authentication.mdx b/fern/products/cli-generator/authentication.mdx index e77ffb427..9dcd49bdb 100644 --- a/fern/products/cli-generator/authentication.mdx +++ b/fern/products/cli-generator/authentication.mdx @@ -198,6 +198,60 @@ export MY_TOKEN=tok-456 my-cli users list ``` +## Named profiles + +For APIs where every call carries a tenant identifier (an account SID, an org slug, a workspace), a profile stores it once instead of it being typed on every command. A profile is a named bundle of request context resolved once per invocation: a credential, parameter defaults, [server URL variables](/learn/cli-generator/get-started/features#server-url-variables), an optional base URL, a [retry limit](/learn/cli-generator/get-started/features#retries-with-backoff), and a default output format. Nothing else is accepted; an unknown key is rejected at write time rather than stored and ignored. + +Profiles are off by default. Enable them with the [`profiles` config option](/learn/cli-generator/get-started/configuration#config-options) in `generators.yml`, which adds a `profiles` command group and a global `--profile` / `-p` flag to the generated CLI. A CLI generated without the option is unchanged. + +```yaml title="generators.yml" +config: + binaryName: acme + profiles: + enabled: true +``` + +### Manage profiles + +```bash +acme profiles create prod --set AccountSid=AC11… --with-token # token read from stdin, stored in the OS keychain +acme profiles create sub --parent prod --set AccountSid=AC99… # subaccount that shares prod's credential +acme profiles set prod ACME_REGION=us1 ACME_RETRIES=3 # add or change values by env var name +acme profiles use sub # make it the active profile + +acme messages list # no --account-sid needed +acme messages list -p prod # one command against another tenant; the active profile is unchanged + +acme profiles list # every profile, with the active one marked +acme profiles current # the profile in effect and how it was selected +acme profiles show staging # one profile's resolved config without selecting it +acme profiles remove sub # also deletes that profile's stored credential +``` + +`profiles create` accepts `--set =` for API parameters (validated against the spec, so a typo is rejected), `--server-var =` or `-- ` for server URL variables, `--base-url`, `--retries `, `--default-format`, `--parent `, and `--with-token` to read a credential from stdin. When several auth schemes are declared, `--scheme` names the one the credential belongs to. + +`profiles set KEY=VALUE ...` takes any environment variable the CLI reads (a credential such as `ACME_AUTH_TOKEN`, or a setting such as `ACME_RETRIES`, `ACME_BASE_URL`, `ACME_OUTPUT`, or `ACME_`) or an API parameter by its spec name, and creates the profile if it doesn't exist. Keys are validated before anything is written, and an unrecognized key fails with a suggestion. `auth login --from-env` captures the credential from the CLI's own environment variables into the active profile. + +### Select a profile + +A profile is selected by, in order, `--profile` / `-p`, the `_PROFILE` environment variable, then the profile marked active by `profiles use`, where `` is the binary name uppercased with `-` replaced by `_`. With no profile selected, the CLI behaves as if the feature were absent. A named profile that doesn't exist is an error rather than a fallthrough to environment credentials, so a command never silently runs against the wrong tenant. The `profiles` group itself always runs unprofiled, so a stale active pointer can be repaired. + +Scripts and agents should pass `-p` per invocation: it mutates no global state, so parallel invocations can't race. + +```bash +for tenant in prod sub; do acme messages list -p "$tenant" --format json; done +``` + +### Precedence + +Each value resolves as `explicit flag → environment variable → profile → spec default`. Environment variables sit above the active profile, so a CI pipeline that exports `ACME_API_KEY` or `ACME_BASE_URL` is never overridden by a profile a developer stored on the same machine. A profile named explicitly with `-p` is itself an explicit choice for that invocation, so its values outrank the environment. Retries resolve as `--retries` → `_RETRIES` → profile → `x-fern-retries`, and `--retries 0` is equivalent to `--no-retry`. `profiles current` reports when an environment variable is overriding a stored credential. + +### Storage and inheritance + +Non-secret settings live in `~/.config//profiles.toml`. Secrets are never written to that file: credentials go to the OS keychain under a profile-scoped account, the same store `auth login` uses. + +`--parent ` sets single-level inheritance. A child inherits its parent's credential, base URL, server variables, parameter defaults (child wins per key), and retries. It doesn't inherit the default output format, since output shape belongs to the invocation rather than the tenant. Because the parent is resolved at read time, editing it propagates to its children. + ## Help output Every generated CLI includes a dynamically rendered `Authentication:` section in its `--help` output listing every scheme, the expected env var or flag, and whether a credential is detected. diff --git a/fern/products/cli-generator/configuration.mdx b/fern/products/cli-generator/configuration.mdx index a3f299485..77ac3cf8c 100644 --- a/fern/products/cli-generator/configuration.mdx +++ b/fern/products/cli-generator/configuration.mdx @@ -97,6 +97,32 @@ config: ``` + +Adds [named profiles](/learn/cli-generator/get-started/authentication#named-profiles) to the generated CLI: a top-level `profiles` subcommand group, a global `--profile` / `-p` flag, and a `_PROFILE` environment variable. A profile stores a tenant's credential, parameter defaults, server URL variables, base URL, retry limit, and default output format once, so users of multi-tenant APIs don't pass an account identifier on every command. + +Off by default. A CLI generated without this block is byte-identical to one generated before the option existed, and a `profiles` block with `enabled` unset or `false` is a no-op. + +```yaml title="generators.yml" +config: + binaryName: acme + profiles: + enabled: true +``` + + + +Emits the profiles surface. Enabling it adds a top-level subcommand group and a global flag to an existing CLI, which is a surface change for its users. + + + +Name of the top-level subcommand group. Must start with a lowercase letter and contain only `[a-z0-9-]`, and can't be one of the built-in groups (`auth`, `completion`, `man`, `errors`, `generate-skills`, `help`). Rename it (for example, to `tenants` or `accounts`) when that reads better for your API. If your spec already declares a `profiles` resource, the built-in subcommands are folded into that group rather than colliding with it, so renaming is optional. + + + +Dotted command path of an operation that revokes a profile's remote credential, such as `iam.keys.remove`. When set, `profiles remove` gains a `--revoke` flag that calls the operation before deleting the profile locally. The operation is invoked with the profile's stored parameters as its arguments, so any parameter it requires must be set on the profile. When omitted, the flag isn't registered. + + + Sets the Cargo `[package]` metadata of the generated crate. Use `packageIdentity` to publish the crate under your own name, license, repository, and authors instead of Fern's. diff --git a/fern/products/cli-generator/features.mdx b/fern/products/cli-generator/features.mdx index 083342715..6bf1d41c2 100644 --- a/fern/products/cli-generator/features.mdx +++ b/fern/products/cli-generator/features.mdx @@ -48,7 +48,7 @@ Failed requests are retried automatically with exponential backoff on status cod | Backoff factor | 2x | | Jitter | 10% | -GET, HEAD, OPTIONS, DELETE, and PUT retry by default. POST and PATCH retry only when the operation declares server-side idempotency support with [`x-fern-idempotent: true`](/learn/api-definitions/openapi/extensions/idempotency), which also exposes an `--idempotency-key` flag on that command, or when the caller passes `--idempotency-key` explicitly. Pass `--no-retry` to disable retries for a single invocation. +GET, HEAD, OPTIONS, DELETE, and PUT retry by default. POST and PATCH retry only when the operation declares server-side idempotency support with [`x-fern-idempotent: true`](/learn/api-definitions/openapi/extensions/idempotency), which also exposes an `--idempotency-key` flag on that command, or when the caller passes `--idempotency-key` explicitly. Pass `--no-retry` to disable retries for a single invocation, or `--retries ` to set the number of retries after the first attempt (`--retries 0` equals `--no-retry`). The limit resolves as `--retries` → `_RETRIES` → [profile](/learn/cli-generator/get-started/authentication#named-profiles) → `x-fern-retries`. An `Idempotency-Key` header is generated automatically for POST, PUT, and PATCH requests unless the invocation carries its own `--idempotency-key` or the operation opts out with `x-fern-cli-idempotency: false`. The same generated key is sent on every attempt of a request, but the key alone doesn't make a request retry-eligible. @@ -151,7 +151,7 @@ contoso plants list --schema } ``` -`input.properties` is keyed by the spec's parameter names, and each property's `flag` gives the exact flag to pass. Properties without a `flag` are passed through `--params` or `--json`. Operations that support [`--page-all`](#pagination), return binary bodies, or stream also report `paginable`, `binaryResponse`, or `streaming`. +`input.properties` is keyed by the spec's parameter names, and each property's `flag` gives the exact flag to pass. Properties without a `flag` are passed through `--params` or `--json`. Operations that support [`--page-all`](#pagination), return binary bodies, or stream also report `paginable`, `binaryResponse`, or `streaming`. The root schema lists the CLI's built-in command groups (`auth`, `completion`, `man`, and `profiles` when [enabled](/learn/cli-generator/get-started/authentication#named-profiles)) under `builtinCommands`, separate from the spec-derived operations. ## Server URL variables @@ -166,6 +166,8 @@ export BIGCOMMERCE_STORE_HASH=abc123 bigcommerce v3 customers list ``` +The environment variable is `_`, where `` is the binary name uppercased with `-` replaced by `_`. Variables declared with `x-fern-sdk-variables` use their bare name instead (`gardenId` reads `GARDEN_ID`). A [named profile](/learn/cli-generator/get-started/authentication#named-profiles) can also store the variable; the flag wins over the environment variable, which wins over the profile. + ## File uploads and downloads For endpoints with `format: binary` request bodies, pass a file path as the `--file` argument. For binary responses, use `--output ` to save the response body to a file. diff --git a/fern/products/cli-generator/state-of-the-world.mdx b/fern/products/cli-generator/state-of-the-world.mdx index 739f1bd68..db667f9f0 100644 --- a/fern/products/cli-generator/state-of-the-world.mdx +++ b/fern/products/cli-generator/state-of-the-world.mdx @@ -58,6 +58,7 @@ Fern generates a command-line interface from the same API definition that alread - Naming, nesting, auth, output shape, errors, and exit codes come from one runtime, so consistency holds by construction. - Bearer, API key, HTTP Basic, and OAuth: client credentials plus interactive `auth login` with Proof Key for Code Exchange (PKCE) and device-code flows, tokens stored in the OS keyring, and `auth status` to show which source is active. +- Named profiles, enabled with `profiles.enabled: true`: a `profiles` command group and a global `-p` flag store a tenant's credential, parameter defaults, server variables, base URL, retries, and default output format once. Secrets stay in the OS keyring; environment variables still outrank the active profile, so CI is never overridden by a stored profile. - `--debug` dumps the fully built request and the response for generated commands and for custom commands that call the co-generated SDK. - Retries with backoff and jitter: always on 408 and 429, and on 5xx when the request is safe to replay; automatic idempotency keys on mutating requests; `--dry-run` prints the exact request without sending it; inputs validated before any request is built. @@ -95,10 +96,10 @@ Fern generates a command-line interface from the same API definition that alread | In active development | Next | Later | | --- | --- | --- | | **Richer error display**: 401 and 422 hints, links into docs | **`serve`**: the same surface over MCP | **Grouped interactive prompts** for missing parameters | -| **Agent-readiness scoring** of generated CLIs | **Multiple credential profiles** | **Interactive explore view** for nested responses | -| | **CLI snippets** in the docs site and API Explorer | **Infrastructure-as-code mode**: state file, plan/diff, apply | -| | **`docs` command**: documentation from inside the CLI | **Per-endpoint default output format** | -| | **Multi-scheme auth refinements** | **Webhook listener**: `webhooks listen` for local development | +| **Agent-readiness scoring** of generated CLIs | **CLI snippets** in the docs site and API Explorer | **Interactive explore view** for nested responses | +| | **`docs` command**: documentation from inside the CLI | **Infrastructure-as-code mode**: state file, plan/diff, apply | +| | **Multi-scheme auth refinements** | **Per-endpoint default output format** | +| | | **Webhook listener**: `webhooks listen` for local development | | | | **GraphQL generation** on the existing runtime support | ## How generation runs