daemon

package
v0.22.170 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 26, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package daemon — HTTP client helper. One canonical dial path for everything that wants to call the local daemon's HTTP listener: CLI subcommands (`clawtool peer …`, `clawtool a2a peers`) and the orchestrator TUI's peers panel both pump through here.

Centralizing this avoids three near-identical copies of "read state, read token, build request, set bearer + Content-Type, do it with a 5s timeout, decode JSON, surface daemon errors as Go errors" — and keeps timeout/auth invariants in one spot when we want to tune them.

Package daemon manages a single persistent `clawtool serve --listen --mcp-http` process the operator's hosts (Codex / OpenCode / Gemini / Claude Code) all fan into. Per ADR-014 (recursive) and the operator's design call: every host that registers clawtool as an MCP server should connect to the SAME backend so BIAM identity, task store, and notify channels are shared. Stdio-spawning a child per host would create N independent identities and N independent BIAM stores — cross-host notify cannot work that way.

State lives at $XDG_CONFIG_HOME/clawtool/daemon.json (LF-delimited, 0600). Token file (bearer) lives at $XDG_CONFIG_HOME/clawtool/ listener-token. Ensure starts the daemon if missing, returns the existing state otherwise; Stop SIGTERMs and cleans up.

This package is the only place that knows the daemon's process lifecycle. Adapters (mcp_host.go) and CLI (`clawtool daemon …`) drive it through Ensure / Stop / Status — they don't touch the state file directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatStatus

func FormatStatus(s *State) string

FormatStatus renders the daemon state as a multi-line human string for `clawtool daemon status`. Used by the CLI; tests assert on substrings not whole layout.

func HTTPRequest added in v0.22.36

func HTTPRequest(method, path string, body *bytes.Reader, out any) error

HTTPRequest dials the local daemon's HTTP listener with the shared bearer token. body may be nil for GET/DELETE; out may be nil when the caller doesn't care about the response payload. Daemon-side errors (HTTP >= 300) are surfaced as Go errors with the daemon's JSON {"error": "..."} string when present.

func IsRunning

func IsRunning(s *State) bool

IsRunning returns true when the recorded PID is alive AND the port still answers /v1/health within a short timeout. Both checks matter: a stale state file from a crashed daemon must not look healthy, and a port that no longer belongs to us (recycled by some other process) must not look ours.

func LanExposureEnabled added in v0.22.170

func LanExposureEnabled() bool

LanExposureEnabled reports whether LAN exposure has been opted into.

func LanExposurePath added in v0.22.170

func LanExposurePath() string

LanExposurePath is the marker file that opts this device's shared daemon into LAN exposure. Present ⇒ Ensure binds all interfaces with --allow-lan + token auth so circle peers can reach the read-only endpoints; absent ⇒ the default 127.0.0.1 --no-auth loopback daemon (the machine is the trust boundary). Same XDG conventions as StatePath.

func LogPath

func LogPath() string

LogPath returns the daemon's combined-output log path.

func ReadToken

func ReadToken() (string, error)

ReadToken returns the bearer token contents (whitespace-trimmed). Empty string + nil error if the file is missing — Ensure ensures the file exists before exposing the token to callers.

func SetLanExposure added in v0.22.170

func SetLanExposure(on bool) error

SetLanExposure toggles the LAN-exposure marker. The caller restarts the daemon afterwards so Ensure re-reads it and rebinds.

func StatePath

func StatePath() string

StatePath returns the file Ensure / Stop persist to. Honors $XDG_CONFIG_HOME, else ~/.config/clawtool/daemon.json.

func Stop

func Stop() error

Stop sends SIGTERM, waits up to 5s, escalates to SIGKILL, then removes the state file. No-op if no daemon is recorded.

func TokenPath

func TokenPath() string

TokenPath returns the bearer-token file the daemon and adapters share. Same XDG conventions as StatePath.

Types

type State

type State struct {
	Version   int       `json:"version"`
	PID       int       `json:"pid"`
	Port      int       `json:"port"`
	StartedAt time.Time `json:"started_at"`
	TokenFile string    `json:"token_file"`
	LogFile   string    `json:"log_file"`
}

State is the persisted snapshot of a running daemon.

func Ensure

func Ensure(ctx context.Context) (*State, error)

Ensure starts the daemon if it isn't already running and returns the live State. Idempotent: if the daemon is already healthy, the existing state is returned without spawning.

Spawn flow: pick a free port, ensure the bearer token, fork the detached process, write the candidate state to daemon.json.pending, poll /v1/health with exponential backoff up to readinessDeadline (60 s), and only then atomically promote .pending → daemon.json. Order matters: daemon.json's appearance is the on-disk signal of "daemon is reachable", not "daemon process was forked." A spawn that crashes before bind leaves no daemon.json behind so the next Ensure invocation cleanly re-spawns instead of trusting a stale PID record. On readiness timeout, the returned error includes a tail of the daemon log so the install verb / operator sees the underlying bind failure without digging through XDG_STATE_HOME.

Concurrency: two CLI invocations within the spawn window (read-state → IsRunning → spawn → write-state) would both see "no daemon" and both fork, leaving an orphan racing for the state file + ports. We bracket the whole sequence with an OS advisory lock on a sibling .lock file (flock on POSIX, LockFileEx on Windows via fileLockExclusive). The fast path — a healthy daemon already running — does not need the lock; we re-check IsRunning inside the lock so a concurrent winner's state is observed before we duplicate-spawn.

func EnsureFrom added in v0.22.36

func EnsureFrom(ctx context.Context, exePath string) (*State, error)

EnsureFrom is Ensure with an explicit binary path. Use this when the caller knows where the canonical clawtool binary lives and can't trust os.Executable() to resolve to the right inode — e.g. `clawtool upgrade` after the install-path swap, where the upgrading CLI process is running from the freshly-renamed `.clawtool.old` backup that may already have been unlinked. An empty exePath falls back to os.Executable() which is correct for every non-upgrade caller.

func ReadState

func ReadState() (*State, error)

ReadState returns the persisted state, or (nil, nil) if no daemon has been started yet. Parse errors are returned verbatim so callers can decide whether to wipe + retry.

func (*State) HealthURL

func (s *State) HealthURL() string

HealthURL is the unauthenticated probe URL the daemon exposes for readiness checks.

func (*State) URL

func (s *State) URL() string

URL is the MCP-over-HTTP endpoint hosts dial.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL