package cli import ( "context" "fmt" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "github.com/claudomator/claudomator/internal/api" "github.com/claudomator/claudomator/internal/executor" "github.com/claudomator/claudomator/internal/storage" "github.com/spf13/cobra" ) func newServeCmd() *cobra.Command { var addr string cmd := &cobra.Command{ Use: "serve", Short: "Start the Claudomator API server", RunE: func(cmd *cobra.Command, args []string) error { return serve(addr) }, } cmd.Flags().StringVar(&addr, "addr", ":8484", "listen address") return cmd } func serve(addr string) error { if err := cfg.EnsureDirs(); err != nil { return fmt.Errorf("creating dirs: %w", err) } store, err := storage.Open(cfg.DBPath) if err != nil { return fmt.Errorf("opening db: %w", err) } defer store.Close() level := slog.LevelInfo if verbose { level = slog.LevelDebug } logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})) runner := &executor.ClaudeRunner{ BinaryPath: cfg.ClaudeBinaryPath, Logger: logger, LogDir: cfg.LogDir, } pool := executor.NewPool(cfg.MaxConcurrent, runner, store, logger) srv := api.NewServer(store, pool, logger) srv.StartHub() httpSrv := &http.Server{ Addr: addr, Handler: srv.Handler(), } // Graceful shutdown. ctx, cancel := context.WithCancel(context.Background()) defer cancel() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) go func() { <-sigCh logger.Info("shutting down server...") shutdownCtx, shutdownCancel := context.WithTimeout(ctx, 5*time.Second) defer shutdownCancel() httpSrv.Shutdown(shutdownCtx) }() fmt.Printf("Claudomator server listening on %s\n", addr) if err := httpSrv.ListenAndServe(); err != http.ErrServerClosed { return err } return nil }