From 1b69450fa56c7c2bc635031b8ffd95d5e050744c Mon Sep 17 00:00:00 2001 From: Chris Garber Date: Fri, 13 Nov 2026 15:34:23 -0500 Subject: [PATCH] PAL-1311 Generate the CLI alongside the shared SDKs [Replace the Vapi CLI with a Fern-generated OAuth beta](https://linear.app/vapi/issue/PAL-1311/replace-the-vapi-cli-with-a-fern-generated-oauth-beta) ## Description Configures CLI generation in this repository alongside the server SDKs. The new `cli-sdk` group targets `VapiAI/cli` in pull-request mode; Release CLI accepts an explicit version and opens a generated PR without publishing. ## How it works The CLI workspace references the existing API's OpenAPI file, naming overrides and parameter overlay directly. Its OAuth/PKCE and staging-server overrides stay isolated from the other SDKs. A separate workspace is necessary because generator-level spec overrides did not reach the CLI's embedded OpenAPI in validation. | Before | After | | --- | --- | | CLI vendors three API inputs and owns separate generation scripts | Shared files are consumed directly from this repo | | CLI generation runs separately | Central Release CLI workflow uses Fern's SDK PR delivery path | The seven existing SDK groups and their effective API configuration remain byte-for-byte unchanged. The paired [CLI #21](https://app.graphite.com/github/pr/VapiAI/cli/21) removes the duplicated inputs, retains repository-owned docs/tests, and uses native Fern Replay for schema and command-binding customizations. Its HTTP runtime is Fernignore-protected because Replay rejects the embedded public test certificate. ## Testing Steps - [x] Central publishing preview succeeds with native PKCE, the staging default server and optional provider Content-Type. - [x] Shared input paths/settings match the API workspace; existing SDK configuration is unchanged. - [x] Two local native Replay cycles preserve runtime, API files, wire fixtures and repository-owned checks without conflicts. - [x] CLI runtime tests (2,265), wire tests (290), end-to-end checks and 500 ms performance budgets pass. - [x] Workflow passes actionlint. ## Risks Merge CLI #21 before the first Release CLI invocation so the destination has its Replay baseline and Fernignore protections. Keep CLI generator 0.59.1 pinned; reconcile the protected HTTP runtime with fresh output when upgrading it. Live OAuth acceptance, production client promotion and package-publishing setup remain release gates. This PR does not publish a package or change server SDK authentication. --- .github/workflows/release-cli.yml | 36 ++++++++++++++++ advanced.md | 27 ++++++++++++ fern/apis/cli/generators.yml | 65 +++++++++++++++++++++++++++++ fern/apis/cli/openapi-overrides.yml | 37 ++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 .github/workflows/release-cli.yml create mode 100644 fern/apis/cli/generators.yml create mode 100644 fern/apis/cli/openapi-overrides.yml diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml new file mode 100644 index 000000000..c907b0e68 --- /dev/null +++ b/.github/workflows/release-cli.yml @@ -0,0 +1,36 @@ +name: Release CLI + +on: + workflow_call: + inputs: + version: + description: CLI version to generate (opens a pull request; does not publish) + required: true + type: string + workflow_dispatch: + inputs: + version: + description: CLI version to generate (opens a pull request; does not publish) + required: true + type: string + +permissions: + contents: read + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install Fern + run: npm install -g fern-api + - name: Generate CLI pull request + env: + FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + CLI_VERSION: ${{ inputs.version }} + run: fern generate --api cli --group cli-sdk --version "$CLI_VERSION" --mode pull-request --log-level debug diff --git a/advanced.md b/advanced.md index a9387a919..71b986934 100644 --- a/advanced.md +++ b/advanced.md @@ -45,6 +45,33 @@ This will generate the SDK and download it to a local folder that can be pip ins pip install -e /fern/.preview/fern-python-sdk ``` +## Managing the CLI + +The `cli-sdk` group in `fern/apis/cli/generators.yml` generates `VapiAI/cli` +from the same OpenAPI definition, naming overrides, and parameter-description +overlay as the server SDKs. Its separate API workspace adds public-client +OAuth with PKCE and a staging default server; the other SDK groups retain their +existing authentication. Promote the CLI issuer, public client, resource and +server together after live OAuth acceptance. + +Run **Release CLI** with an explicit version to open a generated CLI pull +request, just like the SDK release workflows. Generation does not publish a +package. Merge the CLI repository's Fern migration before the first run; +review and merge generated updates there, then use its version-tag publishing +workflow when the release gates are satisfied. + +For a local preview: + +```sh +fern generate --api cli --group cli-sdk --version 1.0.0-beta.1 --preview +``` + +The CLI repository owns its docs, tests and runtime customizations. Its +`.fernignore` preserves repository-owned files, and Fern Replay carries its +schema changes across generated pull requests. The customized HTTP runtime is +protected by `.fernignore` because Replay rejects its embedded test certificate. Keep API naming changes in +shared source annotations or overrides, not CLI-only aliases. + ## How to update documentation? ### Local Development server diff --git a/fern/apis/cli/generators.yml b/fern/apis/cli/generators.yml new file mode 100644 index 000000000..85a1ce27f --- /dev/null +++ b/fern/apis/cli/generators.yml @@ -0,0 +1,65 @@ +# Staging-only beta registration. Promote the issuer, client and API audience +# together after live OAuth acceptance; no client secret belongs in this repo. +auth-schemes: + bearer: + scheme: oauth + type: authorization-code + client-id: client_01M721J47S27BS1GSXK1780TSR + authorization-url: https://nourishing-mulberry-12-staging.authkit.app/oauth2/authorize + token-url: https://nourishing-mulberry-12-staging.authkit.app/oauth2/token + refresh-url: https://nourishing-mulberry-12-staging.authkit.app/oauth2/token + scopes: [openid, offline_access] + redirect-uri: + url: http://127.0.0.1:8484/callback + ports: [8485, 8486] + authorization-parameters: + resource: https://api-mcp-poc.vapi.local + token-parameters: + resource: https://api-mcp-poc.vapi.local + refresh-parameters: + resource: https://api-mcp-poc.vapi.local +# Separate API workspace: the CLI generator embeds the workspace-level spec. +# Generator-level spec overrides affect IR but not that embedded runtime spec. +api: + auth: bearer + specs: + - openapi: ../api/openapi.json + overrides: + - ../api/openapi-overrides.yml + - ./openapi-overrides.yml + overlays: ../api/openapi-parameter-descriptions-overlay.yml + settings: + title-as-schema-name: false + coerce-enums-to-literals: false + respect-nullable-schemas: true +default-group: cli-sdk +groups: + cli-sdk: + generators: + - name: fern-cli-generator + version: 0.59.1 + output: + location: npm + package-name: "@vapi-ai/cli" + token: OIDC + github: + repository: VapiAI/cli + mode: pull-request + config: + binaryName: vapi + customCommands: false + generateWireTests: true + distribution: + homebrew: + tap: VapiAI/homebrew-tap + formula: vapi-cli + profiles: + enabled: true + packageIdentity: + name: vapi-cli + description: Voice AI for developers - Vapi CLI + authors: ["Vapi "] + keywords: ["vapi", "voice", "ai", "cli"] + license: MIT + repository: https://github.com/VapiAI/cli + homepage: https://vapi.ai diff --git a/fern/apis/cli/openapi-overrides.yml b/fern/apis/cli/openapi-overrides.yml new file mode 100644 index 000000000..42af1ec46 --- /dev/null +++ b/fern/apis/cli/openapi-overrides.yml @@ -0,0 +1,37 @@ +# OpenAPI reserves Content-Type for requestBody.content. The upstream +# provider-resource endpoint declares it as a required header instead. +# Fern's IR ignores that header, while the generated runtime requires it. +# Keep the flag available, but let the HTTP stack set Content-Type when +# --json is supplied; callers can still choose --content-type explicitly. +paths: + /provider/{provider}/{resourceName}: + post: + parameters: + - name: content-type + in: header + required: false + schema: + type: string + +# The beta's OAuth issuer and API audience are staging-only. Keep its default +# server in staging too, rather than sending staging credentials to production. +servers: + - url: https://staging-api.vapi.ai +components: + securitySchemes: + bearer: + type: oauth2 + scheme: null + bearerFormat: null + description: Sign in with `vapi auth login`. The CLI uses public-client OAuth with PKCE and refreshes access tokens automatically. + flows: + authorizationCode: + authorizationUrl: https://nourishing-mulberry-12-staging.authkit.app/oauth2/authorize + tokenUrl: https://nourishing-mulberry-12-staging.authkit.app/oauth2/token + refreshUrl: https://nourishing-mulberry-12-staging.authkit.app/oauth2/token + scopes: + openid: Sign in + offline_access: Refresh access + # The shared SDK override adds this unused API-key scheme. The CLI uses + # only the OAuth scheme referenced by the API operations. + BearerAuth: null