import { type MachineLockDeps } from '../machine-lock.js'; import { type PortLane } from '../port-lane.js'; import type { UpdateTag } from '../update/notice.js'; export interface DevSlotEntry { slot: number; /** Epoch ms of the last `bitmagic dev` here — what decides who is evicted when the lane is full. */ lastUsedAt: number; } export declare function devPortsPath(baseDir?: string): string; /** The key a project is filed under: its real path, so a symlinked folder is the same project. */ export declare function projectKey(root: string): string; export interface DevPortsReadOptions { baseDir?: string; tag?: UpdateTag; } /** The slot this project holds on this install's line, or null when it has never run `dev`. */ export declare function readDevSlot(root: string, options?: DevPortsReadOptions): number | null; /** * Every port some project on this machine has been promised in this lane. * * What `reserveLanePort` (local-port.ts) keeps its throwaway ports away from: a verify that * happened to take 3021 while project B's dev was down would push B's next `dev` into a drift, * which is the instability the registry exists to end. `excludeRoot` leaves one project's own * ports out — `dev` wants to avoid its NEIGHBOURS' slots, not its own. */ export declare function registeredSlotPorts(lane: PortLane, options?: DevPortsReadOptions & { excludeRoot?: string; }): Set; export interface ClaimDevSlotDeps extends DevPortsReadOptions { now?: () => number; /** Whether a `bitmagic dev` is serving `root` right now — an entry that must not be evicted. */ isLive?: (root: string) => boolean; lock?: MachineLockDeps; } /** * The slot this project holds, taking one if it has none. * * An existing entry is kept and touched. A new project gets the lowest free slot. When every slot * is held, the entry least recently used whose project has no live `dev` is evicted — a deleted * project has no handle and simply ages out — and a lane where all nine are live right now yields * null, which `dev` answers with the old drifting scan rather than with a refusal. */ export declare function claimDevSlot(root: string, deps?: ClaimDevSlotDeps): Promise; /** * Where a `bitmagic dev` for this project would be listening, or null when there is no reason to * think one exists. * * The recorded handle first: it is what `dev` actually bound, explicit `--editor-port` included. * Then the project's slot, which is where a `dev` started without flags lands. Then NOTHING — not * the lane default. A project that has never run `dev` has no port of its own, and probing the * default reached whichever other project holds slot 0. */ export declare function preferredEditorPort(root: string, options?: DevPortsReadOptions): number | null; /** * Is a `bitmagic dev` serving this project — asked the way `reload` asks it? * * The handle is the fast path, and deliberately: it answers without touching the network, and a * live pid holding it is not something a probe can contradict. A stale handle (a `dev` killed * with SIGKILL leaves one) still names the likeliest port, so that is probed; so is the project's * slot. `GET /api/state` is the probe because it is the cheapest read-only route the dev view * serves, and it names the game it serves, so a server answering for some OTHER project on the * same port is "not running", not "running". */ export declare function isDevServerRunning(root: string, deps?: { fetch?: typeof globalThis.fetch; gameId?: string; } & DevPortsReadOptions): Promise;