/** * Public surface for the cadre-host local UI server. * * `createLocalUiServer(opts)` wires: * - the origin guard + error handler * - existing NAT / update typed handlers, mounted at `/nat/*`, `/update/*` * (the CLI contract) * - the `/api/*` routes (status, nodes, hosted nodes, settings, events) * - the static SPA mount at `/` * * Callers (cadre-host start) own the subsystems and pass them in. The * server doesn't construct them — start has to own their lifecycle anyway * (orchestrator init, NAT bring-up, update timer). */ import type { FastifyInstance } from 'fastify'; import type { HostProcessOrchestrator } from '../orchestrator/index.js'; import type { NatService } from '../nat/index.js'; import type { UpdateService } from '../update/index.js'; import type { HostedNodeService } from '../hosted/index.js'; import { EventBus } from './events/bus.js'; import { HostSettingsStore } from './settings-store.js'; export interface LocalUiServerOptions { /** Configured UI port from host.config.json. May be re-bound on collision. */ uiPort: number; /** Resolved data dir (passed by cadre-host start). */ dataDir: string; /** Wired dependencies — all owned by the caller. */ orchestrator: HostProcessOrchestrator; /** The NAT layer: every node the host runs gets its ports mapped, so `/nat/*` always mounts. */ nat: NatService; /** Optional — 6.4.2 lands this; nullable while still iterating. */ update?: UpdateService; /** * The hosted-node service. When present, mounts `/api/hosted-nodes` and * forwards the service's changes to the bus as `hosted-nodes-changed`. * Optional so callers/tests that don't exercise hosted nodes need not wire it. */ hostedNodes?: HostedNodeService; /** Settings I/O facade. Defaults to a new one rooted at `dataDir`. */ settingsStore?: HostSettingsStore; /** Bus instance — pass the same one to subsystems that publish events. */ events?: EventBus; /** * Static asset root override. Defaults to `/dist/ui`. When the * directory is absent the root handler returns a placeholder HTML. */ staticRoot?: string; /** * Test-only: bind to port 0 (OS picks any free port). The returned URL * reflects the actual bound port. */ forcePort?: 0; /** * SSE heartbeat interval (ms). Defaults to 15 000 ms. Tests reduce this * so the heartbeat can be observed quickly. */ sseHeartbeatMs?: number; } export interface LocalUiServer { app: FastifyInstance; events: EventBus; /** Resolves with the actually-bound port. */ start(): Promise<{ port: number; url: string; }>; stop(): Promise; } export declare function createLocalUiServer(opts: LocalUiServerOptions): LocalUiServer; export { EventBus } from './events/bus.js'; export type { LocalUiEventListener } from './events/bus.js'; export type { LocalUiEvent, LocalUiEventType } from './events/types.js'; export { HostSettingsStore } from './settings-store.js';