main
1// Package cli implements the command-line interface.
2package cli
3
4import (
5 "fmt"
6 "log/slog"
7 "os"
8
9 "github.com/crash/upvs/internal/config"
10 "github.com/spf13/cobra"
11)
12
13var cfg = config.New()
14
15// Global flag variables for file-based password loading
16var passwordFile string
17
18// rootCmd is the base command.
19var rootCmd = &cobra.Command{
20 Use: "upvs",
21 Short: "UniFi Protect Video Summarizer",
22 Long: "Create ~10-minute timelapses from UniFi Protect camera recordings for a single day.",
23 PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
24 // Load password from file if specified
25 if passwordFile != "" && cfg.Password == "" {
26 err := cfg.LoadPasswordFromFile(passwordFile)
27 if err != nil {
28 return err
29 }
30 }
31
32 // Set up logging
33 level := slog.LevelInfo
34 if cfg.Verbose {
35 level = slog.LevelDebug
36 }
37 handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})
38 slog.SetDefault(slog.New(handler))
39
40 return nil
41 },
42}
43
44func init() {
45 // Silence usage and errors on failure - we handle error printing in Execute()
46 rootCmd.SilenceUsage = true
47 rootCmd.SilenceErrors = true
48
49 // Connection flags
50 rootCmd.PersistentFlags().StringVar(&cfg.Host, "host", "", "UniFi Protect URL (env: UPVS_HOST)")
51 rootCmd.PersistentFlags().StringVar(&cfg.Username, "username", "", "Username (env: UPVS_USERNAME)")
52 rootCmd.PersistentFlags().StringVar(&cfg.Password, "password", "", "Password (env: UPVS_PASSWORD)")
53 rootCmd.PersistentFlags().StringVar(&passwordFile, "password-file", "", "Path to file containing password")
54 rootCmd.PersistentFlags().BoolVar(&cfg.TLSInsecure, "tls-insecure", false, "Skip TLS verification")
55 rootCmd.PersistentFlags().BoolVar(&cfg.DirectAPI, "direct-api", false, "Use /api path (for direct NVR connection, not UniFi OS)")
56
57 // Target selection flags
58 rootCmd.PersistentFlags().StringVar(&cfg.Camera, "camera", "", "Camera ID or name")
59
60 // Output flags
61 rootCmd.PersistentFlags().StringVar(&cfg.OutDir, "out", "", "Output directory")
62 rootCmd.PersistentFlags().BoolVarP(&cfg.Verbose, "verbose", "v", false, "Enable debug logging")
63
64 // Bind environment variables
65 bindEnv("UPVS_HOST", &cfg.Host)
66 bindEnv("UPVS_USERNAME", &cfg.Username)
67 bindEnv("UPVS_PASSWORD", &cfg.Password)
68
69 // Add subcommands
70 rootCmd.AddCommand(scanCmd)
71 rootCmd.AddCommand(fetchCmd)
72 rootCmd.AddCommand(renderCmd)
73 rootCmd.AddCommand(runCmd)
74}
75
76// bindEnv sets a flag default from an environment variable if the flag is empty.
77func bindEnv(envVar string, target *string) {
78 if val := os.Getenv(envVar); val != "" && *target == "" {
79 *target = val
80 }
81}
82
83// Execute runs the root command.
84func Execute() {
85 err := rootCmd.Execute()
86 if err != nil {
87 fmt.Fprintln(os.Stderr, err)
88 os.Exit(1)
89 }
90}
91
92// GetConfig returns the global configuration.
93func GetConfig() *config.Config {
94 return cfg
95}