detached 4ddee70

feat(cli): add gen-docs command for auto-generated man pages

commit
4ddee70026fb862e2d81f55ed1a908b8562cb69b
author
bnema · · 2025-12-27 19:31 UTC
parent
02bf2da
signature
GPG Unverified2B82207D370127A0 The key is unknown
7 files · +190 -3
M Makefile +6 -1
@@ -1,6 +1,6 @@
11 # Makefile for dumber (Clean Architecture - puregotk)
22
3-.PHONY: build build-frontend test lint clean install-tools dev generate help init check-docs
3+.PHONY: build build-frontend test lint clean install-tools dev generate help init check-docs man
44
55 # Load local overrides from .env.local if present (Makefile syntax)
66 ifneq (,$(wildcard .env.local))
@@ -137,6 +137,11 @@ check: ## Check that all tools and dependencies are working
137137 @$(MAKE) test
138138 @echo "\nAll checks passed!"
139139
140+# Documentation
141+man: build-quick ## Install man pages to ~/.local/share/man/man1/
142+ @echo "Installing man pages..."
143+ $(DIST_DIR)/$(BINARY_NAME) gen-docs
144+
140145 # Native release targets
141146 .PHONY: release-snapshot release
142147
@@ -1,6 +1,6 @@
1 # Makefile for dumber (Clean Architecture - puregotk)1 # Makefile for dumber (Clean Architecture - puregotk)
2 2
3-.PHONY: build build-frontend test lint clean install-tools dev generate help init check-docs3+.PHONY: build build-frontend test lint clean install-tools dev generate help init check-docs man
4 4
5 # Load local overrides from .env.local if present (Makefile syntax)5 # Load local overrides from .env.local if present (Makefile syntax)
6 ifneq (,$(wildcard .env.local))6 ifneq (,$(wildcard .env.local))
@@ -137,6 +137,11 @@ check: ## Check that all tools and dependencies are working
137 @$(MAKE) test137 @$(MAKE) test
138 @echo "\nAll checks passed!"138 @echo "\nAll checks passed!"
139 139
140+# Documentation
141+man: build-quick ## Install man pages to ~/.local/share/man/man1/
142+ @echo "Installing man pages..."
143+ $(DIST_DIR)/$(BINARY_NAME) gen-docs
144+
140 # Native release targets145 # Native release targets
141 .PHONY: release-snapshot release146 .PHONY: release-snapshot release
142 147
M go.mod +2 -0
@@ -30,6 +30,7 @@ require (
3030 github.com/charmbracelet/x/ansi v0.10.1 // indirect
3131 github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
3232 github.com/charmbracelet/x/term v0.2.1 // indirect
33+ github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
3334 github.com/davecgh/go-spew v1.1.1 // indirect
3435 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
3536 github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
@@ -47,6 +48,7 @@ require (
4748 github.com/ncruces/julianday v1.0.0 // indirect
4849 github.com/pmezard/go-difflib v1.0.0 // indirect
4950 github.com/rivo/uniseg v0.4.7 // indirect
51+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
5052 github.com/sagikazarmark/locafero v0.11.0 // indirect
5153 github.com/sahilm/fuzzy v0.1.1 // indirect
5254 github.com/sethvargo/go-retry v0.3.0 // indirect
@@ -30,6 +30,7 @@ require (
30 github.com/charmbracelet/x/ansi v0.10.1 // indirect30 github.com/charmbracelet/x/ansi v0.10.1 // indirect
31 github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect31 github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
32 github.com/charmbracelet/x/term v0.2.1 // indirect32 github.com/charmbracelet/x/term v0.2.1 // indirect
33+ github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
33 github.com/davecgh/go-spew v1.1.1 // indirect34 github.com/davecgh/go-spew v1.1.1 // indirect
34 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect35 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
35 github.com/go-viper/mapstructure/v2 v2.4.0 // indirect36 github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
@@ -47,6 +48,7 @@ require (
47 github.com/ncruces/julianday v1.0.0 // indirect48 github.com/ncruces/julianday v1.0.0 // indirect
48 github.com/pmezard/go-difflib v1.0.0 // indirect49 github.com/pmezard/go-difflib v1.0.0 // indirect
49 github.com/rivo/uniseg v0.4.7 // indirect50 github.com/rivo/uniseg v0.4.7 // indirect
51+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
50 github.com/sagikazarmark/locafero v0.11.0 // indirect52 github.com/sagikazarmark/locafero v0.11.0 // indirect
51 github.com/sahilm/fuzzy v0.1.1 // indirect53 github.com/sahilm/fuzzy v0.1.1 // indirect
52 github.com/sethvargo/go-retry v0.3.0 // indirect54 github.com/sethvargo/go-retry v0.3.0 // indirect
M go.sum +2 -0
@@ -25,6 +25,7 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod
2525 github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
2626 github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
2727 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
28+github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
2829 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2930 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3031 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -98,6 +99,7 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
9899 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
99100 github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
100101 github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
102+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
101103 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
102104 github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
103105 github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
@@ -25,6 +25,7 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod
25 github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=25 github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
26 github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=26 github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
27 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=27 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
28+github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
28 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=29 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
29 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=30 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
30 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=31 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -98,6 +99,7 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
98 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=99 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
99 github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=100 github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
100 github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=101 github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
102+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
101 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=103 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
102 github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=104 github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
103 github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=105 github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
M internal/application/port/xdg.go +4 -0
@@ -10,4 +10,8 @@ type XDGPaths interface {
1010 FilterJSONDir() (string, error)
1111 FilterStoreDir() (string, error)
1212 FilterCacheDir() (string, error)
13+
14+ // ManDir returns the user man page directory (man1 section).
15+ // Typically $XDG_DATA_HOME/man/man1 or ~/.local/share/man/man1.
16+ ManDir() (string, error)
1317 }
@@ -10,4 +10,8 @@ type XDGPaths interface {
10 FilterJSONDir() (string, error)10 FilterJSONDir() (string, error)
11 FilterStoreDir() (string, error)11 FilterStoreDir() (string, error)
12 FilterCacheDir() (string, error)12 FilterCacheDir() (string, error)
13+
14+ // ManDir returns the user man page directory (man1 section).
15+ // Typically $XDG_DATA_HOME/man/man1 or ~/.local/share/man/man1.
16+ ManDir() (string, error)
13 }17 }
A internal/cli/cmd/gen_docs.go +141 -0
@@ -0,0 +1,141 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+ "os"
6+ "path/filepath"
7+ "time"
8+
9+ "github.com/spf13/cobra"
10+ "github.com/spf13/cobra/doc"
11+
12+ xdgadapter "github.com/bnema/dumber/internal/infrastructure/xdg"
13+)
14+
15+const dirPerm = 0o755
16+
17+var (
18+ genDocsOutputDir string
19+ genDocsFormat string
20+)
21+
22+var genDocsCmd = &cobra.Command{
23+ Use: "gen-docs",
24+ Short: "Generate documentation from CLI commands",
25+ Long: `Generate documentation (man pages or markdown) from CLI command definitions.
26+
27+The documentation is auto-generated from the command structure, including:
28+- Command names and aliases
29+- Short and long descriptions
30+- Flags and their descriptions
31+- Usage examples
32+
33+Supported formats:
34+ man Unix manual pages (groff format)
35+ markdown Markdown files (for websites/wikis)
36+
37+By default, man pages are installed to ~/.local/share/man/man1/ so they
38+are immediately available via 'man dumber'. You may need to run 'mandb'
39+to update the man page index.
40+
41+Examples:
42+ dumber gen-docs # Install man pages to ~/.local/share/man/man1/
43+ dumber gen-docs --format markdown # Generate markdown docs
44+ dumber gen-docs --output ./man # Generate to local directory`,
45+ RunE: runGenDocs,
46+}
47+
48+func init() {
49+ rootCmd.AddCommand(genDocsCmd)
50+ genDocsCmd.Flags().StringVarP(&genDocsOutputDir, "output", "o", "", "Output directory for generated docs")
51+ genDocsCmd.Flags().StringVarP(&genDocsFormat, "format", "f", "man", "Output format: man, markdown")
52+}
53+
54+func runGenDocs(_ *cobra.Command, _ []string) error {
55+ // Resolve output directory
56+ outputDir := genDocsOutputDir
57+ if outputDir == "" {
58+ switch genDocsFormat {
59+ case "man":
60+ xdg := xdgadapter.New()
61+ manDir, err := xdg.ManDir()
62+ if err != nil {
63+ return fmt.Errorf("resolve man directory: %w", err)
64+ }
65+ outputDir = manDir
66+ case "markdown":
67+ outputDir = "./docs"
68+ }
69+ }
70+
71+ // Create output directory if it doesn't exist
72+ if err := os.MkdirAll(outputDir, dirPerm); err != nil {
73+ return fmt.Errorf("create output directory: %w", err)
74+ }
75+
76+ switch genDocsFormat {
77+ case "man":
78+ return generateManPages(outputDir)
79+ case "markdown":
80+ return generateMarkdown(outputDir)
81+ default:
82+ return fmt.Errorf("unsupported format %q (use: man, markdown)", genDocsFormat)
83+ }
84+}
85+
86+func generateManPages(outputDir string) error {
87+ header := &doc.GenManHeader{
88+ Title: "DUMBER",
89+ Section: "1",
90+ Source: "dumber " + buildInfo.Version,
91+ Manual: "Dumber Manual",
92+ Date: func() *time.Time { t := time.Now(); return &t }(),
93+ }
94+
95+ // Disable auto-generation timestamp in the footer for reproducible builds
96+ rootCmd.DisableAutoGenTag = true
97+
98+ if err := doc.GenManTree(rootCmd, header, outputDir); err != nil {
99+ return fmt.Errorf("generate man pages: %w", err)
100+ }
101+
102+ fmt.Printf("Installed man pages to %s\n", outputDir)
103+ fmt.Println("Run 'mandb' if 'man dumber' doesn't work immediately.")
104+
105+ // List generated files
106+ entries, err := os.ReadDir(outputDir)
107+ if err != nil {
108+ return nil // Non-fatal
109+ }
110+ for _, e := range entries {
111+ if filepath.Ext(e.Name()) == ".1" {
112+ fmt.Printf(" - %s\n", e.Name())
113+ }
114+ }
115+
116+ return nil
117+}
118+
119+func generateMarkdown(outputDir string) error {
120+ // Disable auto-generation timestamp for reproducible builds
121+ rootCmd.DisableAutoGenTag = true
122+
123+ if err := doc.GenMarkdownTree(rootCmd, outputDir); err != nil {
124+ return fmt.Errorf("generate markdown docs: %w", err)
125+ }
126+
127+ fmt.Printf("Generated markdown docs in %s\n", outputDir)
128+
129+ // List generated files
130+ entries, err := os.ReadDir(outputDir)
131+ if err != nil {
132+ return nil // Non-fatal
133+ }
134+ for _, e := range entries {
135+ if filepath.Ext(e.Name()) == ".md" {
136+ fmt.Printf(" - %s\n", e.Name())
137+ }
138+ }
139+
140+ return nil
141+}
@@ -0,0 +1,141 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+ "os"
6+ "path/filepath"
7+ "time"
8+
9+ "github.com/spf13/cobra"
10+ "github.com/spf13/cobra/doc"
11+
12+ xdgadapter "github.com/bnema/dumber/internal/infrastructure/xdg"
13+)
14+
15+const dirPerm = 0o755
16+
17+var (
18+ genDocsOutputDir string
19+ genDocsFormat string
20+)
21+
22+var genDocsCmd = &cobra.Command{
23+ Use: "gen-docs",
24+ Short: "Generate documentation from CLI commands",
25+ Long: `Generate documentation (man pages or markdown) from CLI command definitions.
26+
27+The documentation is auto-generated from the command structure, including:
28+- Command names and aliases
29+- Short and long descriptions
30+- Flags and their descriptions
31+- Usage examples
32+
33+Supported formats:
34+ man Unix manual pages (groff format)
35+ markdown Markdown files (for websites/wikis)
36+
37+By default, man pages are installed to ~/.local/share/man/man1/ so they
38+are immediately available via 'man dumber'. You may need to run 'mandb'
39+to update the man page index.
40+
41+Examples:
42+ dumber gen-docs # Install man pages to ~/.local/share/man/man1/
43+ dumber gen-docs --format markdown # Generate markdown docs
44+ dumber gen-docs --output ./man # Generate to local directory`,
45+ RunE: runGenDocs,
46+}
47+
48+func init() {
49+ rootCmd.AddCommand(genDocsCmd)
50+ genDocsCmd.Flags().StringVarP(&genDocsOutputDir, "output", "o", "", "Output directory for generated docs")
51+ genDocsCmd.Flags().StringVarP(&genDocsFormat, "format", "f", "man", "Output format: man, markdown")
52+}
53+
54+func runGenDocs(_ *cobra.Command, _ []string) error {
55+ // Resolve output directory
56+ outputDir := genDocsOutputDir
57+ if outputDir == "" {
58+ switch genDocsFormat {
59+ case "man":
60+ xdg := xdgadapter.New()
61+ manDir, err := xdg.ManDir()
62+ if err != nil {
63+ return fmt.Errorf("resolve man directory: %w", err)
64+ }
65+ outputDir = manDir
66+ case "markdown":
67+ outputDir = "./docs"
68+ }
69+ }
70+
71+ // Create output directory if it doesn't exist
72+ if err := os.MkdirAll(outputDir, dirPerm); err != nil {
73+ return fmt.Errorf("create output directory: %w", err)
74+ }
75+
76+ switch genDocsFormat {
77+ case "man":
78+ return generateManPages(outputDir)
79+ case "markdown":
80+ return generateMarkdown(outputDir)
81+ default:
82+ return fmt.Errorf("unsupported format %q (use: man, markdown)", genDocsFormat)
83+ }
84+}
85+
86+func generateManPages(outputDir string) error {
87+ header := &doc.GenManHeader{
88+ Title: "DUMBER",
89+ Section: "1",
90+ Source: "dumber " + buildInfo.Version,
91+ Manual: "Dumber Manual",
92+ Date: func() *time.Time { t := time.Now(); return &t }(),
93+ }
94+
95+ // Disable auto-generation timestamp in the footer for reproducible builds
96+ rootCmd.DisableAutoGenTag = true
97+
98+ if err := doc.GenManTree(rootCmd, header, outputDir); err != nil {
99+ return fmt.Errorf("generate man pages: %w", err)
100+ }
101+
102+ fmt.Printf("Installed man pages to %s\n", outputDir)
103+ fmt.Println("Run 'mandb' if 'man dumber' doesn't work immediately.")
104+
105+ // List generated files
106+ entries, err := os.ReadDir(outputDir)
107+ if err != nil {
108+ return nil // Non-fatal
109+ }
110+ for _, e := range entries {
111+ if filepath.Ext(e.Name()) == ".1" {
112+ fmt.Printf(" - %s\n", e.Name())
113+ }
114+ }
115+
116+ return nil
117+}
118+
119+func generateMarkdown(outputDir string) error {
120+ // Disable auto-generation timestamp for reproducible builds
121+ rootCmd.DisableAutoGenTag = true
122+
123+ if err := doc.GenMarkdownTree(rootCmd, outputDir); err != nil {
124+ return fmt.Errorf("generate markdown docs: %w", err)
125+ }
126+
127+ fmt.Printf("Generated markdown docs in %s\n", outputDir)
128+
129+ // List generated files
130+ entries, err := os.ReadDir(outputDir)
131+ if err != nil {
132+ return nil // Non-fatal
133+ }
134+ for _, e := range entries {
135+ if filepath.Ext(e.Name()) == ".md" {
136+ fmt.Printf(" - %s\n", e.Name())
137+ }
138+ }
139+
140+ return nil
141+}
M internal/cli/cmd/root.go +18 -2
@@ -16,8 +16,24 @@ var (
1616 buildInfo build.Info
1717 rootCmd = &cobra.Command{
1818 Use: "dumber",
19- Short: "A minimal web browser",
20- Long: `Dumber is a minimal, keyboard-driven web browser built with GTK4 and WebKitGTK.`,
19+ Short: "A fully unfeatured unbloated browser for tiling WMs",
20+ Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer.
21+
22+A fully unfeatured unbloated browser for tiling WMs, built with GTK4 and WebKitGTK.
23+
24+Features:
25+ - Wayland native (Sway, Hyprland, River, Niri, etc.)
26+ - Tabs and workspaces with split or stacked panes
27+ - Keyboard-driven workflow inspired by Zellij
28+ - GPU rendering with automatic VA-API/VDPAU detection
29+ - Built-in ad blocking (UBlock-based network + cosmetic filtering)
30+ - Launcher integration (rofi/fuzzel) with favicons
31+ - Search shortcuts via bangs (!g, !gi, etc.)
32+ - Session management with auto-save and restore
33+
34+Use 'dumber browse' to launch the graphical browser, or explore the
35+subcommands for CLI-based operations like history search and session
36+management.`,
2137 PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
2238 // Skip initialization for help commands
2339 if cmd.Name() == "help" || cmd.Name() == "completion" {
@@ -16,8 +16,24 @@ var (
16 buildInfo build.Info16 buildInfo build.Info
17 rootCmd = &cobra.Command{17 rootCmd = &cobra.Command{
18 Use: "dumber",18 Use: "dumber",
19- Short: "A minimal web browser",19+ Short: "A fully unfeatured unbloated browser for tiling WMs",
20- Long: `Dumber is a minimal, keyboard-driven web browser built with GTK4 and WebKitGTK.`,20+ Long: `Dumber - a dumb browser that works like your favorite terminal multiplexer.
21+
22+A fully unfeatured unbloated browser for tiling WMs, built with GTK4 and WebKitGTK.
23+
24+Features:
25+ - Wayland native (Sway, Hyprland, River, Niri, etc.)
26+ - Tabs and workspaces with split or stacked panes
27+ - Keyboard-driven workflow inspired by Zellij
28+ - GPU rendering with automatic VA-API/VDPAU detection
29+ - Built-in ad blocking (UBlock-based network + cosmetic filtering)
30+ - Launcher integration (rofi/fuzzel) with favicons
31+ - Search shortcuts via bangs (!g, !gi, etc.)
32+ - Session management with auto-save and restore
33+
34+Use 'dumber browse' to launch the graphical browser, or explore the
35+subcommands for CLI-based operations like history search and session
36+management.`,
21 PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {37 PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
22 // Skip initialization for help commands38 // Skip initialization for help commands
23 if cmd.Name() == "help" || cmd.Name() == "completion" {39 if cmd.Name() == "help" || cmd.Name() == "completion" {
M internal/infrastructure/xdg/adapter.go +17 -0
@@ -1,6 +1,7 @@
11 package xdg
22
33 import (
4+ "os"
45 "path/filepath"
56
67 "github.com/bnema/dumber/internal/application/port"
@@ -55,4 +56,20 @@ func (a *Adapter) FilterCacheDir() (string, error) {
5556 return config.GetFilterCacheDir()
5657 }
5758
59+func (a *Adapter) ManDir() (string, error) {
60+ // Man pages go to the user's XDG_DATA_HOME/man/man1, not the dumber-specific dir.
61+ // This allows 'man dumber' to work without custom MANPATH configuration.
62+ home, err := os.UserHomeDir()
63+ if err != nil {
64+ return "", err
65+ }
66+
67+ dataHome := os.Getenv("XDG_DATA_HOME")
68+ if dataHome == "" {
69+ dataHome = filepath.Join(home, ".local", "share")
70+ }
71+
72+ return filepath.Join(dataHome, "man", "man1"), nil
73+}
74+
5875 var _ port.XDGPaths = (*Adapter)(nil)
@@ -1,6 +1,7 @@
1 package xdg1 package xdg
2 2
3 import (3 import (
4+ "os"
4 "path/filepath"5 "path/filepath"
5 6
6 "github.com/bnema/dumber/internal/application/port"7 "github.com/bnema/dumber/internal/application/port"
@@ -55,4 +56,20 @@ func (a *Adapter) FilterCacheDir() (string, error) {
55 return config.GetFilterCacheDir()56 return config.GetFilterCacheDir()
56 }57 }
57 58
59+func (a *Adapter) ManDir() (string, error) {
60+ // Man pages go to the user's XDG_DATA_HOME/man/man1, not the dumber-specific dir.
61+ // This allows 'man dumber' to work without custom MANPATH configuration.
62+ home, err := os.UserHomeDir()
63+ if err != nil {
64+ return "", err
65+ }
66+
67+ dataHome := os.Getenv("XDG_DATA_HOME")
68+ if dataHome == "" {
69+ dataHome = filepath.Join(home, ".local", "share")
70+ }
71+
72+ return filepath.Join(dataHome, "man", "man1"), nil
73+}
74+
58 var _ port.XDGPaths = (*Adapter)(nil)75 var _ port.XDGPaths = (*Adapter)(nil)

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