detached 1630cf5

fix(cli): route invalid browse args to cobra

commit
1630cf54dad8e01052baf720c07f3c2a0dd75327
author
brice · · 2026-04-08 12:05 UTC
parent
7396109
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +44 -0
M cmd/dumber/main.go +7 -0
@@ -7,6 +7,7 @@ import (
77 "os"
88 "os/signal"
99 "runtime"
10+ "strings"
1011 "syscall"
1112
1213 "github.com/bnema/dumber/internal/application/port"
@@ -63,7 +64,13 @@ func launchModeFromArgs(args []string) (launchMode, string) {
6364 if len(args) > 1 {
6465 switch args[1] {
6566 case "browse":
67+ if len(args) > 3 {
68+ return launchModeCLI, ""
69+ }
6670 if len(args) > 2 {
71+ if strings.HasPrefix(args[2], "-") {
72+ return launchModeCLI, ""
73+ }
6774 return launchModeBrowse, args[2]
6875 }
6976 return launchModeBrowse, ""
@@ -7,6 +7,7 @@ import (
7 "os"7 "os"
8 "os/signal"8 "os/signal"
9 "runtime"9 "runtime"
10+ "strings"
10 "syscall"11 "syscall"
11 12
12 "github.com/bnema/dumber/internal/application/port"13 "github.com/bnema/dumber/internal/application/port"
@@ -63,7 +64,13 @@ func launchModeFromArgs(args []string) (launchMode, string) {
63 if len(args) > 1 {64 if len(args) > 1 {
64 switch args[1] {65 switch args[1] {
65 case "browse":66 case "browse":
67+ if len(args) > 3 {
68+ return launchModeCLI, ""
69+ }
66 if len(args) > 2 {70 if len(args) > 2 {
71+ if strings.HasPrefix(args[2], "-") {
72+ return launchModeCLI, ""
73+ }
67 return launchModeBrowse, args[2]74 return launchModeBrowse, args[2]
68 }75 }
69 return launchModeBrowse, ""76 return launchModeBrowse, ""
M cmd/dumber/main_test.go +20 -0
@@ -22,6 +22,26 @@ func TestLaunchModeFromArgs_DetectsBrowseURL(t *testing.T) {
2222 }
2323 }
2424
25+func TestLaunchModeFromArgs_BrowseHelpFallsBackToCLI(t *testing.T) {
26+ mode, browseURL := launchModeFromArgs([]string{"dumber", "browse", "--help"})
27+ if mode != launchModeCLI {
28+ t.Fatalf("expected cli mode for browse help, got %q", mode)
29+ }
30+ if browseURL != "" {
31+ t.Fatalf("expected empty browse url for browse help, got %q", browseURL)
32+ }
33+}
34+
35+func TestLaunchModeFromArgs_BrowseExtraPositionalFallsBackToCLI(t *testing.T) {
36+ mode, browseURL := launchModeFromArgs([]string{"dumber", "browse", "https://example.com", "extra"})
37+ if mode != launchModeCLI {
38+ t.Fatalf("expected cli mode for browse extra args, got %q", mode)
39+ }
40+ if browseURL != "" {
41+ t.Fatalf("expected empty browse url for browse extra args, got %q", browseURL)
42+ }
43+}
44+
2545 func TestLaunchModeFromArgs_DefaultsToCLI(t *testing.T) {
2646 mode, browseURL := launchModeFromArgs([]string{"dumber"})
2747 if mode != launchModeCLI {
@@ -22,6 +22,26 @@ func TestLaunchModeFromArgs_DetectsBrowseURL(t *testing.T) {
22 }22 }
23 }23 }
24 24
25+func TestLaunchModeFromArgs_BrowseHelpFallsBackToCLI(t *testing.T) {
26+ mode, browseURL := launchModeFromArgs([]string{"dumber", "browse", "--help"})
27+ if mode != launchModeCLI {
28+ t.Fatalf("expected cli mode for browse help, got %q", mode)
29+ }
30+ if browseURL != "" {
31+ t.Fatalf("expected empty browse url for browse help, got %q", browseURL)
32+ }
33+}
34+
35+func TestLaunchModeFromArgs_BrowseExtraPositionalFallsBackToCLI(t *testing.T) {
36+ mode, browseURL := launchModeFromArgs([]string{"dumber", "browse", "https://example.com", "extra"})
37+ if mode != launchModeCLI {
38+ t.Fatalf("expected cli mode for browse extra args, got %q", mode)
39+ }
40+ if browseURL != "" {
41+ t.Fatalf("expected empty browse url for browse extra args, got %q", browseURL)
42+ }
43+}
44+
25 func TestLaunchModeFromArgs_DefaultsToCLI(t *testing.T) {45 func TestLaunchModeFromArgs_DefaultsToCLI(t *testing.T) {
26 mode, browseURL := launchModeFromArgs([]string{"dumber"})46 mode, browseURL := launchModeFromArgs([]string{"dumber"})
27 if mode != launchModeCLI {47 if mode != launchModeCLI {
M internal/cli/cmd/omnibox_test.go +16 -0
@@ -11,3 +11,19 @@ func TestRootCommand_RegistersOmniboxCommand(t *testing.T) {
1111 t.Fatalf("expected omnibox command, got %#v", cmd)
1212 }
1313 }
14+
15+func TestRootCommand_BrowseCommandRejectsExtraArgs(t *testing.T) {
16+ cmd, _, err := rootCmd.Find([]string{"browse"})
17+ if err != nil {
18+ t.Fatalf("expected browse command to be registered: %v", err)
19+ }
20+ if cmd == nil || cmd.Name() != "browse" {
21+ t.Fatalf("expected browse command, got %#v", cmd)
22+ }
23+ if cmd.Args == nil {
24+ t.Fatal("expected browse command to define argument validation")
25+ }
26+ if err := cmd.Args(cmd, []string{"https://example.com", "extra"}); err == nil {
27+ t.Fatal("expected browse command to reject extra args")
28+ }
29+}
@@ -11,3 +11,19 @@ func TestRootCommand_RegistersOmniboxCommand(t *testing.T) {
11 t.Fatalf("expected omnibox command, got %#v", cmd)11 t.Fatalf("expected omnibox command, got %#v", cmd)
12 }12 }
13 }13 }
14+
15+func TestRootCommand_BrowseCommandRejectsExtraArgs(t *testing.T) {
16+ cmd, _, err := rootCmd.Find([]string{"browse"})
17+ if err != nil {
18+ t.Fatalf("expected browse command to be registered: %v", err)
19+ }
20+ if cmd == nil || cmd.Name() != "browse" {
21+ t.Fatalf("expected browse command, got %#v", cmd)
22+ }
23+ if cmd.Args == nil {
24+ t.Fatal("expected browse command to define argument validation")
25+ }
26+ if err := cmd.Args(cmd, []string{"https://example.com", "extra"}); err == nil {
27+ t.Fatal("expected browse command to reject extra args")
28+ }
29+}
M internal/cli/cmd/root.go +1 -0
@@ -107,6 +107,7 @@ func GetApp() *cli.App {
107107 var browseCmd = &cobra.Command{
108108 Use: "browse [url]",
109109 Short: "Launch the graphical browser",
110+ Args: cobra.MaximumNArgs(1),
110111 Long: `Launch the GTK4 graphical browser.
111112
112113 If a URL is provided, navigate to it. Otherwise, open the homepage.
@@ -107,6 +107,7 @@ func GetApp() *cli.App {
107 var browseCmd = &cobra.Command{107 var browseCmd = &cobra.Command{
108 Use: "browse [url]",108 Use: "browse [url]",
109 Short: "Launch the graphical browser",109 Short: "Launch the graphical browser",
110+ Args: cobra.MaximumNArgs(1),
110 Long: `Launch the GTK4 graphical browser.111 Long: `Launch the GTK4 graphical browser.
111 112
112 If a URL is provided, navigate to it. Otherwise, open the homepage.113 If a URL is provided, navigate to it. Otherwise, open the homepage.

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 42

↑↓ moveenter openesc close