server

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Overview

Package server wires up musterd's HTTP+WS surface: auth, static serving, the ingest endpoints and the state stream. Business logic (parsing, persistence) is delegated to internal/claudecode and internal/store; handlers here only decode, delegate, encode.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ClaudeCodeInfo

type ClaudeCodeInfo struct {
	Installed *string
	Floor     string
	Verified  string
	Status    string
}

ClaudeCodeInfo is the daemon's startup snapshot of the installed Claude Code against the canary-verified range, used to build the WS `hello` message (docs/protocol.md §5.1) and the issue-capture snapshot. Installed is nil iff Status is "unknown" (the startup version check failed, hung past its timeout, or was unparseable); Floor/Verified are always populated. internal/server carries Status only as a plain string — the status-word type itself lives in internal/claudecode (docs/conventions.md "Boundary").

type ClaudeThemeInfo

type ClaudeThemeInfo struct {
	Family string `json:"family"`
}

ClaudeThemeInfo is the `claudeTheme` object inside a snapshot (docs/protocol.md §5.2/§5.6, plan new-ui-design-colors REQ-15) — the daemon's latest read of Claude Code's own theme family. Family is always present: "unknown" while polling is disabled (-claude-theme-poll 0) or no read has yet succeeded.

type Config

type Config struct {
	Store       *store.Store
	Logger      zerolog.Logger
	UIToken     string
	IngestToken string
	// WebDist, when non-empty, serves the dashboard from this on-disk directory instead
	// of the embedded copy (dev override, plan embed-dashboard REQ-2) — behaviour
	// unchanged from before that plan. Empty (the new -web-dist default) serves
	// internal/webui's go:embed-ed tree instead.
	WebDist       string
	DaemonVersion string
	ClaudeCode    ClaudeCodeInfo

	// IngestQueueSize bounds the ingest queue; a full queue drops and logs rather than
	// blocking the HTTP response (REQ-23). Zero uses a sane default.
	IngestQueueSize int

	// ClaudeBin is the `claude` binary to spawn (REQ-19's -claude-bin, default "claude").
	ClaudeBin string
	// TmuxSocket is the dedicated tmux socket name (REQ-19's -tmux-socket, default "muster").
	TmuxSocket string
	// BrowseRoot is the folder browser's root (protocol §3.6): GET /api/browse's
	// no-param default and the "Up" ceiling. Empty means the daemon user's home
	// directory. E2E passes its per-run scratch dir so browse tests never touch
	// the real home.
	BrowseRoot string
	// HookScript/StatusLineScript are the absolute paths to the generated command-hook
	// wrapper scripts (internal/claudecode.WriteWrapperScripts) — every event, including
	// SessionStart, is registered against HookScript now.
	HookScript       string
	StatusLineScript string
	// LegacyScripts lists prior wrapper paths MergeSettings must still recognise and
	// drop from an already-instrumented directory (REQ-3/REQ-4) — currently just the
	// pre-plan hook-sessionstart.sh path WriteWrapperScripts returns for removal.
	LegacyScripts []string

	// UsagePoll is the poll interval (-usage-poll). <= 0 disables polling entirely:
	// POST /api/usage/refresh then 404s (docs/protocol.md §3.9).
	UsagePoll time.Duration
	// UsageAPIURL is the per-model usage endpoint's base URL (-usage-api-url). main
	// always passes the flag's non-empty default, so this is the *only* place that
	// URL is defined — there is deliberately no fallback constant here duplicating it.
	// Empty disables the poller entirely (same fail-safe shape as UsagePoll <= 0):
	// a zero-value Config must never reach the real api.anthropic.com.
	UsageAPIURL string
	// UsageTokenFile, when non-empty, reads the Claude Code OAuth token from this file
	// instead of the macOS Keychain (-usage-token-file, REQ-13's test seam — makes it
	// structurally impossible for a test using it to fall through to the real Keychain).
	UsageTokenFile string
	// KeychainUser is the account name `security` looks the Keychain item up under
	// (main passes os/user.Current()'s value) — used only when UsageTokenFile is empty.
	KeychainUser string
	// HTTPClient is the client FetchUsage uses; nil defaults to http.DefaultClient.
	HTTPClient *http.Client

	// ClaudeThemePoll is the poll interval (-claude-theme-poll). <= 0 disables polling
	// entirely: snapshot.claudeTheme.family stays "unknown" forever.
	ClaudeThemePoll time.Duration
	// ClaudeConfigFile is Claude Code's global config file to poll
	// (-claude-config-file, default claudecode.DefaultConfigPath()) — a test seam like
	// UsageTokenFile (INV-5: E2E always passes a scratch path).
	ClaudeConfigFile string

	// IssueRepo is the GitHub repo issues are filed against (-issue-repo).
	IssueRepo string
	// IssueAPIURL is the GitHub API's base URL (-issue-api-url). main always passes the
	// flag's non-empty default, so this is the only place that URL is defined — no
	// fallback constant duplicates it here. Empty disables both new endpoints entirely:
	// they 404 not_found (docs/protocol.md §3.12/§3.13).
	IssueAPIURL string
	// IssueTokenFile, when non-empty, reads the bearer token from this file's trimmed
	// contents instead of running `gh auth token` (-issue-token-file, REQ-14's test seam
	// — makes it structurally impossible for a test using it to execute the real gh).
	IssueTokenFile string

	// Locator resolves a dropped file's original path for POST /api/sessions/{id}/locate
	// (plan file-drop-fix, docs/protocol.md §3.14). main always constructs locate.New();
	// tests that never exercise the endpoint may leave this nil.
	Locator *locate.Locator

	// TmuxClient overrides the paneSpawner sessionLauncher and shellRegistry use (plan
	// v1-cleanup REQ-1). Nil constructs the real tmux.New(cfg.TmuxSocket) exactly as
	// before this plan — every production call site, main included, leaves this nil.
	TmuxClient paneSpawner
	// Attach overrides how a terminal socket attaches to a tmux target (plan v1-cleanup
	// REQ-2). Nil goes through termbridge.Attach against the server's own tmux client
	// exactly as before this plan — every production call site, main included, leaves
	// this nil.
	Attach attachFunc
}

Config wires everything a Server needs. Built entirely in main — no init() magic, no package-level state.

type PrefsInfo

type PrefsInfo struct {
	View       string `json:"view"`
	Density    string `json:"density"`
	UsageModel string `json:"usageModel"`
	RailSort   string `json:"railSort"`
	Theme      string `json:"theme"`
}

PrefsInfo is the `prefs` object inside a snapshot (docs/protocol.md §3.3/§5.2). M2 makes this persist (kv) and broadcast; Density is new in M2 (the Tiles grid density). UsageModel is new in the usage-model-bar plan (2026-08-30): which modelScoped entry the masthead's third readout shows, default "Fable". RailSort is new in the order-sidebar plan (2026-08-30): the rail's sort mode, "manual" | "attention", default "manual". Theme is new in new-ui-design-colors (2026-09-02): opaque to the daemon beyond its pattern (docs/protocol.md §3.3), default "follow".

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server holds musterd's HTTP mux and the long-lived pieces (ingest queue, WS registry, session manager) that outlive any single request.

func New

func New(cfg Config) *Server

New builds a Server and wires its routes. Nothing here starts a goroutine; call Start once the caller is ready to begin processing.

func (*Server) EndAllSessions

func (s *Server) EndAllSessions(ctx context.Context) int

EndAllSessions ends every currently alive session (REQ-3's `-on-exit=kill` path): a final snapshot, tmux kill, and alive:=false for each, before the rest of shutdown proceeds. cmd/musterd calls this itself, ahead of Shutdown, once it has resolved (and, for `-on-exit=ask`, prompted) the final leave/kill decision — Shutdown's own signature is unchanged so it keeps working for every existing caller that never deals with the on-exit policy at all. Returns how many sessions were ended.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the root http.Handler to serve.

func (*Server) LiveSessionCount

func (s *Server) LiveSessionCount() int

LiveSessionCount reports how many sessions are currently alive — used by cmd/musterd's `-on-exit=ask` prompt to decide whether to prompt at all (REQ-3: zero live sessions, no prompt, no log line).

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context)

Shutdown closes every open WS connection (unblocking their handler goroutines, which http.Server.Shutdown cannot do for hijacked connections), stops the liveness poll, and drains the ingest queue, giving up when ctx is done.

func (*Server) Start

func (s *Server) Start()

Start reloads persisted sessions, reconciles them against tmux reality, begins the liveness poll, and begins asynchronous ingest processing. Call once, after New — Reconcile (m4-reconcile REQ-1) runs synchronously here, before the caller starts accepting connections, so neither `/ws` nor `GET /api/state` can observe a pre-reconcile session list.

func (*Server) TmuxSocket

func (s *Server) TmuxSocket() string

TmuxSocket returns the dedicated tmux socket this daemon instance is bound to — used by cmd/musterd's `-on-exit=ask` prompt copy ("N live sessions on tmux socket X").

type Snapshot

type Snapshot struct {
	Sessions    []sessionWire   `json:"sessions"`
	Usage       UsageInfo       `json:"usage"`
	Prefs       PrefsInfo       `json:"prefs"`
	ClaudeTheme ClaudeThemeInfo `json:"claudeTheme"`
}

Snapshot is the daemon's full state, shared verbatim (aside from the WS "type" wrapper) between GET /api/state and the WS `snapshot` message (docs/protocol.md §5.2) — one function builds it so the two can never drift apart.

type UsageBucket

type UsageBucket struct {
	UsedPct  float64 `json:"usedPct"`
	ResetsAt string  `json:"resetsAt"`
}

UsageBucket is one of the five-hour/seven-day usage readouts (docs/protocol.md §5.4). M0 never populates it; it exists so the null shape below type-checks.

type UsageInfo

type UsageInfo struct {
	FiveHour          *UsageBucket       `json:"fiveHour"`
	SevenDay          *UsageBucket       `json:"sevenDay"`
	Model             *sessionWireModel  `json:"model"`
	SampledAt         *string            `json:"sampledAt"`
	Source            string             `json:"source"`
	ModelScoped       []UsageModelWindow `json:"modelScoped"`
	ModelScopedAt     *string            `json:"modelScopedAt"`
	ModelScopedError  *string            `json:"modelScopedError"`
	ModelScopedSource string             `json:"modelScopedSource"`
}

UsageInfo is the `usage` object inside a snapshot. M0 has no usage samples yet, so every field renders null/"unknown" (REQ-16). Model is new in M3 (§5.4): the freshest sample's model, for the masthead readout — present as an explicit `null` key (no `omitempty`), matching FiveHour/SevenDay/SampledAt: "null iff buckets null" per the plan's Protocol Contract, not an absent key. ModelScoped/ModelScopedAt/ModelScopedError are the second, independent usage source (plan usage-model-bar, 2026-08-30): nil slice/pointer renders `null` on the wire (no `omitempty`) until the first successful fetch; an empty-but-non-nil ModelScoped is a valid, distinct successful result. ModelScopedSource is a wire constant in v1, never empty.

type UsageModelWindow

type UsageModelWindow struct {
	DisplayName string  `json:"displayName"`
	UsedPct     float64 `json:"usedPct"`
	ResetsAt    string  `json:"resetsAt"`
}

UsageModelWindow is one entry of the `usage.modelScoped` list (docs/protocol.md §5.4, plan usage-model-bar) — one per-model weekly usage window.

Jump to

Keyboard shortcuts

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