mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
restore website docs routes
This commit is contained in:
149
packages/website/src/routes/docs/best-practices.tsx
Normal file
149
packages/website/src/routes/docs/best-practices.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
104
packages/website/src/routes/docs/configuration.tsx
Normal file
104
packages/website/src/routes/docs/configuration.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
118
packages/website/src/routes/docs/index.tsx
Normal file
118
packages/website/src/routes/docs/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
290
packages/website/src/routes/docs/security.tsx
Normal file
290
packages/website/src/routes/docs/security.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user