packages/shared/src/relayClient.ts

import * as Cache from "effect/Cache";
import * as Clock from "effect/Clock";
import type {
  RelayClientInstallProgressEvent,
  RelayClientInstallProgressStage,
} from "@t3tools/contracts";
import * as Config from "effect/Config";
import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Hex from "effect/encoding/Hex";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as PlatformError from "effect/PlatformError";
import * as Schedule from "effect/Schedule";
import * as Semaphore from "effect/Semaphore";
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
import { ChildProcess, ChildProcessSpawner } from "effect/process";
import { HostProcessArchitecture, HostProcessPlatform } from "./hostProcess.ts";

export const CLOUDFLARED_VERSION = "2026.10.0";
// The oldest release that accepts every flag the connector is started with
// (`--output` arrived in 2025.6.1). Override, PATH, and older managed binaries
// below it are skipped, since they exit immediately on the unknown flag.
export const CLOUDFLARED_MIN_VERSION = "2025.6.1";
const CLOUDFLARED_PATH_ENV_NAME = "T3CODE_CLOUDFLARED_PATH";

// Generous bound for a ~40MB binary download; without it a stalled
// connection parks the install forever (same unbounded-wait family as the
// other relay/cloud network calls).
const CLOUDFLARED_DOWNLOAD_TIMEOUT = "10 minutes";

export type RelayClientExecutableSource = "override" | "managed" | "path";

export type RelayClientStatus =
  | {
      readonly status: "available";
      readonly executablePath: string;
      readonly source: RelayClientExecutableSource;
      readonly version: string;
    }
  | {
      readonly status: "missing";
      readonly version: string;
    }
  | {
      readonly status: "unsupported";
      readonly platform: NodeJS.Platform;
      readonly arch: string;
      readonly version: string;
    };

export type AvailableRelayClient = Extract<RelayClientStatus, { readonly status: "available" }>;

export class RelayClientInstallError extends Data.TaggedError("RelayClientInstallError")<{
  readonly reason:
    | "download_failed"
    | "invalid_checksum"
    | "install_locked"
    | "override_missing"
    | "unsupported_platform"
    | "validation_failed"
    | "write_failed";
  readonly message: string;
  readonly cause?: unknown;
}> {}

class CloudflaredCommandError extends Data.TaggedError("CloudflaredCommandError")<{
  readonly command: string;
  readonly exitCode: number;
}> {}

export interface CloudflaredReleaseAsset {
  readonly url: string;
  readonly sha256: string;
  readonly archive: "binary" | "tgz";
}

const CLOUDFLARED_RELEASE_ASSETS: Readonly<
  Partial<Record<`${NodeJS.Platform}-${string}`, CloudflaredReleaseAsset>>
> = {
  "darwin-arm64": {
    url: "https://github.com/cloudflare/cloudflared/releases/download/2026.10.0/cloudflared-darwin-arm64.tgz",
    sha256: "a2f79ff7b9420aa537d74af239f376da170bbabeb529aec416002adac6a72e70",
    archive: "tgz",
  },
  "darwin-x64": {
    url: "https://github.com/cloudflare/cloudflared/releases/download/2026.10.0/cloudflared-darwin-amd64.tgz",
    sha256: "903845b81828c8cb3c5d13d816a2de71c06a3da5785469df8eb0e1b736d92f9f",
    archive: "tgz",
  },
  "linux-arm64": {
    url: "https://github.com/cloudflare/cloudflared/releases/download/2026.10.0/cloudflared-linux-arm64",
    sha256: "e6422b9d4f72d3194bc5a38676f13667c06666523217b842a877d72a80b5ac08",
    archive: "binary",
  },
  "linux-x64": {
    url: "https://github.com/cloudflare/cloudflared/releases/download/2026.10.0/cloudflared-linux-amd64",
    sha256: "d33ff2d14475178d2012c2c56beba87389ac5ded27649519f198a7d3134a99db",
    archive: "binary",
  },
  "win32-x64": {
    url: "https://github.com/cloudflare/cloudflared/releases/download/2026.10.0/cloudflared-windows-amd64.exe",
    sha256: "86aee4017b26625cee8484c113558f48effa4cd47f7aa05fcf425604e5d2b23c",
    archive: "binary",
  },
  // Cloudflare publishes no Windows ARM64 build; Windows 11 on ARM runs x64 under emulation.
  "win32-arm64": {
    url: "https://github.com/cloudflare/cloudflared/releases/download/2026.10.0/cloudflared-windows-amd64.exe",
    sha256: "86aee4017b26625cee8484c113558f48effa4cd47f7aa05fcf425604e5d2b23c",
    archive: "binary",
  },
};

const INSTALL_LOCK_RETRY_COUNT = 100;
const INSTALL_LOCK_RETRY_DELAY = "100 millis";
const INSTALL_LOCK_STALE_MS = 5 * 60 * 1_000;
const VERSION_PROBE_TIMEOUT = "10 seconds";

const ACTIVATE_RETRY_COUNT = 40;
const ACTIVATE_RETRY_DELAY = "250 millis";

const trimmedString = (name: string) =>
  Config.String(name).pipe(
    Config.option,
    Config.map(
      Option.flatMap((value) => {
        const trimmed = value.trim();
        return trimmed.length > 0 ? Option.some(trimmed) : Option.none();
      }),
    ),
  );

const CloudflaredConfig = Config.all({
  executableOverride: trimmedString(CLOUDFLARED_PATH_ENV_NAME),
  path: trimmedString("PATH"),
});

export interface CloudflaredRelayClientOptions {
  readonly baseDir: string;
  readonly releaseAsset?: CloudflaredReleaseAsset;
}

export interface RelayClientShape {
  /**
   * Finds the relay client to run, without downloading anything: the override,
   * else the pinned managed folder, else the newest older managed folder, else
   * `cloudflared` on PATH. Every candidate must report a compatible version, and
   * the status carries the version the binary reports.
   */
  readonly resolve: Effect.Effect<RelayClientStatus>;
  /** Installs the pinned managed release unless it, or a valid override, is already present. */
  readonly install: Effect.Effect<AvailableRelayClient, RelayClientInstallError>;
  readonly installWithProgress: (
    report: (event: RelayClientInstallProgressEvent) => Effect.Effect<void>,
  ) => Effect.Effect<AvailableRelayClient, RelayClientInstallError>;
  /** Removes managed releases older than the pin, except the newest one. Call once the pin has connected. */
  readonly pruneManagedVersions: Effect.Effect<void>;
}

/** True when this is the pinned managed release, which needs no update. */
export function isPinnedManagedRelayClient(client: AvailableRelayClient): boolean {
  return client.source === "managed" && client.version === CLOUDFLARED_VERSION;
}

export class RelayClient extends Context.Service<RelayClient, RelayClientShape>()(
  "@t3tools/shared/relayClient",
) {}

function executableFileName(platform: NodeJS.Platform): string {
  return platform === "win32" ? "cloudflared.exe" : "cloudflared";
}

function resolveReleaseAsset(
  platform: NodeJS.Platform,
  arch: string,
): CloudflaredReleaseAsset | null {
  return CLOUDFLARED_RELEASE_ASSETS[`${platform}-${arch}`] ?? null;
}

const VERSION_PATTERN = /\b(\d{4})\.(\d{1,2})\.(\d+)\b/u;

function parseVersion(value: string): readonly [number, number, number] | null {
  const match = VERSION_PATTERN.exec(value);
  return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
}

/** Orders `YYYY.M.P` cloudflared versions; unparseable versions sort first. */
export function compareCloudflaredVersions(left: string, right: string): number {
  const a = parseVersion(left) ?? [0, 0, 0];
  const b = parseVersion(right) ?? [0, 0, 0];
  return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
}

/** Reads the version from `cloudflared version` output, e.g. `cloudflared version 2026.5.2 (built ...)`. */
export function parseCloudflaredVersionOutput(output: string): string | null {
  const version = parseVersion(output);
  return version ? version.join(".") : null;
}

// A changed file is a new key, so a replaced binary is probed again.
class VersionProbeKey extends Data.Class<{
  readonly executablePath: string;
  readonly size: number;
  readonly mtimeMillis: number;
}> {}

function isAlreadyExists(error: PlatformError.PlatformError): boolean {
  return error.reason._tag === "AlreadyExists";
}

// Windows refuses to rename a file another process briefly holds open, such as a virus
// scanner reading the binary that just ran. These codes clear once it lets go.
function isTransientWindowsLock(error: PlatformError.PlatformError): boolean {
  const code = (error.reason.cause as NodeJS.ErrnoException | undefined)?.code;
  return code === "EBUSY" || code === "EPERM" || code === "EACCES";
}

const wrapInstallFailure =
  (
    reason: RelayClientInstallError["reason"],
    message: string,
  ): (<E, R>(
    effect: Effect.Effect<void, E, R>,
  ) => Effect.Effect<void, RelayClientInstallError, R>) =>
  (effect) =>
    effect.pipe(
      Effect.mapError(
        (cause) =>
          new RelayClientInstallError({
            reason,
            message,
            cause,
          }),
      ),
    );

export const makeCloudflaredRelayClient = Effect.fn("cloudflared.make")(function* (
  options: CloudflaredRelayClientOptions,
): Effect.fn.Return<
  RelayClientShape,
  never,
  | ChildProcessSpawner.ChildProcessSpawner
  | Crypto.Crypto
  | FileSystem.FileSystem
  | HttpClient.HttpClient
  | Path.Path
> {
  const crypto = yield* Crypto.Crypto;
  const fileSystem = yield* FileSystem.FileSystem;
  const httpClient = yield* HttpClient.HttpClient;
  const path = yield* Path.Path;
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
  const installSemaphore = yield* Semaphore.make(1);
  const platform = yield* HostProcessPlatform;
  const arch = yield* HostProcessArchitecture;
  const releaseAsset = options.releaseAsset ?? resolveReleaseAsset(platform, arch);
  const loadCloudflaredConfig = Effect.suspend(() => CloudflaredConfig).pipe(Effect.orDie);
  const managedRoot = path.join(options.baseDir, "tools", "cloudflared");
  const managedPathFor = (version: string) =>
    path.join(managedRoot, version, `${platform}-${arch}`, executableFileName(platform));
  const managedPath = managedPathFor(CLOUDFLARED_VERSION);

  const isExecutableFile = Effect.fn("cloudflared.isExecutableFile")(function* (
    executablePath: string,
  ) {
    const info = yield* fileSystem.stat(executablePath).pipe(Effect.option);
    if (Option.isNone(info) || info.value.type !== "File") return false;
    return platform === "win32" || (info.value.mode & 0o111) !== 0;
  });

  // `cloudflared version` costs a process spawn, so answers are cached per file
  // revision. Before `--no-autoupdate`, a managed binary could replace itself in
  // place, so the version is read from the binary, never from its folder name.
  // A failed or timed-out probe is not cached, so a slow spawn under an AV scan
  // does not hide a good binary until it changes on disk.
  const versionCache = yield* Cache.makeWith(
    (key: VersionProbeKey) =>
      spawner
        .string(
          ChildProcess.make(key.executablePath, ["version"], { stdin: "ignore", stderr: "ignore" }),
        )
        .pipe(
          Effect.map(parseCloudflaredVersionOutput),
          Effect.timeoutOption(VERSION_PROBE_TIMEOUT),
          Effect.map(Option.getOrNull),
          Effect.orElseSucceed(() => null),
        ),
    {
      capacity: 16,
      timeToLive: (exit) =>
        Exit.isSuccess(exit) && exit.value !== null ? Duration.infinity : Duration.zero,
    },
  );
  const probeVersion = Effect.fn("cloudflared.probeVersion")(function* (executablePath: string) {
    const info = yield* fileSystem.stat(executablePath).pipe(Effect.option);
    if (Option.isNone(info) || info.value.type !== "File") return null;
    if (platform !== "win32" && (info.value.mode & 0o111) === 0) return null;
    return yield* Cache.get(
      versionCache,
      new VersionProbeKey({
        executablePath,
        size: Number(info.value.size),
        mtimeMillis: Option.getOrUndefined(info.value.mtime)?.getTime() ?? 0,
      }),
    );
  });

  const compatibleCandidate = Effect.fn("cloudflared.compatibleCandidate")(function* (
    executablePath: string,
    source: RelayClientExecutableSource,
    expectedVersion?: string,
  ) {
    const version = yield* probeVersion(executablePath);
    if (version === null || compareCloudflaredVersions(version, CLOUDFLARED_MIN_VERSION) < 0) {
      return null;
    }
    if (expectedVersion !== undefined && version !== expectedVersion) {
      yield* Effect.logWarning("Ignoring a managed relay client that reports another version", {
        executablePath,
        expectedVersion,
        version,
      });
      return null;
    }
    return { status: "available", executablePath, source, version } satisfies AvailableRelayClient;
  });

  // Managed releases older than the pin, newest first. Newer folders belong to
  // a newer server sharing this base dir and are never used or pruned here.
  const olderManagedVersions = Effect.gen(function* () {
    const entries = yield* fileSystem
      .readDirectory(managedRoot)
      .pipe(Effect.orElseSucceed(() => []));
    return entries
      .filter(
        (entry) =>
          parseVersion(entry) !== null &&
          compareCloudflaredVersions(entry, CLOUDFLARED_VERSION) < 0,
      )
      .sort((left, right) => compareCloudflaredVersions(right, left));
  });

  const resolvePathExecutable = Effect.gen(function* () {
    const config = yield* loadCloudflaredConfig;
    const pathValue = Option.getOrUndefined(config.path);
    if (!pathValue) return null;
    const delimiter = platform === "win32" ? ";" : ":";
    for (const directory of pathValue.split(delimiter)) {
      const trimmed = directory.trim().replace(/^"|"$/gu, "");
      if (trimmed.length === 0) continue;
      const candidate = path.join(trimmed, executableFileName(platform));
      if (!(yield* isExecutableFile(candidate))) continue;
      const available = yield* compatibleCandidate(candidate, "path");
      if (available) return available;
      yield* Effect.logWarning("Skipping an incompatible relay client on PATH", {
        executablePath: candidate,
        minimumVersion: CLOUDFLARED_MIN_VERSION,
      });
    }
    return null;
  });

  const missingStatus: RelayClientStatus = { status: "missing", version: CLOUDFLARED_VERSION };
  const unsupportedStatus: RelayClientStatus = {
    status: "unsupported",
    platform,
    arch,
    version: CLOUDFLARED_VERSION,
  };
  const resolve: RelayClientShape["resolve"] = Effect.gen(function* () {
    const config = yield* loadCloudflaredConfig;
    if (Option.isSome(config.executableOverride)) {
      const override = yield* compatibleCandidate(config.executableOverride.value, "override");
      if (override) return override;
      yield* Effect.logWarning(
        `${CLOUDFLARED_PATH_ENV_NAME} must point to cloudflared ${CLOUDFLARED_MIN_VERSION} or newer`,
        { executablePath: config.executableOverride.value },
      );
      return missingStatus;
    }
    // A managed binary reports the version it actually runs. One that replaced
    // itself in place still serves as a fallback; the runtime sees it is not the
    // pinned release and installs that.
    const pinned = yield* compatibleCandidate(managedPath, "managed");
    if (pinned) return pinned;
    for (const version of yield* olderManagedVersions) {
      const older = yield* compatibleCandidate(managedPathFor(version), "managed");
      if (older) return older;
    }
    const pathExecutable = yield* resolvePathExecutable;
    if (pathExecutable) return pathExecutable;
    return releaseAsset ? missingStatus : unsupportedStatus;
  }).pipe(Effect.withSpan("cloudflared.resolve"));

  const pruneManagedVersions: RelayClientShape["pruneManagedVersions"] = Effect.gen(function* () {
    // The newest older release stays: an older server sharing this base dir, or a
    // rollback after a failed update, may still need it and cannot redownload it.
    for (const version of (yield* olderManagedVersions).slice(1)) {
      // A connector from another server sharing this base dir may still run an
      // older release; Windows refuses to delete it, so the next prune retries.
      yield* fileSystem.remove(path.join(managedRoot, version), { recursive: true }).pipe(
        Effect.tap(() => Effect.logInfo("Removed an older managed relay client", { version })),
        Effect.catch((cause) =>
          Effect.logDebug("Could not remove an older managed relay client", { version, cause }),
        ),
      );
    }
  }).pipe(Effect.withSpan("cloudflared.pruneManagedVersions"));

  /** Moves the new binary toward its final path, waiting out a brief Windows lock. */
  const renameWhenUnlocked = (from: string, to: string) =>
    fileSystem.rename(from, to).pipe(
      Effect.retry({
        times: ACTIVATE_RETRY_COUNT,
        schedule: Schedule.spaced(ACTIVATE_RETRY_DELAY),
        while: (error) => platform === "win32" && isTransientWindowsLock(error),
      }),
    );

  const runCommand = Effect.fn("cloudflared.runCommand")(function* (
    command: string,
    args: ReadonlyArray<string>,
  ) {
    const child = yield* spawner.spawn(
      ChildProcess.make(command, args, {
        shell: false,
        stdout: "ignore",
        stderr: "ignore",
      }),
    );
    const exitCode = Number(yield* child.exitCode);
    if (exitCode !== 0) {
      return yield* new CloudflaredCommandError({ command, exitCode });
    }
  });

  const downloadAsset = Effect.fn("cloudflared.downloadAsset")(function* (
    asset: CloudflaredReleaseAsset,
    report: (stage: RelayClientInstallProgressStage) => Effect.Effect<void>,
  ) {
    yield* report("downloading");
    const response = yield* httpClient.execute(HttpClientRequest.get(asset.url)).pipe(
      Effect.timeout(CLOUDFLARED_DOWNLOAD_TIMEOUT),
      Effect.flatMap(HttpClientResponse.filterStatusOk),
      Effect.mapError(
        (cause) =>
          new RelayClientInstallError({
            reason: "download_failed",
            message: "Could not download the relay client.",
            cause,
          }),
      ),
    );
    const bytes = new Uint8Array(
      yield* response.arrayBuffer.pipe(
        Effect.timeout(CLOUDFLARED_DOWNLOAD_TIMEOUT),
        Effect.mapError(
          (cause) =>
            new RelayClientInstallError({
              reason: "download_failed",
              message: "Could not read the downloaded relay client binary.",
              cause,
            }),
        ),
      ),
    );
    yield* report("verifying");
    const checksum = yield* crypto.digest("SHA-256", bytes).pipe(
      Effect.mapError(
        (cause) =>
          new RelayClientInstallError({
            reason: "validation_failed",
            message: "Could not verify the downloaded relay client checksum.",
            cause,
          }),
      ),
    );
    if (Hex.encode(checksum) !== asset.sha256) {
      return yield* new RelayClientInstallError({
        reason: "invalid_checksum",
        message: "Downloaded relay client checksum did not match the pinned release.",
      });
    }
    return bytes;
  });

  const acquireInstallLock = Effect.fn("cloudflared.acquireInstallLock")(function* (
    lockPath: string,
  ) {
    for (let attempt = 0; attempt < INSTALL_LOCK_RETRY_COUNT; attempt += 1) {
      const acquired = yield* Effect.acquireRelease(
        fileSystem.writeFileString(lockPath, "", { flag: "wx" }),
        () => fileSystem.remove(lockPath, { force: true }).pipe(Effect.ignore),
      ).pipe(
        Effect.as(true),
        Effect.catchIf(isAlreadyExists, () => Effect.succeed(false)),
      );
      if (acquired) return;

      const now = yield* Clock.currentTimeMillis;
      const lockInfo = yield* fileSystem.stat(lockPath).pipe(Effect.option);
      const mtime = Option.flatMap(lockInfo, (info) => info.mtime);
      if (Option.isSome(mtime) && now - mtime.value.getTime() > INSTALL_LOCK_STALE_MS) {
        yield* fileSystem.remove(lockPath, { force: true });
        continue;
      }
      yield* Effect.sleep(INSTALL_LOCK_RETRY_DELAY);
    }
    return yield* new RelayClientInstallError({
      reason: "install_locked",
      message: "Another relay client installation is still in progress.",
    });
  });

  const installUnlocked = Effect.fn("cloudflared.installUnlocked")(function* (
    report: (stage: RelayClientInstallProgressStage) => Effect.Effect<void>,
  ) {
    yield* report("checking");
    const config = yield* loadCloudflaredConfig;
    if (Option.isSome(config.executableOverride)) {
      const override = yield* compatibleCandidate(config.executableOverride.value, "override");
      if (override) return override;
      return yield* new RelayClientInstallError({
        reason: "override_missing",
        message: `${CLOUDFLARED_PATH_ENV_NAME} must point to cloudflared ${CLOUDFLARED_MIN_VERSION} or newer.`,
      });
    }
    const existing = yield* compatibleCandidate(managedPath, "managed", CLOUDFLARED_VERSION);
    if (existing) return existing;
    if (!releaseAsset) {
      return yield* new RelayClientInstallError({
        reason: "unsupported_platform",
        message: `T3 Code does not provide a managed relay client binary for ${platform}-${arch}.`,
      });
    }

    const managedDirectory = path.dirname(managedPath);
    const lockPath = `${managedPath}.lock`;
    yield* fileSystem
      .makeDirectory(managedDirectory, { recursive: true })
      .pipe(
        wrapInstallFailure("write_failed", "Could not create the relay client tool directory."),
      );
    yield* report("waiting_for_lock");
    yield* acquireInstallLock(lockPath).pipe(
      Effect.catchTags({
        PlatformError: (cause) =>
          Effect.fail(
            new RelayClientInstallError({
              reason: "write_failed",
              message: "Could not acquire the relay client installation lock.",
              cause,
            }),
          ),
      }),
    );
    return yield* Effect.gen(function* () {
      const afterLock = yield* compatibleCandidate(managedPath, "managed", CLOUDFLARED_VERSION);
      if (afterLock) return afterLock;

      const tempDirectory = yield* fileSystem.makeTempDirectoryScoped({
        directory: managedDirectory,
        prefix: ".install-",
      });
      const archivePath = path.join(
        tempDirectory,
        releaseAsset.archive === "tgz" ? "cloudflared.tgz" : executableFileName(platform),
      );
      const download = yield* downloadAsset(releaseAsset, report);
      yield* report("installing");
      yield* fileSystem
        .writeFile(archivePath, download)
        .pipe(wrapInstallFailure("write_failed", "Could not write the relay client download."));

      const executablePath = path.join(tempDirectory, executableFileName(platform));
      if (releaseAsset.archive === "tgz") {
        yield* runCommand("tar", ["-xzf", archivePath, "-C", tempDirectory]).pipe(
          wrapInstallFailure("write_failed", "Could not extract the relay client."),
        );
      }
      if (platform !== "win32") {
        yield* fileSystem
          .chmod(executablePath, 0o755)
          .pipe(wrapInstallFailure("write_failed", "Could not make the relay client executable."));
      }
      yield* report("validating");
      // Requiring the pinned version here keeps a mislabelled asset from being
      // activated, ignored by resolve, and downloaded again on every reconcile.
      const installedVersion = yield* probeVersion(executablePath);
      if (installedVersion !== CLOUDFLARED_VERSION) {
        return yield* new RelayClientInstallError({
          reason: "validation_failed",
          message:
            installedVersion === null
              ? "The downloaded relay client binary did not run."
              : `The downloaded relay client reports version ${installedVersion}, not ${CLOUDFLARED_VERSION}.`,
        });
      }

      const stagedPath = `${managedPath}.${yield* crypto.randomUUIDv4}.tmp`;
      yield* report("activating");
      yield* renameWhenUnlocked(executablePath, stagedPath).pipe(
        wrapInstallFailure("write_failed", "Could not stage the relay client."),
      );
      yield* renameWhenUnlocked(stagedPath, managedPath).pipe(
        wrapInstallFailure("write_failed", "Could not activate the relay client."),
        Effect.ensuring(fileSystem.remove(stagedPath, { force: true }).pipe(Effect.ignore)),
      );
      return {
        status: "available",
        executablePath: managedPath,
        source: "managed",
        version: CLOUDFLARED_VERSION,
      } satisfies AvailableRelayClient;
    }).pipe(
      Effect.scoped,
      Effect.catchIf(
        (cause) => !(cause instanceof RelayClientInstallError),
        (cause) =>
          Effect.fail(
            new RelayClientInstallError({
              reason: "write_failed",
              message: "Could not install the relay client.",
              cause,
            }),
          ),
      ),
    );
  });
  const installWithProgress: RelayClientShape["installWithProgress"] = (report) =>
    installSemaphore.withPermit(
      installUnlocked((stage) =>
        report({
          type: "progress",
          stage,
        }),
      ).pipe(Effect.scoped),
    );
  const install = installWithProgress(() => Effect.void);

  return RelayClient.of({ resolve, install, installWithProgress, pruneManagedVersions });
});

export const layerCloudflared = (options: CloudflaredRelayClientOptions) =>
  Layer.effect(RelayClient, makeCloudflaredRelayClient(options));