diff --git a/frontend/src/landing/app/landing/page.tsx b/frontend/src/landing/app/landing/page.tsx index b11ec344d..8c3a345f1 100644 --- a/frontend/src/landing/app/landing/page.tsx +++ b/frontend/src/landing/app/landing/page.tsx @@ -3,9 +3,6 @@ import { LandingHero } from "../../components/LandingHero"; import { LandingAgentsBar } from "../../components/LandingAgentsBar"; import { LandingVideo } from "../../components/LandingVideo"; import { LandingFeatures } from "../../components/LandingFeatures"; -import { LandingHowItWorks } from "../../components/LandingHowItWorks"; -import { LandingArchitecture } from "../../components/LandingArchitecture"; -import { LandingLiveDemo } from "../../components/LandingLiveDemo"; import { LandingSocialProof } from "../../components/LandingSocialProof"; import { LandingFooter } from "../../components/LandingFooter"; import { ScrollRevealProvider } from "../../components/ScrollRevealProvider"; @@ -19,9 +16,6 @@ export default function LandingPage() { - - - diff --git a/frontend/src/landing/components/LandingAgentsBar.tsx b/frontend/src/landing/components/LandingAgentsBar.tsx index 704b69c9b..6bc36ac22 100644 --- a/frontend/src/landing/components/LandingAgentsBar.tsx +++ b/frontend/src/landing/components/LandingAgentsBar.tsx @@ -36,9 +36,9 @@ export function LandingAgentsBar() {
- 01 - coverage + Coverage

- One daemon. 23 agent harnesses. + One Daemon. 23 agent harnesses.

diff --git a/frontend/src/landing/components/LandingFeatures.tsx b/frontend/src/landing/components/LandingFeatures.tsx index 19bb6b838..3d56e5cc7 100644 --- a/frontend/src/landing/components/LandingFeatures.tsx +++ b/frontend/src/landing/components/LandingFeatures.tsx @@ -1,208 +1,1113 @@ -const features = [ +"use client"; + +import { useMemo, useState } from "react"; + +type AgentHarness = { + id: string; + name: string; + org: string; + logo?: string; + command: string; + delivery: string; + restore: string; + hooks: string; +}; + +const primaryAgents: AgentHarness[] = [ { - kicker: "the substrate", - title: "An operating system, not a wrapper.", - desc: "Inbound and outbound port contracts. Swappable adapters. A CDC stream. The kind of substrate that survives the next model upgrade - and the one after that.", - visual: "ports", - accent: true, - span: "lg:col-span-2", + id: "claude-code", + name: "Claude Code", + org: "Anthropic", + logo: "/docs/logos/claude-code.svg", + command: "claude --append-system-prompt-file .ao/AGENTS.md", + delivery: "native CLI launch", + restore: "resume supported", + hooks: "workspace hooks", }, { - kicker: "isolation", - title: "Every agent gets a worktree.", - desc: "No branch collisions. No stash gymnastics. Each session lives in its own git worktree with its own attachable pane.", - visual: "branch", - span: "lg:col-span-1", + id: "codex", + name: "Codex", + org: "OpenAI", + logo: "/docs/logos/codex.svg", + command: "codex --config ao.session=session/ao-204", + delivery: "session flags", + restore: "codex resume", + hooks: "session flags", }, { - kicker: "feedback loop", - title: "PRs watched. Agents nudged.", - desc: "CI failure, requested change, merge conflict - the lifecycle manager routes each fact back to the owning agent automatically.", - visual: "pr", - span: "lg:col-span-1", + id: "opencode", + name: "OpenCode", + org: "OpenCode", + logo: "/docs/logos/opencode.svg", + command: "opencode run --session session/ao-204", + delivery: "terminal agent", + restore: "session API", + hooks: "activity bridge", }, { - kicker: "durability", - title: "Durable facts. Derived status.", - desc: "SQLite stores a small set of session facts. Display state is computed at read time. Triggers append to change_log; CDC fans events out via SSE.", - visual: "log", - span: "lg:col-span-2", + id: "aider", + name: "Aider", + org: "Aider", + logo: "/docs/logos/aider.png", + command: "aider --message-file .ao/prompt.md", + delivery: "prompt file", + restore: "supported", + hooks: "PATH wrappers", }, { - kicker: "trust model", - title: "Bound to 127.0.0.1.", - desc: "No auth, no CORS, no TLS. No SaaS in the loop. Your threat model fits on a sticky note.", - span: "lg:col-span-1", + id: "cursor", + name: "Cursor", + org: "Cursor", + logo: "/docs/logos/cursor.svg", + command: "cursor-agent --print --force", + delivery: "one-shot CLI", + restore: "fresh launch", + hooks: "terminal activity", }, { - kicker: "lifecycle", - title: "Lifecycle manager + reaper.", - desc: "Reduces runtime, activity and PR observations into durable state. Crash-safe reconcile on every boot.", - span: "lg:col-span-1", - }, - { - kicker: "interfaces", - title: "ao CLI and Electron app.", - desc: "Both drive the same daemon over loopback. Spawn from a terminal; supervise in a desktop kanban.", - span: "lg:col-span-1", + id: "goose", + name: "Goose", + org: "Block", + logo: "https://www.google.com/s2/favicons?domain=goose-docs.ai&sz=64", + command: "goose run --resume --session-id ao-204", + delivery: "native CLI launch", + restore: "session id", + hooks: "workspace hooks", }, ]; +const adapterNames = [ + "Claude Code", + "Codex", + "Cursor", + "OpenCode", + "Aider", + "Amp", + "Goose", + "Copilot", + "Grok", + "Qwen", + "Kimi", + "Crush", + "Cline", + "Droid", + "Devin", + "Auggie", + "Continue", + "Kiro", + "Kilo Code", + "Agy", + "Roo Code", + "Windsurf", + "Vibe", +]; + +const workspaceSessions = [ + { + id: "ao-204", + title: "Split terminal mux responsibilities", + agent: "Claude Code", + branch: "session/ao-204", + path: ".ao/worktrees/ao-204", + status: "working", + color: "#f59f4c", + files: ["backend/internal/terminal/manager.go", "frontend/src/renderer/components/TerminalPane.tsx"], + }, + { + id: "int-8", + title: "fix auth timeout retry loop", + agent: "Codex", + branch: "fix/auth-timeouts", + path: ".ao/worktrees/int-8", + status: "ci failed", + color: "#ff6b73", + files: ["backend/internal/httpd/auth.go", "backend/internal/session_manager/restore.go"], + }, + { + id: "ao-211", + title: "publish linux desktop install path", + agent: "Aider", + branch: "docs/linux-install", + path: ".ao/worktrees/ao-211", + status: "approved", + color: "#6ee79a", + files: ["frontend/src/landing/content/docs/installation.mdx", "README.md"], + }, +]; + +const feedbackSessions = [ + { + id: "pr-184", + number: "#184", + title: "fix auth timeout retry loop", + agent: "Codex", + branch: "fix/auth-timeouts", + session: "int-8", + state: "needs you", + color: "#ff6b73", + checks: [ + { name: "lint", state: "passed", color: "#6ee79a" }, + { name: "unit", state: "passed", color: "#6ee79a" }, + { name: "e2e", state: "failed", color: "#ff6b73" }, + ], + comments: ["Auth retry leaks stale token after timeout", "Add regression coverage for 401 retry path"], + nudge: "CI failed on PR #184. Fix auth retry timeout and push an update.", + }, + { + id: "pr-185", + number: "#185", + title: "add rate limit headers", + agent: "OpenCode", + branch: "feat/rate-limit-headers", + session: "ao-185", + state: "in review", + color: "#93b4f8", + checks: [ + { name: "lint", state: "passed", color: "#6ee79a" }, + { name: "unit", state: "passed", color: "#6ee79a" }, + { name: "review", state: "pending", color: "#93b4f8" }, + ], + comments: ["Reviewer asked for header docs", "Open question on retry-after semantics"], + nudge: "Review comments landed on PR #185. Address docs and retry-after behavior.", + }, + { + id: "pr-204", + number: "#204", + title: "Build onboarding test for published npm package", + agent: "Cursor", + branch: "test/onboarding-harness", + session: "ao-204", + state: "ready to merge", + color: "#6ee79a", + checks: [ + { name: "lint", state: "passed", color: "#6ee79a" }, + { name: "unit", state: "passed", color: "#6ee79a" }, + { name: "review", state: "approved", color: "#6ee79a" }, + ], + comments: ["Approved with two reviews", "Mergeability clean"], + nudge: "PR #204 is approved and mergeable. Ready for final merge.", + }, +]; + +const daemonChecks = [ + { label: "daemon", value: "ready on 127.0.0.1:3001", state: "ok" }, + { label: "database", value: "~/.ao/data/ao.sqlite", state: "ok" }, + { label: "git", value: "available", state: "ok" }, + { label: "runtime", value: "tmux detected", state: "ok" }, +]; + export function LandingFeatures() { + const [workerId, setWorkerId] = useState("codex"); + const [orchestratorId, setOrchestratorId] = useState("claude-code"); + const [workspaceId, setWorkspaceId] = useState("int-8"); + const [feedbackId, setFeedbackId] = useState("pr-184"); + + const worker = useMemo(() => primaryAgents.find((agent) => agent.id === workerId) ?? primaryAgents[0], [workerId]); + const orchestrator = useMemo( + () => primaryAgents.find((agent) => agent.id === orchestratorId) ?? primaryAgents[0], + [orchestratorId], + ); + const workspace = useMemo( + () => workspaceSessions.find((session) => session.id === workspaceId) ?? workspaceSessions[0], + [workspaceId], + ); + const feedback = useMemo( + () => feedbackSessions.find((session) => session.id === feedbackId) ?? feedbackSessions[0], + [feedbackId], + ); + return (

-
+
-
02 - what's inside
+
What's inside

- Built like an operating system,{" "} - not a wrapper. + Run the agent you already use. + + AO wraps the workflow around it. +

-

- Inbound/outbound port contracts. Swappable adapters. A CDC stream - the kind of substrate that survives - the next model upgrade. And the one after that. +

+ Claude Code, Codex, Cursor, OpenCode, Aider, Goose, Droid, Kilo and the rest stay native terminal + tools. AO standardizes launch, restore, hooks, activity and PR ownership through one adapter contract.

-
- {features.map((feature, i) => ( - - ))} +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
); } -function FeatureCard({ feature, index }: { feature: (typeof features)[number]; index: number }) { +function FeatureNarrative({ worker, orchestrator }: { worker: AgentHarness; orchestrator: AgentHarness }) { return ( -
-
-
- {String(index + 1).padStart(2, "0")} +
+
+
+
+ feature 01 +
+

+ Agent-agnostic by design. +

+
+
+ 23 harnesses
- - {feature.kicker} -
-

- {feature.title} -

-

{feature.desc}

- {feature.visual === "ports" && } - {feature.visual === "branch" && } - {feature.visual === "pr" && } - {feature.visual === "log" && } + +
+ + + + +
+ +
+
+ adapter surface +
+
+ + + + +
+
+ +
+
+ agent-orchestrator.yaml + +
+
+					{`agents:
+  worker: ${worker.id}
+  orchestrator: ${orchestrator.id}
+workspace: worktree
+runtime: platform-native`}
+				
+
); } -function PortsVisual() { +function AgentHarnessDemo({ + worker, + orchestrator, + workerId, + orchestratorId, + onWorkerChange, + onOrchestratorChange, +}: { + worker: AgentHarness; + orchestrator: AgentHarness; + workerId: string; + orchestratorId: string; + onWorkerChange: (id: string) => void; + onOrchestratorChange: (id: string) => void; +}) { return ( -
- {["Agent", "Runtime", "Workspace", "SCM", "Tracker", "Reviewer"].map((port, i) => ( -
- - {port} +
+
+
+ +
+
Project agents
+
/repo/agent-orchestrator
+
- ))} +
+ adapter contract +
+
+ +
+
+
+ + +
+ +
+ {primaryAgents.map((agent) => ( + + ))} +
+ +
+ Click to set the worker. Double click to promote an agent into the orchestrator slot. +
+
+ +
+
+
+
Launch preview
+
same daemon route, different native CLI
+
+
+ ready +
+
+ +
+
+ + + + ao spawn +
+
+ + + + + + +
+
+ +
+ + + +
+ +
+
+ +
+ +
+ +
+
+
+ {adapterNames.map((name) => ( + + {name} + + ))} +
+
+
+
+
+
+ ); +} + +function ContractRow({ label, value, sub }: { label: string; value: string; sub: string }) { + return ( +
+
+
{label}
+
{value}
+
+
{sub}
); } -function BranchVisual() { +function MiniStat({ title, value }: { title: string; value: string }) { return ( - - - - - - - - - - - +
+
{title}
+
{value}
+
); } -function PrVisual() { +function AgentSelectLabel({ label, agent }: { label: string; agent: AgentHarness }) { return ( -
- - - - -
- -> nudge -> sess_8f2 +
+
{label}
+
+ +
+
{agent.name}
+
{agent.id}
+
); } -function Row({ - color, - label, - status, - highlight, +function AgentLogo({ agent, className }: { agent: AgentHarness; className: string }) { + if (!agent.logo) { + return ( +
+ {agent.name.slice(0, 1)} +
+ ); + } + + return ; +} + +function TerminalLine({ + text, + muted, + accent, + success, }: { - color: string; - label: string; - status: string; - highlight?: boolean; + text: string; + muted?: boolean; + accent?: boolean; + success?: boolean; }) { return ( -
- - - {label} - - - {status} - +
+ {text}
); } -function LogVisual() { +function PipelineStep({ title, detail, active }: { title: string; detail: string; active?: boolean }) { return ( -
- {[ - ["0x1f", "sess.spawn", "var(--fg-muted)"], - ["0x20", "pr.opened", "var(--fg-muted)"], - ["0x21", "ci.fail -> nudge", "var(--status-fail)"], - ["0x22", "agent.resume", "var(--fg-muted)"], - ["0x23", "pr.merged", "var(--status-ok)"], - ].map(([hex, label, color]) => ( -
- {label} - {hex} -
- ))} +
+
+ + {title} +
+
{detail}
+
+ ); +} + +function AdapterNode({ agent, label }: { agent: AgentHarness; label: string }) { + return ( +
+ +
+
{label}
+
{agent.name}
+
+
+ ); +} + +function WorkspaceIsolationDemo({ + activeId, + onSelect, + workspace, +}: { + activeId: string; + onSelect: (id: string) => void; + workspace: (typeof workspaceSessions)[number]; +}) { + return ( +
+
+ + +
+
+
+
+

Session

+ + {workspace.status} + +
+
+ {workspace.agent} {"->"} {workspace.branch} +
+
+
+ + +
+
+ +
+
+
+
+
{workspace.title}
+
{workspace.path}
+
+
+ {workspace.id} +
+
+ +
+
+
+ + + + terminal +
+
+ + + + + {workspace.files.map((file) => ( + + ))} + +
+
+
+
+ + +
+
+
+
+ ); +} + +function WorkspaceNarrative({ workspace }: { workspace: (typeof workspaceSessions)[number] }) { + return ( +
+
+
feature 02
+

+ Every agent gets its own checkout. +

+

+ AO spawns each task into a separate git worktree with its own runtime pane, branch and session metadata. One + agent can fail CI while another keeps moving without branch collisions or stash cleanup. +

+
+ +
+ + + +
+ +
+
+ why it matters +
+
+ + + +
+
+
+ ); +} + +function InspectorFact({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function FeedbackNarrative({ feedback }: { feedback: (typeof feedbackSessions)[number] }) { + return ( +
+
+
feature 03
+

+ PR feedback goes back to the right agent. +

+

+ AO does not just show a board. It watches PR state, CI checks, reviews, mergeability and pending comments, + then routes the actionable fact to the session that owns the work. +

+
+ +
+ + + +
+ +
+
+ routing loop +
+
+ + + +
+
+
+ ); +} + +function FeedbackRoutingDemo({ + activeId, + onSelect, + feedback, +}: { + activeId: string; + onSelect: (id: string) => void; + feedback: (typeof feedbackSessions)[number]; +}) { + return ( +
+
+
+
Pull requests
+
CI, reviews and comments mapped to sessions
+
+
+ lifecycle +
+
+ +
+ + +
+
+
+
+ {feedback.number} +

+ {feedback.title} +

+
+
+ {feedback.branch} {"->"} {feedback.agent} session {feedback.session} +
+
+ +
+ +
+
+
+
+ checks +
+
+ {feedback.checks.map((check) => ( +
+ + + {check.name} + + + {check.state} + +
+ ))} +
+
+ +
+
+ review comments +
+
+ {feedback.comments.map((comment) => ( +
+ {comment} +
+ ))} +
+
+
+ +
+
+
+ + + +
+ ao send +
+
+ + + + + +
+
+
+ +
+ + + +
+
+
+
+ ); +} + +function DaemonControlDemo() { + return ( +
+
+
+
Local control plane
+
desktop and CLI talk to the same daemon
+
+
+ 127.0.0.1 +
+
+ +
+
+
+
+
+ + + +
+ ao doctor +
+
+ + + + + + {daemonChecks.map((check) => ( + + ))} +
+
+ +
+ + + +
+ +
+
+ event stream +
+
+ + + + +
+
+
+ + +
+
+ ); +} + +function DaemonNarrative() { + return ( +
+
+
feature 04
+

+ One local daemon runs the whole loop. +

+

+ The desktop app and `ao` CLI both drive the same loopback daemon. It starts sessions, stores durable facts, + streams changes, attaches terminals and keeps the product local-first. +

+
+ +
+ + + +
+ +
+
+ system pieces +
+
+ + + +
+
+
+ ); +} + +function DaemonNode({ title, body, active }: { title: string; body: string; active?: boolean }) { + return ( +
+
+ + {title} +
+
{body}
+
+ ); +} + +function EventRow({ seq, label, detail }: { seq: string; label: string; detail: string }) { + return ( +
+ {seq} + {label} + {detail}
); } diff --git a/frontend/src/landing/components/LandingFooter.tsx b/frontend/src/landing/components/LandingFooter.tsx index 34f4fae1e..1adf7e71e 100644 --- a/frontend/src/landing/components/LandingFooter.tsx +++ b/frontend/src/landing/components/LandingFooter.tsx @@ -1,4 +1,34 @@ -const LOGO_URL = "/ao-logo.svg"; +const LOGO_URL = "/ao-logo-transparent.png"; + +const columns = [ + { + title: "Product", + links: [ + { label: "Features", href: "#features" }, + { label: "Agents", href: "#agents" }, + { label: "Install", href: "/docs/installation" }, + { label: "CLI", href: "/docs/cli" }, + ], + }, + { + title: "Docs", + links: [ + { label: "Overview", href: "/docs" }, + { label: "Architecture", href: "/docs/architecture" }, + { label: "Plugins", href: "/docs/plugins" }, + { label: "Changelog", href: "/docs/changelog" }, + ], + }, + { + title: "Community", + links: [ + { label: "GitHub", href: "https://github.com/AgentWrapper/agent-orchestrator" }, + { label: "Issues", href: "https://github.com/AgentWrapper/agent-orchestrator/issues" }, + { label: "Pull requests", href: "https://github.com/AgentWrapper/agent-orchestrator/pulls" }, + { label: "Releases", href: "https://github.com/AgentWrapper/agent-orchestrator/releases" }, + ], + }, +]; function GithubIcon({ className = "" }: { className?: string }) { return ( @@ -8,86 +38,69 @@ function GithubIcon({ className = "" }: { className?: string }) { ); } -const productLinks = [ - { label: "Features", href: "#features" }, - { label: "How it works", href: "#how" }, - { label: "Architecture", href: "#architecture" }, - { label: "Quickstart", href: "#quickstart" }, -]; - -const resourceLinks = [ - { label: "GitHub", href: "https://github.com/AgentWrapper/agent-orchestrator" }, - { label: "Architecture docs", href: "/docs/architecture" }, - { label: "CLI reference", href: "/docs/cli" }, - { label: "Releases", href: "https://github.com/AgentWrapper/agent-orchestrator/releases" }, -]; - -const communityLinks = [ - { label: "Contributors", href: "https://github.com/AgentWrapper/agent-orchestrator/graphs/contributors" }, - { label: "Issues", href: "https://github.com/AgentWrapper/agent-orchestrator/issues" }, - { label: "Pull requests", href: "https://github.com/AgentWrapper/agent-orchestrator/pulls" }, - { label: "ao-agents.com", href: "https://ao-agents.com" }, -]; - export function LandingFooter() { return ( -
diff --git a/frontend/src/landing/styles/globals.css b/frontend/src/landing/styles/globals.css index 31b751422..4a9b335dd 100644 --- a/frontend/src/landing/styles/globals.css +++ b/frontend/src/landing/styles/globals.css @@ -239,6 +239,17 @@ body::-webkit-scrollbar, background: var(--bg-deep); } +.tweet-fallback { + min-height: 240px; + border-radius: 14px; + border: 1px solid var(--border); + background: + linear-gradient(180deg, rgba(130, 170, 255, 0.035), transparent 36%), + #050607; + padding: 18px; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03); +} + .tweet-shell .twitter-tweet, .tweet-shell iframe { width: 100% !important; @@ -558,6 +569,54 @@ body::-webkit-scrollbar, animation: landing-sse-pulse 1.6s ease-in-out infinite; } +@keyframes landing-adapter-pulse { + 0% { + transform: translateX(-24px); + opacity: 0; + } + 18%, + 72% { + opacity: 1; + } + 100% { + transform: translateX(24px); + opacity: 0; + } +} + +.landing-adapter-pulse { + animation: landing-adapter-pulse 1.7s cubic-bezier(0.4, 0.8, 0.6, 1) infinite; +} + +.landing-feature-stack-card { + will-change: transform; +} + +.landing-feature-stack-cover { + isolation: isolate; +} + +.landing-feature-stack-cover::before { + content: ""; + position: absolute; + inset: -28px -2px -28px -2px; + z-index: -1; + border-radius: 24px; + background: var(--bg); + box-shadow: 0 -80px 0 80px var(--bg); +} + +@media (max-width: 1023px) { + .landing-feature-stack-card { + position: relative; + top: auto; + } + + .landing-feature-stack-cover::before { + display: none; + } +} + /* Switcher progress bar */ @keyframes landing-switcher-progress { from {