detached 952bc8b

fix: address coderabbit review findings

Add revoked field to auth token JSON output. Fix remotes.go to use
injected writer instead of fmt.Println. Remove dead cliRenderTitle
calls in backup.go. Handle --json for empty secrets list. Document
'configured' status in bootstrap DTO. Enrich rollback list JSON with
domain and current_tag context.

Part of #98.
commit
952bc8bbb5be87f1336f4d890915d6936d66cef7
author
brice · · 2026-03-13 20:59 UTC
parent
3dcd86a
signature
unsigned
6 files · +22 -11
M internal/adapters/dto/admin_bootstrap.go +1 -1
@@ -12,7 +12,7 @@ type BootstrapRequest struct {
1212 // BootstrapStep represents the result of one bootstrap step.
1313 type BootstrapStep struct {
1414 Name string `json:"name"`
15- Status string `json:"status"` // "created", "updated", "noop", "failed"
15+ Status string `json:"status"` // "created", "configured", "updated", "noop", "failed"
1616 }
1717
1818 // BootstrapResponse represents the result of a bootstrap operation.
@@ -12,7 +12,7 @@ type BootstrapRequest struct {
12 // BootstrapStep represents the result of one bootstrap step.12 // BootstrapStep represents the result of one bootstrap step.
13 type BootstrapStep struct {13 type BootstrapStep struct {
14 Name string `json:"name"`14 Name string `json:"name"`
15- Status string `json:"status"` // "created", "updated", "noop", "failed"15+ Status string `json:"status"` // "created", "configured", "updated", "noop", "failed"
16 }16 }
17 17
18 // BootstrapResponse represents the result of a bootstrap operation.18 // BootstrapResponse represents the result of a bootstrap operation.
M internal/adapters/in/cli/auth.go +1 -0
@@ -558,6 +558,7 @@ func runTokenList(configPath string, out io.Writer, jsonOut bool) error {
558558 "scopes": t.Scopes,
559559 "created_at": t.IssuedAt,
560560 "expires_at": expiresAt,
561+ "revoked": t.Revoked,
561562 })
562563 }
563564 return writeJSON(out, payload)
@@ -558,6 +558,7 @@ func runTokenList(configPath string, out io.Writer, jsonOut bool) error {
558 "scopes": t.Scopes,558 "scopes": t.Scopes,
559 "created_at": t.IssuedAt,559 "created_at": t.IssuedAt,
560 "expires_at": expiresAt,560 "expires_at": expiresAt,
561+ "revoked": t.Revoked,
561 })562 })
562 }563 }
563 return writeJSON(out, payload)564 return writeJSON(out, payload)
M internal/adapters/in/cli/backup.go +2 -3
@@ -33,7 +33,8 @@ instance when --remote targeting is configured.`,
3333
3434 func newBackupListCmd() *cobra.Command {
3535 var jsonOut bool
36- _ = cliRenderTitle("Backups")
36+
37+ cliRenderTitle("Backups")
3738
3839 cmd := &cobra.Command{
3940 Use: "list [domain]",
@@ -77,8 +78,6 @@ func printBackupJobs(out io.Writer, jobs []dto.BackupJob, jsonOut bool) error {
7778 return writeJSON(out, jobs)
7879 }
7980
80- _ = cliRenderTitle("Backups")
81-
8281 if err := cliWriteLine(out, cliRenderTitle("Backups")); err != nil {
8382 return err
8483 }
@@ -33,7 +33,8 @@ instance when --remote targeting is configured.`,
33 33
34 func newBackupListCmd() *cobra.Command {34 func newBackupListCmd() *cobra.Command {
35 var jsonOut bool35 var jsonOut bool
36- _ = cliRenderTitle("Backups")36+
37+ cliRenderTitle("Backups")
37 38
38 cmd := &cobra.Command{39 cmd := &cobra.Command{
39 Use: "list [domain]",40 Use: "list [domain]",
@@ -77,8 +78,6 @@ func printBackupJobs(out io.Writer, jobs []dto.BackupJob, jsonOut bool) error {
77 return writeJSON(out, jobs)78 return writeJSON(out, jobs)
78 }79 }
79 80
80- _ = cliRenderTitle("Backups")
81-
82 if err := cliWriteLine(out, cliRenderTitle("Backups")); err != nil {81 if err := cliWriteLine(out, cliRenderTitle("Backups")); err != nil {
83 return err82 return err
84 }83 }
M internal/adapters/in/cli/remotes.go +6 -6
@@ -62,10 +62,10 @@ func runRemotesList(out io.Writer, jsonOut bool) error {
6262 if jsonOut {
6363 return writeJSON(out, []map[string]any{})
6464 }
65- fmt.Println(styles.Theme.Muted.Render("No remotes configured"))
66- fmt.Println()
67- fmt.Println("Add a remote with:")
68- fmt.Println(styles.Theme.Bold.Render(" gordon remotes add <name> <url>"))
65+ fmt.Fprintln(out, styles.Theme.Muted.Render("No remotes configured"))
66+ fmt.Fprintln(out)
67+ fmt.Fprintln(out, "Add a remote with:")
68+ fmt.Fprintln(out, styles.Theme.Bold.Render(" gordon remotes add <name> <url>"))
6969 return nil
7070 }
7171
@@ -88,8 +88,8 @@ func runRemotesList(out io.Writer, jsonOut bool) error {
8888 return writeJSON(out, payload)
8989 }
9090
91- fmt.Println(styles.Theme.Title.Render("Saved Remotes"))
92- fmt.Println()
91+ fmt.Fprintln(out, styles.Theme.Title.Render("Saved Remotes"))
92+ fmt.Fprintln(out)
9393
9494 rows := make([][]string, 0, len(names))
9595 for _, name := range names {
@@ -62,10 +62,10 @@ func runRemotesList(out io.Writer, jsonOut bool) error {
62 if jsonOut {62 if jsonOut {
63 return writeJSON(out, []map[string]any{})63 return writeJSON(out, []map[string]any{})
64 }64 }
65- fmt.Println(styles.Theme.Muted.Render("No remotes configured"))65+ fmt.Fprintln(out, styles.Theme.Muted.Render("No remotes configured"))
66- fmt.Println()66+ fmt.Fprintln(out)
67- fmt.Println("Add a remote with:")67+ fmt.Fprintln(out, "Add a remote with:")
68- fmt.Println(styles.Theme.Bold.Render(" gordon remotes add <name> <url>"))68+ fmt.Fprintln(out, styles.Theme.Bold.Render(" gordon remotes add <name> <url>"))
69 return nil69 return nil
70 }70 }
71 71
@@ -88,8 +88,8 @@ func runRemotesList(out io.Writer, jsonOut bool) error {
88 return writeJSON(out, payload)88 return writeJSON(out, payload)
89 }89 }
90 90
91- fmt.Println(styles.Theme.Title.Render("Saved Remotes"))91+ fmt.Fprintln(out, styles.Theme.Title.Render("Saved Remotes"))
92- fmt.Println()92+ fmt.Fprintln(out)
93 93
94 rows := make([][]string, 0, len(names))94 rows := make([][]string, 0, len(names))
95 for _, name := range names {95 for _, name := range names {
M internal/adapters/in/cli/rollback.go +5 -1
@@ -120,7 +120,11 @@ func runRollbackList(ctx context.Context, w io.Writer, rollbackDomain string, js
120120
121121 func printRollbackTags(w io.Writer, rollbackDomain, currentTag string, tags []string, jsonOut bool) error {
122122 if jsonOut {
123- return writeJSON(w, tags)
123+ return writeJSON(w, map[string]any{
124+ "domain": rollbackDomain,
125+ "current_tag": currentTag,
126+ "tags": tags,
127+ })
124128 }
125129
126130 if _, err := fmt.Fprintf(w, "Available tags for %s:\n", styles.Theme.Bold.Render(rollbackDomain)); err != nil {
@@ -120,7 +120,11 @@ func runRollbackList(ctx context.Context, w io.Writer, rollbackDomain string, js
120 120
121 func printRollbackTags(w io.Writer, rollbackDomain, currentTag string, tags []string, jsonOut bool) error {121 func printRollbackTags(w io.Writer, rollbackDomain, currentTag string, tags []string, jsonOut bool) error {
122 if jsonOut {122 if jsonOut {
123- return writeJSON(w, tags)123+ return writeJSON(w, map[string]any{
124+ "domain": rollbackDomain,
125+ "current_tag": currentTag,
126+ "tags": tags,
127+ })
124 }128 }
125 129
126 if _, err := fmt.Fprintf(w, "Available tags for %s:\n", styles.Theme.Bold.Render(rollbackDomain)); err != nil {130 if _, err := fmt.Fprintf(w, "Available tags for %s:\n", styles.Theme.Bold.Render(rollbackDomain)); err != nil {
M internal/adapters/in/cli/secrets.go +7 -0
@@ -75,6 +75,13 @@ func runSecretsListCmd(cmd *cobra.Command, args []string, jsonOut bool) error {
7575 }
7676
7777 if totalSecrets == 0 {
78+ if jsonOut {
79+ return writeJSON(cmd.OutOrStdout(), map[string]any{
80+ "domain": secretDomain,
81+ "keys": []string{},
82+ "attachments": []any{},
83+ })
84+ }
7885 fmt.Println(styles.Theme.Muted.Render(fmt.Sprintf("No secrets configured for %s", secretDomain)))
7986 return nil
8087 }
@@ -75,6 +75,13 @@ func runSecretsListCmd(cmd *cobra.Command, args []string, jsonOut bool) error {
75 }75 }
76 76
77 if totalSecrets == 0 {77 if totalSecrets == 0 {
78+ if jsonOut {
79+ return writeJSON(cmd.OutOrStdout(), map[string]any{
80+ "domain": secretDomain,
81+ "keys": []string{},
82+ "attachments": []any{},
83+ })
84+ }
78 fmt.Println(styles.Theme.Muted.Render(fmt.Sprintf("No secrets configured for %s", secretDomain)))85 fmt.Println(styles.Theme.Muted.Render(fmt.Sprintf("No secrets configured for %s", secretDomain)))
79 return nil86 return nil
80 }87 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 106

↑↓ moveenter openesc close