Skip to content

Commit ec0765a

Browse files
committed
feat: standardize on --json flag (deprecate --format json)
Addresses F1 from AGENT_NATIVE_AUDIT.md.
1 parent a529f81 commit ec0765a

17 files changed

Lines changed: 302 additions & 75 deletions

F1-json-flag.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# F1: --format json → --json (completed)
2+
3+
Implemented finding F1 from `AGENT_NATIVE_AUDIT.md`. vers-cli now standardizes on `--json` across the entire command surface, with `--format` retained as a hidden, deprecated alias.
4+
5+
## Changed files
6+
7+
```
8+
README.md | 12 +++---
9+
cmd/branch.go | 12 ++++--
10+
cmd/commit.go | 36 ++++++++++++----
11+
cmd/deploy.go | 12 ++++--
12+
cmd/env.go | 10 +++--
13+
cmd/info.go | 12 ++++--
14+
cmd/pause.go | 12 ++++--
15+
cmd/repo.go | 48 ++++++++++++++++-----
16+
cmd/resize.go | 12 ++++--
17+
cmd/resume.go | 12 ++++--
18+
cmd/run.go | 12 ++++--
19+
cmd/run_commit.go | 12 ++++--
20+
cmd/status.go | 12 ++++--
21+
cmd/tag.go | 24 +++++++----
22+
internal/presenters/format.go | 24 +++++++++--
23+
internal/presenters/format_test.go | 37 +++++++++++++----
24+
16 files changed, 224 insertions(+), 75 deletions(-)
25+
```
26+
27+
19 cobra `--format` registrations now have a sibling `--json` BoolVar; 19 `pres.ParseFormat` call sites now pass the JSON flag and propagate the validation error.
28+
29+
Commit: `6744100 feat: standardize on --json flag (deprecate --format json)` on branch `pi-parallel-d95f3d3f-0`.
30+
31+
## Design choices
32+
33+
1. **Centralized validation in `presenters.ParseFormat`.** New signature: `ParseFormat(quiet, jsonFlag bool, formatStr string) (OutputFormat, error)`. Precedence is `quiet > json > format > default`. Invalid `--format` values return an enumerated error rather than silently falling through to default-format (which was the previous behavior — a P3 violation).
34+
2. **Cobra's `MarkDeprecated` handles the warning.** It prints `Flag --format has been deprecated, use --json instead` to stderr automatically when the flag is used, and hides the flag from `--help`. No custom warning code needed.
35+
3. **Stdout/stderr separation preserved.** Deprecation warnings go to stderr; JSON data continues to land cleanly on stdout. `vers status --format json | jq` still works without contamination.
36+
37+
## Validation
38+
39+
- `go build ./...` — clean
40+
- `go vet ./...` — clean
41+
- `go test ./...` — all packages pass (including expanded `format_test.go` with 9 cases covering the new precedence and error semantics)
42+
43+
Smoke tests against built binary:
44+
45+
```
46+
$ vers status --json
47+
[] # exit 0, clean stdout
48+
49+
$ vers status --format json 2>/tmp/err
50+
[] # exit 0, JSON on stdout
51+
$ cat /tmp/err
52+
Flag --format has been deprecated, use --json instead
53+
54+
$ vers status --format yaml
55+
Flag --format has been deprecated, use --json instead
56+
Error: --format must be "json" (got: "yaml"). Note: --format is deprecated, use --json instead
57+
# exit 1
58+
```
59+
60+
`vers status --help` no longer lists `--format`; long descriptions everywhere now read "Use --json for machine-readable output."
61+
62+
## Risks / open questions
63+
64+
- **Usage banner printed on RunE error.** When `--format yaml` is rejected, cobra prints the command's usage block after the error. This is pre-existing behavior — `SilenceUsage = true` is only set in the auth path in `cmd/root.go:210`. Out of scope for F1, but worth a follow-up if cleaner agent-facing errors are wanted (set `SilenceUsage = true` on the root command).
65+
- **`alias` command is still missing JSON output entirely.** This is finding F2, separate task.
66+
- **No tests assert deprecation warning emission to stderr.** The cobra `MarkDeprecated` behavior is library-provided and stable, so I didn't add an integration test capturing stderr; the unit tests exercise only the validation path. If desired, a small `cmd/`-level test could capture stderr from a `--format json` invocation.
67+
68+
## Recommended next step
69+
70+
Hand off to F2 (info → get) and F3 (pagination) as planned. After both land, audit the few commands that have a `--quiet` flag but no `--json` (`alias`, possibly `head`, `checkout`) and unify those — the `ParseFormat` machinery is already in place to extend them cheaply.
71+
72+
---
73+
74+
Implemented F1 (canonical `--json` flag).
75+
Changed files: 16 (13 cmd files, presenters package, README).
76+
Validation: `go build`, `go vet`, `go test ./...` all clean; manual smoke tests confirm `--json` works, legacy `--format json` works with stderr deprecation, invalid `--format` values error with the enumerated message.
77+
Open risks/questions: usage banner on error is pre-existing; no integration test for deprecation warning.
78+
Recommended next step: proceed with F2 and F3 as planned.

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ vers status
5454
vers status -q
5555

5656
# Full JSON output
57-
vers status --format json
57+
vers status --json
5858

5959
# Detailed metadata for a VM (IP, lineage, timestamps)
6060
vers info <vm-id>
61-
vers info --format json
61+
vers info --json
6262

6363
# Execute a command on a VM
6464
vers execute <vm-id> <command> [args...]
@@ -91,7 +91,7 @@ vers commit <vm-id>
9191
# List your commits
9292
vers commit list
9393
vers commit list -q # just IDs
94-
vers commit list --format json
94+
vers commit list --json
9595
vers commit list --public # public commits
9696

9797
# View commit history (parent chain)
@@ -118,7 +118,7 @@ vers tag create production abc-123 -d "stable release"
118118
# List all tags
119119
vers tag list
120120
vers tag list -q # just names
121-
vers tag list --format json
121+
vers tag list --json
122122

123123
# Get tag details
124124
vers tag get <name>
@@ -150,8 +150,8 @@ vers tag delete $(vers tag list -q)
150150
vers info $(vers status -q | head -1)
151151

152152
# JSON piped to jq
153-
vers status --format json | jq '.[].vm_id'
154-
vers info <vm-id> --format json | jq '.ip'
153+
vers status --json | jq '.[].vm_id'
154+
vers info <vm-id> --json | jq '.ip'
155155
```
156156

157157
`ps` is an alias for `status`:

cmd/branch.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
var (
1212
alias string
1313
branchCount int
14+
branchJSON bool
1415
branchFormat string
1516
branchWait bool
1617
)
@@ -21,7 +22,7 @@ var branchCmd = &cobra.Command{
2122
Short: "Create a new VM from an existing VM",
2223
Long: `Create a new VM (branch) from the state of an existing VM. If no VM ID or alias is provided, uses the current HEAD.
2324
24-
Use --format json for machine-readable output.
25+
Use --json for machine-readable output.
2526
Use --wait to block until new VMs are running.`,
2627
Args: cobra.MaximumNArgs(1),
2728
RunE: func(cmd *cobra.Command, args []string) error {
@@ -45,7 +46,10 @@ Use --wait to block until new VMs are running.`,
4546
return err
4647
}
4748

48-
format := pres.ParseFormat(false, branchFormat)
49+
format, err := pres.ParseFormat(false, branchJSON, branchFormat)
50+
if err != nil {
51+
return err
52+
}
4953
switch format {
5054
case pres.FormatJSON:
5155
pres.PrintJSON(res)
@@ -62,6 +66,8 @@ func init() {
6266
branchCmd.Flags().StringVarP(&alias, "alias", "n", "", "Alias for the new VM")
6367
branchCmd.Flags().BoolP("checkout", "c", false, "Switch to the new VM after creation")
6468
branchCmd.Flags().IntVar(&branchCount, "count", 1, "Number of branches to create")
65-
branchCmd.Flags().StringVar(&branchFormat, "format", "", "Output format (json)")
69+
branchCmd.Flags().BoolVar(&branchJSON, "json", false, "Output as JSON")
70+
branchCmd.Flags().StringVar(&branchFormat, "format", "", "Output format (json) [deprecated: use --json]")
71+
_ = branchCmd.Flags().MarkDeprecated("format", "use --json instead")
6672
branchCmd.Flags().BoolVar(&branchWait, "wait", false, "Wait until new VMs are running")
6773
}

cmd/commit.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/spf13/cobra"
1010
)
1111

12+
var commitJSON bool
1213
var commitFormat string
1314

1415
// commitCmd is the parent command for commit operations.
@@ -42,7 +43,7 @@ var commitCreateCmd = &cobra.Command{
4243
Long: `Save the current state of a VM as a commit.
4344
If no VM ID or alias is provided, commits the current HEAD VM.
4445
45-
Use --format json for machine-readable output.`,
46+
Use --json for machine-readable output.`,
4647
Args: cobra.MaximumNArgs(1),
4748
RunE: func(cmd *cobra.Command, args []string) error {
4849
target := ""
@@ -60,7 +61,10 @@ Use --format json for machine-readable output.`,
6061
return err
6162
}
6263

63-
format := pres.ParseFormat(false, commitFormat)
64+
format, err := pres.ParseFormat(false, commitJSON, commitFormat)
65+
if err != nil {
66+
return err
67+
}
6468
switch format {
6569
case pres.FormatJSON:
6670
pres.PrintJSON(res)
@@ -78,6 +82,7 @@ Use --format json for machine-readable output.`,
7882
var (
7983
commitListPublic bool
8084
commitListQuiet bool
85+
commitListJSON bool
8186
commitListFormat string
8287
)
8388

@@ -89,7 +94,7 @@ var commitListCmd = &cobra.Command{
8994
Use -q/--quiet to output just commit IDs (one per line), useful for scripting:
9095
vers commit delete $(vers commit list -q) # delete all commits
9196
92-
Use --format json for machine-readable output.`,
97+
Use --json for machine-readable output.`,
9398
Args: cobra.NoArgs,
9499
RunE: func(cmd *cobra.Command, args []string) error {
95100
apiCtx, cancel := context.WithTimeout(context.Background(), application.Timeouts.APIMedium)
@@ -102,7 +107,10 @@ Use --format json for machine-readable output.`,
102107
return err
103108
}
104109

105-
format := pres.ParseFormat(commitListQuiet, commitListFormat)
110+
format, err := pres.ParseFormat(commitListQuiet, commitListJSON, commitListFormat)
111+
if err != nil {
112+
return err
113+
}
106114
switch format {
107115
case pres.FormatQuiet:
108116
ids := make([]string, len(res.Commits))
@@ -151,14 +159,15 @@ Examples:
151159
},
152160
}
153161

162+
var commitHistoryJSON bool
154163
var commitHistoryFormat string
155164

156165
var commitHistoryCmd = &cobra.Command{
157166
Use: "history <commit-id>",
158167
Short: "Show the parent commit chain",
159168
Long: `Display the chain of parent commits leading up to a given commit.
160169
161-
Use --format json for machine-readable output.`,
170+
Use --json for machine-readable output.`,
162171
Args: cobra.ExactArgs(1),
163172
RunE: func(cmd *cobra.Command, args []string) error {
164173
apiCtx, cancel := context.WithTimeout(context.Background(), application.Timeouts.APIMedium)
@@ -171,7 +180,10 @@ Use --format json for machine-readable output.`,
171180
return err
172181
}
173182

174-
format := pres.ParseFormat(false, commitHistoryFormat)
183+
format, err := pres.ParseFormat(false, commitHistoryJSON, commitHistoryFormat)
184+
if err != nil {
185+
return err
186+
}
175187
switch format {
176188
case pres.FormatJSON:
177189
pres.PrintJSON(res.Parents)
@@ -227,16 +239,22 @@ var commitUnpublishCmd = &cobra.Command{
227239
func init() {
228240
rootCmd.AddCommand(commitCmd)
229241

230-
commitCreateCmd.Flags().StringVar(&commitFormat, "format", "", "Output format (json)")
242+
commitCreateCmd.Flags().BoolVar(&commitJSON, "json", false, "Output as JSON")
243+
commitCreateCmd.Flags().StringVar(&commitFormat, "format", "", "Output format (json) [deprecated: use --json]")
244+
_ = commitCreateCmd.Flags().MarkDeprecated("format", "use --json instead")
231245
commitCmd.AddCommand(commitCreateCmd)
232246

233247
commitListCmd.Flags().BoolVar(&commitListPublic, "public", false, "List public commits instead of your own")
234248
commitListCmd.Flags().BoolVarP(&commitListQuiet, "quiet", "q", false, "Only display commit IDs")
235-
commitListCmd.Flags().StringVar(&commitListFormat, "format", "", "Output format (json)")
249+
commitListCmd.Flags().BoolVar(&commitListJSON, "json", false, "Output as JSON")
250+
commitListCmd.Flags().StringVar(&commitListFormat, "format", "", "Output format (json) [deprecated: use --json]")
251+
_ = commitListCmd.Flags().MarkDeprecated("format", "use --json instead")
236252
commitCmd.AddCommand(commitListCmd)
237253
commitCmd.AddCommand(commitDeleteCmd)
238254

239-
commitHistoryCmd.Flags().StringVar(&commitHistoryFormat, "format", "", "Output format (json)")
255+
commitHistoryCmd.Flags().BoolVar(&commitHistoryJSON, "json", false, "Output as JSON")
256+
commitHistoryCmd.Flags().StringVar(&commitHistoryFormat, "format", "", "Output format (json) [deprecated: use --json]")
257+
_ = commitHistoryCmd.Flags().MarkDeprecated("format", "use --json instead")
240258
commitCmd.AddCommand(commitHistoryCmd)
241259
commitCmd.AddCommand(commitPublishCmd)
242260
commitCmd.AddCommand(commitUnpublishCmd)

cmd/deploy.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var (
1515
deployBuildCommand string
1616
deployRunCommand string
1717
deployWorkingDirectory string
18+
deployJSON bool
1819
deployFormat string
1920
deployWait bool
2021
)
@@ -38,7 +39,7 @@ Examples:
3839
vers deploy hdresearch/my-app --branch develop
3940
vers deploy hdresearch/my-app --name my-project --install "npm install" --build "npm run build" --run "npm start"
4041
vers deploy hdresearch/my-app --working-dir packages/web
41-
vers deploy hdresearch/my-app --format json`,
42+
vers deploy hdresearch/my-app --json`,
4243
Args: cobra.ExactArgs(1),
4344
RunE: func(cmd *cobra.Command, args []string) error {
4445
apiCtx, cancel := context.WithTimeout(context.Background(), application.Timeouts.APILong)
@@ -60,7 +61,10 @@ Examples:
6061
return err
6162
}
6263

63-
format := pres.ParseFormat(false, deployFormat)
64+
format, err := pres.ParseFormat(false, deployJSON, deployFormat)
65+
if err != nil {
66+
return err
67+
}
6468
switch format {
6569
case pres.FormatJSON:
6670
pres.PrintJSON(view)
@@ -80,6 +84,8 @@ func init() {
8084
deployCmd.Flags().StringVar(&deployBuildCommand, "build", "", "Build command (e.g. \"npm run build\")")
8185
deployCmd.Flags().StringVar(&deployRunCommand, "run", "", "Run command (e.g. \"npm start\")")
8286
deployCmd.Flags().StringVar(&deployWorkingDirectory, "working-dir", "", "Working directory relative to repo root")
83-
deployCmd.Flags().StringVar(&deployFormat, "format", "", "Output format (json)")
87+
deployCmd.Flags().BoolVar(&deployJSON, "json", false, "Output as JSON")
88+
deployCmd.Flags().StringVar(&deployFormat, "format", "", "Output format (json) [deprecated: use --json]")
89+
_ = deployCmd.Flags().MarkDeprecated("format", "use --json instead")
8490
deployCmd.Flags().BoolVar(&deployWait, "wait", false, "Wait until the VM is running before returning")
8591
}

cmd/env.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/spf13/cobra"
1313
)
1414

15+
var envJSON bool
1516
var envFormat string
1617

1718
// envCmd represents the env command
@@ -43,7 +44,10 @@ These variables will be injected into newly created VMs at boot time.`,
4344
return err
4445
}
4546

46-
format := pres.ParseFormat(false, envFormat)
47+
format, err := pres.ParseFormat(false, envJSON, envFormat)
48+
if err != nil {
49+
return err
50+
}
4751
switch format {
4852
case pres.FormatJSON:
4953
pres.PrintJSON(vars)
@@ -188,5 +192,7 @@ func init() {
188192
envCmd.AddCommand(envDeleteCmd)
189193

190194
// Add flags
191-
envListCmd.Flags().StringVar(&envFormat, "format", "", "Output format (json)")
195+
envListCmd.Flags().BoolVar(&envJSON, "json", false, "Output as JSON")
196+
envListCmd.Flags().StringVar(&envFormat, "format", "", "Output format (json) [deprecated: use --json]")
197+
_ = envListCmd.Flags().MarkDeprecated("format", "use --json instead")
192198
}

cmd/info.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
var (
1212
infoQuiet bool
13+
infoJSON bool
1314
infoFormat string
1415
)
1516

@@ -20,7 +21,7 @@ var infoCmd = &cobra.Command{
2021
grandparent VM), and timestamps. If no VM is specified, uses the current HEAD.
2122
2223
Use -q/--quiet to output just the VM ID.
23-
Use --format json for machine-readable output.`,
24+
Use --json for machine-readable output.`,
2425
Args: cobra.MaximumNArgs(1),
2526
RunE: func(cmd *cobra.Command, args []string) error {
2627
target := ""
@@ -36,7 +37,10 @@ Use --format json for machine-readable output.`,
3637
return err
3738
}
3839

39-
format := pres.ParseFormat(infoQuiet, infoFormat)
40+
format, err := pres.ParseFormat(infoQuiet, infoJSON, infoFormat)
41+
if err != nil {
42+
return err
43+
}
4044
switch format {
4145
case pres.FormatQuiet:
4246
pres.PrintQuiet([]string{res.Metadata.VmID})
@@ -52,5 +56,7 @@ Use --format json for machine-readable output.`,
5256
func init() {
5357
rootCmd.AddCommand(infoCmd)
5458
infoCmd.Flags().BoolVarP(&infoQuiet, "quiet", "q", false, "Only display VM ID")
55-
infoCmd.Flags().StringVar(&infoFormat, "format", "", "Output format (json)")
59+
infoCmd.Flags().BoolVar(&infoJSON, "json", false, "Output as JSON")
60+
infoCmd.Flags().StringVar(&infoFormat, "format", "", "Output format (json) [deprecated: use --json]")
61+
_ = infoCmd.Flags().MarkDeprecated("format", "use --json instead")
5662
}

0 commit comments

Comments
 (0)