restore website docs routes

This commit is contained in:
Mohamed Boudra
2026-02-05 14:20:20 +07:00
parent f3cc01f659
commit e6c3f2838b
4 changed files with 661 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/docs/best-practices')({
head: () => ({
meta: [
{ title: 'Best Practices - Paseo Docs' },
{
name: 'description',
content: 'Tips for getting the most out of Paseo and mobile-first agent workflows.',
},
],
}),
component: BestPractices,
})
function BestPractices() {
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-medium font-serif mb-4">Best Practices</h1>
<p className="text-white/60 leading-relaxed">
What I've learned from using Paseo daily. Not rules, just patterns
that have worked for me.
</p>
</div>
<section className="space-y-4">
<h2 className="text-xl font-medium">Agents replace typing, not thinking</h2>
<p className="text-white/60 leading-relaxed">
Your role has changed. You're no longer the one writing code line by
line. You're the one making decisions: what to build, how it should
work, what the architecture looks like. The agent executes, but you
direct.
</p>
<p className="text-white/60 leading-relaxed">
You can't just say "implement feature X" and walk away. You still
have to do the hard part: deciding what to build, how it fits into
the system, what trade-offs to make. Thinking is not optional. At
least for now, agents replace the typing, not the thinking.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Verification loops</h2>
<p className="text-white/60 leading-relaxed">
The agent needs a way to verify its work. TDD is one implementation of
this pattern: get the agent to write a failing test, verify it fails
for the right reasons, then tell it to make the test pass. The agent
can loop on its own because it knows what "done" means.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Invest in tooling</h2>
<p className="text-white/60 leading-relaxed">
It's not just test runners. For web apps, something like Playwright
MCP lets the agent take screenshots and verify UI changes. For a SaaS
app I built a CLI that wraps all the business logic so the agent could
launch jobs, check statuses, and scrape data without going through the
UI.
</p>
<p className="text-white/60 leading-relaxed">
Code is cheap with coding agents. I would have never written that CLI
before because it felt like wasted effort. Now I bootstrap tooling
first. It pays off exponentially.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Agents are cheap</h2>
<p className="text-white/60 leading-relaxed">
Don't be shy about running multiple agents. Paseo lets you launch
agents in isolated worktrees. Kick one off with voice while walking,
then kick off another. They work independently. You get a notification
when they're done.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Use voice extensively</h2>
<p className="text-white/60 leading-relaxed">
It's much more natural to use voice to communicate ideas and pull them
out of your brain. The agent will parse and organize your thoughts
better than if you try to write the perfect prompt. You don't need to
organize anything. Just talk.
</p>
<p className="text-white/60 leading-relaxed">
Current speech-to-text models are really good. They catch accents,
acronyms, technical terms. And even when they don't, the LLM will
infer what you meant.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Understand the type of work</h2>
<p className="text-white/60 leading-relaxed">
Sometimes you need to plan: design a spec, verify it, get the agent to
follow through. Maybe it takes a couple of agents to work through it.
Other times it's conversational: kick off a single agent and start
talking, asking questions. Match your approach to the task.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Iterate and refactor often</h2>
<p className="text-white/60 leading-relaxed">
Don't expect perfect. Expect working. Make it work, make it correct,
make it beautiful. Each iteration gets you closer. With tests,
refactoring is cheap.
</p>
<p className="text-white/60 leading-relaxed">
I don't let myself add too many features before stopping to refactor.
Sometimes I kick off an agent and have it trace code paths, explain
dependencies, show me how modules connect. I make mental notes during
code review and circle back.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Use agents to check agents</h2>
<p className="text-white/60 leading-relaxed">
If an agent implements something and you ask it to review its own
work, it will never find issues. Launch a separate agent with a fresh
context to review the first agent's code. It will catch things the
first agent missed or glossed over. An agent might say it's done when
it's not. Another agent can detect that.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Learn your agents' quirks</h2>
<p className="text-white/60 leading-relaxed">
People argue about which model is better. That's the wrong question.
Each model has strengths and weaknesses. Knowing them is more useful
than chasing benchmarks. Benchmarks don't mean anything. You need to
try the models yourself to form an opinion.
</p>
<p className="text-white/60 leading-relaxed">
I use Claude Code as my main driver because it's quick and uses tools
well. But sometimes it jumps to conclusions and gives up too easily.
Codex is frustratingly slow but goes deep, doesn't stop, and is
methodical. It's also stubborn and too serious. These aren't good or
bad traits, just differences you learn to work around. Use the right
model for the job.
</p>
</section>
</div>
)
}

View File

@@ -0,0 +1,104 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/docs/configuration')({
head: () => ({
meta: [
{ title: 'Configuration - Paseo Docs' },
{
name: 'description',
content: 'Configure Paseo via config.json, environment variables, and CLI overrides.',
},
],
}),
component: Configuration,
})
function Configuration() {
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-medium font-serif mb-4">Configuration</h1>
<p className="text-white/60 leading-relaxed">
Paseo loads configuration from a single JSON file in your Paseo home directory, with optional
environment variable and CLI overrides.
</p>
</div>
<section className="space-y-4">
<h2 className="text-xl font-medium">Where config lives</h2>
<p className="text-white/60 leading-relaxed">
By default, Paseo uses <code className="font-mono">~/.paseo</code> as its home directory.
The configuration file is:
</p>
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
<span className="text-muted-foreground select-none">$ </span>
<span>~/.paseo/config.json</span>
</div>
<p className="text-white/60 leading-relaxed">
You can change the home directory by setting <code className="font-mono">PASEO_HOME</code>{' '}
or passing <code className="font-mono">--home</code> to <code className="font-mono">paseo daemon start</code>.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Precedence</h2>
<p className="text-white/60 leading-relaxed">
Paseo merges configuration in this order:
</p>
<ol className="text-white/60 space-y-2 list-decimal list-inside">
<li>Defaults</li>
<li><code className="font-mono">config.json</code></li>
<li>Environment variables</li>
<li>CLI flags</li>
</ol>
<p className="text-white/60 leading-relaxed">
Lists append across sources (for example, <code className="font-mono">allowedHosts</code> and
<code className="font-mono">cors.allowedOrigins</code>).
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Example</h2>
<p className="text-white/60 leading-relaxed">
Minimal example that configures listening address, host allowlist, provider keys, and MCP:
</p>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
{`{
"$schema": "https://paseo.dev/schemas/paseo.config.v1.json",
"version": 1,
"providers": {
"openai": { "apiKey": "..." },
"openrouter": { "apiKey": "..." }
},
"daemon": {
"listen": "127.0.0.1:6767",
"allowedHosts": ["localhost", ".localhost"],
"mcp": { "enabled": true }
}
}`}
</pre>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Common env vars</h2>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li><code className="font-mono">PASEO_HOME</code> set Paseo home directory</li>
<li><code className="font-mono">PASEO_LISTEN</code> override <code className="font-mono">daemon.listen</code></li>
<li><code className="font-mono">PASEO_ALLOWED_HOSTS</code> override/extend <code className="font-mono">daemon.allowedHosts</code></li>
<li><code className="font-mono">OPENAI_API_KEY</code> and <code className="font-mono">OPENROUTER_API_KEY</code> override provider keys</li>
</ul>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Schema</h2>
<p className="text-white/60 leading-relaxed">
For editor autocomplete/validation, set <code className="font-mono">$schema</code> to:
</p>
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
<span>https://paseo.dev/schemas/paseo.config.v1.json</span>
</div>
</section>
</div>
)
}

View File

@@ -0,0 +1,118 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/docs/')({
head: () => ({
meta: [
{ title: 'Getting Started - Paseo Docs' },
{
name: 'description',
content: 'Learn how to set up and use Paseo to manage your coding agents from anywhere.',
},
],
}),
component: GettingStarted,
})
function GettingStarted() {
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-medium font-serif mb-4">Getting Started</h1>
<p className="text-white/60 leading-relaxed">
Paseo connects to your local development environment and lets you manage your coding agents from anywhere.
</p>
</div>
<section className="space-y-4">
<h2 className="text-xl font-medium">Installation</h2>
<p className="text-white/60">
Install the server globally and run it on your machine:
</p>
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
<span className="text-muted-foreground select-none">$ </span>
<span>npm install -g @paseohq/server</span>
</div>
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
<span className="text-muted-foreground select-none">$ </span>
<span>paseo</span>
</div>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Connect the App</h2>
<p className="text-white/60">
Open Paseo on your phone and scan the QR code displayed in your terminal, or enter the server address manually.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Prerequisites</h2>
<p className="text-white/60">
Paseo wraps CLI tools like Claude Code and Codex. You'll need to have them installed and configured with your own credentials before Paseo can manage them.
</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<a
href="https://docs.anthropic.com/en/docs/claude-code"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-white/80"
>
Claude Code
</a>
</li>
<li>
<a
href="https://github.com/openai/codex"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-white/80"
>
Codex
</a>
</li>
<li>
<a
href="https://github.com/opencode-ai/opencode"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-white/80"
>
OpenCode
</a>
</li>
</ul>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Voice Setup</h2>
<p className="text-white/60">
Voice features currently require an OpenAI API key. Set it as an environment variable before running the server:
</p>
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
<span className="text-muted-foreground select-none">$ </span>
<span>export OPENAI_API_KEY=your-key-here</span>
</div>
<p className="text-white/60">
Local voice support is coming soon.
</p>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Next</h2>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<a href="/docs/configuration" className="underline hover:text-white/80">
Configuration
</a>
</li>
<li>
<a href="/docs/security" className="underline hover:text-white/80">
Security
</a>
</li>
</ul>
</section>
</div>
)
}

View File

@@ -0,0 +1,290 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/docs/security')({
head: () => ({
meta: [
{ title: 'Security - Paseo Docs' },
{
name: 'description',
content:
'Security model for Paseo: architecture overview, connection methods, relay encryption, and best practices.',
},
],
}),
component: Security,
})
function Callout({ children }: { children: React.ReactNode }) {
return (
<div className="bg-primary/10 border border-primary/30 rounded-lg p-4 text-white/80">
{children}
</div>
)
}
function Security() {
return (
<div className="space-y-10">
<div>
<h1 className="text-3xl font-medium font-serif mb-4">Security</h1>
<p className="text-white/60 leading-relaxed">
Paseo follows a client-server architecture, similar to Docker. The daemon runs on your
machine and manages your coding agents. Clients (the mobile app, CLI, or web interface)
connect to the daemon to monitor and control those agents.
</p>
<p className="text-white/60 leading-relaxed mt-3">
Your code never leaves your machine. Paseo is a local-first tool that connects directly to
your development environment.
</p>
</div>
{/* Architecture Overview */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Architecture</h2>
<p className="text-white/60 leading-relaxed">
The Paseo daemon can run anywhere you want to execute agents: your laptop, a Mac Mini, a
VPS, or a Docker container. The daemon listens for connections and manages agent
lifecycles.
</p>
<p className="text-white/60 leading-relaxed">
Clients connect to the daemon over WebSocket. There are two ways to establish this
connection:
</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<strong className="text-white/80">Relay connection (recommended)</strong> The daemon
connects outbound to our relay server, and clients meet it there. No open ports
required.
</li>
<li>
<strong className="text-white/80">Direct connection</strong> The daemon listens on a
network address and clients connect directly
</li>
</ul>
</section>
{/* Relay Connection */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Relay connections (recommended)</h2>
<p className="text-white/60 leading-relaxed">
The relay is the simplest way to connect from your phone. It requires no VPN setup, no
port forwarding, and no firewall configuration. The daemon can stay bound to localhost or
a socket file it connects <em>outbound</em> to the relay, and your phone meets it there.
</p>
<Callout>
<strong>The relay is designed to be untrusted.</strong> All traffic between your phone and
daemon is end-to-end encrypted. The relay server cannot read your messages, see your code,
or modify traffic without detection. Even if the relay is compromised, your data remains
protected.
</Callout>
<h3 className="text-lg font-medium mt-6">How it works</h3>
<ol className="text-white/60 space-y-2 list-decimal list-inside">
<li>
The daemon generates a persistent ECDH keypair and stores it in{' '}
<code className="font-mono">$PASEO_HOME/daemon-keypair.json</code>
</li>
<li>
When you scan the QR code or click the pairing link, your phone receives the daemon's
public key
</li>
<li>
Your phone sends a handshake message with its own public key. The daemon will not accept
any commands until this handshake completes.
</li>
<li>
Both sides perform an ECDH key exchange to derive a shared secret. All subsequent
messages are encrypted with AES-256-GCM.
</li>
</ol>
<p className="text-white/60 leading-relaxed mt-3">
The relay sees only: IP addresses, timing, message sizes, and session IDs. It cannot read
message contents, forge messages, or derive encryption keys from observing the handshake.
</p>
<h3 className="text-lg font-medium mt-6">Why the relay can't attack you</h3>
<p className="text-white/60 leading-relaxed">
The daemon requires a valid cryptographic handshake before processing any commands. A
compromised relay cannot:
</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<strong className="text-white/80">Send commands</strong> Without your phone's private
key, it cannot complete the handshake
</li>
<li>
<strong className="text-white/80">Read your traffic</strong> — All messages are
encrypted with AES-256-GCM after the handshake
</li>
<li>
<strong className="text-white/80">Forge messages</strong> — GCM provides authenticated
encryption; tampered messages are rejected
</li>
<li>
<strong className="text-white/80">Replay old messages</strong> — Each session derives
fresh encryption keys
</li>
</ul>
<h3 className="text-lg font-medium mt-6">Trust model</h3>
<p className="text-white/60 leading-relaxed">
The QR code or pairing link is the trust anchor. It contains the daemon's public key,
which is required to establish the encrypted connection. Treat it like a password don't
share it publicly.
</p>
<p className="text-white/60 leading-relaxed">
If you believe a pairing offer has been compromised, restart the daemon to generate a new
session ID and rotate the relay pairing.
</p>
</section>
{/* Direct Connection */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Direct connections</h2>
<p className="text-white/60 leading-relaxed">
By default, the daemon listens on <code className="font-mono">127.0.0.1:6767</code>{' '}
(localhost only). This is safe for local CLI usage but not reachable from your phone or
other devices.
</p>
<h3 className="text-lg font-medium mt-6">Socket file (CLI only)</h3>
<p className="text-white/60 leading-relaxed">
For maximum isolation, you can configure the daemon to listen on a Unix socket file
instead of a TCP port. This prevents any network access entirely — only processes on the
same machine can connect. The CLI supports this mode, but the mobile app and web interface
require a network connection.
</p>
<h3 className="text-lg font-medium mt-6">VPN access</h3>
<p className="text-white/60 leading-relaxed">
If you prefer direct connections over the relay, you can use a VPN like{' '}
<a
href="https://tailscale.com"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-white/80"
>
Tailscale
</a>
. Tailscale creates a private network between your devices, so you can access your daemon
without exposing it to the public internet.
</p>
<p className="text-white/60 leading-relaxed">To set this up:</p>
<ol className="text-white/60 space-y-2 list-decimal list-inside">
<li>
Install Tailscale on your machine and phone and join them to the same{' '}
<a
href="https://tailscale.com/kb/1136/tailnet"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-white/80"
>
tailnet
</a>
</li>
<li>
Configure the daemon to listen on your Tailscale IP (e.g.,{' '}
<code className="font-mono">100.x.y.z:6767</code>)
</li>
<li>
Add your Tailscale hostname to <code className="font-mono">allowedHosts</code> and{' '}
<code className="font-mono">cors.allowedOrigins</code>
</li>
<li>Add the daemon as a direct connection in the Paseo app using the Tailscale address</li>
</ol>
<h3 className="text-lg font-medium mt-6">Binding to 0.0.0.0</h3>
<div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4 text-white/80">
<strong>Warning:</strong> Binding to <code className="font-mono">0.0.0.0</code> makes the
daemon reachable on all network interfaces, including public Wi-Fi and local networks.
This can expose your daemon to unauthorized access. If you must bind to all interfaces,
ensure you have proper firewall rules and review your{' '}
<code className="font-mono">allowedHosts</code> configuration.
</div>
</section>
{/* DNS Rebinding Protection */}
<section className="space-y-4">
<h2 className="text-xl font-medium">DNS rebinding protection</h2>
<p className="text-white/60 leading-relaxed">
<strong className="text-white/80">CORS is not a complete security boundary.</strong> It
controls which browser origins can make requests, but does not prevent a malicious website
from resolving its domain to your local machine (DNS rebinding).
</p>
<p className="text-white/60 leading-relaxed">
Paseo uses a host allowlist to validate the <code className="font-mono">Host</code> header
on incoming requests. Requests with unrecognized hosts are rejected.
</p>
<p className="text-white/60 leading-relaxed">
Configure via <code className="font-mono">daemon.allowedHosts</code> in{' '}
<code className="font-mono">config.json</code>:
</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
Default (<code className="font-mono">[]</code>): allow{' '}
<code className="font-mono">localhost</code>,{' '}
<code className="font-mono">*.localhost</code>, and all IP addresses
</li>
<li>
<code className="font-mono">['.example.com']</code>: allow{' '}
<code className="font-mono">example.com</code> and any subdomain, plus defaults
</li>
<li>
<code className="font-mono">true</code>: allow any host (not recommended)
</li>
</ul>
</section>
{/* Agent Authentication */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Agent authentication</h2>
<p className="text-white/60 leading-relaxed">
Paseo wraps agent CLIs (Claude Code, Codex, OpenCode) but does not manage their
authentication. Each agent provider handles its own credentials:
</p>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<strong className="text-white/80">Claude Code</strong> — authenticates via Anthropic's
OAuth flow, stored in <code className="font-mono">~/.claude/</code>
</li>
<li>
<strong className="text-white/80">Codex</strong> uses your OpenAI API key or OAuth
session
</li>
<li>
<strong className="text-white/80">OpenCode</strong> configured via provider-specific
API keys
</li>
</ul>
<p className="text-white/60 leading-relaxed">
Paseo never stores or transmits provider API keys. Agents run in your user context with
your existing credentials.
</p>
</section>
{/* Best Practices Summary */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Recommendations</h2>
<ul className="text-white/60 space-y-3 list-disc list-inside">
<li>
<strong className="text-white/80">Use the relay</strong> for mobile access it's the
simplest option and all traffic is end-to-end encrypted
</li>
<li>
<strong className="text-white/80">Treat the QR code like a password</strong> anyone
with the pairing offer can connect to your daemon
</li>
<li>
<strong className="text-white/80">Never bind to 0.0.0.0</strong> unless you understand
the implications and have proper firewall rules
</li>
<li>
<strong className="text-white/80">Keep your daemon updated</strong> security
improvements are released regularly
</li>
</ul>
</section>
</div>
)
}