import * as React from 'react' import { motion, AnimatePresence } from 'framer-motion' import { CursorFieldProvider } from '~/components/butterfly' import websitePackage from '../../package.json' import '~/styles.css' const desktopVersion = websitePackage.version const releaseBase = `https://github.com/getpaseo/paseo/releases/download/v${desktopVersion}` const androidApkHref = `${releaseBase}/paseo-v${desktopVersion}-android.apk` type Platform = 'mac-silicon' | 'mac-intel' | 'windows' | 'linux' interface DownloadOption { platform: Platform label: string href: string icon: (props: React.SVGProps) => React.ReactElement } const downloadOptions: DownloadOption[] = [ { platform: 'mac-silicon', label: 'Mac', href: `${releaseBase}/Paseo_${desktopVersion}_aarch64.dmg`, icon: AppleIcon, }, { platform: 'mac-intel', label: 'Mac Intel', href: `${releaseBase}/Paseo_${desktopVersion}_x86_64.dmg`, icon: AppleIcon, }, { platform: 'windows', label: 'Windows', href: `${releaseBase}/Paseo_${desktopVersion}_x64-setup.exe`, icon: WindowsIcon, }, { platform: 'linux', label: 'Linux', href: `${releaseBase}/Paseo_${desktopVersion}_amd64.AppImage`, icon: LinuxIcon, }, ] function useDetectedPlatform(): Platform { const [platform, setPlatform] = React.useState('mac-silicon') React.useEffect(() => { const ua = navigator.userAgent.toLowerCase() if (ua.includes('win')) { setPlatform('windows') } else if (ua.includes('linux')) { setPlatform('linux') } else if (ua.includes('mac')) { // Check for Apple Silicon vs Intel // navigator.platform is deprecated but still the most reliable check const isARM = /arm|aarch64/i.test(navigator.platform) || // Chrome/Edge on Apple Silicon report x86 platform but have ARM in userAgentData (navigator as unknown as { userAgentData?: { architecture?: string } }).userAgentData?.architecture === 'arm' setPlatform(isARM ? 'mac-silicon' : 'mac-silicon') // Default to Silicon for modern Macs } }, []) return platform } interface LandingPageProps { title: React.ReactNode subtitle: string } export function LandingPage({ title, subtitle }: LandingPageProps) { return ( {/* Hero section with background image */}
{/* Mockup - inside hero so it's above the gradient, positioned to overflow into black section */}
Paseo app showing agent management interface
{/* Content section */} ) } function Nav() { return ( ) } function Hero({ title, subtitle }: { title: React.ReactNode; subtitle: string }) { return (
{title} {subtitle}
) } function AgentBadge({ name, icon }: { name: string; icon: React.ReactNode }) { const [hovered, setHovered] = React.useState(false) return ( setHovered(true)} onMouseLeave={() => setHovered(false)} > {icon} {hovered && ( {name} )} ) } function Features() { return (
{/* Primary differentiators - 2x2 */}
{[ { title: 'Self-hosted', description: 'Agents run on your machine with your full dev environment. Use your tools, your configs and your skills.', icon: ( ), }, { title: 'Multi-provider', description: 'Claude Code, Codex, and OpenCode through the same interface. Pick the right model for each job.', icon: ( ), }, { title: 'Voice control', description: 'Dictate tasks or talk through problems in voice mode. Hands-free when you need it.', icon: ( ), }, { title: 'Cross-device', description: 'iOS, Android, desktop, web, and CLI. Start work at your desk, check in from your phone, script it from the terminal.', icon: ( ), }, ].map((feature, i) => (
{feature.icon}

{feature.title}

{feature.description}

))}
{/* Also */} Multi-host Built-in terminal Git worktrees E2E encrypted relay Open source
) } function GetStarted() { return (
Supports
} /> } /> } />
) } function DownloadButton() { const detectedPlatform = useDetectedPlatform() const [open, setOpen] = React.useState(false) const ref = React.useRef(null) React.useEffect(() => { function handleClickOutside(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { setOpen(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) const primary = downloadOptions.find((o) => o.platform === detectedPlatform)! const secondaryOptions = downloadOptions.filter((o) => o.platform !== detectedPlatform) const PrimaryIcon = primary.icon return (
Download for {primary.label}
{open && ( {secondaryOptions.map((option) => { const Icon = option.icon return ( setOpen(false)} className="flex items-center gap-2 px-4 py-2 text-sm text-white/80 hover:bg-white/10 transition-colors" > {option.label} ) })} )}
) } function ServerInstallButton() { const [open, setOpen] = React.useState(false) const ref = React.useRef(null) React.useEffect(() => { function handleClickOutside(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { setOpen(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) return (
{open && ( <> setOpen(false)} />

Run agents on a remote machine

For headless machines you want to connect to from the Paseo apps. The desktop app already includes a built-in daemon.

npm install -g @getpaseo/cli && paseo

Requires Node.js 18+. Run paseo to start the daemon.

)}
) } function ClaudeCodeIcon(props: React.SVGProps) { return ( ) } function CodexIcon(props: React.SVGProps) { return ( ) } function OpenCodeIcon(props: React.SVGProps) { return ( ) } function AppleIcon(props: React.SVGProps) { return ( ) } function AndroidIcon(props: React.SVGProps) { return ( ) } function AppStoreIcon(props: React.SVGProps) { return ( ) } function WindowsIcon(props: React.SVGProps) { return ( ) } function LinuxIcon(props: React.SVGProps) { return ( ) } function TerminalIcon(props: React.SVGProps) { return ( ) } function ChevronDownIcon(props: React.SVGProps) { return ( ) } function GlobeIcon(props: React.SVGProps) { return ( ) } function Step({ number, children }: { number: number; children: React.ReactNode }) { return (
{number}
{children}
) } function CodeBlock({ children }: { children: React.ReactNode }) { const [copied, setCopied] = React.useState(false) const text = typeof children === 'string' ? children : '' function handleCopy() { navigator.clipboard.writeText(text) setCopied(true) setTimeout(() => setCopied(false), 2000) } return (
$ {children}
) } const bashKeywords = new Set(['while', 'do', 'done', 'if', 'then', 'fi', 'else', 'break', 'true', 'false']) const bashCommands = new Set(['paseo', 'echo', 'jq']) function highlightBash(code: string): React.ReactNode { const tokens: React.ReactNode[] = [] let i = 0 let key = 0 while (i < code.length) { if (code[i] === '#' && (i === 0 || /[\s(]/.test(code[i - 1]))) { const end = code.indexOf('\n', i) const comment = end === -1 ? code.slice(i) : code.slice(i, end) tokens.push({comment}) i += comment.length continue } if (code[i] === '"') { let j = i + 1 while (j < code.length && code[j] !== '"') { if (code[j] === '\\') j++ j++ } const str = code.slice(i, j + 1) tokens.push({str}) i = j + 1 continue } if (code[i] === "'") { let j = i + 1 while (j < code.length && code[j] !== "'") j++ const str = code.slice(i, j + 1) tokens.push({str}) i = j + 1 continue } if (code[i] === '$') { if (code[i + 1] === '(') { tokens.push($() i += 2 continue } let j = i + 1 while (j < code.length && /\w/.test(code[j])) j++ tokens.push({code.slice(i, j)}) i = j continue } if (code[i] === '-' && (i === 0 || /\s/.test(code[i - 1])) && i + 1 < code.length && /[\w-]/.test(code[i + 1])) { let j = i if (code[j + 1] === '-') j++ j++ while (j < code.length && /[\w-]/.test(code[j])) j++ tokens.push({code.slice(i, j)}) i = j continue } if (/[a-zA-Z_]/.test(code[i])) { let j = i while (j < code.length && /\w/.test(code[j])) j++ const word = code.slice(i, j) if (bashKeywords.has(word)) { tokens.push({word}) } else if (bashCommands.has(word)) { tokens.push({word}) } else { tokens.push(word) key++ } i = j continue } if (code[i] === '|' || (code[i] === '&' && code[i + 1] === '&')) { const op = code[i] === '|' ? '|' : '&&' tokens.push({op}) i += op.length continue } if (code[i] === '\\') { tokens.push(\) i++ continue } if (code[i] === ')') { tokens.push()) i++ continue } tokens.push(code[i]) i++ } return <>{tokens} } function CLICodeBlock({ children }: { children: string }) { const [copied, setCopied] = React.useState(false) function handleCopy() { navigator.clipboard.writeText(children) setCopied(true) setTimeout(() => setCopied(false), 2000) } return (
{highlightBash(children)}
) } interface CLIExample { title: string description: string code: string } const cliExamples: CLIExample[] = [ { title: 'Launch and monitor', description: 'Give an agent a task and watch it work. The --worktree flag spins up an isolated git branch so you can run multiple agents on the same repo without conflicts.', code: `paseo run --provider claude/opus-4.6 "implement user authentication" paseo run --provider codex/gpt-5.4 --worktree feature-x "implement feature X" paseo ls # list running agents paseo attach abc123 # stream live output paseo send abc123 "also add tests" # follow-up task`, }, { title: 'Orchestration', description: 'Agents can use the CLI too. Tell one agent to spawn others, split up the work, and pull everything together when they\'re done.', code: `# Spawn two agents in parallel, wait, then synthesize paseo run --detach "implement the frontend" --name frontend paseo run --detach "implement the API layer" --name api paseo wait frontend api paseo run "review both branches and write an integration plan"`, }, { title: 'Structured output', description: 'Pass a JSON schema and get typed data back from any agent run. No output parsing, just the structured result you asked for.', code: `result=$(paseo run \\ --output-schema '{ "type": "object", "properties": { "severity": { "type": "string", "enum": ["low", "medium", "high"] }, "issues": { "type": "array", "items": { "type": "string" } } }, "required": ["severity", "issues"] }' \\ "audit this codebase for security issues") echo $result | jq '.severity' # "high" echo $result | jq '.issues[0]' # "SQL injection on line 42"`, }, { title: 'Remote', description: 'Point at any daemon on your network or over the internet. Run agents on a beefy server from your laptop.', code: `# Set once for the session export PASEO_HOST=workstation.local:6767 paseo run "run the full test suite" paseo ls # Or per-command paseo --host gpu-server:6767 run "train the model" paseo --host gpu-server:6767 attach abc123`, }, { title: 'Worker-judge loops', description: 'Have one agent do the work and another judge the result. Loop until it passes. Good for test fixes, code review, or any task with clear acceptance criteria.', code: `while true; do paseo run "make all tests pass" verdict=$(paseo run \\ --output-schema '{"type":"object","properties":{"passed":{"type":"boolean"}},"required":["passed"]}' \\ "verify tests pass and the code is production-ready") echo "$verdict" | jq -e '.passed' && break done`, }, ] function CLISection() { const [activeIndex, setActiveIndex] = React.useState(0) const active = cliExamples[activeIndex] return (

CLI

Everything you can do in the app, you can do from the terminal.

{cliExamples.map((example, i) => ( ))}

{active.description}

{active.code}
Full CLI reference
) } function FAQ() { return (

FAQ

Yes. Paseo is free and open source. You need Claude Code, Codex, or OpenCode installed with your own credentials. Voice is local-first by default and can optionally use OpenAI speech providers if you configure them. Paseo doesn't send your code anywhere. Agents run locally and talk to their own APIs as they normally would. For remote access, you can use the optional{' '} end-to-end encrypted relay, connect directly over your local network, or use your own tunnel. Claude Code, Codex, and OpenCode. Each agent runs as its own process using its own CLI. Paseo doesn't modify or wrap their behavior. No. You can run the daemon headless with{' '} npm install -g @getpaseo/cli && paseo{' '} and use the CLI, web app, or mobile app to connect. The desktop app just bundles the daemon with a UI. Voice runs locally on your device by default. You talk, the app transcribes and sends it to your agent as text. Optionally, you can configure OpenAI speech providers for higher-quality transcription and text-to-speech. See the voice docs. Yes. You can use the hosted relay (end-to-end encrypted, Paseo can't read your traffic), set up your own tunnel (Tailscale, Cloudflare Tunnel, etc.), or expose the daemon port directly. See configuration. No. Paseo works in any directory. Worktrees are optional and only relevant if you use git. You can run agents anywhere you'd normally work.

We can't make promises on behalf of providers.

That said, Paseo launches the official first-party CLIs (Claude Code, Codex, OpenCode) as subprocesses. It doesn't extract tokens or call inference APIs directly. From the provider's perspective, usage through Paseo is indistinguishable from running the CLI yourself.

I've been using Paseo with all providers for months without issue.

When you launch an agent with the worktree option (from the app, desktop, or CLI), Paseo creates a git worktree and runs the agent inside it. The agent works on an isolated branch without touching your main working directory. See the{' '} worktrees docs.
) } function SponsorCTA() { return (

Paseo is an independent project

I built Paseo because the existing tools weren't good enough for me. There's no VC or big team behind this. If it saves you time, sponsoring keeps development going.

) } function FAQItem({ question, children }: { question: string; children: React.ReactNode }) { return (
+ - {question}
{children}
) }