Documentation
¶
Overview ¶
Package server wires up musterd's HTTP+WS surface. server.go is the composition root only (docs/conventions.md § Composition roots): it builds Config, registers each feature once, and owns the core routes/lifecycle — every other endpoint is a feature method.
Index ¶
- type ClaudeCodeInfo
- type ClaudeThemeInfo
- type Config
- type IssueConfig
- type LaunchConfig
- type PrefsInfo
- type RepoRefreshConfig
- type Server
- func (s *Server) EndAllSessions(ctx context.Context) int
- func (s *Server) Handler() http.Handler
- func (s *Server) KillAllShells(ctx context.Context) (int, error)
- func (s *Server) LiveSessionCount() int
- func (s *Server) RestartRequests() <-chan struct{}
- func (s *Server) ShellCount(ctx context.Context) (int, error)
- func (s *Server) Shutdown(ctx context.Context)
- func (s *Server) Start()
- func (s *Server) StopLivenessPoll(ctx context.Context)
- func (s *Server) TmuxSocket() string
- type Snapshot
- type ThemeConfig
- type UpdateApplyInfo
- type UpdateConfig
- type UpdateInfo
- type UsageBucket
- type UsageConfig
- type UsageInfo
- type UsageModelWindow
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ClaudeCodeInfo ¶
ClaudeCodeInfo is the daemon's startup snapshot of the installed Claude Code against the canary-verified range, used to build the WS `hello` message (kb:anchor/ws.hello) 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. A Config field (server.go, not ws.go): main computes it once, at startup, before New exists.
type ClaudeThemeInfo ¶
type ClaudeThemeInfo struct {
Family string `json:"family"`
}
ClaudeThemeInfo is the `claudeTheme` object inside a snapshot (kb:anchor/ws.snapshot / kb:anchor/ws.claude-theme) — 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, non-empty, serves the dashboard from disk instead of the embedded copy.
WebDist string
DaemonVersion string
ClaudeCode ClaudeCodeInfo
IngestQueueSize int
TmuxSocket string
// HTTPClient is shared by every feature with outbound HTTP calls (usage, issue,
// update); nil defaults to http.DefaultClient, resolved once by New (like TmuxClient
// and Attach below) since all three features need the same default.
HTTPClient *http.Client
// TmuxClient overrides the tmux client sessionLauncher/shellRegistry use; nil constructs tmux.New(cfg.TmuxSocket).
TmuxClient paneSpawner
// Attach overrides how a terminal socket attaches to a tmux target; nil uses termbridge.Attach.
Attach attachFunc
// ShellScroll overrides the shell socket's copy-mode driver (kb:anchor/terminal.shell-ws); nil
// uses the real tmux client (the same one TmuxClient's override does not affect —
// production always wants real tmux copy-mode commands regardless of a paneSpawner
// test double).
ShellScroll shellScroller
// Locator resolves a dropped file's path (kb:anchor/sessions.locate); nil answers 500, never a panic.
Locator *locate.Locator
// InterruptChecker is handed to the session manager's interrupt sweep
// (session.Config.InterruptChecker); nil disables it. cmd/musterd wires the Claude Code
// transcript reader, so internal/server stays free of transcript knowledge.
InterruptChecker session.InterruptChecker
Launch LaunchConfig
Usage UsageConfig
Theme ThemeConfig
Issue IssueConfig
Update UpdateConfig
RepoRefresh RepoRefreshConfig
}
Config wires everything a Server needs, built entirely in main (no init() magic, no package-level state). Core fields are shared by more than one feature or by the root itself; everything else groups into one sub-struct per feature, declared in that feature's own file.
type IssueConfig ¶ added in v0.12.2
type IssueConfig struct {
// Repo is the GitHub repo issues are filed against.
Repo string
// APIURL is the GitHub API's base 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 (kb:anchor/issue.captures / kb:anchor/issue.create).
APIURL string
// TokenFile, when non-empty, reads the bearer token from this file's trimmed
// contents instead of running `gh auth token` — a test seam that makes it
// structurally impossible for a test using it to execute the real gh.
TokenFile string
}
IssueConfig groups the file-an-issue button's config. Empty APIURL disables both endpoints entirely (mirrors UsageConfig.APIURL's shape): a zero-value Config must never reach the real GitHub API host or execute `gh`.
type LaunchConfig ¶ added in v0.12.2
type LaunchConfig struct {
// ClaudeBin is the `claude` binary to spawn (default "claude").
ClaudeBin 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.
HookScript string
StatusLineScript string
// LegacyScripts lists prior wrapper paths MergeSettings must still recognise and
// drop from an already-instrumented directory.
LegacyScripts []string
// BrowseRoot is the folder browser's root (kb:anchor/browse.get): GET /api/browse's
// no-param default and the "Up" ceiling. Empty means the daemon user's home
// directory.
BrowseRoot string
// ProjectsDir is Claude Code's transcript store (claudecode.ProjectsDir's default, or
// -claude-projects-dir): the resume-from-list branch's and GET /api/past-sessions'
// one read root, read-only.
ProjectsDir string
}
LaunchConfig groups the launch path's config: the claude binary, the generated wrapper script paths, and the folder browser's root — every field sessionLauncher/browseFeature need, declared here since launcher.go is the launch service's home file.
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"`
UpdateCheck bool `json:"updateCheck"`
RailDensity string `json:"railDensity"`
RailActivity string `json:"railActivity"`
}
PrefsInfo is the `prefs` object inside a snapshot (kb:anchor/prefs.put / kb:anchor/ws.snapshot), persisted in kv and broadcast on change. Density is the Tiles grid density. UsageModel is which modelScoped entry the masthead's third readout shows, default "Fable". RailSort is the rail's sort mode, "manual" | "attention", default "manual". Theme is opaque to the daemon beyond its pattern (kb:anchor/prefs.put), default "follow". UpdateCheck is whether the daemon checks GitHub Releases for a newer musterd, default true. RailDensity and RailActivity are the rail/Tiles-strip card density ("compact" | "comfortable" | "expanded", default "comfortable") and which text a card's activity line shows ("turn" | "prompt" | "reply" | "both", default "turn").
type RepoRefreshConfig ¶ added in v0.19.4
type RepoRefreshConfig struct {
// Poll is the poll interval (-repo-poll). <= 0 runs no timer: the poll then runs once at
// start and on a nudge only (kb:adr/lifecycle-branch-refreshed-by-repo-poll).
Poll time.Duration
}
RepoRefreshConfig groups the repo poll's config.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server holds the HTTP mux and long-lived pieces, plus one field per feature.
func New ¶
New builds a Server and wires its routes. Nothing here starts a goroutine; call Start once the caller is ready to begin processing. Its length is one construction-plus-register line per feature (16 features) plus the four Config overrides below whose default is another root-built object (spawner, attach, httpClient, shellScroll) — every one of those four is resolved here, in this one place, and nowhere else (kb:adr/process-size-linters-warn-never-fail: the funlen warning this trips has that recorded reason; no split asked).
func (*Server) EndAllSessions ¶
EndAllSessions ends every alive session (the `-on-exit=kill` path), ahead of Shutdown. Returns how many were ended.
func (*Server) KillAllShells ¶ added in v0.14.1
KillAllShells kills every shell tmux session on the socket — the `-on-exit=kill` companion to EndAllSessions, run after it (sessions may hold the socket busy), per kb:adr/surfaces-shell-dies-at-kill-shutdown-too.
func (*Server) LiveSessionCount ¶
LiveSessionCount reports how many sessions are alive — cmd/musterd's `-on-exit=ask` uses it to decide whether to prompt at all.
func (*Server) RestartRequests ¶ added in v0.12.0
func (s *Server) RestartRequests() <-chan struct{}
RestartRequests reports each in-place re-exec an apply requests (kb:anchor/update.apply). A disabled update feature yields a nil channel, so a select never fires on it.
func (*Server) ShellCount ¶ added in v0.14.1
ShellCount counts every shell tmux session on the socket — the on-exit prompt's count, per kb:adr/surfaces-shell-dies-at-kill-shutdown-too.
func (*Server) Shutdown ¶
Shutdown closes every open WS connection (unblocking hijacked-connection goroutines http.Server.Shutdown cannot reach) and terminal socket, stops the liveness poll, then stops every lifecycle feature in Start's registration order.
func (*Server) Start ¶
func (s *Server) Start()
Start reloads and reconciles sessions, then starts every lifecycle feature in registration order. Call once, after New — Reconcile runs synchronously here so neither `/ws` nor `GET /api/state` can observe a pre-reconcile session list.
func (*Server) StopLivenessPoll ¶ added in v0.14.1
StopLivenessPoll stops the session manager's background liveness poll and the repo poll that reads the same rows, and waits for them to exit, without touching any other feature. Call this first, before any other shutdown step (ShellCount's tmux round trip, the on-exit prompt, EndAllSessions/KillAllShells): those steps run for up to several seconds after the shutdown signal arrives, and the poll's own 5s ticker is not otherwise paused during that window. A pane that dies right then would otherwise race its own liveness tick against the fresh process's next-startup Reconcile — the still-shutting-down process marking+persisting alive:false before the row is ever handed to Reconcile makes Reconcile sweep it instead of keeping it ended (kb:adr/lifecycle-reconcile-converges-with-the-socket only keeps a row that was still alive:true at Reconcile time). Deliberately narrower than Shutdown, which also closes WS/terminal connections and stops every other feature that EndAllSessions/KillAllShells still need; Shutdown stops the poll again itself (idempotent) for callers, such as the auto-update restart path, that go straight to it without this earlier call.
func (*Server) TmuxSocket ¶
TmuxSocket returns the dedicated tmux socket this daemon instance is bound to.
type Snapshot ¶
type Snapshot struct {
Sessions []sessionWire `json:"sessions"`
Usage UsageInfo `json:"usage"`
Prefs PrefsInfo `json:"prefs"`
ClaudeTheme ClaudeThemeInfo `json:"claudeTheme"`
// Update (kb:anchor/ws.update): always present, even on a daemon with updates disabled
// entirely.
Update UpdateInfo `json:"update"`
// ShellsBusy (kb:anchor/ws.shell-activity): session ids whose shell is busy right now,
// always present as [] when none — never null — so a reconnecting client re-syncs
// without waiting for a shellActivity transition.
ShellsBusy []int64 `json:"shellsBusy"`
// Groups and Ungrouped (kb:anchor/ws.groups): the rail's sections, always present — Groups
// as [] when there are none.
Groups []groupWire `json:"groups"`
Ungrouped ungroupedWire `json:"ungrouped"`
}
Snapshot is the daemon's full state, shared verbatim (aside from the WS "type" wrapper) between GET /api/state and the WS `snapshot` message (kb:anchor/ws.snapshot) — one function builds it so the two can never drift apart.
type ThemeConfig ¶ added in v0.12.2
type ThemeConfig struct {
// Poll is the poll interval. <= 0 disables polling entirely: snapshot.claudeTheme.family
// stays "unknown" forever.
Poll time.Duration
// ConfigFile is Claude Code's global config file to poll
// (claudecode.DefaultConfigPath() in production) — a test seam like UsageConfig.TokenFile.
ConfigFile string
}
ThemeConfig groups the Claude-theme poller's config. Poll <= 0 means the poller is never constructed at all (mirrors UsageConfig.Poll's shape) — a zero-value Config never opens ConfigFile.
type UpdateApplyInfo ¶ added in v0.12.0
type UpdateApplyInfo struct {
Phase string `json:"phase"`
Version *string `json:"version"`
Error *string `json:"error"`
}
UpdateApplyInfo is the `update.apply` object (kb:anchor/ws.update). Error is non-nil iff Phase is "failed"; Version is nil iff Phase is "idle".
type UpdateConfig ¶ added in v0.12.2
type UpdateConfig struct {
// BaseURL is the GitHub Releases base URL; "" disables checking and apply entirely.
BaseURL string
// CheckInterval is how often the daemon re-checks for a newer release.
CheckInterval time.Duration
// PublicKey is the minisign public key file's raw bytes verification trusts
// (overrides the embedded selfupdate.PublicKey() — a test seam; production always
// passes the embedded key).
PublicKey []byte
// Install is the startup install classification (selfupdate.Classify), computed
// once in cmd/musterd from the resolved executable path. installer/unmanaged are
// re-derived at the start of every check by the Reclassify closure below, from
// exePath/home/the write probe, not from this field; Install only decides whether
// that re-derivation runs (dev/homebrew stay fixed for the daemon's life).
Install selfupdate.Install
// ExePath is the resolved (os.Executable + filepath.EvalSymlinks) real path of the
// running binary — where an apply installs the new one and what swap detection stats.
ExePath string
// Reclassify re-derives installer/unmanaged (kb:adr/update-install-rechecked-on-every-check),
// built in cmd/musterd from the same exePath/home/access inputs that produced Install.
// Nil (every test daemon that doesn't exercise reclassification) means installer/
// unmanaged never change after startup.
Reclassify func() selfupdate.Install
}
UpdateConfig groups auto-update's checker/apply config. BaseURL empty means no updateManager is constructed at all (mirrors UsageConfig's nil-when-disabled shape) — a zero-value Config must never reach github.com.
type UpdateInfo ¶ added in v0.12.0
type UpdateInfo struct {
Running string `json:"running"`
Install string `json:"install"`
Remedy *string `json:"remedy"`
CanCheck bool `json:"canCheck"`
Available *string `json:"available"`
CheckedAt *string `json:"checkedAt"`
Installed *string `json:"installed"`
Apply UpdateApplyInfo `json:"apply"`
}
UpdateInfo is the `update` object, shared verbatim by the WS `update` broadcast and snapshot.update (kb:anchor/ws.update) — always present, even on a daemon with updates disabled entirely (-update-base-url "").
type UsageBucket ¶
UsageBucket is one of the five-hour/seven-day usage readouts (kb:anchor/ws.usage).
type UsageConfig ¶ added in v0.12.2
type UsageConfig struct {
// Poll is the poll interval. <= 0 disables polling entirely: POST /api/usage/refresh
// then 404s (kb:anchor/usage.refresh).
Poll time.Duration
// APIURL is the per-model usage endpoint's base 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 Poll <= 0).
APIURL string
// TokenFile, when non-empty, reads the Claude Code OAuth token from this file
// instead of the macOS Keychain — a test seam that makes it structurally impossible
// for a test using it to fall through to the real Keychain.
TokenFile string
// KeychainUser is the account name `security` looks the Keychain item up under
// (main passes os/user.Current()'s value) — used only when TokenFile is empty.
KeychainUser string
}
UsageConfig groups the per-model usage poller's config. Poll <= 0 means the poller is never constructed at all — a zero-value Config never reaches api.anthropic.com or the Keychain.
type UsageInfo ¶
type UsageInfo struct {
FiveHour *UsageBucket `json:"fiveHour"`
SevenDay *UsageBucket `json:"sevenDay"`
SampledAt *string `json:"sampledAt"`
Source string `json:"source"`
ModelScoped []UsageModelWindow `json:"modelScoped"`
ModelScopedAt *string `json:"modelScopedAt"`
ModelScopedError *string `json:"modelScopedError"`
ModelScopedSource string `json:"modelScopedSource"`
}
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 (kb:anchor/ws.usage, kb:adr/usage-masthead-one-selectable-model-window) — one per-model weekly usage window.
Source Files
¶
- actions.go
- auth.go
- bgloop.go
- browse.go
- groups.go
- ingest.go
- issue.go
- issuecapture.go
- issuesnapshot.go
- launchcreate.go
- launcher.go
- launchergroup.go
- launchermodels.go
- launcherpast.go
- launcherpastlist.go
- launcherrors.go
- locate.go
- prefs.go
- rail.go
- reader.go
- readerwire.go
- rename.go
- reporefresh.go
- repos.go
- respond.go
- server.go
- sessionwire.go
- shellactivity.go
- shells.go
- state.go
- terminal.go
- themepoll.go
- update.go
- updatemanager.go
- updatewire.go
- usage.go
- usagepoll.go
- usagewire.go
- ws.go