);
}
function DownloadButton() {
const detectedPlatform = useDetectedPlatform();
const primary = downloadOptions.find((o) => o.platform === detectedPlatform)!;
const PrimaryIcon = primary.icon;
return (
Download for {primary.label}
);
}
function ServerInstallButton() {
return (
}
title="Run agents on a remote machine"
description="For headless machines you want to connect to from the Paseo apps. The desktop app already includes a built-in daemon."
command="npm install -g @getpaseo/cli && paseo"
footnote={
<>
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 AppStoreIcon(props: React.SVGProps) {
return (
);
}
function ChevronDownIcon(props: React.SVGProps) {
return (
);
}
function Step({ number, children }: { number: number; children: React.ReactNode }) {
return (
{number}
{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: "Run agents",
description:
"Launch agents locally or on any remote host. The --worktree flag spins up an isolated git branch so you can run multiple agents on the same repo without conflicts.",
code: `paseo run "implement user authentication"
paseo run --provider codex --worktree feature-x "implement feature X"
paseo run --host devbox:6767 "run the full test suite"
paseo ls # list running agents
paseo attach abc123 # stream live output
paseo send abc123 "also add tests" # follow-up task`,
},
{
title: "Loops",
description:
"Have one agent do the work, another verify the result, and loop until it passes. Built-in, no shell scripting needed.",
code: `# Worker-verifier loop: fix tests until they pass
paseo loop run "make all tests pass" \\
--verify "verify tests pass and the code is production-ready" \\
--verify-check "npm test" \\
--max-iterations 5
paseo loop ls # list running loops
paseo loop logs abc123 # stream loop output`,
},
{
title: "Schedules",
description:
"Run agents on a cron schedule. Automate recurring tasks like dependency updates, security audits, or report generation.",
code: `# Run a security audit every Monday at 9am
paseo schedule create --cron "0 9 * * 1" \\
"audit the codebase for security issues and open PRs for fixes"
paseo schedule ls # list all schedules
paseo schedule pause abc123 # pause a schedule
paseo schedule delete abc123 # remove a schedule`,
},
];
function PhoneShowcase() {
const containerRef = React.useRef(null);
const textInView = useInView(containerRef, { once: true, margin: "-80px" });
// Scroll-linked animation: track how far through the container the user has scrolled
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "center center"],
});
// Responsive slide distance
const [slideDistance, setSlideDistance] = React.useState(260);
React.useEffect(() => {
function update() {
setSlideDistance(window.innerWidth < 768 ? 140 : 260);
}
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
// Side phones start at x=0 (behind center) and slide out to final position
const sideOpacity = useTransform(scrollYProgress, [0.2, 0.6], [0, 1]);
const leftX = useTransform(scrollYProgress, [0.2, 0.6], [0, -slideDistance]);
const rightX = useTransform(scrollYProgress, [0.2, 0.6], [0, slideDistance]);
return (
{/* Arrow + text */}
When you want to step away from your desk, you can.
The native mobile app has full feature parity with desktop.
{/* Phone trio — side phones are absolute, start behind center, slide outward with perspective rotation */}
{/* Left phone — rotated to face inward */}
{/* Center phone */}
{/* Right phone — rotated to face inward */}
);
}
function CLISection() {
const [activeIndex, setActiveIndex] = React.useState(0);
const active = cliExamples[activeIndex];
return (
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 (
I built Paseo because I wanted better tools for coding agents on my own setup. It's an independent open source project, built around freedom of choice and real workflows. If you like what I'm building, consider becoming a supporter.