cmd/portr/cmd/watch.go

// `portr watch` — long-running mode that re-runs checks on a timer.
//
// See mercemay.top/src/portr/ for context.
package cmd

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/spf13/cobra"

	"github.com/mercemay/portr/internal/check"
	"github.com/mercemay/portr/internal/tui"
)

var (
	watchInterval time.Duration
	watchTUI      bool
)

var watchCmd = &cobra.Command{
	Use:   "watch",
	Short: "Periodically re-check targets (optional TUI)",
	RunE: func(cmd *cobra.Command, _ []string) error {
		cfg, err := loadConfig()
		if err != nil {
			return err
		}
		ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
		defer cancel()

		if watchTUI {
			tui.Apply()
			return tui.New(cfg).Run(ctx)
		}

		t := time.NewTicker(watchInterval)
		defer t.Stop()
		for {
			rep, err := check.Run(ctx, cfg)
			if err != nil {
				return err
			}
			open, closed := rep.Summary()
			fmt.Fprintf(os.Stderr, "%s  %d open / %d closed\n",
				time.Now().Format(time.RFC3339), open, closed)
			select {
			case <-ctx.Done():
				return nil
			case <-t.C:
			}
		}
	},
}

func init() {
	watchCmd.Flags().DurationVar(&watchInterval, "interval", 30*time.Second, "time between checks")
	watchCmd.Flags().BoolVar(&watchTUI, "tui", false, "interactive TUI instead of log lines")
	_ = context.Canceled // keep context imported across refactors
}