diff --git a/web/app/download/download.module.css b/web/app/download/download.module.css
new file mode 100644
index 00000000..5511f69b
--- /dev/null
+++ b/web/app/download/download.module.css
@@ -0,0 +1,266 @@
+.page {
+ min-height: 100svh;
+ background: var(--bg);
+ color: var(--fg);
+ font-family: var(--font-geist-sans), sans-serif;
+ -webkit-font-smoothing: antialiased;
+ transition:
+ background 0.3s ease,
+ color 0.3s ease;
+}
+
+.main {
+ max-width: 72rem;
+ margin: 0 auto;
+ padding: 3.5rem 1.25rem 5rem;
+}
+
+/* ── Hero ── */
+
+.hero {
+ text-align: center;
+ padding: 1rem 0 2.75rem;
+}
+
+.eyebrow {
+ font-size: 0.72rem;
+ font-weight: 700;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--primary);
+ margin: 0 0 12px;
+}
+
+.headline {
+ font-family: var(--font-heading), sans-serif;
+ font-size: clamp(2.4rem, 6vw, 4rem);
+ font-weight: 700;
+ letter-spacing: -0.04em;
+ color: var(--primary);
+ margin: 0 0 16px;
+}
+
+.lead {
+ max-width: 42rem;
+ margin: 0 auto;
+ color: var(--fg-muted);
+ font-size: 1.05rem;
+ line-height: 1.7;
+}
+
+.recommendation {
+ width: fit-content;
+ max-width: 100%;
+ margin: 2rem auto 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.65rem;
+}
+
+.detected {
+ margin: 0;
+ color: var(--fg-faint);
+ font-size: 0.8rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.primary {
+ composes: btn btn-primary from global;
+ min-height: 56px;
+ padding: 16px 28px;
+ font-size: 1.0625rem;
+ gap: 10px;
+}
+
+.installNote {
+ margin: 0;
+ color: var(--fg-muted);
+ font-size: 0.9rem;
+ line-height: 1.5;
+}
+
+.warning {
+ max-width: 36rem;
+ margin: 0.25rem auto 0;
+ color: var(--fg-faint);
+ font-size: 0.82rem;
+ line-height: 1.55;
+}
+
+.warning em {
+ color: var(--fg-muted);
+ font-style: italic;
+}
+
+.warning strong {
+ color: var(--fg-muted);
+ font-weight: 600;
+}
+
+.choose {
+ margin: 1.75rem 0 0;
+ color: var(--fg-muted);
+ font-size: 0.95rem;
+}
+
+/* ── Platform cards ── */
+
+.platforms {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1rem;
+ margin-top: 1rem;
+}
+
+.platform {
+ display: flex;
+ flex-direction: column;
+ gap: 1.1rem;
+ padding: 1.35rem;
+ border: 1px solid var(--line);
+ border-radius: var(--r-lg);
+ background: var(--card-bg);
+ transition:
+ border-color 0.2s,
+ box-shadow 0.2s;
+}
+
+.platformRecommended {
+ border-color: color-mix(in srgb, var(--primary) 55%, var(--line));
+ box-shadow: 0 2px 20px color-mix(in srgb, var(--primary) 8%, transparent);
+}
+
+.platformHeader h2 {
+ margin: 0 0 0.45rem;
+ font-family: var(--font-heading), sans-serif;
+ font-size: 1.35rem;
+ letter-spacing: -0.02em;
+ color: var(--fg);
+}
+
+.platformHeader p {
+ margin: 0;
+ color: var(--fg-muted);
+ font-size: 0.9rem;
+ line-height: 1.55;
+}
+
+.choices {
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+}
+
+.choice {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem;
+ min-height: 48px;
+ padding: 0.7rem 0.9rem;
+ border: 1px solid var(--line);
+ border-radius: var(--r-md);
+ background: var(--surface);
+ color: var(--fg);
+ text-decoration: none;
+ transition:
+ border-color 0.2s,
+ background 0.2s;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .choice:hover {
+ border-color: var(--primary);
+ background: var(--secondary-surface);
+ }
+}
+
+.choiceRecommended {
+ border-color: var(--primary);
+ background: color-mix(in srgb, var(--primary) 10%, var(--surface));
+}
+
+.choiceArch {
+ font-size: 0.92rem;
+ font-weight: 600;
+}
+
+.choiceFormat {
+ color: var(--fg-faint);
+ font-family: var(--font-geist-mono), monospace;
+ font-size: 0.78rem;
+}
+
+.alternative {
+ margin: -0.35rem 0 0;
+ color: var(--fg-faint);
+ font-size: 0.82rem;
+}
+
+.alternative a {
+ color: var(--primary);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+.platformNote {
+ margin: auto 0 0;
+ color: var(--fg-faint);
+ font-size: 0.78rem;
+ line-height: 1.55;
+}
+
+/* ── Release note ── */
+
+.releaseNote {
+ max-width: 46rem;
+ margin: 2.5rem auto 0;
+ padding-top: 1.75rem;
+ border-top: 1px solid var(--line);
+ text-align: center;
+ color: var(--fg-faint);
+ font-size: 0.86rem;
+ line-height: 1.65;
+}
+
+.releaseNote p {
+ margin: 0.4rem 0;
+}
+
+.releaseNote a {
+ color: var(--primary);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+@media (max-width: 900px) {
+ .platforms {
+ grid-template-columns: 1fr;
+ }
+
+ .main {
+ padding-top: 2.5rem;
+ }
+}
+
+@media (max-width: 560px) {
+ .main {
+ padding-inline: 1rem;
+ }
+
+ .headline {
+ font-size: clamp(2.1rem, 10vw, 2.8rem);
+ }
+
+ .primary {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .platform {
+ padding: 1.1rem;
+ }
+}
diff --git a/web/app/download/page.tsx b/web/app/download/page.tsx
new file mode 100644
index 00000000..e9c452a7
--- /dev/null
+++ b/web/app/download/page.tsx
@@ -0,0 +1,188 @@
+import type { Metadata } from 'next';
+import { headers } from 'next/headers';
+
+import { cn } from '../../lib/utils';
+import { SiteFooter } from '../../components/SiteFooter';
+import { SiteNav } from '../../components/SiteNav';
+import {
+ LINUX_TARBALLS,
+ WINDOWS_PORTABLE,
+ detectDesktopDownload,
+ downloadLabel,
+ downloadsFor,
+ platformName,
+ type DesktopPlatform,
+} from '../../lib/desktop-downloads';
+import { defaultOgImage } from '../../lib/og-meta';
+import { absoluteUrl, SITE_NAME } from '../../lib/site';
+import s from './download.module.css';
+
+// The recommendation comes from the request's User-Agent, so each visitor
+// gets their platform's download in the first response.
+export const dynamic = 'force-dynamic';
+
+const TITLE = 'Download Agent Relay for macOS, Windows, and Linux';
+const DESCRIPTION =
+ 'Download Agent Relay for macOS, Windows, or Linux. The same local relay for Claude Code, Codex, and your other agents on every desktop platform.';
+
+export const metadata: Metadata = {
+ title: TITLE,
+ description: DESCRIPTION,
+ alternates: {
+ canonical: absoluteUrl('/download'),
+ },
+ openGraph: {
+ siteName: SITE_NAME,
+ title: TITLE,
+ description: DESCRIPTION,
+ url: absoluteUrl('/download'),
+ type: 'website',
+ images: [defaultOgImage()],
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: TITLE,
+ description: DESCRIPTION,
+ images: [defaultOgImage().url],
+ },
+};
+
+const platforms: Array<{
+ id: DesktopPlatform;
+ name: string;
+ blurb: string;
+ note: string;
+}> = [
+ {
+ id: 'macos',
+ name: 'macOS',
+ blurb: 'For Apple silicon and Intel Macs.',
+ note: 'Requires macOS 13 or later. Open the disk image and drag Agent Relay to Applications.',
+ },
+ {
+ id: 'windows',
+ name: 'Windows',
+ blurb: 'For Intel, AMD, and Arm PCs.',
+ note: 'Windows 10 and 11. Run the installer: it installs for your account without administrator rights. Releases are not code-signed yet, so SmartScreen may ask once; Smart App Control must be off.',
+ },
+ {
+ id: 'linux',
+ name: 'Linux',
+ blurb: 'For x64 and arm64 distributions.',
+ note: 'Install the .deb with your package manager, or use a tarball if you prefer a relocatable install.',
+ },
+];
+
+export default async function DownloadPage() {
+ const requestHeaders = await headers();
+ const detected = detectDesktopDownload({
+ userAgent: requestHeaders.get('user-agent') ?? '',
+ platform: requestHeaders.get('sec-ch-ua-platform'),
+ architecture: requestHeaders.get('sec-ch-ua-arch'),
+ });
+
+ return (
+
+
+
+
+
+ Agent Relay Desktop
+ Download Agent Relay
+
+ The same window for your local agents on macOS, Windows, and Linux. Sign in once and your
+ sessions, agents, and messages are there.
+
+
+ {detected ? (
+
+
+ Detected {platformName(detected.platform)}
+ {detected.arch === 'arm64' ? ' on Arm' : ''}
+
+
+ {downloadLabel(detected)}
+
+
{detected.installNote}
+ {detected.platform === 'windows' ? (
+
+ The Windows installer is not code-signed yet: SmartScreen may show Windows protected your PC {' '}
+ once. Choose More info → Run anyway .
+
+ ) : null}
+
+ ) : (
+ Choose the download for your computer.
+ )}
+
+
+
+ {platforms.map((platform) => {
+ const items = downloadsFor(platform.id);
+ const isRecommended = detected?.platform === platform.id;
+
+ return (
+
+
+ {platform.name}
+ {platform.blurb}
+
+
+
+ {items.map((item) => {
+ const isRecommendedChoice =
+ detected?.platform === item.platform && detected.arch === item.arch;
+
+ return (
+
+ {item.archLabel}
+ .{item.format}
+
+ );
+ })}
+
+
+ {platform.id === 'windows' ? (
+
+ Prefer portable? x64 .zip or{' '}
+ arm64 .zip
+
+ ) : null}
+
+ {platform.id === 'linux' ? (
+
+ Prefer a tarball? x64 or arm64
+
+ ) : null}
+
+ {platform.note}
+
+ );
+ })}
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/app/sitemap.ts b/web/app/sitemap.ts
index 21b8901c..27404a28 100644
--- a/web/app/sitemap.ts
+++ b/web/app/sitemap.ts
@@ -21,6 +21,12 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: 'weekly',
priority: 1.0,
},
+ {
+ url: absoluteUrl('/download'),
+ lastModified: now,
+ changeFrequency: 'weekly',
+ priority: 0.9,
+ },
{
url: absoluteUrl('/pear'),
lastModified: now,
diff --git a/web/components/SiteFooter.tsx b/web/components/SiteFooter.tsx
index 39c34ba5..d30b63b0 100644
--- a/web/components/SiteFooter.tsx
+++ b/web/components/SiteFooter.tsx
@@ -21,6 +21,7 @@ const footerColumns: FooterColumn[] = [
title: 'Products',
links: [
{ label: 'Relay Cloud', href: 'https://agentrelay.com/cloud' },
+ { label: 'Download', href: '/download' },
{ label: 'Flows', href: '/flows' },
{ label: 'Skip', href: 'https://heyskip.dev' },
{ label: 'Pear', href: '/pear' },
diff --git a/web/components/SiteNav.tsx b/web/components/SiteNav.tsx
index b91f0453..e49f3f69 100644
--- a/web/components/SiteNav.tsx
+++ b/web/components/SiteNav.tsx
@@ -101,6 +101,11 @@ export function SiteNav({
Log in
+
+
+ Download
+
+
Docs
@@ -172,6 +177,11 @@ export function SiteNav({
Log in
)}
+ {!hideLinks && (
+ setMenuOpen(false)}>
+ Download
+
+ )}
{!hideLinks && !hideMobileDocsLink && (
setMenuOpen(false)}>
Docs
diff --git a/web/lib/desktop-downloads.ts b/web/lib/desktop-downloads.ts
new file mode 100644
index 00000000..d0216847
--- /dev/null
+++ b/web/lib/desktop-downloads.ts
@@ -0,0 +1,176 @@
+const RELEASES = 'https://github.com/AgentWorkforce/relay-desktop-releases/releases/latest/download';
+
+export type DesktopPlatform = 'macos' | 'windows' | 'linux';
+export type DesktopArch = 'arm64' | 'x64';
+export type DesktopDownloadFormat = 'dmg' | 'exe' | 'zip' | 'deb';
+
+export type DesktopDownload = {
+ platform: DesktopPlatform;
+ arch: DesktopArch;
+ format: DesktopDownloadFormat;
+ filename: string;
+ url: string;
+ /** Short architecture name for a button, such as "Apple silicon" or "x64". */
+ archLabel: string;
+ /** What to do with the file once it arrives. */
+ installNote: string;
+};
+
+const download = (
+ platform: DesktopPlatform,
+ arch: DesktopArch,
+ format: DesktopDownloadFormat,
+ filename: string,
+ archLabel: string,
+ installNote: string,
+): DesktopDownload => ({
+ platform,
+ arch,
+ format,
+ filename,
+ url: `${RELEASES}/${filename}`,
+ archLabel,
+ installNote,
+});
+
+/**
+ * Every download link the website offers, one entry per platform and
+ * architecture. The stable asset names are published by relay-desktop's
+ * Release workflow, so these URLs follow a new release without a website
+ * deploy.
+ */
+export const DESKTOP_DOWNLOADS: readonly DesktopDownload[] = [
+ download(
+ 'macos',
+ 'arm64',
+ 'dmg',
+ 'AgentRelay-macOS-arm64.dmg',
+ 'Apple silicon',
+ 'Open the disk image and drag Agent Relay to Applications.',
+ ),
+ download(
+ 'macos',
+ 'x64',
+ 'dmg',
+ 'AgentRelay-macOS-x64.dmg',
+ 'Intel',
+ 'Open the disk image and drag Agent Relay to Applications.',
+ ),
+ download(
+ 'windows',
+ 'x64',
+ 'exe',
+ 'AgentRelay-Windows-x64-Setup.exe',
+ 'x64',
+ 'Run the installer. It installs for your account without administrator rights.',
+ ),
+ download(
+ 'windows',
+ 'arm64',
+ 'exe',
+ 'AgentRelay-Windows-arm64-Setup.exe',
+ 'Arm64',
+ 'Run the installer. It installs for your account without administrator rights.',
+ ),
+ download(
+ 'linux',
+ 'x64',
+ 'deb',
+ 'AgentRelay-Linux-x64.deb',
+ 'x64',
+ 'Install the .deb with your package manager.',
+ ),
+ download(
+ 'linux',
+ 'arm64',
+ 'deb',
+ 'AgentRelay-Linux-arm64.deb',
+ 'arm64',
+ 'Install the .deb with your package manager.',
+ ),
+];
+
+/**
+ * The zips are the portable alternative to the Windows installer: unpack one
+ * and run agent-relay.exe without installing anything.
+ */
+export const WINDOWS_PORTABLE: Record = {
+ x64: `${RELEASES}/AgentRelay-Windows-x64.zip`,
+ arm64: `${RELEASES}/AgentRelay-Windows-arm64.zip`,
+};
+
+export const LINUX_TARBALLS: Record = {
+ x64: `${RELEASES}/AgentRelay-Linux-x64.tar.gz`,
+ arm64: `${RELEASES}/AgentRelay-Linux-arm64.tar.gz`,
+};
+
+export function platformName(platform: DesktopPlatform): string {
+ if (platform === 'macos') return 'macOS';
+ if (platform === 'windows') return 'Windows';
+ return 'Linux';
+}
+
+export function downloadsFor(platform: DesktopPlatform): DesktopDownload[] {
+ return DESKTOP_DOWNLOADS.filter((item) => item.platform === platform);
+}
+
+export function downloadFor(platform: DesktopPlatform, arch: DesktopArch): DesktopDownload {
+ const match = DESKTOP_DOWNLOADS.find((item) => item.platform === platform && item.arch === arch);
+ if (!match) throw new Error(`No ${platform} ${arch} download`);
+ return match;
+}
+
+export function downloadLabel(item: DesktopDownload): string {
+ return `Download for ${platformName(item.platform)} (${item.archLabel})`;
+}
+
+export type DesktopDetectInput = {
+ userAgent: string;
+ /** `navigator.platform`, or the quoted `Sec-CH-UA-Platform` request header. */
+ platform?: string | null;
+ /** `Sec-CH-UA-Arch`, when a browser sends it. */
+ architecture?: string | null;
+};
+
+/**
+ * Work out which download to put first. Runs on the server from the request's
+ * User-Agent (and Client Hint headers), so Windows visitors get the Windows
+ * build in the first response instead of a Mac download that swaps after
+ * hydration. When the platform is unclear, the page shows every option.
+ */
+export function detectDesktopDownload(input: DesktopDetectInput): DesktopDownload | null {
+ const userAgent = input.userAgent ?? '';
+ const platformHint = (input.platform ?? '').replaceAll('"', '').toLowerCase();
+
+ // Phones, tablets, and ChromeOS are not desktop installs.
+ if (/iphone|ipad|ipod|android|windows phone|mobile/i.test(userAgent) || /cros/i.test(userAgent)) {
+ return null;
+ }
+
+ let platform: DesktopPlatform | null = null;
+ if (platformHint.includes('windows') || /windows|win32|win64/i.test(userAgent)) {
+ platform = 'windows';
+ } else if (platformHint.includes('mac') || /macintosh|mac os x/i.test(userAgent)) {
+ platform = 'macos';
+ } else if (platformHint.includes('linux') || /linux|x11/i.test(userAgent)) {
+ platform = 'linux';
+ }
+
+ if (!platform) return null;
+
+ return downloadFor(platform, detectArch(platform, userAgent, input.architecture));
+}
+
+function detectArch(
+ platform: DesktopPlatform,
+ userAgent: string,
+ architecture: string | null | undefined,
+): DesktopArch {
+ const hint = (architecture ?? '').replaceAll('"', '').toLowerCase();
+ if (hint === 'arm' || /arm64|aarch64|armv8/i.test(userAgent)) return 'arm64';
+ if (hint === 'x86' || /x86_64|amd64|win64/i.test(userAgent)) return 'x64';
+
+ // Safari does not expose the Mac's architecture. Apple silicon is the
+ // common case; Intel Macs can still choose Intel from the options below.
+ return platform === 'macos' ? 'arm64' : 'x64';
+}
diff --git a/web/lib/test/desktop-downloads.test.ts b/web/lib/test/desktop-downloads.test.ts
new file mode 100644
index 00000000..0439a0fc
--- /dev/null
+++ b/web/lib/test/desktop-downloads.test.ts
@@ -0,0 +1,84 @@
+import { describe, expect, it } from 'vitest';
+
+import {
+ LINUX_TARBALLS,
+ WINDOWS_PORTABLE,
+ downloadFor,
+ detectDesktopDownload,
+ downloadLabel,
+} from '../desktop-downloads';
+
+const WINDOWS_X64 =
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36';
+const WINDOWS_ARM64 =
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; ARM64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36';
+const MAC_APPLE_SILICON =
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Safari/605.1.15';
+const LINUX_X64 =
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36';
+
+describe('desktop downloads', () => {
+ it('detects Windows and recommends the Windows installer, not the Mac disk image', () => {
+ const detected = detectDesktopDownload({ userAgent: WINDOWS_X64 });
+
+ expect(detected?.platform).toBe('windows');
+ expect(detected?.arch).toBe('x64');
+ expect(detected?.filename).toBe('AgentRelay-Windows-x64-Setup.exe');
+ expect(detected?.url).toBe(
+ 'https://github.com/AgentWorkforce/relay-desktop-releases/releases/latest/download/AgentRelay-Windows-x64-Setup.exe',
+ );
+ expect(downloadLabel(detected!)).toBe('Download for Windows (x64)');
+ });
+
+ it('uses Client Hints or the user agent to pick the Arm installer', () => {
+ expect(
+ detectDesktopDownload({ userAgent: WINDOWS_X64, architecture: '"arm"' })?.filename,
+ ).toBe('AgentRelay-Windows-arm64-Setup.exe');
+ expect(detectDesktopDownload({ userAgent: WINDOWS_ARM64 })?.filename).toBe(
+ 'AgentRelay-Windows-arm64-Setup.exe',
+ );
+ });
+
+ it('uses the Sec-CH-UA-Platform header when the user agent is reduced', () => {
+ expect(
+ detectDesktopDownload({ userAgent: 'Mozilla/5.0', platform: '"Windows"' })?.platform,
+ ).toBe('windows');
+ expect(
+ detectDesktopDownload({ userAgent: 'Mozilla/5.0', platform: '"macOS"' })?.platform,
+ ).toBe('macos');
+ });
+
+ it('defaults Macs to Apple silicon and honors an Intel Client Hint', () => {
+ expect(detectDesktopDownload({ userAgent: MAC_APPLE_SILICON })?.filename).toBe(
+ 'AgentRelay-macOS-arm64.dmg',
+ );
+ expect(
+ detectDesktopDownload({ userAgent: MAC_APPLE_SILICON, architecture: '"x86"' })?.filename,
+ ).toBe('AgentRelay-macOS-x64.dmg');
+ });
+
+ it('detects Linux and links both package formats', () => {
+ expect(detectDesktopDownload({ userAgent: LINUX_X64 })?.filename).toBe('AgentRelay-Linux-x64.deb');
+ expect(LINUX_TARBALLS.x64).toMatch(/AgentRelay-Linux-x64\.tar\.gz$/);
+ expect(LINUX_TARBALLS.arm64).toMatch(/AgentRelay-Linux-arm64\.tar\.gz$/);
+ });
+
+ it('offers every option when the platform is unclear or not a desktop', () => {
+ expect(detectDesktopDownload({ userAgent: 'curl/8.0' })).toBeNull();
+ expect(
+ detectDesktopDownload({
+ userAgent:
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',
+ }),
+ ).toBeNull();
+ expect(
+ detectDesktopDownload({
+ userAgent:
+ 'Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
+ }),
+ ).toBeNull();
+ expect(downloadFor('windows', 'arm64').format).toBe('exe');
+ expect(WINDOWS_PORTABLE.x64).toMatch(/AgentRelay-Windows-x64\.zip$/);
+ expect(WINDOWS_PORTABLE.arm64).toMatch(/AgentRelay-Windows-arm64\.zip$/);
+ });
+});