/**
 * launchd service manager implementation.
 *
 * Generates plist files and manages service lifecycle via launchctl.
 * Supports both system-level (/Library/LaunchDaemons/) and
 * user-level (~/Library/LaunchAgents/) agents.
 */
import type { ServiceConfig, ServiceManager, ServiceStatus, LogOptions, DiscoveredInstance } from './types.js';
export declare class LaunchdServiceManager implements ServiceManager {
    /** Build the launchd label for a service. */
    private label;
    /** Resolve the log directory based on service level. */
    private logDir;
    /** Resolve the plist file path based on service level. */
    private plistPath;
    /** Parse a .env file into key-value pairs. */
    private parseEnvFile;
    /** Escape XML special characters. */
    private escapeXml;
    /**
     * Generate a launchd plist XML string from a service config.
     * Visible for testing.
     */
    generatePlist(config: ServiceConfig): string;
    /**
     * Resolve the launchctl domain string for a service config.
     *
     *   - User-level (LaunchAgent at `~/Library/LaunchAgents/…`) → `gui/<uid>`
     *   - System-level (LaunchDaemon at `/Library/LaunchDaemons/…`) → `system`
     *
     * The legacy `launchctl load` / `unload` verbs implicitly inferred the
     * domain from the plist's filesystem location, but they fail silently on
     * headless macOS hosts where no GUI session exists (the `gui/<uid>` domain
     * is not available without a console login). The modern `bootstrap` /
     * `bootout` / `kickstart` / `kill` verbs take the domain explicitly, so a
     * system-level LaunchDaemon loads regardless of whether anyone is logged
     * in at the console — exactly what we need for headless deploy targets.
     */
    private domain;
    /** `<domain>/<label>` — the target string for kickstart/kill/print/enable. */
    private domainTarget;
    /** Is the service currently loaded in its target domain? */
    private isLoaded;
    install(config: ServiceConfig): Promise<void>;
    /**
     * Poll until the service is no longer loaded in its target domain, or a
     * short deadline elapses. `launchctl bootout` is asynchronous — it returns
     * before launchd has finished releasing the service — so a bootstrap issued
     * immediately afterward races the teardown and fails with EIO. Waiting for
     * the unload to complete closes that race for the common case; the residual
     * window is covered by bootstrapWithRetry.
     */
    private waitUntilUnloaded;
    /**
     * Bootstrap into the target domain, retrying on the transient EIO
     * ("5: Input/output error") launchd returns when a just-removed service has
     * not finished tearing down. A genuine, non-transient failure (bad plist,
     * permission denied) is re-thrown on the first attempt. If a stale instance
     * reappears between attempts, it is booted out before the next try.
     */
    private bootstrapWithRetry;
    uninstall(config: ServiceConfig): Promise<void>;
    start(config: ServiceConfig): Promise<void>;
    stop(config: ServiceConfig): Promise<void>;
    restart(config: ServiceConfig): Promise<void>;
    status(config: ServiceConfig): Promise<ServiceStatus>;
    logs(config: ServiceConfig, options: LogOptions): Promise<void>;
    isInstalled(config: ServiceConfig): Promise<boolean>;
    list(isUserLevel: boolean): Promise<DiscoveredInstance[]>;
}
//# sourceMappingURL=launchd.d.ts.map