summaryrefslogtreecommitdiff
path: root/internal/cli/serve.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-02-08 21:35:45 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-02-08 21:35:45 -1000
commit2e2b2187b957e9af78797a67ec5c6874615fae02 (patch)
tree1181dbb7e43f5d30cb025fa4d50fd4e7a2c893b3 /internal/cli/serve.go
Initial project: task model, executor, API server, CLI, storage, reporter
Claudomator automation toolkit for Claude Code with: - Task model with YAML parsing, validation, state machine (49 tests, 0 races) - SQLite storage for tasks and executions - Executor pool with bounded concurrency, timeout, cancellation - REST API + WebSocket for mobile PWA integration - Webhook/multi-notifier system - CLI: init, run, serve, list, status commands - Console, JSON, HTML reporters with cost tracking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/cli/serve.go')
-rw-r--r--internal/cli/serve.go86
1 files changed, 86 insertions, 0 deletions
diff --git a/internal/cli/serve.go b/internal/cli/serve.go
new file mode 100644
index 0000000..5d41395
--- /dev/null
+++ b/internal/cli/serve.go
@@ -0,0 +1,86 @@
+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
+}