diff --git a/apps/cli/internal/app/enhanced_root.go b/apps/cli/internal/app/enhanced_root.go index 2d5e3ce..6710af9 100644 --- a/apps/cli/internal/app/enhanced_root.go +++ b/apps/cli/internal/app/enhanced_root.go @@ -4,8 +4,12 @@ import ( "fmt" "os" + "github.com/charmbracelet/lipgloss" tea "github.com/charmbracelet/bubbletea" + + "github.com/nutcas3/telecom-platform/apps/cli/internal/api" "github.com/nutcas3/telecom-platform/apps/cli/internal/commands" + "github.com/nutcas3/telecom-platform/apps/cli/internal/config" "github.com/nutcas3/telecom-platform/apps/cli/internal/types" ) @@ -13,20 +17,55 @@ type EnhancedCLI struct { config *types.CLIConfig } +// NewEnhancedCLI builds the CLI with defaults and attempts to load the persisted +// configuration from disk. The returned CLI always has a valid (defaulted) +// config, even if loading failed. func NewEnhancedCLI() *EnhancedCLI { - return &EnhancedCLI{ - config: &types.CLIConfig{ - APIEndpoint: "http://localhost:8000", - APIToken: "", - Profile: "default", - Verbose: false, - NoColor: false, - Theme: "default", - }, + cfg := &types.CLIConfig{ + APIEndpoint: "http://localhost:8000", + APIToken: "", + Profile: "default", + Verbose: false, + NoColor: false, + Theme: "default", } + + // Attempt to hydrate from the viper-backed configuration. + if persisted := loadPersistedConfig(); persisted != nil { + if persisted.GetAPIEndpoint() != "" { + cfg.APIEndpoint = persisted.GetAPIEndpoint() + } + if persisted.GetAPIToken() != "" { + cfg.APIToken = persisted.GetAPIToken() + } + if persisted.Profile != "" { + cfg.Profile = persisted.Profile + } + if persisted.UI.Theme != "" { + cfg.Theme = persisted.UI.Theme + } + if persisted.NoColors() { + cfg.NoColor = true + } + } + + return &EnhancedCLI{config: cfg} +} + +// loadPersistedConfig is a best-effort load of the CLI configuration; errors +// are swallowed so the CLI still works with sane defaults. +func loadPersistedConfig() *config.Config { + c := config.NewConfig() + if err := c.Load(); err != nil { + return c // still return defaults so callers can use GetAPIEndpoint() + } + return c } func (cli *EnhancedCLI) Run(args []string) error { + // Parse any global flags first so subcommands see the updated config. + _ = cli.parseConfig(args) + if len(args) < 2 { cli.showHelp() return nil @@ -54,44 +93,94 @@ func (cli *EnhancedCLI) Run(args []string) error { return commands.HandlePluginsEnhanced(commandArgs, cli.config) case "automation": return commands.HandleAutomationEnhanced(commandArgs, cli.config) + case "help", "--help", "-h": + cli.showHelp() + return nil + case "version", "--version", "-v": + fmt.Println("telecom-cli v1.0.0") + return nil default: - fmt.Printf("Unknown command: %s\n", command) + fmt.Printf("Unknown command: %s\n\n", command) cli.showHelp() return fmt.Errorf("unknown command: %s", command) } } func (cli *EnhancedCLI) runDashboard() error { - p := tea.NewProgram(NewDashboard()) + p := tea.NewProgram(NewDashboardWithConfig(cli.config)) if _, err := p.Run(); err != nil { return fmt.Errorf("error running dashboard: %w", err) } return nil } +// showHelp renders a lipgloss-styled help screen and an API connectivity banner +// so the user immediately sees whether the CLI can reach the backend. func (cli *EnhancedCLI) showHelp() { - fmt.Println("Telecom Platform CLI - Enhanced") - fmt.Println("Usage: telecom-cli [options]") + useColor := !cli.config.NoColor + + headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")) + subHeaderStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("14")) + mutedStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("241")) + cmdStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("12")) + + render := func(s lipgloss.Style, text string) string { + if !useColor { + return text + } + return s.Render(text) + } + + fmt.Println(render(headerStyle, "Telecom Platform CLI")) + fmt.Println(render(mutedStyle, "Usage: telecom-cli [options]")) fmt.Println() - fmt.Println("Available commands:") - fmt.Println(" dashboard - Interactive dashboard for platform overview") - fmt.Println(" subscribers - Subscriber management") - fmt.Println(" services - Service management") - fmt.Println(" billing - Billing and invoice management") - fmt.Println(" monitoring - Monitoring and metrics") - fmt.Println(" config - Configuration management") - fmt.Println(" deploy - Deployment management") - fmt.Println(" plugins - Plugin management") - fmt.Println(" automation - Automation and scripting") + + // API connectivity banner + client := api.NewClient(cli.config) + if client.IsConnected() { + fmt.Println(render( + lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Bold(true), + "✓ Connected to API: "+cli.config.APIEndpoint, + )) + } else { + fmt.Println(render( + lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Bold(true), + "⚠ API unreachable at "+cli.config.APIEndpoint+" - commands will show sample data", + )) + } fmt.Println() - fmt.Println("Global options:") - fmt.Println(" --endpoint API endpoint (default: http://localhost:8000)") - fmt.Println(" --token API authentication token") - fmt.Println(" --profile Configuration profile") - fmt.Println(" --verbose Enable verbose output") - fmt.Println(" --no-color Disable color output") + + fmt.Println(render(subHeaderStyle, "Available commands:")) + rows := [][2]string{ + {"dashboard", "Interactive dashboard for platform overview"}, + {"subscribers", "Subscriber management (list, show, create, delete, ...)"}, + {"services", "Service management (list, status, restart, logs)"}, + {"billing", "Billing and invoice management"}, + {"monitoring", "Monitoring, alerts, and health checks"}, + {"config", "Configuration management (show, get, set, validate)"}, + {"deploy", "Deployment management"}, + {"plugins", "Plugin management"}, + {"automation", "Automation and scripting"}, + } + for _, r := range rows { + fmt.Printf(" %-14s %s\n", render(cmdStyle, r[0]), render(mutedStyle, r[1])) + } + fmt.Println() - fmt.Println("Use 'telecom-cli --help' for more information") + fmt.Println(render(subHeaderStyle, "Global options:")) + opts := [][2]string{ + {"--endpoint ", "API endpoint (default: http://localhost:8000)"}, + {"--token ", "API authentication token"}, + {"--profile ", "Configuration profile"}, + {"--verbose", "Enable verbose output"}, + {"--no-color", "Disable color output"}, + } + for _, r := range opts { + fmt.Printf(" %-22s %s\n", render(cmdStyle, r[0]), render(mutedStyle, r[1])) + } + + fmt.Println() + fmt.Println(render(mutedStyle, "Use 'telecom-cli ' with no args to see its subcommand help.")) } func (cli *EnhancedCLI) parseConfig(args []string) error { @@ -121,10 +210,15 @@ func (cli *EnhancedCLI) parseConfig(args []string) error { return nil } +// Config returns a read-only view of the CLI configuration; primarily used by +// tests and by subcommands that need to bypass the command parser. +func (cli *EnhancedCLI) Config() *types.CLIConfig { + return cli.config +} + func Main() { cli := NewEnhancedCLI() - // Parse global options if err := cli.parseConfig(os.Args); err != nil { fmt.Fprintf(os.Stderr, "Error parsing options: %v\n", err) os.Exit(1)