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>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
sockName = "daemon.sock"
|
||||
pidName = "daemon.pid"
|
||||
logName = "daemon.log"
|
||||
)
|
||||
|
||||
// Paths holds filesystem locations for one DataDir's daemon instance.
|
||||
type Paths struct {
|
||||
RuntimeDir string
|
||||
Socket string
|
||||
PIDFile string
|
||||
LogFile string
|
||||
}
|
||||
|
||||
// PathsForDataDir returns runtime paths unique to dataDir.
|
||||
func PathsForDataDir(dataDir string) (Paths, error) {
|
||||
dataDir = filepath.Clean(dataDir)
|
||||
sum := sha256.Sum256([]byte(dataDir))
|
||||
id := hex.EncodeToString(sum[:8])
|
||||
|
||||
dir, err := mkdirRuntime(id, dataDir)
|
||||
if err != nil {
|
||||
return Paths{}, err
|
||||
}
|
||||
return Paths{
|
||||
RuntimeDir: dir,
|
||||
Socket: filepath.Join(dir, sockName),
|
||||
PIDFile: filepath.Join(dir, pidName),
|
||||
LogFile: filepath.Join(dir, logName),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mkdirRuntime(id, dataDir string) (string, error) {
|
||||
candidates := []string{}
|
||||
if r := strings.TrimSpace(os.Getenv("XDG_RUNTIME_DIR")); r != "" {
|
||||
candidates = append(candidates, filepath.Join(r, "task-master-"+id))
|
||||
}
|
||||
candidates = append(candidates,
|
||||
filepath.Join(os.TempDir(), fmt.Sprintf("task-master-%d-%s", os.Getuid(), id)),
|
||||
filepath.Join(dataDir, ".runtime"),
|
||||
)
|
||||
var last error
|
||||
for _, dir := range candidates {
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
last = err
|
||||
continue
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
if last == nil {
|
||||
last = fmt.Errorf("no runtime dir candidate")
|
||||
}
|
||||
return "", fmt.Errorf("create runtime dir: %w", last)
|
||||
}
|
||||
|
||||
func writePID(path string, pid int) error {
|
||||
return os.WriteFile(path, []byte(strconv.Itoa(pid)+"\n"), 0o600)
|
||||
}
|
||||
|
||||
func readPID(path string) (int, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
s := strings.TrimSpace(string(data))
|
||||
if s == "" {
|
||||
return 0, fmt.Errorf("empty pid file")
|
||||
}
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
|
||||
func processAlive(pid int) bool {
|
||||
if pid <= 0 {
|
||||
return false
|
||||
}
|
||||
err := syscall.Kill(pid, 0)
|
||||
return err == nil
|
||||
}
|
||||
Reference in New Issue
Block a user