detached c188211

feat(cli): add about command with build info display

commit
c1882116413af395d6b325a952cf89e698bf770f
author
bnema · · 2025-12-18 20:50 UTC
parent
151b026
signature
unsigned
7 files · +156 -6
M cmd/dumber/main.go +9 -0
@@ -10,6 +10,7 @@ import (
1010 "github.com/bnema/dumber/assets"
1111 "github.com/bnema/dumber/internal/application/usecase"
1212 "github.com/bnema/dumber/internal/cli/cmd"
13+ "github.com/bnema/dumber/internal/domain/build"
1314 "github.com/bnema/dumber/internal/infrastructure/clipboard"
1415 "github.com/bnema/dumber/internal/infrastructure/config"
1516 "github.com/bnema/dumber/internal/infrastructure/media"
@@ -43,6 +44,14 @@ func main() {
4344 return
4445 }
4546
47+ // Pass build info to CLI
48+ cmd.SetBuildInfo(build.Info{
49+ Version: version,
50+ Commit: commit,
51+ BuildDate: buildDate,
52+ GoVersion: runtime.Version(),
53+ })
54+
4655 // Default: run CLI (shows help if no subcommand)
4756 cmd.Execute()
4857 }
@@ -10,6 +10,7 @@ import (
10 "github.com/bnema/dumber/assets"10 "github.com/bnema/dumber/assets"
11 "github.com/bnema/dumber/internal/application/usecase"11 "github.com/bnema/dumber/internal/application/usecase"
12 "github.com/bnema/dumber/internal/cli/cmd"12 "github.com/bnema/dumber/internal/cli/cmd"
13+ "github.com/bnema/dumber/internal/domain/build"
13 "github.com/bnema/dumber/internal/infrastructure/clipboard"14 "github.com/bnema/dumber/internal/infrastructure/clipboard"
14 "github.com/bnema/dumber/internal/infrastructure/config"15 "github.com/bnema/dumber/internal/infrastructure/config"
15 "github.com/bnema/dumber/internal/infrastructure/media"16 "github.com/bnema/dumber/internal/infrastructure/media"
@@ -43,6 +44,14 @@ func main() {
43 return44 return
44 }45 }
45 46
47+ // Pass build info to CLI
48+ cmd.SetBuildInfo(build.Info{
49+ Version: version,
50+ Commit: commit,
51+ BuildDate: buildDate,
52+ GoVersion: runtime.Version(),
53+ })
54+
46 // Default: run CLI (shows help if no subcommand)55 // Default: run CLI (shows help if no subcommand)
47 cmd.Execute()56 cmd.Execute()
48 }57 }
M internal/cli/app.go +6 -4
@@ -10,6 +10,7 @@ import (
1010
1111 "github.com/bnema/dumber/internal/application/usecase"
1212 "github.com/bnema/dumber/internal/cli/styles"
13+ "github.com/bnema/dumber/internal/domain/build"
1314 "github.com/bnema/dumber/internal/domain/repository"
1415 "github.com/bnema/dumber/internal/infrastructure/config"
1516 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
@@ -18,10 +19,11 @@ import (
1819
1920 // App holds CLI dependencies.
2021 type App struct {
21- Config *config.Config
22- Theme *styles.Theme
23- db *sql.DB
24- History repository.HistoryRepository
22+ Config *config.Config
23+ Theme *styles.Theme
24+ BuildInfo build.Info
25+ db *sql.DB
26+ History repository.HistoryRepository
2527
2628 // Use cases
2729 SearchHistoryUC *usecase.SearchHistoryUseCase
@@ -10,6 +10,7 @@ import (
10 10
11 "github.com/bnema/dumber/internal/application/usecase"11 "github.com/bnema/dumber/internal/application/usecase"
12 "github.com/bnema/dumber/internal/cli/styles"12 "github.com/bnema/dumber/internal/cli/styles"
13+ "github.com/bnema/dumber/internal/domain/build"
13 "github.com/bnema/dumber/internal/domain/repository"14 "github.com/bnema/dumber/internal/domain/repository"
14 "github.com/bnema/dumber/internal/infrastructure/config"15 "github.com/bnema/dumber/internal/infrastructure/config"
15 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"16 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
@@ -18,10 +19,11 @@ import (
18 19
19 // App holds CLI dependencies.20 // App holds CLI dependencies.
20 type App struct {21 type App struct {
21- Config *config.Config22+ Config *config.Config
22- Theme *styles.Theme23+ Theme *styles.Theme
23- db *sql.DB24+ BuildInfo build.Info
24- History repository.HistoryRepository25+ db *sql.DB
26+ History repository.HistoryRepository
25 27
26 // Use cases28 // Use cases
27 SearchHistoryUC *usecase.SearchHistoryUseCase29 SearchHistoryUC *usecase.SearchHistoryUseCase
A internal/cli/cmd/about.go +33 -0
@@ -0,0 +1,33 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+
6+ "github.com/spf13/cobra"
7+
8+ "github.com/bnema/dumber/internal/cli/styles"
9+)
10+
11+var aboutCmd = &cobra.Command{
12+ Use: "about",
13+ Short: "Show version and build information",
14+ Long: `Display version, build info, repository URL, and contributors.`,
15+ RunE: runAbout,
16+}
17+
18+func init() {
19+ rootCmd.AddCommand(aboutCmd)
20+}
21+
22+func runAbout(cmd *cobra.Command, args []string) error {
23+ app := GetApp()
24+ if app == nil {
25+ return fmt.Errorf("app not initialized")
26+ }
27+
28+ renderer := styles.NewAboutRenderer(app.Theme)
29+ output := renderer.Render(app.BuildInfo)
30+
31+ fmt.Println(output)
32+ return nil
33+}
@@ -0,0 +1,33 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+
6+ "github.com/spf13/cobra"
7+
8+ "github.com/bnema/dumber/internal/cli/styles"
9+)
10+
11+var aboutCmd = &cobra.Command{
12+ Use: "about",
13+ Short: "Show version and build information",
14+ Long: `Display version, build info, repository URL, and contributors.`,
15+ RunE: runAbout,
16+}
17+
18+func init() {
19+ rootCmd.AddCommand(aboutCmd)
20+}
21+
22+func runAbout(cmd *cobra.Command, args []string) error {
23+ app := GetApp()
24+ if app == nil {
25+ return fmt.Errorf("app not initialized")
26+ }
27+
28+ renderer := styles.NewAboutRenderer(app.Theme)
29+ output := renderer.Render(app.BuildInfo)
30+
31+ fmt.Println(output)
32+ return nil
33+}
M internal/cli/cmd/root.go +11 -2
@@ -8,11 +8,13 @@ import (
88 "github.com/spf13/cobra"
99
1010 "github.com/bnema/dumber/internal/cli"
11+ "github.com/bnema/dumber/internal/domain/build"
1112 )
1213
1314 var (
14- app *cli.App
15- rootCmd = &cobra.Command{
15+ app *cli.App
16+ buildInfo build.Info
17+ rootCmd = &cobra.Command{
1618 Use: "dumber",
1719 Short: "A minimal web browser",
1820 Long: `Dumber is a minimal, keyboard-driven web browser built with GTK4 and WebKitGTK.`,
@@ -27,6 +29,8 @@ var (
2729 if err != nil {
2830 return fmt.Errorf("initialize app: %w", err)
2931 }
32+ // Set build info from main.go
33+ app.BuildInfo = buildInfo
3034 return nil
3135 },
3236 PersistentPostRun: func(cmd *cobra.Command, args []string) {
@@ -69,3 +73,8 @@ Examples:
6973 func init() {
7074 rootCmd.AddCommand(browseCmd)
7175 }
76+
77+// SetBuildInfo sets the build information (called from main.go before Execute).
78+func SetBuildInfo(info build.Info) {
79+ buildInfo = info
80+}
@@ -8,11 +8,13 @@ import (
8 "github.com/spf13/cobra"8 "github.com/spf13/cobra"
9 9
10 "github.com/bnema/dumber/internal/cli"10 "github.com/bnema/dumber/internal/cli"
11+ "github.com/bnema/dumber/internal/domain/build"
11 )12 )
12 13
13 var (14 var (
14- app *cli.App15+ app *cli.App
15- rootCmd = &cobra.Command{16+ buildInfo build.Info
17+ rootCmd = &cobra.Command{
16 Use: "dumber",18 Use: "dumber",
17 Short: "A minimal web browser",19 Short: "A minimal web browser",
18 Long: `Dumber is a minimal, keyboard-driven web browser built with GTK4 and WebKitGTK.`,20 Long: `Dumber is a minimal, keyboard-driven web browser built with GTK4 and WebKitGTK.`,
@@ -27,6 +29,8 @@ var (
27 if err != nil {29 if err != nil {
28 return fmt.Errorf("initialize app: %w", err)30 return fmt.Errorf("initialize app: %w", err)
29 }31 }
32+ // Set build info from main.go
33+ app.BuildInfo = buildInfo
30 return nil34 return nil
31 },35 },
32 PersistentPostRun: func(cmd *cobra.Command, args []string) {36 PersistentPostRun: func(cmd *cobra.Command, args []string) {
@@ -69,3 +73,8 @@ Examples:
69 func init() {73 func init() {
70 rootCmd.AddCommand(browseCmd)74 rootCmd.AddCommand(browseCmd)
71 }75 }
76+
77+// SetBuildInfo sets the build information (called from main.go before Execute).
78+func SetBuildInfo(info build.Info) {
79+ buildInfo = info
80+}
A internal/cli/styles/about.go +62 -0
@@ -0,0 +1,62 @@
1+package styles
2+
3+import (
4+ "fmt"
5+ "strings"
6+
7+ "github.com/charmbracelet/lipgloss"
8+
9+ "github.com/bnema/dumber/internal/domain/build"
10+)
11+
12+// AboutRenderer renders build info in fastfetch style.
13+type AboutRenderer struct {
14+ theme *Theme
15+}
16+
17+// NewAboutRenderer creates a new about renderer with the given theme.
18+func NewAboutRenderer(theme *Theme) *AboutRenderer {
19+ return &AboutRenderer{theme: theme}
20+}
21+
22+// Render renders build info with ASCII logo and styled info lines.
23+func (r *AboutRenderer) Render(info build.Info) string {
24+ logo := r.renderLogo()
25+ lines := r.renderInfoLines(info)
26+
27+ // Combine horizontally: logo | info
28+ return lipgloss.JoinHorizontal(lipgloss.Top, logo, " ", lines)
29+}
30+
31+func (r *AboutRenderer) renderLogo() string {
32+ // ASCII art: Lightning bolt + D (matches pixel logo)
33+ // All in accent color (green)
34+ logoStyle := lipgloss.NewStyle().Foreground(r.theme.Accent).Bold(true)
35+
36+ // Simple bold D
37+ logo := `██████▄
38+██ ██
39+██ ██
40+██ ██
41+██████▀`
42+
43+ return logoStyle.MarginTop(1).MarginLeft(2).Render(logo)
44+}
45+
46+func (r *AboutRenderer) renderInfoLines(info build.Info) string {
47+ keyStyle := r.theme.Subtle
48+ valStyle := r.theme.Highlight
49+ iconStyle := lipgloss.NewStyle().Foreground(r.theme.Accent)
50+
51+ lines := []string{
52+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconVersion), keyStyle.Render("Version"), valStyle.Render(info.Version)),
53+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconGitBranch), keyStyle.Render("Commit"), valStyle.Render(info.Commit)),
54+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconCalendar), keyStyle.Render("Built"), valStyle.Render(info.BuildDate)),
55+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconGo), keyStyle.Render("Go"), valStyle.Render(info.GoVersion)),
56+ "",
57+ fmt.Sprintf("%s %s", iconStyle.Render(IconGithub), keyStyle.Render(build.RepoURL())),
58+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconHeart), keyStyle.Render("Made with love by"), valStyle.Render(strings.Join(build.Contributors(), ", "))),
59+ }
60+
61+ return strings.Join(lines, "\n")
62+}
@@ -0,0 +1,62 @@
1+package styles
2+
3+import (
4+ "fmt"
5+ "strings"
6+
7+ "github.com/charmbracelet/lipgloss"
8+
9+ "github.com/bnema/dumber/internal/domain/build"
10+)
11+
12+// AboutRenderer renders build info in fastfetch style.
13+type AboutRenderer struct {
14+ theme *Theme
15+}
16+
17+// NewAboutRenderer creates a new about renderer with the given theme.
18+func NewAboutRenderer(theme *Theme) *AboutRenderer {
19+ return &AboutRenderer{theme: theme}
20+}
21+
22+// Render renders build info with ASCII logo and styled info lines.
23+func (r *AboutRenderer) Render(info build.Info) string {
24+ logo := r.renderLogo()
25+ lines := r.renderInfoLines(info)
26+
27+ // Combine horizontally: logo | info
28+ return lipgloss.JoinHorizontal(lipgloss.Top, logo, " ", lines)
29+}
30+
31+func (r *AboutRenderer) renderLogo() string {
32+ // ASCII art: Lightning bolt + D (matches pixel logo)
33+ // All in accent color (green)
34+ logoStyle := lipgloss.NewStyle().Foreground(r.theme.Accent).Bold(true)
35+
36+ // Simple bold D
37+ logo := `██████▄
38+██ ██
39+██ ██
40+██ ██
41+██████▀`
42+
43+ return logoStyle.MarginTop(1).MarginLeft(2).Render(logo)
44+}
45+
46+func (r *AboutRenderer) renderInfoLines(info build.Info) string {
47+ keyStyle := r.theme.Subtle
48+ valStyle := r.theme.Highlight
49+ iconStyle := lipgloss.NewStyle().Foreground(r.theme.Accent)
50+
51+ lines := []string{
52+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconVersion), keyStyle.Render("Version"), valStyle.Render(info.Version)),
53+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconGitBranch), keyStyle.Render("Commit"), valStyle.Render(info.Commit)),
54+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconCalendar), keyStyle.Render("Built"), valStyle.Render(info.BuildDate)),
55+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconGo), keyStyle.Render("Go"), valStyle.Render(info.GoVersion)),
56+ "",
57+ fmt.Sprintf("%s %s", iconStyle.Render(IconGithub), keyStyle.Render(build.RepoURL())),
58+ fmt.Sprintf("%s %s %s", iconStyle.Render(IconHeart), keyStyle.Render("Made with love by"), valStyle.Render(strings.Join(build.Contributors(), ", "))),
59+ }
60+
61+ return strings.Join(lines, "\n")
62+}
A internal/cli/styles/icons.go +15 -0
@@ -0,0 +1,15 @@
1+// Package styles provides reusable lipgloss-based TUI components.
2+package styles
3+
4+// Nerd Font icons (requires a Nerd Font to display correctly)
5+const (
6+ IconGlobe = "\uf0ac" // browser/web
7+ IconVersion = "\uf02b" // tag
8+ IconGitBranch = "\ue725" // git branch
9+ IconCalendar = "\uf073" // calendar
10+ IconCode = "\uf121" // code
11+ IconGithub = "\uf09b" // github
12+ IconHeart = "\uf004" // heart
13+ IconGo = "\ue627" // go gopher
14+ IconArrow = "\uf061" // arrow right
15+)
@@ -0,0 +1,15 @@
1+// Package styles provides reusable lipgloss-based TUI components.
2+package styles
3+
4+// Nerd Font icons (requires a Nerd Font to display correctly)
5+const (
6+ IconGlobe = "\uf0ac" // browser/web
7+ IconVersion = "\uf02b" // tag
8+ IconGitBranch = "\ue725" // git branch
9+ IconCalendar = "\uf073" // calendar
10+ IconCode = "\uf121" // code
11+ IconGithub = "\uf09b" // github
12+ IconHeart = "\uf004" // heart
13+ IconGo = "\ue627" // go gopher
14+ IconArrow = "\uf061" // arrow right
15+)
A internal/domain/build/info.go +20 -0
@@ -0,0 +1,20 @@
1+// Package build provides domain entities for build information.
2+package build
3+
4+// Info holds build-time information injected via ldflags.
5+type Info struct {
6+ Version string
7+ Commit string
8+ BuildDate string
9+ GoVersion string
10+}
11+
12+// Contributors returns the list of project contributors.
13+func Contributors() []string {
14+ return []string{"bnema"}
15+}
16+
17+// RepoURL returns the GitHub repository URL.
18+func RepoURL() string {
19+ return "https://github.com/bnema/dumber"
20+}
@@ -0,0 +1,20 @@
1+// Package build provides domain entities for build information.
2+package build
3+
4+// Info holds build-time information injected via ldflags.
5+type Info struct {
6+ Version string
7+ Commit string
8+ BuildDate string
9+ GoVersion string
10+}
11+
12+// Contributors returns the list of project contributors.
13+func Contributors() []string {
14+ return []string{"bnema"}
15+}
16+
17+// RepoURL returns the GitHub repository URL.
18+func RepoURL() string {
19+ return "https://github.com/bnema/dumber"
20+}

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