Files
skrueckenandCursor 2dbf0918de Add local JSON daemon and route TUI/CLI through it.
A per-DataDir background process owns ticket JSON, history, and sync so
multiple TUI instances stay consistent; MCP token APIs are ready for phase 2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-01 16:32:13 +01:00

80 lines
1.7 KiB
Go

package daemon
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"task/internal/command"
"task/internal/config"
"task/internal/daemon/api"
"task/internal/project"
"task/internal/ticket"
)
// Run starts the foreground daemon for cfg until signal or context cancel.
func Run(cfg *config.Config) error {
abs, err := AbsoluteDataDir(cfg.DataDir)
if err != nil {
return err
}
cfg.DataDir = abs
p, err := PathsForDataDir(cfg.DataDir)
if err != nil {
return err
}
cleanupStale(p)
store := ticket.NewJSONStore(cfg.DataDir)
timeStore := ticket.NewJSONTimeStore(cfg.DataDir)
projects := project.NewDirStore(cfg.DataDir)
history, err := command.OpenHistory(command.HistoryPath(cfg.DataDir), store, timeStore)
if err != nil {
return fmt.Errorf("history: %w", err)
}
srvAPI := api.New(store, timeStore, projects, history)
server := &http.Server{Handler: srvAPI.Handler()}
ln, err := net.Listen("unix", p.Socket)
if err != nil {
return fmt.Errorf("listen %s: %w", p.Socket, err)
}
if err := os.Chmod(p.Socket, 0o600); err != nil {
_ = ln.Close()
return fmt.Errorf("chmod socket: %w", err)
}
if err := writePID(p.PIDFile, os.Getpid()); err != nil {
_ = ln.Close()
return fmt.Errorf("pid file: %w", err)
}
defer cleanupStale(p)
errCh := make(chan error, 1)
go func() {
errCh <- server.Serve(ln)
}()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
select {
case <-sigCh:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
_ = server.Shutdown(ctx)
return nil
case err := <-errCh:
if err == http.ErrServerClosed {
return nil
}
return err
}
}