Files
paseo/packages/website/src/routes/index.tsx
T

347 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as React from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { CursorFieldProvider } from '~/components/butterfly'
import '~/styles.css'
export const Route = createFileRoute('/')({
head: () => ({
meta: [
{ title: 'Paseo – Run coding agents on your machines, from desktop and phone' },
{
name: 'description',
content:
'A self-hosted daemon for Claude Code, Codex, and OpenCode. Agents run on your machine with your full dev environment. Connect from phone, desktop, or web.',
},
],
}),
component: Home,
})
function Home() {
return (
<CursorFieldProvider>
{/* Hero section with background image */}
<div
className="relative min-h-[80vh] bg-cover bg-center bg-no-repeat overflow-hidden"
style={{ backgroundImage: 'url(/hero-bg.jpg)' }}
>
<div className="absolute inset-0 bg-background/80" />
<div className="absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-black to-transparent" />
<div className="relative p-6 md:p-20 max-w-3xl mx-auto">
<Nav />
<Hero />
<GetStarted />
</div>
</div>
{/* Content section with black background */}
<div className="bg-black">
<main className="p-6 md:p-20 max-w-3xl mx-auto">
<Features />
<Story />
<FAQ />
</main>
</div>
</CursorFieldProvider>
)
}
function Nav() {
return (
<nav className="flex items-center justify-between mb-16">
<div className="flex items-center gap-3">
<img src="/logo.svg" alt="Paseo" className="w-7 h-7" />
<span className="text-lg font-medium">Paseo</span>
</div>
<div className="flex items-center gap-4">
<a
href="/docs"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Docs
</a>
<a
href="https://github.com/moboudra/paseo"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1.5"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 -0.5 25 25"
>
<path d="m12.301 0h.093c2.242 0 4.34.613 6.137 1.68l-.055-.031c1.871 1.094 3.386 2.609 4.449 4.422l.031.058c1.04 1.769 1.654 3.896 1.654 6.166 0 5.406-3.483 10-8.327 11.658l-.087.026c-.063.02-.135.031-.209.031-.162 0-.312-.054-.433-.144l.002.001c-.128-.115-.208-.281-.208-.466 0-.005 0-.01 0-.014v.001q0-.048.008-1.226t.008-2.154c.007-.075.011-.161.011-.249 0-.792-.323-1.508-.844-2.025.618-.061 1.176-.163 1.718-.305l-.076.017c.573-.16 1.073-.373 1.537-.642l-.031.017c.508-.28.938-.636 1.292-1.058l.006-.007c.372-.476.663-1.036.84-1.645l.009-.035c.209-.683.329-1.468.329-2.281 0-.045 0-.091-.001-.136v.007c0-.022.001-.047.001-.072 0-1.248-.482-2.383-1.269-3.23l.003.003c.168-.44.265-.948.265-1.479 0-.649-.145-1.263-.404-1.814l.011.026c-.115-.022-.246-.035-.381-.035-.334 0-.649.078-.929.216l.012-.005c-.568.21-1.054.448-1.512.726l.038-.022-.609.384c-.922-.264-1.981-.416-3.075-.416s-2.153.152-3.157.436l.081-.02q-.256-.176-.681-.433c-.373-.214-.814-.421-1.272-.595l-.066-.022c-.293-.154-.64-.244-1.009-.244-.124 0-.246.01-.364.03l.013-.002c-.248.524-.393 1.139-.393 1.788 0 .531.097 1.04.275 1.509l-.01-.029c-.785.844-1.266 1.979-1.266 3.227 0 .025 0 .051.001.076v-.004c-.001.039-.001.084-.001.13 0 .809.12 1.591.344 2.327l-.015-.057c.189.643.476 1.202.85 1.693l-.009-.013c.354.435.782.793 1.267 1.062l.022.011c.432.252.933.465 1.46.614l.046.011c.466.125 1.024.227 1.595.284l.046.004c-.431.428-.718 1-.784 1.638l-.001.012c-.207.101-.448.183-.699.236l-.021.004c-.256.051-.549.08-.85.08-.022 0-.044 0-.066 0h.003c-.394-.008-.756-.136-1.055-.348l.006.004c-.371-.259-.671-.595-.881-.986l-.007-.015c-.198-.336-.459-.614-.768-.827l-.009-.006c-.225-.169-.49-.301-.776-.38l-.016-.004-.32-.048c-.023-.002-.05-.003-.077-.003-.14 0-.273.028-.394.077l.007-.003q-.128.072-.08.184c.039.086.087.16.145.225l-.001-.001c.061.072.13.135.205.19l.003.002.112.08c.283.148.516.354.693.603l.004.006c.191.237.359.505.494.792l.01.024.16.368c.135.402.38.738.7.981l.005.004c.3.234.662.402 1.057.478l.016.002c.33.064.714.104 1.106.112h.007c.045.002.097.002.15.002.261 0 .517-.021.767-.062l-.027.004.368-.064q0 .609.008 1.418t.008.873v.014c0 .185-.08.351-.208.466h-.001c-.119.089-.268.143-.431.143-.075 0-.147-.011-.214-.032l.005.001c-4.929-1.689-8.409-6.283-8.409-11.69 0-2.268.612-4.393 1.681-6.219l-.032.058c1.094-1.871 2.609-3.386 4.422-4.449l.058-.031c1.739-1.034 3.835-1.645 6.073-1.645h.098-.005zm-7.64 17.666q.048-.112-.112-.192-.16-.048-.208.032-.048.112.112.192.144.096.208-.032zm.497.545q.112-.08-.032-.256-.16-.144-.256-.048-.112.08.032.256.159.157.256.047zm.48.72q.144-.112 0-.304-.128-.208-.272-.096-.144.08 0 .288t.272.112zm.672.673q.128-.128-.064-.304-.192-.192-.32-.048-.144.128.064.304.192.192.32.044zm.913.4q.048-.176-.208-.256-.24-.064-.304.112t.208.24q.24.097.304-.096zm1.009.08q0-.208-.272-.176-.256 0-.256.176 0 .208.272.176.256.001.256-.175zm.929-.16q-.032-.176-.288-.144-.256.048-.224.24t.288.128.225-.224z" />
</svg>
GitHub
</a>
</div>
</nav>
)
}
function Hero() {
return (
<div className="space-y-6">
<h1 className="text-4xl md:text-5xl font-medium tracking-tight">
Manage coding agents from your phone and desktop
</h1>
<p className="text-white/70 text-lg leading-relaxed">
Agents run on your machine with your full dev environment. Connect from
phone, desktop, or web.
</p>
</div>
)
}
function Differentiator({
title,
description,
}: {
title: string
description: string
}) {
return (
<div>
<p className="font-medium text-sm">{title}</p>
<p className="text-sm text-muted-foreground">{description}</p>
</div>
)
}
function Features() {
return (
<div className="space-y-6">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
<Feature
title="Self-hosted"
description="The daemon runs on your laptop, home server, or VPS. Allowing you to take full advantage of your dev environment."
/>
<Feature
title="Multi-provider"
description="Works with existing agent harnesses like Claude Code, Codex, and OpenCode from one interface."
/>
<Feature
title="Multi-host"
description="Connect to multiple daemons and see all your agents in one place."
/>
<Feature
title="Voice input"
description="Dictate prompts when you're away from your keyboard."
/>
<Feature
title="Optional relay"
description="Use the hosted end-to-end encrypted relay for remote access, or connect directly over your network."
/>
<Feature
title="Cross-device"
description="Jump seamlessly between iOS, Android, desktop, web, and CLI."
/>
<Feature
title="Git integration"
description="Manage agents in isolated worktrees. Review diffs and ship directly from the app."
/>
<Feature
title="Open source"
description="Free and open source. Run it yourself, fork it, contribute."
/>
</div>
</div>
)
}
function Feature({
title,
description,
}: {
title: string
description: string
}) {
return (
<div className="space-y-1">
<p className="font-medium text-base">{title}</p>
<p className="text-sm text-white/60">{description}</p>
</div>
)
}
function GetStarted() {
return (
<div className="pt-10 space-y-6">
<div className="space-y-4">
<Step number={1}>
<p className="text-sm">Install and run the daemon</p>
<CodeBlock>npm install -g @getpaseo/cli && paseo</CodeBlock>
</Step>
<Step number={2}>
<p className="text-sm pt-0.5">
Open the app and connect to your daemon
</p>
</Step>
<Step number={3}>
<p className="text-sm pt-0.5">
Start managing your agents from anywhere
</p>
</Step>
</div>
<p className="text-sm text-white/70 pt-2">
Free and open source. Works on iOS, Android, web, and desktop.
</p>
</div>
)
}
function Step({
number,
children,
}: {
number: number
children: React.ReactNode
}) {
return (
<div className="flex gap-4">
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-white/20 flex items-center justify-center text-xs font-medium">
{number}
</span>
<div className="space-y-2 flex-1">{children}</div>
</div>
)
}
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 (
<div className="bg-card rounded-lg p-3 md:p-4 font-mono text-sm flex items-center justify-between gap-2">
<div>
<span className="text-muted-foreground select-none">$ </span>
<span className="text-foreground">{children}</span>
</div>
<button
onClick={handleCopy}
className="text-muted-foreground hover:text-foreground transition-colors p-1"
title="Copy to clipboard"
>
{copied ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 256 256"
>
<path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 256 256"
>
<path d="M216,28H88A20,20,0,0,0,68,48V76H40A20,20,0,0,0,20,96V216a20,20,0,0,0,20,20H168a20,20,0,0,0,20-20V188h28a20,20,0,0,0,20-20V48A20,20,0,0,0,216,28ZM164,212H44V100H164Zm48-48H188V96a20,20,0,0,0-20-20H92V52H212Z" />
</svg>
)}
</button>
</div>
)
}
function Story() {
return (
<div className="pt-16 space-y-4">
<h2 className="text-2xl font-medium">Background</h2>
<div className="space-y-4 text-sm text-white/60">
<p>
I started using Claude Code soon after it launched, often on my phone
while going on walks to spend less time at my desk. I'd SSH into Tmux
from my phone. It worked, but the UX was rough. Dictation was bad, the
virtual keyboard was awkward, and the TUI would randomly start
flickering, which forced me to start over very often.
</p>
<p>
I started building a simple app to manage agents via voice. I continued
adding features as I needed them, and slowly ended up building what
Paseo is today.
</p>
<p>
Anthropic and OpenAI added coding agents to their mobile apps since I
started working on this, but they force you into cloud sandboxes where
you lose your whole setup. I also like testing different agents, so
locking myself to a single harness or model wasn't an option.
</p>
</div>
</div>
)
}
function FAQ() {
return (
<div className="pt-16 space-y-6">
<h2 className="text-2xl font-medium">FAQ</h2>
<div className="space-y-6">
<FAQItem question="Is this free?">
Paseo is free and open source. It wraps CLI tools like Claude Code and
Codex, which you'll need to have installed and configured with your
own credentials. Voice features currently require an OpenAI API key,
but local voice is coming soon.
</FAQItem>
<FAQItem question="Does my code leave my machine?">
Paseo itself doesn't send your code anywhere. Agents run locally and
communicate with their own APIs as they normally would. We provide an
optional end-to-end encrypted relay for remote access, but you can
also connect directly over your local network or use your own tunnel.
</FAQItem>
<FAQItem question="What agents does it support?">
Claude Code, Codex, and OpenCode.
</FAQItem>
<FAQItem question="What's the business model?">There isn't one.</FAQItem>
<FAQItem question="Isn't this just more screen time?">
I won't pretend this can't be misused. But for me it means less time
at my desk, not more. I brainstorm whole features with voice. I kick
off work at my desk, then check in from my phone during a walk. I see
what an agent needs, send a voice reply, and put my phone away.
</FAQItem>
<FAQItem question="What does Paseo mean?">
Stroll, in Spanish.
</FAQItem>
</div>
</div>
)
}
function FAQItem({
question,
children,
}: {
question: string
children: React.ReactNode
}) {
return (
<details className="group">
<summary className="font-medium text-sm cursor-pointer list-none flex items-start gap-2">
<span className="font-mono text-white/40 group-open:hidden">+</span>
<span className="font-mono text-white/40 hidden group-open:inline">
-
</span>
{question}
</summary>
<div className="text-sm text-white/60 space-y-2 mt-2 ml-4">
{children}
</div>
</details>
)
}