interface BrandConfig {
    configDir: string;
    productName: string;
    vncDisplay: number;
}
export declare function loadBrand(): BrandConfig;
/**
 * Test-only: reset the cached brand so a subsequent loadBrand() re-reads
 * the manifest. Production code never calls this.
 */
export declare function _resetBrandCache(): void;
export declare function findBinary(): string | null;
export declare function isInstalled(): boolean;
export declare function version(): string | null;
interface AccountBinding {
    accountId: string;
    boundAt: string;
}
export declare function readAccountBinding(): AccountBinding | null;
/**
 * @internal — production code MUST go through `materializeBinding` so the
 * source-of-truth lifecycle (fresh-login vs migration) is preserved and
 * logged. Exported only so unit tests can pre-seed bindings without
 * re-implementing the file format.
 */
export declare function writeAccountBinding(accountId: string): AccountBinding;
export type RefusalReason = "account-drift" | "unbound-device" | "post-flight-fqdn-mismatch" | "bound-account-does-not-own-hostname" | "hostname-zone-not-routable";
export interface RefusalDetail {
    reason: RefusalReason;
    message: string;
    /** Structured fields for the [cloudflare:refuse] log line. */
    fields?: {
        requestedHostname?: string;
        requestedDomain?: string;
        boundAccountId?: string | null;
        certAccountId?: string | null;
        actualFqdn?: string;
        tunnelId?: string;
    };
}
export declare function logRefuse(detail: RefusalDetail): void;
/**
 * The only recovery instruction — every refusal that involves the wrong
 * Cloudflare account points here. Single source of truth so the four
 * surfaces that quote it (tunnel-status, tunnel-add-hostname, personal
 * assistant template, setup-tunnel skill) cannot drift.
 */
export declare const DASHBOARD_SWITCH_ACCOUNTS_INSTRUCTION: string;
export declare function recoveryMessage(): string;
export declare function resetAuth(): {
    deletedCert: boolean;
    deletedBinding: boolean;
};
export declare function hasCert(): boolean;
interface CertIdentity {
    accountId: string;
}
/**
 * Parse the ARGO TUNNEL TOKEN PEM block from cert.pem to extract the
 * AccountTag. Only the account ID is returned — nothing else in cert.pem
 * is used by this codebase. The cert itself stays on disk; `cloudflared`
 * shell-outs read it via `--origincert` for tunnel CRUD and DNS routing.
 */
export declare function parseCertPem(): CertIdentity | null;
/** Wraps a structured RefusalDetail so call sites can branch on `reason`. */
export declare class CloudflareRefusalError extends Error {
    refusal: RefusalDetail;
    constructor(detail: RefusalDetail);
}
export interface AuthValidation {
    hasCert: boolean;
    hasBinding: boolean;
    /** True when both present AND cert.accountId === binding.accountId. */
    bound: boolean;
    certAccountId: string | null;
    boundAccountId: string | null;
}
export declare function validateAuth(): AuthValidation;
/**
 * Establish the device-local account binding from cert.pem. Used by:
 *   (1) tunnel-login fresh-success path — first time creds are derived.
 *   (2) migration path — when cert.pem exists from a prior install but no
 *       binding has yet been written (silent materialization, since the
 *       cert was already trust-established by the operator's prior login).
 */
export declare function materializeBinding(source: "fresh-login" | "migration"): AccountBinding;
export declare function createTunnelCli(name: string): {
    tunnelId: string;
    tunnelName: string;
    credentialsPath: string;
};
export declare function getTunnelTokenCli(tunnelId: string): string;
/**
 * Write a local config.yml for cloudflared tunnel run.
 * This is the ONLY tunnel startup configuration — no remote config, no API.
 * hostnames: the actual FQDNs to route (e.g. ["joel.maxy.bot", "shop.maxy.bot"]).
 */
export declare function writeLocalConfig(tunnelId: string, credentialsPath: string, hostnames: string[], port: number): string;
export declare function parseConfiguredHostnames(configYmlPath: string): string[];
export interface NsPreflightResult {
    zoneParent: string;
    nameservers: string[];
    onCloudflare: boolean;
    error?: string;
}
export declare function checkZoneParentOnCloudflare(hostname: string): Promise<NsPreflightResult>;
export declare function routeDnsCli(tunnelId: string, hostname: string): Promise<{
    created: boolean;
    output: string;
    fqdn: string;
}>;
interface LoginState {
    pid: number;
    startedAt: number;
    authUrl: string;
    logPath: string;
    /**
     * Byte offset in `logPath` at the moment this login was spawned. Every
     * subsequent failure-marker parse slices the log from this offset forward
     * so historical markers from prior (reset + respawned) attempts cannot
     * re-trigger the failed state. Without this, `cloudflared-login.log`
     * being append-mode across spawns would cause the handler to loop on
     * old "Failed to fetch resource" lines forever after the first recovery.
     *
     * Defaults to 0 when absent (backward compatibility with pre-541 state
     * files): a zero offset scans the whole file, which is the pre-541
     * behaviour — safe as a one-time fallthrough because the handler's
     * `failed` branch calls `resetAuth()` which clears the login state,
     * guaranteeing the next spawn writes a state file with a real offset.
     */
    logOffset?: number;
}
export interface TunnelLoginResult {
    authUrl: string;
    alreadyRunning?: boolean;
}
/**
 * Login process state from the agent's perspective. Three states plus one
 * non-terminal advisory decoration.
 *
 *   in-progress               — PID alive. The OAuth-callback loop is still
 *                               listening, regardless of what the log
 *                               contains. `browserLaunchFailed: true` when
 *                               the courtesy-marker is present: cloudflared
 *                               attempted to open the browser itself and
 *                               couldn't (headless spawn context, DISPLAY
 *                               unreachable, etc.), but the sign-in can
 *                               still complete if the user opens the auth
 *                               URL in any reachable browser.
 *   failed                    — PID dead AND cert.pem absent. The login
 *                               process tore down without writing the cert.
 *                               Caller must restart. `reason` distinguishes
 *                               *why* the process is gone: `-with-marker`
 *                               when the courtesy marker was observed during
 *                               its lifetime, `-without-marker` otherwise.
 *                               Both variants trigger the same handler
 *                               response — reason is for log-side
 *                               observability only.
 *   idle                      — nothing recorded; fresh spawn required.
 *
 * `complete` (cert.pem written) is determined upstream in the tunnel-login
 * handler via `findCert()` / `validateAuth().bound` before this helper is
 * called — so it is not represented here.
 *
 * rewrite: the old `failed` state fired on marker-alone, which
 * looped restart-respawn hermetically because cloudflared re-emits the
 * courtesy marker on every spawn whose browser-launch subcommand fails.
 * Liveness (PID alive) is now the sole discriminant for in-progress; the
 * marker is informational and drives an operator-facing advisory, not a
 * restart.
 */
export type LoginStatus = {
    state: "in-progress";
    loginState: LoginState;
    browserLaunchFailed: boolean;
} | {
    state: "failed";
    loginState: LoginState | null;
    reason: "login-process-exited-without-cert" | "login-process-exited-with-marker";
} | {
    state: "idle";
};
export declare function getLoginStatus(): LoginStatus;
export declare function tunnelLogin(): Promise<TunnelLoginResult>;
interface TunnelState {
    tunnelId: string;
    tunnelName: string;
    domain: string;
    configPath: string;
    credentialsPath: string;
    adminHostname: string | null;
    publicHostname: string | null;
    pid: number | null;
    startedAt: number | null;
    connectorToken?: string;
}
export declare function getPersistedTunnelId(): string | null;
export declare function getPersistedDomain(): string | null;
export declare function getPersistedState(): TunnelState | null;
export declare function saveTunnelIdentity(params: {
    tunnelId: string;
    tunnelName: string;
    domain: string;
    configPath: string;
    credentialsPath: string;
    adminHostname: string;
    publicHostname: string | null;
}): void;
/**
 * Return the actual hostnames from persisted state (or derive from domain
 * for pre-521 state files).
 */
export declare function getPersistedHostnames(): string[];
export type HostnameFailureMode = "ok" | "dns-missing" | "cname-points-elsewhere" | "edge-unreachable" | "tunnel-not-matched";
/**
 * Exhaustive enum of reasons `tunnel-status` can return `healthy: false`.
 * The agent's recovery sentence is a total function of this enum — every
 * arm must map to exactly one prescribed sentence in the `tunnel-status`
 * handler's `buildTunnelStatusGuidance`. Adding a value here without
 * updating the handler is a compile error (exhaustiveness check).
 */
export type TunnelUnhealthyReason = "not-running" | "no-tunnel-configured" | "bound-account-does-not-own-hostname" | "hostname-probes-failed";
export interface HostnameProbeResult {
    hostname: string;
    resolves: boolean;
    cname: string | null;
    httpStatus: number | null;
    failureMode: HostnameFailureMode;
}
export declare function probeHostname(hostname: string, expectedTunnelId: string | null): Promise<HostnameProbeResult>;
export interface TunnelStatus {
    installed: boolean;
    version: string | null;
    hasCert: boolean;
    hasBinding: boolean;
    /** True when cert and binding agree. */
    bound: boolean;
    certAccountId: string | null;
    boundAccountId: string | null;
    running: boolean;
    pid: number | null;
    tunnelId: string | null;
    domain: string | null;
    /** Hostnames parsed from config.yml (authoritative for ingress). */
    configuredHostnames: string[];
    /** Hostnames from tunnel.state — may diverge from config.yml. */
    persistedHostnames: string[];
    adminHostname: string | null;
    publicHostname: string | null;
    upSince: number | null;
    probes: HostnameProbeResult[];
    /**
     * True iff tunnel is running AND at least one hostname's CNAME points
     * back to this tunnel (proving the signed-in Cloudflare account owns
     * the domain being served). False means the laptop is running a tunnel
     * that nothing on the internet is reaching — almost always "wrong
     * Cloudflare account signed in."
     */
    boundAccountOwnsHostnames: boolean;
    /**
     * Aggregated health: true only when the tunnel is running, at least one
     * hostname is configured, and every configured hostname probes `ok`.
     */
    healthy: boolean;
    /**
     * Structured reason for unhealthy status. "no-tunnel-configured" when
     * config.yml or hostnames are absent; "not-running" when cloudflared is
     * not alive; "bound-account-does-not-own-hostname" when the dominant
     * failure mode is wrong-account; "hostname-probes-failed" otherwise.
     */
    unhealthyReason: TunnelUnhealthyReason | null;
}
export declare function getStatus(domain?: string): Promise<TunnelStatus>;
export declare function startTunnel(params: {
    tunnelId: string;
    tunnelName: string;
    domain: string;
    configPath: string;
    credentialsPath: string;
}): void;
export declare function stopTunnel(): void;
export {};
//# sourceMappingURL=cloudflared.d.ts.map