Repository navigation
fix: make YAML input and output usable, and drop flags that do nothing - #7
Merged
Merged
Conversation
YAML is the default output of every command that returns one resource:
`get`, `create`, `usage get`, `usage report`, `tokens create`. It was not
usable there. The API's types describe themselves in JSON only, through
json tags and MarshalJSON methods, and the YAML encoder reads neither:
- a field was named after the Go one, lowercased: `externalcustomerid`,
`createdat`, `limitcapexceededoveragepercent`;
- a typed value printed as an empty mapping, so `licenses entitlements
get` and `instances usage get` showed `value: {}` and `limit: {}`;
- every absent optional field was printed, as `null`.
YAML output is now built from the JSON encoding of the value: the same
keys, in the same order, with the same values. A number is carried over
as JSON writes it, and a string that would read as another type (a date,
"true", "1024") is quoted so that it stays a string.
The CLI's own documents (`doctor`, `config view`, the message of a
mutation) already carried matching json and yaml tags and print as
before.
Signed-off-by: Alexandre Bergere <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A `--file` or `--payload` written in YAML lost fields without an error. The request types name their fields for JSON only, so the YAML decoder filled the fields whose key is a single word and silently dropped every other one: - `customers create --file customer.yaml` sent no `externalCustomerId`; - an instance file lost `customerId`, `licenseId` and both license dates; - a feature flag went out with `"default_variant": null` and an empty `event_name`; - a grant value (`type: number`, `value: 2000`) decoded to nothing. Each command still exited 0, or failed on a request it had emptied itself. A YAML document is now decoded as the JSON document it stands for, so it makes the request the same document written in JSON makes: the same field names, typed values included. Where the two formats part ways on what a value is, the text wins, as it did before for single-word keys: a bare `version: 1.0` is the text "1.0" in a text field, and a date stays the text it is in JSON instead of being respelled as a timestamp. A YAML key the request has no field for is now an error that names the key, before any request is sent. JSON input is unchanged and still skips such a key, which is what lets a resource printed by `--output json` be edited and written back with its read-only fields still in it. Signed-off-by: Alexandre Bergere <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
`licenses create` required --version with inline flags and then never sent it. The API numbers the versions of a license itself and refuses a create that names one, so the request has no such field: the flag was mandatory and had no effect. --version is no longer a flag of `create`: passing it is an unknown flag and exits 2, and an inline create needs --name, --description, --type and --default. `licenses update` keeps the flag, where the API does act on it, but not as a setting: it accepts the version the license already has, refuses any other (422 UpdateLicense.VersionNotSettable, exit 5) and keeps the stored one when none is sent. So --version is no longer required there. Given, it is sent for the API to check; left out, the update applies whatever the version. The help of both commands says so. Signed-off-by: Alexandre Bergere <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each of these parsed, exited 0 and changed nothing: - `components update --previous-component-id`. The link to the version a component follows is set once, at creation: the API's update refuses the field (422 UpdateComponent.PreviousComponentNotSettable) and the request built there never carried it. The flag stays on `create`. - `instances audit-trails --offset`. The audit trail is paged by cursor and the API declares no offset parameter, so none was sent. --limit still caps how many entries come back. - `instances list --include-deleted`. It was sent as include_deleted, which the list has no parameter for and ignores: the result was the same with and without it. All three are now unknown flags and exit 2, as --slug already is on the updates that cannot rename. Signed-off-by: Alexandre Bergere <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
`instances update` with inline flags and no --metadata-json sent
`"metadata": {}`. The API replaces an instance's metadata with what it
is sent, so an update that only meant to change a description or a
license date cleared the instance's metadata, and exited 0.
The request cannot leave the metadata out, so the command now reads the
instance first when the flags name none, and sends its metadata back.
--metadata-json is sent as given, without the read, and `'{}'` clears
the metadata on purpose. If the instance cannot be read, nothing is
sent. --file and --payload are still sent as written.
The deployment zone and the slug needed nothing: the API already keeps
them when the request leaves them out. The command's help now says what
each optional flag does when absent.
Signed-off-by: Alexandre Bergere <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The README said things the code does not do, and left out things the previous commits changed. - `--output` was said to default to `table`. That holds for lists only: a single resource prints YAML, with or without `--output table`, and a command the API answers with no body prints one line. JSON and YAML now carry the same document, which is stated. - `--file` and `--payload` were said to be sent as written by both update commands. That is `entitlements update`. `licenses entitlements update` reads the grant and keeps its allowance whenever `--overage-percent` is absent, whatever carries the value, and a file or a payload holds only the value there. Each is described on its own. - The exit codes of a destructive command were missing: 2 when it cannot prompt and was not given `--yes`, 1 when the prompt is declined. - New: what a file or a payload is made of, how YAML is read and what it refuses, what an inline `instances update` keeps, and where a license's version comes from. - The audit-trail example filtered on `instance.updated`, a name no event has: the API matches the exact event name, INSTANCE_UPDATED. The Installation and Development sections are left as they are. Signed-off-by: Alexandre Bergere <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
fuzcap
approved these changes
Oct 14, 2026
backtrack5r3 (tomflenner)
approved these changes
Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR change?
--output jsonprints: same keys, same order, same values. It printed Go field names (externalcustomerid), every absent field asnull, and typed values such as a grant's value as{}.--fileor--payloadlost every field whose name has more than one word (externalCustomerId,default_variant) without an error, so the request went out incomplete. A key the request has no field for now stops the command and is named. JSON input is unchanged.licenses createhas no--version. It was required and never sent: the API numbers the versions itself and refuses one it is sent. Onlicenses updatethe flag stays, optional, as a check the API makes.components update --previous-component-id(the API refuses the field on update),instances audit-trails --offsetandinstances list --include-deleted(the API declares neither).instances updatekeeps the instance's metadata. It sent"metadata": {}, which cleared it. It now reads the instance and sends its metadata back unless--metadata-jsongives another;--metadata-json '{}'clears it.--outputdefault for each kind of command, the two entitlement update commands described as each behaves, the exit codes of destructive commands, the rules for--fileand--payload. The example--event-name instance.updatedbecomesINSTANCE_UPDATED. Installation and Development are untouched (feat: install scripts, Linux packages, a container image and automated releases #2 rewrites them).Why?
YAML is the default output of every command that returns one resource, and it could not be read. YAML input lost data silently. Four flags were accepted and had no effect, one update cleared data it was not asked to touch, and the README described defaults and update behaviour the code does not have.
For scripts (nothing is released yet): the keys of YAML output change;
licenses create --versionand the three removed flags now exit 2; a YAMLyesornois no longer read as a boolean.One point to decide. JSON input still skips a key it does not know, which is what lets
get --output json | update --file -work. YAML input now refuses one, so the same write-back in YAML stops on the first read-only field (createdAt). Refusing catches a snake_case key written by hand, the failure this fixes; skipping would make the two formats agree. Accepting a resource's read-only fields in both formats would give both, and needs a map from each request to its resource.Testing
mainand passes here: 18 tests added, 15 of which fail onmain; the other three pin behaviour kept as is.go build ./...,go vet ./...,go test -race -shuffle=on ./...andgolangci-lint runare clean.--outputsentence, which both rewrite: keep this one and addndjson.Checklist
CONTRIBUTING.md.Signed-off-byline.🤖 Generated with Claude Code · ✅ Tested and approved by Alex (@Alexkuva), maintainer