Skip to content

Generate API commands from the OpenAPI spec - #32

Open
dennis-chen-vanta wants to merge 9 commits into
mainfrom
dennis-chen-vanta/command-codegen-oxfmt
Open

dennis-chen-vanta wants to merge 9 commits into
mainfrom
dennis-chen-vanta/command-codegen-oxfmt

Conversation

@dennis-chen-vanta

@dennis-chen-vanta dennis-chen-vanta commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the generator for the API commands in src/commands/, so they're regenerated from api-spec.json instead of edited by hand.

Test plan

  • Check CI
  • Check that no files changed beyond comments added (and one bug fix that the codegen surfaced where we weren't setting the file download type correctly)

🤖 Generated with Claude Code

dennis-chen-vanta and others added 8 commits October 26, 2026 22:35
Add scripts/codegen/generate-commands.ts, which derives every API command
in src/commands/ from api-spec.json using the conventions in AGENTS.md.
Exceptions to the rules and help text overrides live in
scripts/codegen/config.ts.

- `pnpm generate:commands` regenerates src/commands/
- CI fails if the generated commands are out of date
- Generated files are marked with a "Code generated" header; the
  generator removes generated files for groups that no longer exist

The generated output matches the previous commit apart from the header.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Semgrep flags `new RegExp` built from a path segment
(detect-non-literal-regexp). Compare the suffix as a string instead;
generated output is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Resolve every $ref once when the spec loads, instead of calling
  deref() wherever a schema is read
- Type the parts of the OpenAPI spec the generator reads; no `any`
- Keep groups in a map keyed by CLI path instead of a tree with a fake
  root node
- Clearer names (`placement`, `topGroup`, `valueSchema`)

Generated commands are byte-for-byte identical.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- `links` in config.ts lists operations that attach or detach an existing
  resource; the generator names them add/remove. The spec can't mark these
  itself, since its summaries also say "Add" for creates.
- GETs that return a file use `download`.
- `commandPlural` lets `uploads` read as `files` in list commands.
- Drops the overrides that shortened names repeating the group
  (`list-library`, `list-deactivated`, `add-document`, `add-test`).
- Removes mounts: every group is its path's first segment, so the
  generator no longer nests groups.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…xfmt

- One module per stage (spec, naming, options, help, groups, render),
  with main.ts running them in order. Values that were module-level
  globals (the group names, the groups with endpoints) are computed in
  buildGroups and passed in.
- The renderer writes plain templates, and oxfmt lays them out using
  the repo's .oxfmtrc.json, replacing the hand-written line-width logic.
- pluralize, indefinite, and change-case replace the hand-rolled
  singular/plural, a/an, and case helpers. Output is unchanged.
- commandDescription is split by kind of command: actions, the group's
  own resources, resources under a collection, and resources under an item.
- Downloads pass `parseAs: "blob"`. #30 added this to documents.ts by
  hand, so regenerating dropped it; `trust-centers
  download-resource-media` gets it too.
- `pnpm generate:commands` runs scripts/codegen/main.ts.

Apart from the header and parseAs, the generated commands match the
previous generator's output after formatting.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each file follows one part of the rules:

- spec.ts: what the API has
- commands.ts: which commands exist and what they're called (AGENTS.md
  "Command structure")
- help.ts: what each command's flags and help say (AGENTS.md "Help text")
- config.ts: the exceptions to those rules
- generate.ts: builds the groups, renders the files, formats them with oxfmt

Drops the `indefinite` dependency for the one-line a/an rule it replaced,
which gives the same output for every word in the spec. Generated
commands are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Lists an agent would edit for new commands now live with the other
exceptions:

- placeholderNouns: parameter nouns that are their own placeholder
- uncountableNouns: nouns that stay as written when singularized ("data")
- params: the search parameters (`<query>`) and orderBy (`<field>`), which
  were special cases in the placeholder code

Generated commands are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
types.ts holds the command model (Operation, Group, Command, Option, ...)
and the shape of config.ts entries, so config.ts is only the exceptions
themselves. Types used in one file stay there. Generated commands are
unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@dennis-chen-vanta
dennis-chen-vanta force-pushed the dennis-chen-vanta/command-codegen-oxfmt branch from fd9d01e to 64372c5 Compare October 26, 2026 22:36
@dennis-chen-vanta
dennis-chen-vanta changed the base branch from dennis-chen-vanta/oxfmt to main October 26, 2026 22:36
The generator only needs case conversion and a few singular rules, which
match the libraries on every word in the spec. Irregular plurals go in
config.irregularNouns (people → person); config.uncountableNouns is no
longer needed, since the rules leave "data" alone. Generated commands
are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@OpenVanta OpenVanta deleted a comment from socket-security Bot Oct 26, 2026
Comment thread scripts/codegen/generate.ts Dismissed
await withClient(getFlags, async (api, flags) => {
const result = await getTrustCenterResourceMedia({
client: api.client,
parseAs: "blob",

@dennis-chen-vanta dennis-chen-vanta Oct 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only other codegen produced change - fixes a bug where we weren't parsing certain types of downloaded files correctly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants