Switch to double quotes and reformat codebase with Biome

This commit is contained in:
Mohamed Boudra
2026-03-21 20:22:15 +07:00
parent ab40786882
commit f6579dbeb4
805 changed files with 38132 additions and 40175 deletions
+160 -131
View File
@@ -1,123 +1,123 @@
import { createContext, useContext, useEffect, useRef, useState } from 'react'
import { createContext, useContext, useEffect, useRef, useState } from "react";
// --- Vector math utilities ---
interface Vec2 {
x: number
y: number
x: number;
y: number;
}
function vec2(x: number, y: number): Vec2 {
return { x, y }
return { x, y };
}
function add(a: Vec2, b: Vec2): Vec2 {
return { x: a.x + b.x, y: a.y + b.y }
return { x: a.x + b.x, y: a.y + b.y };
}
function sub(a: Vec2, b: Vec2): Vec2 {
return { x: a.x - b.x, y: a.y - b.y }
return { x: a.x - b.x, y: a.y - b.y };
}
function scale(v: Vec2, s: number): Vec2 {
return { x: v.x * s, y: v.y * s }
return { x: v.x * s, y: v.y * s };
}
function length(v: Vec2): number {
return Math.sqrt(v.x * v.x + v.y * v.y)
return Math.sqrt(v.x * v.x + v.y * v.y);
}
function normalize(v: Vec2): Vec2 {
const len = length(v)
if (len === 0) return { x: 0, y: 0 }
return { x: v.x / len, y: v.y / len }
const len = length(v);
if (len === 0) return { x: 0, y: 0 };
return { x: v.x / len, y: v.y / len };
}
function lerpAngle(a: number, b: number, t: number): number {
// Handle angle wrapping for smooth interpolation
let diff = b - a
while (diff > 180) diff -= 360
while (diff < -180) diff += 360
return a + diff * t
let diff = b - a;
while (diff > 180) diff -= 360;
while (diff < -180) diff += 360;
return a + diff * t;
}
// --- Cursor context ---
const CursorContext = createContext<Vec2 | null>(null)
const CursorContext = createContext<Vec2 | null>(null);
export function CursorFieldProvider({ children }: { children: React.ReactNode }) {
const [cursor, setCursor] = useState<Vec2 | null>(null)
const [cursor, setCursor] = useState<Vec2 | null>(null);
useEffect(() => {
function handleMouseMove(e: MouseEvent) {
setCursor({ x: e.clientX, y: e.clientY })
setCursor({ x: e.clientX, y: e.clientY });
}
function handleMouseLeave() {
setCursor(null)
setCursor(null);
}
window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('mouseleave', handleMouseLeave)
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseleave", handleMouseLeave);
return () => {
window.removeEventListener('mousemove', handleMouseMove)
window.removeEventListener('mouseleave', handleMouseLeave)
}
}, [])
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseleave", handleMouseLeave);
};
}, []);
return <CursorContext.Provider value={cursor}>{children}</CursorContext.Provider>
return <CursorContext.Provider value={cursor}>{children}</CursorContext.Provider>;
}
// --- Physics simulation state ---
interface ButterflyState {
position: Vec2 // Current offset from origin
velocity: Vec2 // Current velocity
heading: number // Current facing angle in degrees (0 = down, positive = clockwise)
targetHeading: number // Target heading to smoothly rotate towards
position: Vec2; // Current offset from origin
velocity: Vec2; // Current velocity
heading: number; // Current facing angle in degrees (0 = down, positive = clockwise)
targetHeading: number; // Target heading to smoothly rotate towards
}
// Physics constants
const WANDER_STRENGTH = 0.15 // How strongly butterflies wander
const WANDER_RADIUS = 40 // Max wander distance from origin
const RETURN_STRENGTH = 0.02 // How strongly butterflies return to origin
const REPULSION_RADIUS = 150 // Cursor repulsion detection radius
const REPULSION_STRENGTH = 2.5 // How strongly cursor repels
const MAX_SPEED = 1.5 // Maximum velocity
const FRICTION = 0.98 // Velocity damping per frame
const HEADING_SMOOTHING = 0.08 // How quickly heading catches up to velocity direction
const WANDER_STRENGTH = 0.15; // How strongly butterflies wander
const WANDER_RADIUS = 40; // Max wander distance from origin
const RETURN_STRENGTH = 0.02; // How strongly butterflies return to origin
const REPULSION_RADIUS = 150; // Cursor repulsion detection radius
const REPULSION_STRENGTH = 2.5; // How strongly cursor repels
const MAX_SPEED = 1.5; // Maximum velocity
const FRICTION = 0.98; // Velocity damping per frame
const HEADING_SMOOTHING = 0.08; // How quickly heading catches up to velocity direction
// --- Butterfly visual component ---
interface ButterflyVisualProps {
size?: number
color?: string
delay?: number
duration?: number
direction?: 'left' | 'right'
heading?: number
size?: number;
color?: string;
delay?: number;
duration?: number;
direction?: "left" | "right";
heading?: number;
}
function ButterflyVisual({
size = 40,
color = '#e8a87c',
color = "#e8a87c",
delay = 0,
duration = 0.5,
direction = 'right',
direction = "right",
heading = 0,
}: ButterflyVisualProps) {
const wingStyle = {
animationDelay: `${delay}s`,
animationDuration: `${duration}s`,
}
};
const scaleX = direction === 'left' ? -1 : 1
const scaleX = direction === "left" ? -1 : 1;
// Base butterfly has rotate(30deg), so it's already looking 30° down from horizontal.
// To keep it in the bottom half (never looking up), rotateZ range is -30° to +90°
// -30° = horizontal, 0° = 30° down (base), +90° = 120° down (nearly straight down)
const clampedHeading = Math.max(-30, Math.min(90, heading))
const clampedHeading = Math.max(-30, Math.min(90, heading));
return (
<div
@@ -132,15 +132,21 @@ function ButterflyVisual({
width={size}
height={size * 0.8}
style={{
filter: 'url(#painterly)',
filter: "url(#painterly)",
transform: `scaleX(${scaleX}) rotateY(25deg) rotate(30deg) rotateZ(${clampedHeading}deg)`,
transition: 'transform 0.3s ease-out',
transition: "transform 0.3s ease-out",
}}
>
<defs>
<filter id="painterly" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.03" numOctaves="2" result="noise" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="2" xChannelSelector="R" yChannelSelector="G" />
<feDisplacementMap
in="SourceGraphic"
in2="noise"
scale="2"
xChannelSelector="R"
yChannelSelector="G"
/>
</filter>
</defs>
@@ -159,184 +165,200 @@ function ButterflyVisual({
<path d="M38 20 Q20 2 8 10 Q0 18 10 28 Q25 32 38 22" fill={color} />
</g>
<g className="animate-flutter-left origin-[38px_20px]" style={{ ...wingStyle, animationDelay: `${delay + 0.05}s` }}>
<g
className="animate-flutter-left origin-[38px_20px]"
style={{ ...wingStyle, animationDelay: `${delay + 0.05}s` }}
>
<path d="M38 22 Q25 30 18 38 Q28 42 36 34 Q40 28 38 24" fill={color} />
</g>
</svg>
</div>
)
);
}
function darken(hex: string, amount: number): string {
const num = parseInt(hex.replace('#', ''), 16)
const r = Math.max(0, (num >> 16) - amount)
const g = Math.max(0, ((num >> 8) & 0x00ff) - amount)
const b = Math.max(0, (num & 0x0000ff) - amount)
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
const num = parseInt(hex.replace("#", ""), 16);
const r = Math.max(0, (num >> 16) - amount);
const g = Math.max(0, ((num >> 8) & 0x00ff) - amount);
const b = Math.max(0, (num & 0x0000ff) - amount);
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, "0")}`;
}
// --- Main FloatingButterfly component with physics ---
interface FloatingButterflyProps {
style?: React.CSSProperties
size?: number
color?: string
delay?: number
duration?: number
direction?: 'left' | 'right'
style?: React.CSSProperties;
size?: number;
color?: string;
delay?: number;
duration?: number;
direction?: "left" | "right";
}
export function FloatingButterfly({ style, size, color, delay, duration, direction }: FloatingButterflyProps) {
export function FloatingButterfly({
style,
size,
color,
delay,
duration,
direction,
}: FloatingButterflyProps) {
// Initialize state lazily to avoid hydration mismatch
const initialStateRef = useRef<{ x: number; y: number; heading: number } | null>(null)
if (initialStateRef.current === null && typeof window !== 'undefined') {
const initialStateRef = useRef<{ x: number; y: number; heading: number } | null>(null);
if (initialStateRef.current === null && typeof window !== "undefined") {
// Random starting position within wander radius
const angle = Math.random() * Math.PI * 2
const dist = Math.random() * WANDER_RADIUS * 0.5
const angle = Math.random() * Math.PI * 2;
const dist = Math.random() * WANDER_RADIUS * 0.5;
initialStateRef.current = {
x: Math.cos(angle) * dist,
y: Math.sin(angle) * dist,
heading: Math.random() * 40 - 10,
}
};
}
const [mounted, setMounted] = useState(false)
const [renderState, setRenderState] = useState({ x: 0, y: 0, heading: 0 })
const [mounted, setMounted] = useState(false);
const [renderState, setRenderState] = useState({ x: 0, y: 0, heading: 0 });
const stateRef = useRef<ButterflyState | null>(null)
const elementRef = useRef<HTMLDivElement>(null)
const rafRef = useRef<number | null>(null)
const wanderAngleRef = useRef(Math.random() * Math.PI * 2)
const lastTimeRef = useRef<number | null>(null)
const stateRef = useRef<ButterflyState | null>(null);
const elementRef = useRef<HTMLDivElement>(null);
const rafRef = useRef<number | null>(null);
const wanderAngleRef = useRef(Math.random() * Math.PI * 2);
const lastTimeRef = useRef<number | null>(null);
const cursor = useContext(CursorContext)
const cursorRef = useRef<Vec2 | null>(null)
const cursor = useContext(CursorContext);
const cursorRef = useRef<Vec2 | null>(null);
useEffect(() => {
// Initialize physics state on mount with pre-computed random values
const init = initialStateRef.current || { x: 0, y: 0, heading: 0 }
const init = initialStateRef.current || { x: 0, y: 0, heading: 0 };
stateRef.current = {
position: vec2(init.x, init.y),
velocity: vec2(0, 0),
heading: init.heading,
targetHeading: init.heading,
}
};
// Set initial render state to match physics state
setRenderState({ x: init.x, y: init.y, heading: init.heading })
setMounted(true)
}, [])
setRenderState({ x: init.x, y: init.y, heading: init.heading });
setMounted(true);
}, []);
// Keep cursor ref in sync
useEffect(() => {
cursorRef.current = cursor
}, [cursor])
cursorRef.current = cursor;
}, [cursor]);
useEffect(() => {
if (!mounted) return
if (!mounted) return;
function simulate(timestamp: number) {
const state = stateRef.current
if (!state) return
const state = stateRef.current;
if (!state) return;
// Skip first frame to let things settle, then start tracking time
if (lastTimeRef.current === null) {
lastTimeRef.current = timestamp
rafRef.current = requestAnimationFrame(simulate)
return
lastTimeRef.current = timestamp;
rafRef.current = requestAnimationFrame(simulate);
return;
}
// Calculate delta time (cap at 50ms to avoid huge jumps)
const dt = Math.min((timestamp - lastTimeRef.current) / 16.67, 3)
lastTimeRef.current = timestamp
const dt = Math.min((timestamp - lastTimeRef.current) / 16.67, 3);
lastTimeRef.current = timestamp;
let acceleration = vec2(0, 0)
let acceleration = vec2(0, 0);
// 1. Wander behavior - gentle random movement
wanderAngleRef.current += (Math.random() - 0.5) * 0.3 * dt
wanderAngleRef.current += (Math.random() - 0.5) * 0.3 * dt;
const wanderForce = vec2(
Math.cos(wanderAngleRef.current) * WANDER_STRENGTH,
Math.sin(wanderAngleRef.current) * WANDER_STRENGTH
)
acceleration = add(acceleration, wanderForce)
Math.sin(wanderAngleRef.current) * WANDER_STRENGTH,
);
acceleration = add(acceleration, wanderForce);
// 2. Return to origin - soft spring force
const distFromOrigin = length(state.position)
const distFromOrigin = length(state.position);
if (distFromOrigin > WANDER_RADIUS * 0.5) {
const returnForce = scale(normalize(scale(state.position, -1)), RETURN_STRENGTH * (distFromOrigin / WANDER_RADIUS))
acceleration = add(acceleration, returnForce)
const returnForce = scale(
normalize(scale(state.position, -1)),
RETURN_STRENGTH * (distFromOrigin / WANDER_RADIUS),
);
acceleration = add(acceleration, returnForce);
}
// 3. Cursor repulsion
const currentCursor = cursorRef.current
const currentCursor = cursorRef.current;
if (currentCursor && elementRef.current) {
const rect = elementRef.current.getBoundingClientRect()
const worldPos = vec2(rect.left + rect.width / 2 + state.position.x, rect.top + rect.height / 2 + state.position.y)
const rect = elementRef.current.getBoundingClientRect();
const worldPos = vec2(
rect.left + rect.width / 2 + state.position.x,
rect.top + rect.height / 2 + state.position.y,
);
const toCursor = sub(worldPos, currentCursor)
const distToCursor = length(toCursor)
const toCursor = sub(worldPos, currentCursor);
const distToCursor = length(toCursor);
if (distToCursor < REPULSION_RADIUS && distToCursor > 0) {
// Inverse square falloff for more natural feel
const strength = REPULSION_STRENGTH * Math.pow(1 - distToCursor / REPULSION_RADIUS, 2)
const repulsionForce = scale(normalize(toCursor), strength)
acceleration = add(acceleration, repulsionForce)
const strength = REPULSION_STRENGTH * Math.pow(1 - distToCursor / REPULSION_RADIUS, 2);
const repulsionForce = scale(normalize(toCursor), strength);
acceleration = add(acceleration, repulsionForce);
}
}
// 4. Update velocity with acceleration
state.velocity = add(state.velocity, scale(acceleration, dt))
state.velocity = add(state.velocity, scale(acceleration, dt));
// 5. Apply friction
state.velocity = scale(state.velocity, Math.pow(FRICTION, dt))
state.velocity = scale(state.velocity, Math.pow(FRICTION, dt));
// 6. Clamp velocity to max speed
const speed = length(state.velocity)
const speed = length(state.velocity);
if (speed > MAX_SPEED) {
state.velocity = scale(normalize(state.velocity), MAX_SPEED)
state.velocity = scale(normalize(state.velocity), MAX_SPEED);
}
// 7. Update position
state.position = add(state.position, scale(state.velocity, dt))
state.position = add(state.position, scale(state.velocity, dt));
// 8. Soft clamp position to wander radius
const posLen = length(state.position)
const posLen = length(state.position);
if (posLen > WANDER_RADIUS) {
state.position = scale(normalize(state.position), WANDER_RADIUS)
state.position = scale(normalize(state.position), WANDER_RADIUS);
// Dampen velocity when hitting boundary
state.velocity = scale(state.velocity, 0.8)
state.velocity = scale(state.velocity, 0.8);
}
// 9. Calculate target heading from velocity direction
if (speed > 0.1) {
// Convert velocity to angle: 0 = moving down screen (+Y), positive = clockwise
const rawHeading = Math.atan2(state.velocity.x, state.velocity.y) * (180 / Math.PI)
const rawHeading = Math.atan2(state.velocity.x, state.velocity.y) * (180 / Math.PI);
// Clamp to valid range: -30° to +90° (relative to base 30° rotation)
state.targetHeading = Math.max(-30, Math.min(90, rawHeading))
state.targetHeading = Math.max(-30, Math.min(90, rawHeading));
}
// 10. Smoothly interpolate heading towards target (already clamped)
state.heading = lerpAngle(state.heading, state.targetHeading, HEADING_SMOOTHING * dt)
state.heading = lerpAngle(state.heading, state.targetHeading, HEADING_SMOOTHING * dt);
// Update render state
setRenderState({
x: state.position.x,
y: state.position.y,
heading: state.heading,
})
});
rafRef.current = requestAnimationFrame(simulate)
rafRef.current = requestAnimationFrame(simulate);
}
rafRef.current = requestAnimationFrame(simulate)
rafRef.current = requestAnimationFrame(simulate);
return () => {
if (rafRef.current) {
cancelAnimationFrame(rafRef.current)
cancelAnimationFrame(rafRef.current);
}
}
}, [mounted])
};
}, [mounted]);
if (!mounted) return null
if (!mounted) return null;
return (
<div
@@ -347,7 +369,14 @@ export function FloatingButterfly({ style, size, color, delay, duration, directi
transform: `translate(${renderState.x}px, ${renderState.y}px)`,
}}
>
<ButterflyVisual size={size} color={color} delay={delay} duration={duration} direction={direction} heading={renderState.heading} />
<ButterflyVisual
size={size}
color={color}
delay={delay}
duration={duration}
direction={direction}
heading={renderState.heading}
/>
</div>
)
);
}
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,8 +1,8 @@
export function pageMeta(title: string, description: string) {
return [
{ title },
{ name: 'description', content: description },
{ property: 'og:title', content: title },
{ property: 'og:description', content: description },
]
{ name: "description", content: description },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
];
}
+284 -284
View File
@@ -8,332 +8,332 @@
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as PrivacyRouteImport } from './routes/privacy'
import { Route as OpencodeRouteImport } from './routes/opencode'
import { Route as DocsRouteImport } from './routes/docs'
import { Route as CodexRouteImport } from './routes/codex'
import { Route as ClaudeCodeRouteImport } from './routes/claude-code'
import { Route as ChangelogRouteImport } from './routes/changelog'
import { Route as IndexRouteImport } from './routes/index'
import { Route as DocsIndexRouteImport } from './routes/docs/index'
import { Route as DocsWorktreesRouteImport } from './routes/docs/worktrees'
import { Route as DocsVoiceRouteImport } from './routes/docs/voice'
import { Route as DocsUpdatesRouteImport } from './routes/docs/updates'
import { Route as DocsSecurityRouteImport } from './routes/docs/security'
import { Route as DocsConfigurationRouteImport } from './routes/docs/configuration'
import { Route as DocsCliRouteImport } from './routes/docs/cli'
import { Route as DocsBestPracticesRouteImport } from './routes/docs/best-practices'
import { Route as rootRouteImport } from "./routes/__root";
import { Route as PrivacyRouteImport } from "./routes/privacy";
import { Route as OpencodeRouteImport } from "./routes/opencode";
import { Route as DocsRouteImport } from "./routes/docs";
import { Route as CodexRouteImport } from "./routes/codex";
import { Route as ClaudeCodeRouteImport } from "./routes/claude-code";
import { Route as ChangelogRouteImport } from "./routes/changelog";
import { Route as IndexRouteImport } from "./routes/index";
import { Route as DocsIndexRouteImport } from "./routes/docs/index";
import { Route as DocsWorktreesRouteImport } from "./routes/docs/worktrees";
import { Route as DocsVoiceRouteImport } from "./routes/docs/voice";
import { Route as DocsUpdatesRouteImport } from "./routes/docs/updates";
import { Route as DocsSecurityRouteImport } from "./routes/docs/security";
import { Route as DocsConfigurationRouteImport } from "./routes/docs/configuration";
import { Route as DocsCliRouteImport } from "./routes/docs/cli";
import { Route as DocsBestPracticesRouteImport } from "./routes/docs/best-practices";
const PrivacyRoute = PrivacyRouteImport.update({
id: '/privacy',
path: '/privacy',
id: "/privacy",
path: "/privacy",
getParentRoute: () => rootRouteImport,
} as any)
} as any);
const OpencodeRoute = OpencodeRouteImport.update({
id: '/opencode',
path: '/opencode',
id: "/opencode",
path: "/opencode",
getParentRoute: () => rootRouteImport,
} as any)
} as any);
const DocsRoute = DocsRouteImport.update({
id: '/docs',
path: '/docs',
id: "/docs",
path: "/docs",
getParentRoute: () => rootRouteImport,
} as any)
} as any);
const CodexRoute = CodexRouteImport.update({
id: '/codex',
path: '/codex',
id: "/codex",
path: "/codex",
getParentRoute: () => rootRouteImport,
} as any)
} as any);
const ClaudeCodeRoute = ClaudeCodeRouteImport.update({
id: '/claude-code',
path: '/claude-code',
id: "/claude-code",
path: "/claude-code",
getParentRoute: () => rootRouteImport,
} as any)
} as any);
const ChangelogRoute = ChangelogRouteImport.update({
id: '/changelog',
path: '/changelog',
id: "/changelog",
path: "/changelog",
getParentRoute: () => rootRouteImport,
} as any)
} as any);
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
id: "/",
path: "/",
getParentRoute: () => rootRouteImport,
} as any)
} as any);
const DocsIndexRoute = DocsIndexRouteImport.update({
id: '/',
path: '/',
id: "/",
path: "/",
getParentRoute: () => DocsRoute,
} as any)
} as any);
const DocsWorktreesRoute = DocsWorktreesRouteImport.update({
id: '/worktrees',
path: '/worktrees',
id: "/worktrees",
path: "/worktrees",
getParentRoute: () => DocsRoute,
} as any)
} as any);
const DocsVoiceRoute = DocsVoiceRouteImport.update({
id: '/voice',
path: '/voice',
id: "/voice",
path: "/voice",
getParentRoute: () => DocsRoute,
} as any)
} as any);
const DocsUpdatesRoute = DocsUpdatesRouteImport.update({
id: '/updates',
path: '/updates',
id: "/updates",
path: "/updates",
getParentRoute: () => DocsRoute,
} as any)
} as any);
const DocsSecurityRoute = DocsSecurityRouteImport.update({
id: '/security',
path: '/security',
id: "/security",
path: "/security",
getParentRoute: () => DocsRoute,
} as any)
} as any);
const DocsConfigurationRoute = DocsConfigurationRouteImport.update({
id: '/configuration',
path: '/configuration',
id: "/configuration",
path: "/configuration",
getParentRoute: () => DocsRoute,
} as any)
} as any);
const DocsCliRoute = DocsCliRouteImport.update({
id: '/cli',
path: '/cli',
id: "/cli",
path: "/cli",
getParentRoute: () => DocsRoute,
} as any)
} as any);
const DocsBestPracticesRoute = DocsBestPracticesRouteImport.update({
id: '/best-practices',
path: '/best-practices',
id: "/best-practices",
path: "/best-practices",
getParentRoute: () => DocsRoute,
} as any)
} as any);
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/changelog': typeof ChangelogRoute
'/claude-code': typeof ClaudeCodeRoute
'/codex': typeof CodexRoute
'/docs': typeof DocsRouteWithChildren
'/opencode': typeof OpencodeRoute
'/privacy': typeof PrivacyRoute
'/docs/best-practices': typeof DocsBestPracticesRoute
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
'/docs/': typeof DocsIndexRoute
"/": typeof IndexRoute;
"/changelog": typeof ChangelogRoute;
"/claude-code": typeof ClaudeCodeRoute;
"/codex": typeof CodexRoute;
"/docs": typeof DocsRouteWithChildren;
"/opencode": typeof OpencodeRoute;
"/privacy": typeof PrivacyRoute;
"/docs/best-practices": typeof DocsBestPracticesRoute;
"/docs/cli": typeof DocsCliRoute;
"/docs/configuration": typeof DocsConfigurationRoute;
"/docs/security": typeof DocsSecurityRoute;
"/docs/updates": typeof DocsUpdatesRoute;
"/docs/voice": typeof DocsVoiceRoute;
"/docs/worktrees": typeof DocsWorktreesRoute;
"/docs/": typeof DocsIndexRoute;
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/changelog': typeof ChangelogRoute
'/claude-code': typeof ClaudeCodeRoute
'/codex': typeof CodexRoute
'/opencode': typeof OpencodeRoute
'/privacy': typeof PrivacyRoute
'/docs/best-practices': typeof DocsBestPracticesRoute
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
'/docs': typeof DocsIndexRoute
"/": typeof IndexRoute;
"/changelog": typeof ChangelogRoute;
"/claude-code": typeof ClaudeCodeRoute;
"/codex": typeof CodexRoute;
"/opencode": typeof OpencodeRoute;
"/privacy": typeof PrivacyRoute;
"/docs/best-practices": typeof DocsBestPracticesRoute;
"/docs/cli": typeof DocsCliRoute;
"/docs/configuration": typeof DocsConfigurationRoute;
"/docs/security": typeof DocsSecurityRoute;
"/docs/updates": typeof DocsUpdatesRoute;
"/docs/voice": typeof DocsVoiceRoute;
"/docs/worktrees": typeof DocsWorktreesRoute;
"/docs": typeof DocsIndexRoute;
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/changelog': typeof ChangelogRoute
'/claude-code': typeof ClaudeCodeRoute
'/codex': typeof CodexRoute
'/docs': typeof DocsRouteWithChildren
'/opencode': typeof OpencodeRoute
'/privacy': typeof PrivacyRoute
'/docs/best-practices': typeof DocsBestPracticesRoute
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
'/docs/': typeof DocsIndexRoute
__root__: typeof rootRouteImport;
"/": typeof IndexRoute;
"/changelog": typeof ChangelogRoute;
"/claude-code": typeof ClaudeCodeRoute;
"/codex": typeof CodexRoute;
"/docs": typeof DocsRouteWithChildren;
"/opencode": typeof OpencodeRoute;
"/privacy": typeof PrivacyRoute;
"/docs/best-practices": typeof DocsBestPracticesRoute;
"/docs/cli": typeof DocsCliRoute;
"/docs/configuration": typeof DocsConfigurationRoute;
"/docs/security": typeof DocsSecurityRoute;
"/docs/updates": typeof DocsUpdatesRoute;
"/docs/voice": typeof DocsVoiceRoute;
"/docs/worktrees": typeof DocsWorktreesRoute;
"/docs/": typeof DocsIndexRoute;
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths:
| '/'
| '/changelog'
| '/claude-code'
| '/codex'
| '/docs'
| '/opencode'
| '/privacy'
| '/docs/best-practices'
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
| '/docs/'
fileRoutesByTo: FileRoutesByTo
| "/"
| "/changelog"
| "/claude-code"
| "/codex"
| "/docs"
| "/opencode"
| "/privacy"
| "/docs/best-practices"
| "/docs/cli"
| "/docs/configuration"
| "/docs/security"
| "/docs/updates"
| "/docs/voice"
| "/docs/worktrees"
| "/docs/";
fileRoutesByTo: FileRoutesByTo;
to:
| '/'
| '/changelog'
| '/claude-code'
| '/codex'
| '/opencode'
| '/privacy'
| '/docs/best-practices'
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
| '/docs'
| "/"
| "/changelog"
| "/claude-code"
| "/codex"
| "/opencode"
| "/privacy"
| "/docs/best-practices"
| "/docs/cli"
| "/docs/configuration"
| "/docs/security"
| "/docs/updates"
| "/docs/voice"
| "/docs/worktrees"
| "/docs";
id:
| '__root__'
| '/'
| '/changelog'
| '/claude-code'
| '/codex'
| '/docs'
| '/opencode'
| '/privacy'
| '/docs/best-practices'
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
| '/docs/'
fileRoutesById: FileRoutesById
| "__root__"
| "/"
| "/changelog"
| "/claude-code"
| "/codex"
| "/docs"
| "/opencode"
| "/privacy"
| "/docs/best-practices"
| "/docs/cli"
| "/docs/configuration"
| "/docs/security"
| "/docs/updates"
| "/docs/voice"
| "/docs/worktrees"
| "/docs/";
fileRoutesById: FileRoutesById;
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ChangelogRoute: typeof ChangelogRoute
ClaudeCodeRoute: typeof ClaudeCodeRoute
CodexRoute: typeof CodexRoute
DocsRoute: typeof DocsRouteWithChildren
OpencodeRoute: typeof OpencodeRoute
PrivacyRoute: typeof PrivacyRoute
IndexRoute: typeof IndexRoute;
ChangelogRoute: typeof ChangelogRoute;
ClaudeCodeRoute: typeof ClaudeCodeRoute;
CodexRoute: typeof CodexRoute;
DocsRoute: typeof DocsRouteWithChildren;
OpencodeRoute: typeof OpencodeRoute;
PrivacyRoute: typeof PrivacyRoute;
}
declare module '@tanstack/react-router' {
declare module "@tanstack/react-router" {
interface FileRoutesByPath {
'/privacy': {
id: '/privacy'
path: '/privacy'
fullPath: '/privacy'
preLoaderRoute: typeof PrivacyRouteImport
parentRoute: typeof rootRouteImport
}
'/opencode': {
id: '/opencode'
path: '/opencode'
fullPath: '/opencode'
preLoaderRoute: typeof OpencodeRouteImport
parentRoute: typeof rootRouteImport
}
'/docs': {
id: '/docs'
path: '/docs'
fullPath: '/docs'
preLoaderRoute: typeof DocsRouteImport
parentRoute: typeof rootRouteImport
}
'/codex': {
id: '/codex'
path: '/codex'
fullPath: '/codex'
preLoaderRoute: typeof CodexRouteImport
parentRoute: typeof rootRouteImport
}
'/claude-code': {
id: '/claude-code'
path: '/claude-code'
fullPath: '/claude-code'
preLoaderRoute: typeof ClaudeCodeRouteImport
parentRoute: typeof rootRouteImport
}
'/changelog': {
id: '/changelog'
path: '/changelog'
fullPath: '/changelog'
preLoaderRoute: typeof ChangelogRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/docs/': {
id: '/docs/'
path: '/'
fullPath: '/docs/'
preLoaderRoute: typeof DocsIndexRouteImport
parentRoute: typeof DocsRoute
}
'/docs/worktrees': {
id: '/docs/worktrees'
path: '/worktrees'
fullPath: '/docs/worktrees'
preLoaderRoute: typeof DocsWorktreesRouteImport
parentRoute: typeof DocsRoute
}
'/docs/voice': {
id: '/docs/voice'
path: '/voice'
fullPath: '/docs/voice'
preLoaderRoute: typeof DocsVoiceRouteImport
parentRoute: typeof DocsRoute
}
'/docs/updates': {
id: '/docs/updates'
path: '/updates'
fullPath: '/docs/updates'
preLoaderRoute: typeof DocsUpdatesRouteImport
parentRoute: typeof DocsRoute
}
'/docs/security': {
id: '/docs/security'
path: '/security'
fullPath: '/docs/security'
preLoaderRoute: typeof DocsSecurityRouteImport
parentRoute: typeof DocsRoute
}
'/docs/configuration': {
id: '/docs/configuration'
path: '/configuration'
fullPath: '/docs/configuration'
preLoaderRoute: typeof DocsConfigurationRouteImport
parentRoute: typeof DocsRoute
}
'/docs/cli': {
id: '/docs/cli'
path: '/cli'
fullPath: '/docs/cli'
preLoaderRoute: typeof DocsCliRouteImport
parentRoute: typeof DocsRoute
}
'/docs/best-practices': {
id: '/docs/best-practices'
path: '/best-practices'
fullPath: '/docs/best-practices'
preLoaderRoute: typeof DocsBestPracticesRouteImport
parentRoute: typeof DocsRoute
}
"/privacy": {
id: "/privacy";
path: "/privacy";
fullPath: "/privacy";
preLoaderRoute: typeof PrivacyRouteImport;
parentRoute: typeof rootRouteImport;
};
"/opencode": {
id: "/opencode";
path: "/opencode";
fullPath: "/opencode";
preLoaderRoute: typeof OpencodeRouteImport;
parentRoute: typeof rootRouteImport;
};
"/docs": {
id: "/docs";
path: "/docs";
fullPath: "/docs";
preLoaderRoute: typeof DocsRouteImport;
parentRoute: typeof rootRouteImport;
};
"/codex": {
id: "/codex";
path: "/codex";
fullPath: "/codex";
preLoaderRoute: typeof CodexRouteImport;
parentRoute: typeof rootRouteImport;
};
"/claude-code": {
id: "/claude-code";
path: "/claude-code";
fullPath: "/claude-code";
preLoaderRoute: typeof ClaudeCodeRouteImport;
parentRoute: typeof rootRouteImport;
};
"/changelog": {
id: "/changelog";
path: "/changelog";
fullPath: "/changelog";
preLoaderRoute: typeof ChangelogRouteImport;
parentRoute: typeof rootRouteImport;
};
"/": {
id: "/";
path: "/";
fullPath: "/";
preLoaderRoute: typeof IndexRouteImport;
parentRoute: typeof rootRouteImport;
};
"/docs/": {
id: "/docs/";
path: "/";
fullPath: "/docs/";
preLoaderRoute: typeof DocsIndexRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/worktrees": {
id: "/docs/worktrees";
path: "/worktrees";
fullPath: "/docs/worktrees";
preLoaderRoute: typeof DocsWorktreesRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/voice": {
id: "/docs/voice";
path: "/voice";
fullPath: "/docs/voice";
preLoaderRoute: typeof DocsVoiceRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/updates": {
id: "/docs/updates";
path: "/updates";
fullPath: "/docs/updates";
preLoaderRoute: typeof DocsUpdatesRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/security": {
id: "/docs/security";
path: "/security";
fullPath: "/docs/security";
preLoaderRoute: typeof DocsSecurityRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/configuration": {
id: "/docs/configuration";
path: "/configuration";
fullPath: "/docs/configuration";
preLoaderRoute: typeof DocsConfigurationRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/cli": {
id: "/docs/cli";
path: "/cli";
fullPath: "/docs/cli";
preLoaderRoute: typeof DocsCliRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/best-practices": {
id: "/docs/best-practices";
path: "/best-practices";
fullPath: "/docs/best-practices";
preLoaderRoute: typeof DocsBestPracticesRouteImport;
parentRoute: typeof DocsRoute;
};
}
}
interface DocsRouteChildren {
DocsBestPracticesRoute: typeof DocsBestPracticesRoute
DocsCliRoute: typeof DocsCliRoute
DocsConfigurationRoute: typeof DocsConfigurationRoute
DocsSecurityRoute: typeof DocsSecurityRoute
DocsUpdatesRoute: typeof DocsUpdatesRoute
DocsVoiceRoute: typeof DocsVoiceRoute
DocsWorktreesRoute: typeof DocsWorktreesRoute
DocsIndexRoute: typeof DocsIndexRoute
DocsBestPracticesRoute: typeof DocsBestPracticesRoute;
DocsCliRoute: typeof DocsCliRoute;
DocsConfigurationRoute: typeof DocsConfigurationRoute;
DocsSecurityRoute: typeof DocsSecurityRoute;
DocsUpdatesRoute: typeof DocsUpdatesRoute;
DocsVoiceRoute: typeof DocsVoiceRoute;
DocsWorktreesRoute: typeof DocsWorktreesRoute;
DocsIndexRoute: typeof DocsIndexRoute;
}
const DocsRouteChildren: DocsRouteChildren = {
@@ -345,9 +345,9 @@ const DocsRouteChildren: DocsRouteChildren = {
DocsVoiceRoute: DocsVoiceRoute,
DocsWorktreesRoute: DocsWorktreesRoute,
DocsIndexRoute: DocsIndexRoute,
}
};
const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren);
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
@@ -357,16 +357,16 @@ const rootRouteChildren: RootRouteChildren = {
DocsRoute: DocsRouteWithChildren,
OpencodeRoute: OpencodeRoute,
PrivacyRoute: PrivacyRoute,
}
};
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
._addFileTypes<FileRouteTypes>();
import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
import type { getRouter } from "./router.tsx";
import type { createStart } from "@tanstack/react-start";
declare module "@tanstack/react-start" {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
ssr: true;
router: Awaited<ReturnType<typeof getRouter>>;
}
}
+5 -5
View File
@@ -1,15 +1,15 @@
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
export function getRouter() {
return createRouter({
routeTree,
scrollRestoration: true,
})
});
}
declare module '@tanstack/react-router' {
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof getRouter>
router: ReturnType<typeof getRouter>;
}
}
+16 -21
View File
@@ -1,38 +1,33 @@
import type { ReactNode } from 'react'
import {
Outlet,
createRootRoute,
HeadContent,
Scripts,
} from '@tanstack/react-router'
import type { ReactNode } from "react";
import { Outlet, createRootRoute, HeadContent, Scripts } from "@tanstack/react-router";
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ name: 'theme-color', content: '#0a0a0a' },
{ property: 'og:site_name', content: 'Paseo' },
{ property: 'og:type', content: 'website' },
{ property: 'og:image', content: 'https://paseo.sh/og-image.png' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:image', content: 'https://paseo.sh/og-image.png' },
{ charSet: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ name: "theme-color", content: "#0a0a0a" },
{ property: "og:site_name", content: "Paseo" },
{ property: "og:type", content: "website" },
{ property: "og:image", content: "https://paseo.sh/og-image.png" },
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:image", content: "https://paseo.sh/og-image.png" },
],
links: [
{ rel: 'icon', href: '/favicon.ico', sizes: '48x48' },
{ rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
{ rel: 'apple-touch-icon', href: '/favicon.svg' },
{ rel: "icon", href: "/favicon.ico", sizes: "48x48" },
{ rel: "icon", href: "/favicon.svg", type: "image/svg+xml" },
{ rel: "apple-touch-icon", href: "/favicon.svg" },
],
}),
component: RootComponent,
})
});
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
);
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
@@ -46,5 +41,5 @@ function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
<Scripts />
</body>
</html>
)
);
}
+10 -10
View File
@@ -1,18 +1,18 @@
import { Link, createFileRoute } from '@tanstack/react-router'
import ReactMarkdown from 'react-markdown'
import changelogMarkdown from '../../../../CHANGELOG.md?raw'
import { pageMeta } from '~/meta'
import '~/styles.css'
import { Link, createFileRoute } from "@tanstack/react-router";
import ReactMarkdown from "react-markdown";
import changelogMarkdown from "../../../../CHANGELOG.md?raw";
import { pageMeta } from "~/meta";
import "~/styles.css";
export const Route = createFileRoute('/changelog')({
export const Route = createFileRoute("/changelog")({
head: () => ({
meta: pageMeta(
'Changelog - Paseo',
'Product updates, fixes, and improvements shipped in each Paseo release.',
"Changelog - Paseo",
"Product updates, fixes, and improvements shipped in each Paseo release.",
),
}),
component: Changelog,
})
});
function Changelog() {
return (
@@ -73,5 +73,5 @@ function Changelog() {
</article>
</div>
</div>
)
);
}
+8 -8
View File
@@ -1,16 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'
import { LandingPage } from '~/components/landing-page'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { LandingPage } from "~/components/landing-page";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/claude-code')({
export const Route = createFileRoute("/claude-code")({
head: () => ({
meta: pageMeta(
'Claude Code Mobile App – Ship from your phone | Paseo',
'Run Claude Code from your phone. Launch agents, check on progress, review diffs, and merge — all from your pocket. Self-hosted, your code stays on your machine.',
"Claude Code Mobile App – Ship from your phone | Paseo",
"Run Claude Code from your phone. Launch agents, check on progress, review diffs, and merge — all from your pocket. Self-hosted, your code stays on your machine.",
),
}),
component: ClaudeCodePage,
})
});
function ClaudeCodePage() {
return (
@@ -18,5 +18,5 @@ function ClaudeCodePage() {
title="Ship with Claude Code from your phone"
subtitle="Launch agents, check on progress, and merge from anywhere. Your Claude Code setup, your machine, your pocket."
/>
)
);
}
+8 -8
View File
@@ -1,16 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'
import { LandingPage } from '~/components/landing-page'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { LandingPage } from "~/components/landing-page";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/codex')({
export const Route = createFileRoute("/codex")({
head: () => ({
meta: pageMeta(
'Codex Mobile App – Run Codex from anywhere | Paseo',
'Run OpenAI Codex from your phone. Kick off agents, monitor progress, and ship code without being at your desk. Self-hosted, your code never leaves your machine.',
"Codex Mobile App – Run Codex from anywhere | Paseo",
"Run OpenAI Codex from your phone. Kick off agents, monitor progress, and ship code without being at your desk. Self-hosted, your code never leaves your machine.",
),
}),
component: CodexPage,
})
});
function CodexPage() {
return (
@@ -18,5 +18,5 @@ function CodexPage() {
title="Run Codex from anywhere"
subtitle="Kick off Codex agents on your machine from your phone. Check in on the train, review on the couch, merge from the park."
/>
)
);
}
+16 -16
View File
@@ -1,20 +1,20 @@
import { createFileRoute, Link, Outlet } from '@tanstack/react-router'
import '~/styles.css'
import { createFileRoute, Link, Outlet } from "@tanstack/react-router";
import "~/styles.css";
export const Route = createFileRoute('/docs')({
export const Route = createFileRoute("/docs")({
component: DocsLayout,
})
});
const navigation = [
{ name: 'Getting started', href: '/docs' },
{ name: 'Updates', href: '/docs/updates' },
{ name: 'Voice', href: '/docs/voice' },
{ name: 'Git worktrees', href: '/docs/worktrees' },
{ name: 'CLI', href: '/docs/cli' },
{ name: 'Configuration', href: '/docs/configuration' },
{ name: 'Security', href: '/docs/security' },
{ name: 'Best practices', href: '/docs/best-practices' },
]
{ name: "Getting started", href: "/docs" },
{ name: "Updates", href: "/docs/updates" },
{ name: "Voice", href: "/docs/voice" },
{ name: "Git worktrees", href: "/docs/worktrees" },
{ name: "CLI", href: "/docs/cli" },
{ name: "Configuration", href: "/docs/configuration" },
{ name: "Security", href: "/docs/security" },
{ name: "Best practices", href: "/docs/best-practices" },
];
function DocsLayout() {
return (
@@ -32,7 +32,7 @@ function DocsLayout() {
to={item.href}
activeOptions={{ exact: true }}
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
activeProps={{ className: 'text-foreground' }}
activeProps={{ className: "text-foreground" }}
>
{item.name}
</Link>
@@ -54,7 +54,7 @@ function DocsLayout() {
to={item.href}
activeOptions={{ exact: true }}
className="block px-3 py-2 text-sm rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
activeProps={{ className: 'bg-muted text-foreground' }}
activeProps={{ className: "bg-muted text-foreground" }}
>
{item.name}
</Link>
@@ -66,5 +66,5 @@ function DocsLayout() {
</main>
</div>
</div>
)
);
}
@@ -1,15 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/docs/best-practices')({
export const Route = createFileRoute("/docs/best-practices")({
head: () => ({
meta: pageMeta(
'Best Practices - Paseo Docs',
'Tips for getting the most out of Paseo and mobile-first agent workflows.',
"Best Practices - Paseo Docs",
"Tips for getting the most out of Paseo and mobile-first agent workflows.",
),
}),
component: BestPractices,
})
});
function BestPractices() {
return (
@@ -17,131 +17,118 @@ function BestPractices() {
<div>
<h1 className="text-3xl font-medium font-title 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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>
)
);
}
+25 -23
View File
@@ -1,22 +1,22 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/docs/cli')({
export const Route = createFileRoute("/docs/cli")({
head: () => ({
meta: pageMeta(
'CLI - Paseo Docs',
'Paseo CLI reference: manage agents, daemons, permissions, and worktrees from your terminal.',
"CLI - Paseo Docs",
"Paseo CLI reference: manage agents, daemons, permissions, and worktrees from your terminal.",
),
}),
component: CLI,
})
});
function Code({ children }: { children: React.ReactNode }) {
return (
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto">
{children}
</div>
)
);
}
function CLI() {
@@ -25,8 +25,8 @@ function CLI() {
<div>
<h1 className="text-3xl font-medium font-title mb-4">CLI</h1>
<p className="text-white/60 leading-relaxed">
The Paseo CLI lets you manage agents from your terminal. It's the same interface exposed by
the daemon's API, so anything you can do in the app you can do from the command line.
The Paseo CLI lets you manage agents from your terminal. It's the same interface exposed
by the daemon's API, so anything you can do in the app you can do from the command line.
</p>
</div>
@@ -67,16 +67,16 @@ paseo run --output-schema schema.json "extract release notes"
paseo run --output-schema '{"type":"object","properties":{"summary":{"type":"string"}},"required":["summary"]}' "summarize release notes"`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
The <code className="font-mono">--worktree</code> flag creates the agent in an isolated git
worktree, useful for parallel feature development.
The <code className="font-mono">--worktree</code> flag creates the agent in an isolated
git worktree, useful for parallel feature development.
</p>
<p className="text-white/60 leading-relaxed">
Use <code className="font-mono">--output-schema</code> to return only matching JSON output.
You can pass a schema file path or an inline JSON schema object.
This mode cannot be used with <code className="font-mono">--detach</code>.
Use <code className="font-mono">--output-schema</code> to return only matching JSON
output. You can pass a schema file path or an inline JSON schema object. This mode cannot
be used with <code className="font-mono">--detach</code>.
</p>
<p className="text-white/60 leading-relaxed">
By default, <code className="font-mono">paseo run</code> waits for completion. Use{' '}
By default, <code className="font-mono">paseo run</code> waits for completion. Use{" "}
<code className="font-mono">--detach</code> to run in the background.
</p>
</section>
@@ -96,13 +96,15 @@ paseo ls -a -g --json # Full list as JSON`}</pre>
<section className="space-y-4">
<h2 className="text-xl font-medium">Streaming output</h2>
<p className="text-white/60 leading-relaxed">
Use <code className="font-mono">paseo attach</code> to stream an agent's output in real-time:
Use <code className="font-mono">paseo attach</code> to stream an agent's output in
real-time:
</p>
<Code>
<pre className="text-white/80">{`paseo attach abc123 # Attach to agent (Ctrl+C to detach)`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
Agent IDs can be shortened — <code className="font-mono">abc</code> works if it's unambiguous.
Agent IDs can be shortened — <code className="font-mono">abc</code> works if it's
unambiguous.
</p>
</section>
@@ -180,7 +182,8 @@ paseo daemon status # Check status
paseo daemon stop # Stop the daemon`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
Use <code className="font-mono">PASEO_HOME</code> to run multiple isolated daemon instances.
Use <code className="font-mono">PASEO_HOME</code> to run multiple isolated daemon
instances.
</p>
</section>
@@ -197,9 +200,7 @@ paseo run --detach "implement the API" --name api-agent
paseo wait api-agent
paseo logs api-agent --tail 5`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
Simple implement + verify loop:
</p>
<p className="text-white/60 leading-relaxed">Simple implement + verify loop:</p>
<Code>
<pre className="text-white/80">{`# Requires jq
while true; do
@@ -236,7 +237,8 @@ paseo ls -q # IDs only (quiet)`}</pre>
<h2 className="text-xl font-medium">Global options</h2>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>
<code className="font-mono">--host &lt;host:port&gt;</code> — connect to a different daemon
<code className="font-mono">--host &lt;host:port&gt;</code> — connect to a different
daemon
</li>
<li>
<code className="font-mono">--json</code> — JSON output
@@ -250,5 +252,5 @@ paseo ls -q # IDs only (quiet)`}</pre>
</ul>
</section>
</div>
)
);
}
@@ -1,15 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/docs/configuration')({
export const Route = createFileRoute("/docs/configuration")({
head: () => ({
meta: pageMeta(
'Configuration - Paseo Docs',
'Configure Paseo via config.json, environment variables, and CLI overrides.',
"Configuration - Paseo Docs",
"Configure Paseo via config.json, environment variables, and CLI overrides.",
),
}),
component: Configuration,
})
});
function Configuration() {
return (
@@ -17,8 +17,8 @@ function Configuration() {
<div>
<h1 className="text-3xl font-medium font-title 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.
Paseo loads configuration from a single JSON file in your Paseo home directory, with
optional environment variable and CLI overrides.
</p>
</div>
@@ -33,24 +33,26 @@ function Configuration() {
<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>.
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>
<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>
<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
Lists append across sources (for example, <code className="font-mono">allowedHosts</code>{" "}
and
<code className="font-mono">cors.allowedOrigins</code>).
</p>
</section>
@@ -61,7 +63,7 @@ function Configuration() {
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.sh/schemas/paseo.config.v1.json",
"version": 1,
"providers": {
@@ -79,19 +81,21 @@ function Configuration() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Agent provider runtime settings</h2>
<p className="text-white/60 leading-relaxed">
Use <code className="font-mono">agents.providers</code> to customize how Paseo launches agent provider CLIs.
This works for <code className="font-mono">claude</code>, <code className="font-mono">codex</code>, and
Use <code className="font-mono">agents.providers</code> to customize how Paseo launches
agent provider CLIs. This works for <code className="font-mono">claude</code>,{" "}
<code className="font-mono">codex</code>, and
<code className="font-mono"> opencode</code>.
</p>
<p className="text-white/60 leading-relaxed">
<code className="font-mono">command.mode</code> can be <code className="font-mono">default</code>,{' '}
<code className="font-mono">append</code>, or <code className="font-mono">replace</code>. Use{' '}
<code className="font-mono">env</code> to inject provider-specific environment variables.
<code className="font-mono">command.mode</code> can be{" "}
<code className="font-mono">default</code>, <code className="font-mono">append</code>, or{" "}
<code className="font-mono">replace</code>. Use <code className="font-mono">env</code> to
inject provider-specific environment variables.
</p>
<h3 className="text-lg font-medium">Enable Claude Code Chrome MCP</h3>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
{`{
{`{
"agents": {
"providers": {
"claude": {
@@ -105,9 +109,11 @@ function Configuration() {
}`}
</pre>
<h3 className="text-lg font-medium">Point Claude to Anthropic-compatible endpoints (z.ai example)</h3>
<h3 className="text-lg font-medium">
Point Claude to Anthropic-compatible endpoints (z.ai example)
</h3>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
{`{
{`{
"agents": {
"providers": {
"claude": {
@@ -124,11 +130,11 @@ function Configuration() {
<h3 className="text-lg font-medium">Run Claude through Docker</h3>
<p className="text-white/60 leading-relaxed">
Create a wrapper script that runs Claude in Docker, then tell Paseo to replace the Claude launch command
with that script.
Create a wrapper script that runs Claude in Docker, then tell Paseo to replace the Claude
launch command with that script.
</p>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
{`{
{`{
"agents": {
"providers": {
"claude": {
@@ -142,7 +148,7 @@ function Configuration() {
}`}
</pre>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
{`#!/usr/bin/env bash
{`#!/usr/bin/env bash
set -euo pipefail
docker run --rm -i \\
-v "$PWD":"$PWD" \\
@@ -156,13 +162,16 @@ docker run --rm -i \\
<section className="space-y-4">
<h2 className="text-xl font-medium">Voice</h2>
<p className="text-white/60 leading-relaxed">
Voice is configured through <code className="font-mono">features.dictation</code> and{' '}
<code className="font-mono">features.voiceMode</code>, with provider credentials under{' '}
Voice is configured through <code className="font-mono">features.dictation</code> and{" "}
<code className="font-mono">features.voiceMode</code>, with provider credentials under{" "}
<code className="font-mono">providers</code>.
</p>
<p className="text-white/60 leading-relaxed">
For voice philosophy, architecture, and complete local/OpenAI setup examples, see{' '}
<a href="/docs/voice" className="underline hover:text-white/80">Voice docs</a>.
For voice philosophy, architecture, and complete local/OpenAI setup examples, see{" "}
<a href="/docs/voice" className="underline hover:text-white/80">
Voice docs
</a>
.
</p>
</section>
@@ -176,16 +185,16 @@ docker run --rm -i \\
Console: <code className="font-mono">info</code> and above
</li>
<li>
File (<code className="font-mono">$PASEO_HOME/daemon.log</code>):{' '}
File (<code className="font-mono">$PASEO_HOME/daemon.log</code>):{" "}
<code className="font-mono">trace</code> and above
</li>
<li>
File rotation: <code className="font-mono">10m</code> max file size,{' '}
File rotation: <code className="font-mono">10m</code> max file size,{" "}
<code className="font-mono">2</code> retained files total (active + 1 rotated)
</li>
</ul>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
{`{
{`{
"log": {
"console": {
"level": "info",
@@ -203,7 +212,7 @@ docker run --rm -i \\
}`}
</pre>
<p className="text-white/60 leading-relaxed">
Legacy fields <code className="font-mono">log.level</code> and{' '}
Legacy fields <code className="font-mono">log.level</code> and{" "}
<code className="font-mono">log.format</code> are still supported and map to the new
destination settings.
</p>
@@ -212,22 +221,75 @@ docker run --rm -i \\
<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">PASEO_LOG_CONSOLE_LEVEL</code> — override <code className="font-mono">log.console.level</code></li>
<li><code className="font-mono">PASEO_LOG_FILE_LEVEL</code> — override <code className="font-mono">log.file.level</code></li>
<li><code className="font-mono">PASEO_LOG_FILE_PATH</code> — override <code className="font-mono">log.file.path</code></li>
<li><code className="font-mono">PASEO_LOG_FILE_ROTATE_SIZE</code> — override <code className="font-mono">log.file.rotate.maxSize</code></li>
<li><code className="font-mono">PASEO_LOG_FILE_ROTATE_COUNT</code> — override <code className="font-mono">log.file.rotate.maxFiles</code></li>
<li><code className="font-mono">PASEO_LOG</code>, <code className="font-mono">PASEO_LOG_FORMAT</code> — legacy log overrides (still supported)</li>
<li><code className="font-mono">OPENAI_API_KEY</code> — override OpenAI provider key</li>
<li><code className="font-mono">PASEO_VOICE_LLM_PROVIDER</code> — override voice LLM provider (<code className="font-mono">claude</code>, <code className="font-mono">codex</code>, <code className="font-mono">opencode</code>)</li>
<li><code className="font-mono">PASEO_DICTATION_STT_PROVIDER</code>, <code className="font-mono">PASEO_VOICE_STT_PROVIDER</code>, <code className="font-mono">PASEO_VOICE_TTS_PROVIDER</code> — override voice provider selection (<code className="font-mono">local</code> or <code className="font-mono">openai</code>)</li>
<li><code className="font-mono">PASEO_LOCAL_MODELS_DIR</code> — control local model directory</li>
<li><code className="font-mono">PASEO_DICTATION_LOCAL_STT_MODEL</code> — override local dictation STT model</li>
<li><code className="font-mono">PASEO_VOICE_LOCAL_STT_MODEL</code>, <code className="font-mono">PASEO_VOICE_LOCAL_TTS_MODEL</code> — override local voice STT/TTS models</li>
<li><code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEAKER_ID</code>, <code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEED</code> — optional local voice TTS tuning</li>
<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">PASEO_LOG_CONSOLE_LEVEL</code> — override{" "}
<code className="font-mono">log.console.level</code>
</li>
<li>
<code className="font-mono">PASEO_LOG_FILE_LEVEL</code> — override{" "}
<code className="font-mono">log.file.level</code>
</li>
<li>
<code className="font-mono">PASEO_LOG_FILE_PATH</code> — override{" "}
<code className="font-mono">log.file.path</code>
</li>
<li>
<code className="font-mono">PASEO_LOG_FILE_ROTATE_SIZE</code> — override{" "}
<code className="font-mono">log.file.rotate.maxSize</code>
</li>
<li>
<code className="font-mono">PASEO_LOG_FILE_ROTATE_COUNT</code> — override{" "}
<code className="font-mono">log.file.rotate.maxFiles</code>
</li>
<li>
<code className="font-mono">PASEO_LOG</code>,{" "}
<code className="font-mono">PASEO_LOG_FORMAT</code> — legacy log overrides (still
supported)
</li>
<li>
<code className="font-mono">OPENAI_API_KEY</code> — override OpenAI provider key
</li>
<li>
<code className="font-mono">PASEO_VOICE_LLM_PROVIDER</code> — override voice LLM
provider (<code className="font-mono">claude</code>,{" "}
<code className="font-mono">codex</code>, <code className="font-mono">opencode</code>)
</li>
<li>
<code className="font-mono">PASEO_DICTATION_STT_PROVIDER</code>,{" "}
<code className="font-mono">PASEO_VOICE_STT_PROVIDER</code>,{" "}
<code className="font-mono">PASEO_VOICE_TTS_PROVIDER</code> — override voice provider
selection (<code className="font-mono">local</code> or{" "}
<code className="font-mono">openai</code>)
</li>
<li>
<code className="font-mono">PASEO_LOCAL_MODELS_DIR</code> — control local model
directory
</li>
<li>
<code className="font-mono">PASEO_DICTATION_LOCAL_STT_MODEL</code> — override local
dictation STT model
</li>
<li>
<code className="font-mono">PASEO_VOICE_LOCAL_STT_MODEL</code>,{" "}
<code className="font-mono">PASEO_VOICE_LOCAL_TTS_MODEL</code> — override local voice
STT/TTS models
</li>
<li>
<code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEAKER_ID</code>,{" "}
<code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEED</code> — optional local voice
TTS tuning
</li>
</ul>
</section>
@@ -241,5 +303,5 @@ docker run --rm -i \\
</div>
</section>
</div>
)
);
}
+18 -16
View File
@@ -1,15 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/docs/')({
export const Route = createFileRoute("/docs/")({
head: () => ({
meta: pageMeta(
'Getting Started - Paseo Docs',
'Learn how to set up and use Paseo to manage your coding agents from anywhere.',
"Getting Started - Paseo Docs",
"Learn how to set up and use Paseo to manage your coding agents from anywhere.",
),
}),
component: GettingStarted,
})
});
function GettingStarted() {
return (
@@ -17,15 +17,14 @@ function GettingStarted() {
<div>
<h1 className="text-3xl font-medium font-title 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.
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 CLI globally and run it on your machine:
</p>
<p className="text-white/60">Install the CLI 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 @getpaseo/cli</span>
@@ -39,14 +38,16 @@ function GettingStarted() {
<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.
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.
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>
@@ -85,11 +86,12 @@ function GettingStarted() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Voice Setup</h2>
<p className="text-white/60">
Paseo includes first-class voice support with a local-first architecture and configurable speech
providers.
Paseo includes first-class voice support with a local-first architecture and configurable
speech providers.
</p>
<p className="text-white/60">
For architecture, local model behavior, and provider configuration, see the Voice docs page.
For architecture, local model behavior, and provider configuration, see the Voice docs
page.
</p>
<a href="/docs/voice" className="underline hover:text-white/80">
Voice docs
@@ -122,5 +124,5 @@ function GettingStarted() {
</ul>
</section>
</div>
)
);
}
+22 -20
View File
@@ -1,22 +1,22 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/docs/security')({
export const Route = createFileRoute("/docs/security")({
head: () => ({
meta: pageMeta(
'Security - Paseo Docs',
'Security model for Paseo: architecture overview, connection methods, relay encryption, and best practices.',
"Security - Paseo Docs",
"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() {
@@ -79,7 +79,7 @@ function Security() {
<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{' '}
The daemon generates a persistent ECDH keypair and stores it in{" "}
<code className="font-mono">$PASEO_HOME/daemon-keypair.json</code>
</li>
<li>
@@ -140,7 +140,7 @@ function Security() {
<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>{' '}
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>
@@ -155,7 +155,7 @@ function Security() {
<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{' '}
If you prefer direct connections over the relay, you can use a VPN like{" "}
<a
href="https://tailscale.com"
target="_blank"
@@ -170,7 +170,7 @@ function Security() {
<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{' '}
Install Tailscale on your machine and phone and join them to the same{" "}
<a
href="https://tailscale.com/kb/1136/tailnet"
target="_blank"
@@ -181,14 +181,16 @@ function Security() {
</a>
</li>
<li>
Configure the daemon to listen on your Tailscale IP (e.g.,{' '}
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{' '}
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>
<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>
@@ -196,7 +198,7 @@ function Security() {
<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{' '}
ensure you have proper firewall rules and review your{" "}
<code className="font-mono">allowedHosts</code> configuration.
</div>
</section>
@@ -214,17 +216,17 @@ function Security() {
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{' '}
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>,{' '}
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>: allow{" "}
<code className="font-mono">example.com</code> and any subdomain, plus defaults
</li>
<li>
@@ -283,5 +285,5 @@ function Security() {
</ul>
</section>
</div>
)
);
}
+8 -8
View File
@@ -1,15 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/docs/updates')({
export const Route = createFileRoute("/docs/updates")({
head: () => ({
meta: pageMeta(
'Updates - Paseo Docs',
'How to update Paseo daemon and apps across web, desktop, and mobile.',
"Updates - Paseo Docs",
"How to update Paseo daemon and apps across web, desktop, and mobile.",
),
}),
component: UpdatesDocs,
})
});
function UpdatesDocs() {
return (
@@ -53,7 +53,7 @@ function UpdatesDocs() {
className="underline hover:text-white/80"
>
app.paseo.sh
</a>{' '}
</a>{" "}
is always up to date. No manual update needed.
</p>
</section>
@@ -82,5 +82,5 @@ function UpdatesDocs() {
</p>
</section>
</div>
)
);
}
+60 -31
View File
@@ -1,15 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/docs/voice')({
export const Route = createFileRoute("/docs/voice")({
head: () => ({
meta: pageMeta(
'Voice - Paseo Docs',
'Paseo voice architecture, local-first model execution, and provider configuration.',
"Voice - Paseo Docs",
"Paseo voice architecture, local-first model execution, and provider configuration.",
),
}),
component: VoiceDocs,
})
});
function VoiceDocs() {
return (
@@ -17,17 +17,17 @@ function VoiceDocs() {
<div>
<h1 className="text-3xl font-medium font-title mb-4">Voice</h1>
<p className="text-white/60 leading-relaxed">
Paseo has first-class voice support for dictation and realtime conversations with your coding
environment.
Paseo has first-class voice support for dictation and realtime conversations with your
coding environment.
</p>
</div>
<section className="space-y-4">
<h2 className="text-xl font-medium">Philosophy</h2>
<p className="text-white/60 leading-relaxed">
Voice is local-first. You can run speech fully on-device, or choose OpenAI for speech features.
For voice reasoning/orchestration, Paseo reuses agent providers already installed and authenticated
on your machine.
Voice is local-first. You can run speech fully on-device, or choose OpenAI for speech
features. For voice reasoning/orchestration, Paseo reuses agent providers already
installed and authenticated on your machine.
</p>
<p className="text-white/60 leading-relaxed">
This keeps credentials and execution in your environment and avoids introducing a separate
@@ -38,9 +38,16 @@ function VoiceDocs() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Architecture</h2>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li>Speech I/O: STT and TTS providers per feature (<code className="font-mono">local</code> or <code className="font-mono">openai</code>)</li>
<li>
Speech I/O: STT and TTS providers per feature (<code className="font-mono">local</code>{" "}
or <code className="font-mono">openai</code>)
</li>
<li>Local speech runtime: ONNX models executed on CPU by default</li>
<li>Voice LLM orchestration: hidden agent session using your configured provider (<code className="font-mono">claude</code>, <code className="font-mono">codex</code>, or <code className="font-mono">opencode</code>)</li>
<li>
Voice LLM orchestration: hidden agent session using your configured provider (
<code className="font-mono">claude</code>, <code className="font-mono">codex</code>, or{" "}
<code className="font-mono">opencode</code>)
</li>
<li>Tooling path: MCP stdio bridge for voice tools and agent control</li>
</ul>
</section>
@@ -48,16 +55,17 @@ function VoiceDocs() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Local Speech</h2>
<p className="text-white/60 leading-relaxed">
Local speech defaults to model IDs <code className="font-mono">parakeet-tdt-0.6b-v3-int8</code>{' '}
(STT) and <code className="font-mono">kokoro-en-v0_19</code> (TTS, speaker 0 / voice 00).
Local speech defaults to model IDs{" "}
<code className="font-mono">parakeet-tdt-0.6b-v3-int8</code> (STT) and{" "}
<code className="font-mono">kokoro-en-v0_19</code> (TTS, speaker 0 / voice 00).
</p>
<p className="text-white/60 leading-relaxed">
Missing models are downloaded at daemon startup into{' '}
<code className="font-mono">$PASEO_HOME/models/local-speech</code>.
Downloads happen only for missing files.
Missing models are downloaded at daemon startup into{" "}
<code className="font-mono">$PASEO_HOME/models/local-speech</code>. Downloads happen only
for missing files.
</p>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
{`{
{`{
"version": 1,
"features": {
"dictation": { "stt": { "provider": "local", "model": "parakeet-tdt-0.6b-v3-int8" } },
@@ -79,11 +87,12 @@ function VoiceDocs() {
<section className="space-y-4">
<h2 className="text-xl font-medium">OpenAI Speech Option</h2>
<p className="text-white/60 leading-relaxed">
You can switch dictation, voice STT, and voice TTS to OpenAI by setting provider fields to{' '}
<code className="font-mono">openai</code> and providing <code className="font-mono">OPENAI_API_KEY</code>.
You can switch dictation, voice STT, and voice TTS to OpenAI by setting provider fields to{" "}
<code className="font-mono">openai</code> and providing{" "}
<code className="font-mono">OPENAI_API_KEY</code>.
</p>
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
{`{
{`{
"version": 1,
"features": {
"dictation": { "stt": { "provider": "openai" } },
@@ -102,22 +111,42 @@ function VoiceDocs() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Environment Variables</h2>
<ul className="text-white/60 space-y-2 list-disc list-inside">
<li><code className="font-mono">OPENAI_API_KEY</code> — OpenAI speech credentials</li>
<li><code className="font-mono">PASEO_VOICE_LLM_PROVIDER</code> — voice agent provider override</li>
<li><code className="font-mono">PASEO_LOCAL_MODELS_DIR</code> — local model storage directory</li>
<li><code className="font-mono">PASEO_DICTATION_LOCAL_STT_MODEL</code> — local dictation STT model ID</li>
<li><code className="font-mono">PASEO_VOICE_LOCAL_STT_MODEL</code>, <code className="font-mono">PASEO_VOICE_LOCAL_TTS_MODEL</code> — local voice STT/TTS model IDs</li>
<li><code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEAKER_ID</code>, <code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEED</code> — optional local voice TTS tuning</li>
<li>
<code className="font-mono">OPENAI_API_KEY</code> — OpenAI speech credentials
</li>
<li>
<code className="font-mono">PASEO_VOICE_LLM_PROVIDER</code> — voice agent provider
override
</li>
<li>
<code className="font-mono">PASEO_LOCAL_MODELS_DIR</code> — local model storage
directory
</li>
<li>
<code className="font-mono">PASEO_DICTATION_LOCAL_STT_MODEL</code> — local dictation STT
model ID
</li>
<li>
<code className="font-mono">PASEO_VOICE_LOCAL_STT_MODEL</code>,{" "}
<code className="font-mono">PASEO_VOICE_LOCAL_TTS_MODEL</code> — local voice STT/TTS
model IDs
</li>
<li>
<code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEAKER_ID</code>,{" "}
<code className="font-mono">PASEO_VOICE_LOCAL_TTS_SPEED</code> — optional local voice
TTS tuning
</li>
</ul>
</section>
<section className="space-y-4">
<h2 className="text-xl font-medium">Operational Notes</h2>
<p className="text-white/60 leading-relaxed">
Realtime voice can launch and control agents. Treat voice prompts with the same care as direct
agent instructions, especially when specifying working directories or destructive operations.
Realtime voice can launch and control agents. Treat voice prompts with the same care as
direct agent instructions, especially when specifying working directories or destructive
operations.
</p>
</section>
</div>
)
);
}
+53 -71
View File
@@ -1,22 +1,22 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/docs/worktrees')({
export const Route = createFileRoute("/docs/worktrees")({
head: () => ({
meta: pageMeta(
'Git Worktrees - Paseo Docs',
'Run agents in isolated git worktrees for parallel feature development.',
"Git Worktrees - Paseo Docs",
"Run agents in isolated git worktrees for parallel feature development.",
),
}),
component: Worktrees,
})
});
function Code({ children }: { children: React.ReactNode }) {
return (
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto">
{children}
</div>
)
);
}
function Worktrees() {
@@ -25,8 +25,8 @@ function Worktrees() {
<div>
<h1 className="text-3xl font-medium font-title mb-4">Git Worktrees</h1>
<p className="text-white/60 leading-relaxed">
Git worktrees let you have multiple working directories from the same repository.
Paseo uses them to run agents in isolated branches without switching contexts.
Git worktrees let you have multiple working directories from the same repository. Paseo
uses them to run agents in isolated branches without switching contexts.
</p>
</div>
@@ -34,14 +34,14 @@ function Worktrees() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Why worktrees?</h2>
<p className="text-white/60 leading-relaxed">
Without worktrees, running multiple agents on the same repo means they share the
working directory. One agent's changes interfere with another's. You can't safely
run parallel tasks.
Without worktrees, running multiple agents on the same repo means they share the working
directory. One agent's changes interfere with another's. You can't safely run parallel
tasks.
</p>
<p className="text-white/60 leading-relaxed">
With worktrees, each agent gets its own directory and branch. They can work
simultaneously without conflict. When an agent finishes, you review the diff,
merge the branch, and archive the worktree.
With worktrees, each agent gets its own directory and branch. They can work simultaneously
without conflict. When an agent finishes, you review the diff, merge the branch, and
archive the worktree.
</p>
</section>
@@ -61,9 +61,9 @@ function Worktrees() {
└── swift-hare/ # random slug`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
The hash avoids collisions between repositories that share the same directory or
remote name. Worktree directory names are random slugs — the branch name is
separate and chosen when you first launch an agent in the worktree.
The hash avoids collisions between repositories that share the same directory or remote
name. Worktree directory names are random slugs — the branch name is separate and chosen
when you first launch an agent in the worktree.
</p>
</section>
@@ -71,12 +71,12 @@ function Worktrees() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Branches</h2>
<p className="text-white/60 leading-relaxed">
When you create a worktree, Paseo generates a random directory name. The branch name
is set when you first launch an agent — Paseo generates one automatically.
When you create a worktree, Paseo generates a random directory name. The branch name is
set when you first launch an agent — Paseo generates one automatically.
</p>
<p className="text-white/60 leading-relaxed">
This means the worktree directory and branch are independent. You can rename the
branch later without affecting the worktree path.
This means the worktree directory and branch are independent. You can rename the branch
later without affecting the worktree path.
</p>
</section>
@@ -84,14 +84,13 @@ function Worktrees() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Multiple agents per worktree</h2>
<p className="text-white/60 leading-relaxed">
You can launch multiple agents into the same worktree. They share the working
directory and branch, which is useful when you want agents to collaborate on
the same feature or when one agent hands off to another.
You can launch multiple agents into the same worktree. They share the working directory
and branch, which is useful when you want agents to collaborate on the same feature or
when one agent hands off to another.
</p>
<p className="text-white/60 leading-relaxed">
Be mindful of conflicts — agents working on the same files simultaneously can
step on each other. This works best when agents have distinct responsibilities
or run sequentially.
Be mindful of conflicts — agents working on the same files simultaneously can step on each
other. This works best when agents have distinct responsibilities or run sequentially.
</p>
</section>
@@ -100,7 +99,7 @@ function Worktrees() {
<h2 className="text-xl font-medium">Setup with paseo.json</h2>
<p className="text-white/60 leading-relaxed">
When Paseo creates a worktree, it's a fresh checkout. Dependencies aren't installed,
config files aren't copied. You can automate setup by creating a{' '}
config files aren't copied. You can automate setup by creating a{" "}
<code className="font-mono">paseo.json</code> file in your repository root:
</p>
<Code>
@@ -114,14 +113,14 @@ function Worktrees() {
}`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
The <code className="font-mono">setup</code> array contains shell commands that run
after the worktree is created. Use it to install dependencies, copy local config
files, or run any other initialization.
The <code className="font-mono">setup</code> array contains shell commands that run after
the worktree is created. Use it to install dependencies, copy local config files, or run
any other initialization.
</p>
<div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4 text-white/80">
<strong>Important:</strong> Setup commands come from{' '}
<code className="font-mono">paseo.json</code> in the selected base branch. If you pick{' '}
<code className="font-mono">main</code>, Paseo reads the committed file on{' '}
<strong>Important:</strong> Setup commands come from{" "}
<code className="font-mono">paseo.json</code> in the selected base branch. If you pick{" "}
<code className="font-mono">main</code>, Paseo reads the committed file on{" "}
<code className="font-mono">main</code>. Local or uncommitted changes in another branch
are not used for that worktree.
</div>
@@ -139,7 +138,7 @@ function Worktrees() {
path (original repository root)
</li>
<li>
<code className="font-mono">$PASEO_ROOT_PATH</code> — legacy alias of{' '}
<code className="font-mono">$PASEO_ROOT_PATH</code> — legacy alias of{" "}
<code className="font-mono">$PASEO_SOURCE_CHECKOUT_PATH</code>
</li>
<li>
@@ -149,7 +148,8 @@ function Worktrees() {
<code className="font-mono">$PASEO_BRANCH_NAME</code> — the branch name created
</li>
<li>
<code className="font-mono">$PASEO_WORKTREE_PORT</code> — an available local port for setup scripts
<code className="font-mono">$PASEO_WORKTREE_PORT</code> — an available local port for
setup scripts
</li>
</ul>
<p className="text-white/60 leading-relaxed">
@@ -211,47 +211,29 @@ function Worktrees() {
{/* Workflow */}
<section className="space-y-4">
<h2 className="text-xl font-medium">Workflow</h2>
<p className="text-white/60 leading-relaxed">
The typical workflow is:
</p>
<p className="text-white/60 leading-relaxed">The typical workflow is:</p>
<ol className="text-white/60 space-y-2 list-decimal list-inside">
<li>
Create a worktree — Paseo creates the directory and runs setup
</li>
<li>
Launch an agent — Paseo creates or assigns a branch
</li>
<li>
Agent works in isolation — changes stay in its worktree
</li>
<li>
Review the diff — compare against the base branch
</li>
<li>
Merge or discard — if approved, merge the branch; otherwise archive
</li>
<li>
Archive the worktree — cleans up the directory and optionally the branch
</li>
<li>Create a worktree — Paseo creates the directory and runs setup</li>
<li>Launch an agent — Paseo creates or assigns a branch</li>
<li>Agent works in isolation — changes stay in its worktree</li>
<li>Review the diff — compare against the base branch</li>
<li>Merge or discard — if approved, merge the branch; otherwise archive</li>
<li>Archive the worktree — cleans up the directory and optionally the branch</li>
</ol>
<p className="text-white/60 leading-relaxed">
You can run multiple agents in different worktrees simultaneously. Each worktree
has its own branch and working directory.
You can run multiple agents in different worktrees simultaneously. Each worktree has its
own branch and working directory.
</p>
</section>
{/* CLI reference */}
<section className="space-y-4">
<h2 className="text-xl font-medium">CLI reference</h2>
<p className="text-white/60 leading-relaxed">
Create an agent in a new worktree:
</p>
<p className="text-white/60 leading-relaxed">Create an agent in a new worktree:</p>
<Code>
<pre className="text-white/80">{`paseo run --worktree feature-auth --base main "implement auth"`}</pre>
</Code>
<p className="text-white/60 leading-relaxed">
List all worktrees:
</p>
<p className="text-white/60 leading-relaxed">List all worktrees:</p>
<Code>
<pre className="text-white/80">{`paseo worktree ls`}</pre>
</Code>
@@ -267,14 +249,14 @@ function Worktrees() {
<section className="space-y-4">
<h2 className="text-xl font-medium">Metadata</h2>
<p className="text-white/60 leading-relaxed">
Paseo stores metadata in each worktree's git directory to track the base branch.
This is used for diff operations and to know what branch to merge into.
Paseo stores metadata in each worktree's git directory to track the base branch. This is
used for diff operations and to know what branch to merge into.
</p>
<p className="text-white/60 leading-relaxed">
You don't need to manage this manually — Paseo handles it when creating and
archiving worktrees.
You don't need to manage this manually — Paseo handles it when creating and archiving
worktrees.
</p>
</section>
</div>
)
);
}
+14 -9
View File
@@ -1,22 +1,27 @@
import { createFileRoute } from '@tanstack/react-router'
import { LandingPage } from '~/components/landing-page'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { LandingPage } from "~/components/landing-page";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/')({
export const Route = createFileRoute("/")({
head: () => ({
meta: pageMeta(
'Paseo – Run Claude Code, Codex, and OpenCode from everywhere',
'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.',
"Paseo – Run Claude Code, Codex, and OpenCode from everywhere",
"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 (
<LandingPage
title={<>All your coding agents,<br className="hidden md:block" /> from anywhere</>}
title={
<>
All your coding agents,
<br className="hidden md:block" /> from anywhere
</>
}
subtitle="Run any coding agent from your phone, desktop, or terminal. Self-hosted, multi-provider, open source."
/>
)
);
}
+8 -8
View File
@@ -1,16 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'
import { LandingPage } from '~/components/landing-page'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { LandingPage } from "~/components/landing-page";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/opencode')({
export const Route = createFileRoute("/opencode")({
head: () => ({
meta: pageMeta(
'OpenCode Mobile App – Code from anywhere | Paseo',
'Run OpenCode from your phone. Launch agents, watch them work, and ship code from wherever you are. Self-hosted, open source, your code stays local.',
"OpenCode Mobile App – Code from anywhere | Paseo",
"Run OpenCode from your phone. Launch agents, watch them work, and ship code from wherever you are. Self-hosted, open source, your code stays local.",
),
}),
component: OpenCodePage,
})
});
function OpenCodePage() {
return (
@@ -18,5 +18,5 @@ function OpenCodePage() {
title="Run OpenCode from your phone"
subtitle="Launch agents, check on builds, and ship code from anywhere. Same setup, same machine, just not at your desk."
/>
)
);
}
+22 -31
View File
@@ -1,15 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import { pageMeta } from '~/meta'
import { createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
export const Route = createFileRoute('/privacy')({
export const Route = createFileRoute("/privacy")({
head: () => ({
meta: pageMeta(
'Privacy Policy - Paseo',
'Privacy policy for Paseo - a self-hosted agent manager with no tracking or analytics.',
"Privacy Policy - Paseo",
"Privacy policy for Paseo - a self-hosted agent manager with no tracking or analytics.",
),
}),
component: Privacy,
})
});
function Privacy() {
return (
@@ -19,8 +19,8 @@ function Privacy() {
<div className="space-y-6 text-white/70 leading-relaxed">
<p>
Paseo is a self-hosted tool for managing coding agents. Your code and
data stay on your machine.
Paseo is a self-hosted tool for managing coding agents. Your code and data stay on your
machine.
</p>
<section className="space-y-3">
@@ -31,8 +31,8 @@ function Privacy() {
<section className="space-y-3">
<h2 className="text-xl font-medium text-white">The relay server</h2>
<p>
If you use the optional encrypted relay to connect your phone to your
daemon, the relay sees:
If you use the optional encrypted relay to connect your phone to your daemon, the
relay sees:
</p>
<ul className="list-disc list-inside space-y-1 ml-4">
<li>IP addresses and connection timing</li>
@@ -40,49 +40,40 @@ function Privacy() {
<li>Session IDs</li>
</ul>
<p>
All messages between your phone and daemon are end-to-end encrypted
with AES-256-GCM. The relay cannot read your messages, see your code,
or decrypt your traffic.
All messages between your phone and daemon are end-to-end encrypted with AES-256-GCM.
The relay cannot read your messages, see your code, or decrypt your traffic.
</p>
</section>
<section className="space-y-3">
<h2 className="text-xl font-medium text-white">Analytics and tracking</h2>
<p>
We don't use analytics, tracking pixels, cookies, or ads. The app
doesn't phone home.
We don't use analytics, tracking pixels, cookies, or ads. The app doesn't phone home.
</p>
</section>
<section className="space-y-3">
<h2 className="text-xl font-medium text-white">Third-party services</h2>
<p>
Paseo wraps agent providers like Claude Code, Codex, and OpenCode.
Those tools communicate with their own APIs (Anthropic, OpenAI, etc.)
using your credentials. Paseo doesn't manage or intercept those API
calls.
Paseo wraps agent providers like Claude Code, Codex, and OpenCode. Those tools
communicate with their own APIs (Anthropic, OpenAI, etc.) using your credentials.
Paseo doesn't manage or intercept those API calls.
</p>
<p>
If you use voice features with cloud providers (OpenAI speech), your
voice data is sent to those services according to their privacy
policies.
If you use voice features with cloud providers (OpenAI speech), your voice data is
sent to those services according to their privacy policies.
</p>
</section>
<section className="space-y-3">
<h2 className="text-xl font-medium text-white">
We don't sell your data
</h2>
<p>
We don't have your data to sell. Paseo is self-hosted and
local-first.
</p>
<h2 className="text-xl font-medium text-white">We don't sell your data</h2>
<p>We don't have your data to sell. Paseo is self-hosted and local-first.</p>
</section>
<section className="space-y-3">
<h2 className="text-xl font-medium text-white">Questions</h2>
<p>
If you have questions about privacy, open an issue on{' '}
If you have questions about privacy, open an issue on{" "}
<a
href="https://github.com/getpaseo/paseo"
target="_blank"
@@ -99,5 +90,5 @@ function Privacy() {
</div>
</div>
</div>
)
);
}
+1 -1
View File
@@ -1,4 +1,4 @@
@import 'tailwindcss';
@import "tailwindcss";
/* Title font - centralized for easy changes */
.font-title {
+3 -3
View File
@@ -1,4 +1,4 @@
declare module '*.md?raw' {
const content: string
export default content
declare module "*.md?raw" {
const content: string;
export default content;
}