Skip to content

Commit f171851

Browse files
committed
fix: simplify tree output defaults
1 parent d0eae15 commit f171851

8 files changed

Lines changed: 87 additions & 23 deletions

File tree

.claude/docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Thin CLI layer. Each file is one Cobra command. Flag variables are package-level
7272
- `probe_ssh.go``probe ssh` subcommand. Connects without authenticating, prints banner/algorithm details, and supports `--fips-140-2` / `--fips-140-3` policy heuristics for SSH transport algorithms.
7373
- `policy.go` — Shared CLI flag-to-policy selection helper used by `connect` and `probe ssh`.
7474
- `sign.go` — Sign certificates. Parent command with `self-signed` and `csr` subcommands for creating self-signed certs and signing CSRs with a CA.
75-
- `tree.go``tree` subcommand. Renders the full Cobra command graph, including built-in commands plus local and inherited flags accepted by each command; honors the global `--json` flag with structured command/flag output.
75+
- `tree.go``tree` subcommand. Renders the full Cobra command graph with a command-focused default text view; `--flags` and `--inherited` opt into text-mode flag detail, and the global `--json` flag still returns the structured command/flag surface.
7676
- `ocsp.go` — Check certificate revocation status via OCSP; `--format` flag.
7777
- `crl.go` — Parse and inspect Certificate Revocation Lists; `--check` to verify a cert against the CRL; `--format` flag.
7878
- `convert.go` — Convert certificates and keys between PEM, DER, PKCS#12, JKS, and PKCS#7 formats; `--to`, `-o` flags.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3939
- Parse extensionless or renamed DER/PKCS#7/PKCS#12/JKS inputs during certstore ingestion instead of skipping binary crypto content based on filename extension alone ([#172])
4040
- Harden browser/WASM AIA and upload handling by blocking obvious internal hostnames without DNS, streaming AIA response-size enforcement before buffering, and rejecting oversized uploads before `arrayBuffer()` reads them into JS memory ([#172])
4141
- Stop draining oversized `tar.gz` members after the size violation is already known, treat `gh api graphql` string variables literally in `pr-comments.py`, and add JSON output support for `certkit tree --json` so the global flag matches documented behavior ([#172])
42+
- Make `certkit tree` default to a command-focused text view, with `--flags` and `--inherited` available when you want flag detail in text mode ([#172])
4243
- Encrypt private key in YAML bundle output (`.yaml`) when an export password is supplied; previously leaked plaintext key ([#167])
4344
- Reject malformed `ENCRYPTED PRIVATE KEY` blocks with invalid AES IV length instead of panicking ([#167])
4445
- Trim whitespace from web UI export password so whitespace-only input is treated as blank ([#167])

EXAMPLES.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ A practical guide to common certificate tasks. No prior TLS/SSL knowledge requir
7474

7575
### See the full command surface
7676

77-
Print the actual CLI command tree, including built-in Cobra commands and the
78-
flags each command accepts:
77+
Print the actual CLI command tree, including built-in Cobra commands:
7978

8079
```sh
8180
certkit tree
@@ -84,6 +83,12 @@ certkit tree
8483
This is useful when you want a quick map of the CLI without hopping through
8584
`--help` output command by command.
8685

86+
If you also want the flag surface, opt in explicitly:
87+
88+
```sh
89+
certkit tree --flags --inherited
90+
```
91+
8792
---
8893

8994
## Inspecting

RALPH.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,10 @@ Area: [internal/certstore/process_test.go](internal/certstore/process_test.go)
231231
Summary: direct unit tests of unexported algorithm-conversion helpers were too implementation-coupled for the compat parser behavior they were intended to protect.
232232
Source: Follow-up PR review
233233
Fix: Removed the helper-specific tests and asserted public-key/signature algorithm mapping through the compatibility ingestion path instead
234+
235+
33. `tree-default-flags-noise`
236+
Status: fixed
237+
Area: [cmd/certkit/tree.go](cmd/certkit/tree.go), [cmd/certkit/cli_semantics_test.go](cmd/certkit/cli_semantics_test.go), [README.md](README.md), [EXAMPLES.md](EXAMPLES.md)
238+
Summary: the default `tree` output included every local and inherited flag, which made the command map harder to scan than a command-focused tree.
239+
Source: User feedback
240+
Fix: `tree` now defaults to commands-only text output, with `--flags` and `--inherited` opt-ins for text-mode flag detail; JSON output remains the detailed machine-readable surface

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ See [EXAMPLES.md](EXAMPLES.md) for a walkthrough of the main certificate workflo
110110
| `certkit sign self-signed` | Create a self-signed certificate |
111111
| `certkit sign csr <file>` | Sign a CSR with a CA certificate and key |
112112
| `certkit scan <path>` | Scan a directory and catalog everything found |
113-
| `certkit tree` | Print the full CLI command and flag surface as a tree |
113+
| `certkit tree` | Print the full CLI command tree |
114114
| `certkit keygen` | Generate a new key pair (and optionally a CSR) |
115115
| `certkit csr` | Generate a CSR from a template, cert, or existing CSR |
116116
| `certkit ocsp <file>` | Check certificate revocation status via OCSP |

cmd/certkit/cli_semantics_test.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,17 @@ func TestTreeCommand(t *testing.T) {
144144
}
145145
})
146146

147-
t.Run("includes help and version flags", func(t *testing.T) {
147+
t.Run("default text output omits flags", func(t *testing.T) {
148148
for _, flag := range []string{"--help", "--version"} {
149-
if !strings.Contains(rootOutput, flag) {
150-
t.Errorf("tree output missing flag %q", flag)
149+
if strings.Contains(rootOutput, flag) {
150+
t.Errorf("default tree output should omit flag %q", flag)
151151
}
152152
}
153153
})
154154

155-
t.Run("includes subcommand help and inherited global flags", func(t *testing.T) {
155+
t.Run("includes subcommand help and inherited global flags when requested", func(t *testing.T) {
156+
treeIncludeFlags = true
157+
treeIncludeInherited = true
156158
var bundleTree strings.Builder
157159
fmt.Fprintf(&bundleTree, "%s — %s\n", bundleCmd.Name(), bundleCmd.Short)
158160
printCommandTree(&bundleTree, printCommandTreeInput{cmd: bundleCmd})
@@ -173,22 +175,48 @@ func TestTreeCommand(t *testing.T) {
173175
})
174176

175177
t.Run("prefers long flag names over shorthand pairs", func(t *testing.T) {
178+
treeIncludeFlags = true
179+
treeIncludeInherited = true
180+
var flagsTree strings.Builder
181+
printCommandTree(&flagsTree, printCommandTreeInput{cmd: rootCmd})
182+
flagsOutput := flagsTree.String()
176183
for _, flag := range []string{"-h, --help", "-l, --log-level", "-p, --passwords", "-v, --verbose"} {
177-
if strings.Contains(rootOutput, flag) {
184+
if strings.Contains(flagsOutput, flag) {
178185
t.Errorf("tree output should omit shorthand pair %q", flag)
179186
}
180187
}
181188
})
182189

183-
t.Run("collapses inherited flags to one summary line", func(t *testing.T) {
184-
if strings.Count(rootOutput, "inherits: ") == 0 {
190+
t.Run("collapses inherited flags to one summary line when requested", func(t *testing.T) {
191+
treeIncludeFlags = true
192+
treeIncludeInherited = true
193+
var flagsTree strings.Builder
194+
printCommandTree(&flagsTree, printCommandTreeInput{cmd: rootCmd})
195+
flagsOutput := flagsTree.String()
196+
if strings.Count(flagsOutput, "inherits: ") == 0 {
185197
t.Fatal("tree output missing inherited flag summaries")
186198
}
187-
if strings.Count(rootOutput, "\n│ ├── --json\n")+strings.Count(rootOutput, "\n│ └── --json\n") != 0 {
199+
if strings.Count(flagsOutput, "\n│ ├── --json\n")+strings.Count(flagsOutput, "\n│ └── --json\n") != 0 {
188200
t.Fatal("tree output should not repeat inherited flags as standalone subcommand entries")
189201
}
190202
})
191203

204+
t.Run("includes local flags only when requested", func(t *testing.T) {
205+
treeIncludeFlags = true
206+
treeIncludeInherited = false
207+
var flagsTree strings.Builder
208+
printCommandTree(&flagsTree, printCommandTreeInput{cmd: rootCmd})
209+
flagsOutput := flagsTree.String()
210+
for _, flag := range []string{"--help", "--version"} {
211+
if !strings.Contains(flagsOutput, flag) {
212+
t.Errorf("tree output missing requested local flag %q", flag)
213+
}
214+
}
215+
if strings.Contains(flagsOutput, "inherits: ") {
216+
t.Fatal("tree output should omit inherited summaries unless requested")
217+
}
218+
})
219+
192220
t.Run("rejects arguments", func(t *testing.T) {
193221
if treeCmd.Args == nil {
194222
t.Fatal("treeCmd.Args is nil; expected cobra.NoArgs")

cmd/certkit/readonly_commands_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ type readonlyGlobals struct {
7272
// inspect flags
7373
inspectFormat string
7474
inspectAllowPrivateNetwork bool
75+
76+
// tree flags
77+
treeIncludeFlags bool
78+
treeIncludeInherited bool
7579
}
7680

7781
func snapshotReadonlyGlobals() readonlyGlobals {
@@ -120,6 +124,9 @@ func snapshotReadonlyGlobals() readonlyGlobals {
120124

121125
inspectFormat: inspectFormat,
122126
inspectAllowPrivateNetwork: inspectAllowPrivateNetwork,
127+
128+
treeIncludeFlags: treeIncludeFlags,
129+
treeIncludeInherited: treeIncludeInherited,
123130
}
124131
}
125132

@@ -166,6 +173,9 @@ func restoreReadonlyGlobals(g readonlyGlobals) {
166173

167174
inspectFormat = g.inspectFormat
168175
inspectAllowPrivateNetwork = g.inspectAllowPrivateNetwork
176+
177+
treeIncludeFlags = g.treeIncludeFlags
178+
treeIncludeInherited = g.treeIncludeInherited
169179
readonlyGlobalsMu.Unlock()
170180
}
171181

cmd/certkit/tree.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@ import (
1313
var treeCmd = &cobra.Command{
1414
Use: "tree",
1515
Short: "Display the full command tree",
16-
Long: "Display every command, subcommand, and flag in a tree layout.",
16+
Long: "Display the command and subcommand tree. Use --flags to include flag details.",
1717
Args: cobra.NoArgs,
1818
RunE: runTree,
1919
}
2020

21+
var (
22+
treeIncludeFlags bool
23+
treeIncludeInherited bool
24+
)
25+
2126
type printCommandTreeInput struct {
2227
cmd *cobra.Command
2328
prefix string
@@ -32,6 +37,8 @@ type commandTreeJSON struct {
3237
}
3338

3439
func init() {
40+
treeCmd.Flags().BoolVar(&treeIncludeFlags, "flags", false, "Include local flags in text tree output")
41+
treeCmd.Flags().BoolVar(&treeIncludeInherited, "inherited", false, "Include inherited flags in text tree output")
3542
rootCmd.AddCommand(treeCmd)
3643
}
3744

@@ -66,7 +73,8 @@ func initTreeSurface(cmd *cobra.Command) {
6673
}
6774

6875
// printCommandTree recursively prints a command and its children with
69-
// box-drawing connectors. Each command shows its flags indented beneath it.
76+
// box-drawing connectors. Flag details are opt-in so the default tree stays
77+
// focused on the command surface.
7078
func printCommandTree(b *strings.Builder, in printCommandTreeInput) {
7179
cmd := in.cmd
7280
prefix := in.prefix
@@ -87,23 +95,28 @@ func printCommandTree(b *strings.Builder, in printCommandTreeInput) {
8795
}
8896
}
8997

90-
total := len(localFlags) + len(visible)
91-
if len(inheritedFlags) > 0 {
98+
total := len(visible)
99+
if treeIncludeFlags {
100+
total += len(localFlags)
101+
}
102+
if treeIncludeInherited && len(inheritedFlags) > 0 {
92103
total++
93104
}
94105
idx := 0
95106

96107
// Print local flags.
97-
for _, flag := range localFlags {
98-
idx++
99-
connector := "├── "
100-
if idx == total {
101-
connector = "└── "
108+
if treeIncludeFlags {
109+
for _, flag := range localFlags {
110+
idx++
111+
connector := "├── "
112+
if idx == total {
113+
connector = "└── "
114+
}
115+
fmt.Fprintf(b, "%s%s%s\n", prefix, connector, flag)
102116
}
103-
fmt.Fprintf(b, "%s%s%s\n", prefix, connector, flag)
104117
}
105118

106-
if len(inheritedFlags) > 0 {
119+
if treeIncludeInherited && len(inheritedFlags) > 0 {
107120
idx++
108121
connector := "├── "
109122
if idx == total {

0 commit comments

Comments
 (0)