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; 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; 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; 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; 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