1// Package cli provides the command-line interface for dumber.
2package cli
3
4import (
5 "database/sql"
6 "fmt"
7 "log"
8 "os"
9 "path/filepath"
10
11 "github.com/bnema/dumber/internal/config"
12 "github.com/bnema/dumber/internal/db"
13 "github.com/bnema/dumber/internal/services"
14
15 _ "github.com/ncruces/go-sqlite3/driver" // SQLite driver for database/sql
16 _ "github.com/ncruces/go-sqlite3/embed" // Embed SQLite for cross-platform compatibility
17 "github.com/spf13/cobra"
18)
19
20// File permission constants
21const (
22 dirPerm = 0755 // Standard directory permissions (rwxr-xr-x)
23)
24
25// CLI holds the database connection, queries, and configuration for the CLI commands
26type CLI struct {
27 DB *sql.DB
28 Queries *db.Queries
29 Config *config.Config
30 ParserService *services.ParserService
31}
32
33// NewCLI creates a new CLI instance with database connection and configuration
34func NewCLI() (*CLI, error) {
35 // Get configuration
36 cfg := config.Get()
37
38 // Use configured database path
39 dbPath := cfg.Database.Path
40 if dbPath == "" {
41 // Fallback to default path if not configured
42 var err error
43 dbPath, err = config.GetDatabaseFile()
44 if err != nil {
45 return nil, fmt.Errorf("failed to get database path: %w", err)
46 }
47 }
48
49 // Ensure database directory exists
50 if err := os.MkdirAll(filepath.Dir(dbPath), dirPerm); err != nil {
51 return nil, fmt.Errorf("failed to create database directory: %w", err)
52 }
53
54 // Open database connection
55 database, err := sql.Open("sqlite3", dbPath)
56 if err != nil {
57 return nil, fmt.Errorf("failed to open database: %w", err)
58 }
59
60 // Test the connection
61 if err := database.Ping(); err != nil {
62 if err := database.Close(); err != nil {
63 log.Printf("Warning: failed to close database: %v", err)
64 }
65 return nil, fmt.Errorf("failed to connect to database: %w", err)
66 }
67
68 queries := db.New(database)
69 parserService := services.NewParserService(cfg, queries)
70
71 return &CLI{
72 DB: database,
73 Queries: queries,
74 Config: cfg,
75 ParserService: parserService,
76 }, nil
77}
78
79// Close closes the database connection
80func (c *CLI) Close() error {
81 if c.DB != nil {
82 return c.DB.Close()
83 }
84 return nil
85}
86
87// NewRootCmd creates the root command for dumber
88func NewRootCmd(version, commit, buildDate string) *cobra.Command {
89 rootCmd := &cobra.Command{
90 Use: "dumber",
91 Short: "A dumb browser for Wayland window managers",
92 Long: `A fast, simple browser with rofi/dmenu integration for sway and hyprland window managers.`,
93 RunE: func(cmd *cobra.Command, _ []string) error {
94 // Show help when no subcommand is provided
95 return cmd.Help()
96 },
97 }
98
99 // Add dmenu flag for launcher integration
100 rootCmd.Flags().Bool("dmenu", false, "Run in dmenu mode for launcher integration")
101
102 // Version command
103 versionCmd := &cobra.Command{
104 Use: "version",
105 Short: "Print version information",
106 Run: func(_ *cobra.Command, _ []string) {
107 fmt.Printf("dumber %s\n", version)
108 fmt.Printf("commit: %s\n", commit)
109 fmt.Printf("built: %s\n", buildDate)
110 },
111 }
112
113 // Init command (enhanced)
114 initCmd := &cobra.Command{
115 Use: "init",
116 Short: "Initialize dumber database and configuration",
117 RunE: func(_ *cobra.Command, _ []string) error {
118 cli, err := NewCLI()
119 if err != nil {
120 return fmt.Errorf("failed to initialize CLI: %w", err)
121 }
122 defer func() {
123 if closeErr := cli.Close(); closeErr != nil {
124 fmt.Fprintf(os.Stderr, "Warning: failed to close database: %v\n", closeErr)
125 }
126 }()
127
128 fmt.Printf("dumber %s - Initialization complete!\n", version)
129 fmt.Println("Database initialized at:", cli.Config.Database.Path)
130
131 // Show XDG directories
132 xdgDirs, err := config.GetXDGDirs()
133 if err == nil {
134 fmt.Println("Configuration directories:")
135 fmt.Printf("- Config: %s\n", xdgDirs.ConfigHome)
136 fmt.Printf("- Data: %s\n", xdgDirs.DataHome)
137 fmt.Printf("- State: %s\n", xdgDirs.StateHome)
138 }
139
140 // Show configured shortcuts
141 fmt.Println("Configured shortcuts:")
142 for shortcut, cfg := range cli.Config.SearchShortcuts {
143 fmt.Printf("- %s:%s -> %s\n", shortcut, "query", cfg.Description)
144 }
145 return nil
146 },
147 }
148
149 // Add subcommands
150 rootCmd.AddCommand(versionCmd)
151 rootCmd.AddCommand(initCmd)
152 rootCmd.AddCommand(NewBrowseCmd())
153 rootCmd.AddCommand(NewDmenuCmd())
154 rootCmd.AddCommand(NewHistoryCmd())
155 rootCmd.AddCommand(NewPurgeCmd())
156 rootCmd.AddCommand(NewLogsCmd())
157 rootCmd.AddCommand(NewConfigCmd())
158
159 return rootCmd
160}