diff --git a/controlplane/src/index.ts b/controlplane/src/index.ts
index 2a31ff99..09fa8860 100644
--- a/controlplane/src/index.ts
+++ b/controlplane/src/index.ts
@@ -52,6 +52,7 @@ import { createApiToken, listApiTokens, revokeApiToken } from './auth/api-token'
import { checkAndCacheSandbоxHealth, getCachedHealth } from './health/checker';
import { sendEmail, buildInterestThankYouEmail, buildInterestNotificationEmail, buildTemplateReviewEmail } from './email/resend';
import * as blog from './blog/handler';
+import * as releases from './releases/handler';
import { sandboxHeaders, sandboxUrl } from './sandbox/fetch';
// Export Durable Objects
@@ -837,6 +838,12 @@ async function handleRequest(request: Request, env: EnvWithBindings, ctx: Pick {
+ try {
+ const resp = await fetch(GITHUB_LATEST, {
+ headers: {
+ // GitHub rejects API requests without a User-Agent.
+ 'User-Agent': 'orcabot-controlplane',
+ Accept: 'application/vnd.github+json',
+ },
+ // Cache at the edge so repeated page loads don't re-hit GitHub.
+ cf: { cacheTtl: 300, cacheEverything: true },
+ });
+
+ if (!resp.ok) {
+ return Response.json(
+ { error: `GitHub returned ${resp.status}` },
+ { status: 502, headers: { 'Cache-Control': 'public, max-age=60' } }
+ );
+ }
+
+ const gh = (await resp.json()) as {
+ tag_name?: string;
+ name?: string;
+ body?: string;
+ html_url?: string;
+ published_at?: string;
+ assets?: Array<{
+ name: string;
+ size: number;
+ browser_download_url: string;
+ content_type: string;
+ download_count: number;
+ }>;
+ };
+
+ const release: LatestRelease = {
+ version: (gh.tag_name || '').replace(/^v/, ''),
+ name: gh.name || gh.tag_name || '',
+ notes: gh.body || '',
+ htmlUrl: gh.html_url || `https://github.com/${RELEASES_REPO}/releases/latest`,
+ publishedAt: gh.published_at || '',
+ assets: (gh.assets || []).map((a) => ({
+ name: a.name,
+ size: a.size,
+ downloadUrl: a.browser_download_url,
+ contentType: a.content_type,
+ downloadCount: a.download_count,
+ })),
+ };
+
+ return Response.json(release, {
+ headers: { 'Cache-Control': 'public, max-age=300' },
+ });
+ } catch {
+ return Response.json(
+ { error: 'Failed to fetch latest release' },
+ { status: 502 }
+ );
+ }
+}
diff --git a/frontend/src/app/download/download.css b/frontend/src/app/download/download.css
new file mode 100644
index 00000000..00ec9301
--- /dev/null
+++ b/frontend/src/app/download/download.css
@@ -0,0 +1,225 @@
+/* Copyright 2026 Rob Macrae. All rights reserved. */
+/* On-brand skin for the /download page (mirrors the splash/header palette). */
+
+.dl-page {
+ min-height: 100vh;
+ background:
+ radial-gradient(1200px 600px at 50% -10%, rgba(59, 130, 246, 0.15), transparent 60%),
+ linear-gradient(180deg, #06101f 0%, #081426 100%);
+ color: #e6eefc;
+}
+
+.dl-main {
+ max-width: 46rem;
+ margin: 0 auto;
+ padding: 3rem 1.5rem 5rem;
+}
+
+.dl-hero {
+ text-align: center;
+ padding: 2rem 0 1rem;
+}
+
+.dl-orca {
+ width: 84px;
+ height: 84px;
+ object-fit: contain;
+ margin: 0 auto 1rem;
+ display: block;
+}
+
+.dl-hero h1 {
+ font-size: 2.25rem;
+ font-weight: 800;
+ margin: 0 0 0.5rem;
+ color: #fff;
+}
+
+.dl-sub {
+ color: rgba(230, 238, 252, 0.7);
+ margin: 0 0 1.75rem;
+ font-size: 1.05rem;
+}
+
+.dl-cta {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 1rem;
+}
+
+.dl-btn {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ text-decoration: none;
+ font-weight: 700;
+ border-radius: 12px;
+ padding: 14px 28px;
+ line-height: 1.2;
+}
+
+.dl-btn-primary {
+ color: #06101f;
+ background: linear-gradient(180deg, #cfe0ff, #8fb4ff);
+ box-shadow:
+ 0 1px 0 rgba(255, 255, 255, 0.4) inset,
+ 0 10px 30px rgba(59, 130, 246, 0.35);
+ font-size: 1.05rem;
+ transition: filter 0.15s ease;
+}
+
+.dl-btn-primary:hover {
+ filter: brightness(1.05);
+}
+
+.dl-btn-sub {
+ font-weight: 600;
+ font-size: 0.8rem;
+ opacity: 0.75;
+ margin-top: 2px;
+}
+
+.dl-meta {
+ color: rgba(230, 238, 252, 0.6);
+ font-size: 0.9rem;
+}
+
+.dl-meta a {
+ color: #9fc0ff;
+ text-decoration: none;
+}
+
+.dl-meta a:hover {
+ text-decoration: underline;
+}
+
+.dl-version {
+ font-weight: 700;
+ color: #cfe0ff;
+}
+
+.dl-badge {
+ display: inline-block;
+ margin-left: 0.5rem;
+ padding: 1px 8px;
+ font-size: 0.7rem;
+ font-weight: 700;
+ letter-spacing: 0.02em;
+ text-transform: uppercase;
+ color: #7ee0a8;
+ background: rgba(46, 204, 113, 0.14);
+ border: 1px solid rgba(46, 204, 113, 0.35);
+ border-radius: 999px;
+ vertical-align: middle;
+}
+
+.dl-card {
+ margin-top: 2rem;
+ padding: 1.5rem 1.75rem;
+ background: rgba(255, 255, 255, 0.04);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 16px;
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+}
+
+.dl-card h2 {
+ font-size: 1.15rem;
+ font-weight: 700;
+ margin: 0 0 1rem;
+ color: #fff;
+}
+
+.dl-notes {
+ line-height: 1.7;
+ color: rgba(230, 238, 252, 0.85);
+ word-wrap: break-word;
+}
+
+.dl-notes h1,
+.dl-notes h2,
+.dl-notes h3 {
+ color: #fff;
+ margin: 1.25rem 0 0.5rem;
+ font-size: 1.05rem;
+}
+
+.dl-notes ul,
+.dl-notes ol {
+ padding-left: 1.25rem;
+}
+
+.dl-notes li {
+ margin: 0.25rem 0;
+}
+
+.dl-notes a {
+ color: #9fc0ff;
+}
+
+.dl-notes code {
+ background: rgba(255, 255, 255, 0.08);
+ padding: 0.1rem 0.35rem;
+ border-radius: 5px;
+ font-size: 0.9em;
+}
+
+.dl-notes pre {
+ background: rgba(0, 0, 0, 0.3);
+ padding: 1rem;
+ border-radius: 10px;
+ overflow-x: auto;
+}
+
+.dl-notes pre code {
+ background: none;
+ padding: 0;
+}
+
+.dl-assets {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.dl-assets li {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.6rem 0;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+ gap: 1rem;
+}
+
+.dl-assets li:last-child {
+ border-bottom: none;
+}
+
+.dl-assets a {
+ color: #cfe0ff;
+ text-decoration: none;
+ word-break: break-all;
+}
+
+.dl-assets a:hover {
+ text-decoration: underline;
+}
+
+.dl-muted {
+ color: rgba(230, 238, 252, 0.5);
+ font-size: 0.85rem;
+ white-space: nowrap;
+}
+
+.dl-error {
+ color: #ffb4b4;
+}
+
+.dl-error a {
+ color: #9fc0ff;
+}
+
+.dl-foot {
+ text-align: center;
+ margin-top: 2.5rem;
+}
diff --git a/frontend/src/app/download/page.tsx b/frontend/src/app/download/page.tsx
new file mode 100644
index 00000000..677f5a3f
--- /dev/null
+++ b/frontend/src/app/download/page.tsx
@@ -0,0 +1,135 @@
+// Copyright 2026 Rob Macrae. All rights reserved.
+// SPDX-License-Identifier: LicenseRef-Proprietary
+
+// REVISION: download-page-v1
+"use client";
+
+import "./download.css";
+import * as React from "react";
+import Markdown from "react-markdown";
+import { SiteHeader } from "@/components/SiteHeader";
+import {
+ getLatestRelease,
+ pickMacInstaller,
+ formatBytes,
+ type LatestRelease,
+} from "@/lib/api/releases";
+
+const GITHUB_RELEASES = "https://github.com/Hyper-Int/OrcaBot/releases/latest";
+
+export default function DownloadPage() {
+ const [release, setRelease] = React.useState(null);
+ const [error, setError] = React.useState(null);
+ const [loading, setLoading] = React.useState(true);
+
+ React.useEffect(() => {
+ let cancelled = false;
+ getLatestRelease()
+ .then((r) => {
+ if (!cancelled) {
+ setRelease(r);
+ setLoading(false);
+ }
+ })
+ .catch((e) => {
+ if (!cancelled) {
+ setError(e instanceof Error ? e.message : "Failed to load release");
+ setLoading(false);
+ }
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ const primary = release ? pickMacInstaller(release.assets) : null;
+ const assets = release ? release.assets : [];
+
+ return (
+
+
+
+
+
+ Download Orcabot
+
+ Run Claude Code, Codex, and a full sandboxed stack locally — no setup.
+
+
+ {loading && Loading the latest release…
}
+
+ {error && (
+
+ Couldn’t load the release.{" "}
+ Download on GitHub →
+
+ )}
+
+ {release && (
+ <>
+
+
+
v{release.version}
+
Latest
+ {release.publishedAt && (
+
+ {" · "}
+ {new Date(release.publishedAt).toLocaleDateString(undefined, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ })}
+
+ )}
+ {" · "}
+
+ View on GitHub ↗
+
+
+ >
+ )}
+
+
+ {release && release.notes && (
+
+ What’s new{release.name ? ` — ${release.name}` : ""}
+
+ {release.notes}
+
+
+ )}
+
+ {release && assets.length > 0 && (
+
+ Assets
+
+ {assets.map((a) => (
+ -
+ {a.name}
+ {formatBytes(a.size)}
+
+ ))}
+
+
+ )}
+
+
+ macOS 13+ · Apple Silicon. Existing installs update automatically.
+
+
+
+ );
+}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index 0db8801c..5798c62e 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -628,6 +628,19 @@ export default function Home() {
Terms of Service and{" "}
Privacy Policy
+
+ Download the macOS app →
+
Explore