feat: add interactive setup wizard for first-run configuration

Implement a comprehensive setup wizard to improve onboarding and
configuration experience for both personal and server deployments.

Key features:
- Interactive wizard with profile selection (personal/server/custom)
- Quick setup mode with sensible defaults
- First-run detection with helpful welcome message
- Directory configuration with validation
- Server OAuth/JWT configuration with auto-generation
- Environment file creation for server deployments
- Template generators for systemd service and env files

New commands:
- opal setup              # Interactive wizard
- opal setup --quick      # Quick setup with defaults
- opal setup --profile    # Use specific profile
- opal setup --show-systemd  # Show systemd template
- opal setup --show-env   # Show environment file template

Implementation:
- internal/wizard/prompts.go: Reusable prompt utilities
- internal/wizard/profiles.go: Profile definitions and templates
- cmd/setup.go: Main setup command implementation
- cmd/root.go: First-run detection and welcome message
- internal/engine/config.go: ConfigExists() and IsFirstRun() helpers

User experience:
- On first run, shows welcome message suggesting 'opal setup'
- Non-intrusive - creates defaults automatically if skipped
- Wizard guides through all configuration options
- Server setup includes OAuth/JWT configuration
- Environment file created with proper permissions (0600)
- Clear next steps displayed after completion
This commit is contained in:
2026-01-06 21:49:13 +01:00
parent 5d01c9f564
commit 140d9f7f25
6 changed files with 721 additions and 2 deletions
+23 -1
View File
@@ -31,7 +31,7 @@ var (
var commandNames = []string{
"add", "done", "modify", "delete",
"start", "stop", "count", "projects", "tags",
"info", "edit", "server", "sync", "reports",
"info", "edit", "server", "sync", "reports", "setup",
}
// Report names (dynamically populated)
@@ -224,6 +224,11 @@ func init() {
}
func initializeApp() {
// Skip initialization for commands that handle their own setup
if len(os.Args) > 1 && os.Args[1] == "setup" {
return
}
// Set directory overrides from flags if provided
if configDirFlag != "" {
engine.SetConfigDirOverride(configDirFlag)
@@ -232,6 +237,9 @@ func initializeApp() {
engine.SetDataDirOverride(dataDirFlag)
}
// Check for first run (before initializing DB/config)
isFirstRun := engine.IsFirstRun()
// Initialize database
if err := engine.InitDB(); err != nil {
fmt.Fprintf(os.Stderr, "Error initializing database: %v\n", err)
@@ -243,4 +251,18 @@ func initializeApp() {
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
os.Exit(1)
}
// Show first-run message after config is created
if isFirstRun {
showFirstRunMessage()
}
}
func showFirstRunMessage() {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "👋 Welcome to Opal Task Manager!")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Default configuration created. To customize your setup,")
fmt.Fprintln(os.Stderr, "run: opal setup")
fmt.Fprintln(os.Stderr, "")
}