package main import ( "context" "errors" "fmt" "os" "time" "github.com/spf13/cobra" "gitea.oblak.solutions/dimitar/gitFlow/internal/app" "gitea.oblak.solutions/dimitar/gitFlow/internal/config" "gitea.oblak.solutions/dimitar/gitFlow/internal/presenter" "gitea.oblak.solutions/dimitar/gitFlow/internal/scheduler" "gitea.oblak.solutions/dimitar/gitFlow/pkg/status" ) // newWatchCmd repeatedly scans on an interval until interrupted. func newWatchCmd() *cobra.Command { cmd := &cobra.Command{ Use: "watch", Short: "Repeatedly scan repositories on an interval", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { cfg, err := config.Load(cmd.Flags()) if err != nil { return err } if cfg.Interval <= 0 { return errors.New("watch requires a positive --interval (e.g. --interval 30s)") } // Same interactive prompt as scan, per the README. if !cmd.Flags().Changed("dir") && isTerminal(os.Stdin) { if d, err := promptDir(cfg.Dir); err == nil { cfg.Dir = d } } ctx, stop := signalContext() defer stop() a, err := app.New(cfg) if err != nil { return err } opts := presenter.Options{Color: presenter.ParseColorMode(cfg.Color)} var prev status.ScanResult first := true run := func(ctx context.Context) error { result, err := a.ScanOnce(ctx) if err != nil { return err } if cfg.Format == "json" { return presenter.Present(os.Stdout, cfg.Format, result, opts) } renderWatchFrame(result, prev, first, cfg, opts) // Ask the AI only on the first frame and when something // changed, so the provider is not hammered every interval. if cfg.AI.Enabled && (first || len(status.Changed(prev, result)) > 0) { renderSuggestions(ctx, cfg, opts, result) } prev = result first = false return nil } sched := scheduler.New(cfg.Interval, run) return sched.Run(ctx) }, } config.RegisterFlags(cmd.Flags()) return cmd } // renderWatchFrame clears the screen (or prints a timestamp header when // output is not a terminal), renders the scan, and prints a footer with // changed repositories and the next scan time. func renderWatchFrame(result, prev status.ScanResult, first bool, cfg *config.Config, opts presenter.Options) { if isTerminal(os.Stdout) { fmt.Fprint(os.Stdout, "\x1b[2J\x1b[H") } else { fmt.Fprintf(os.Stdout, "--- %s ---\n", time.Now().Format(time.RFC3339)) } if err := presenter.Present(os.Stdout, cfg.Format, result, opts); err != nil { fmt.Fprintf(os.Stderr, "render: %v\n", err) return } if !first { for _, r := range status.Changed(prev, result) { fmt.Fprintf(os.Stdout, "▲ %s: %s\n", r.Name, r.Status) } } fmt.Fprintf(os.Stdout, "watching %s every %s — next scan at %s (Ctrl-C to stop)\n", cfg.Dir, cfg.Interval, time.Now().Add(cfg.Interval).Format("15:04:05")) }