docs: lowercase internal docs + migrate website docs to public-docs/ (#634)
* docs: rename to lowercase + drop leftover plans Rename docs in docs/ to lowercase kebab-case for consistency and update all references in CLAUDE.md, CONTRIBUTING.md, CHANGELOG.md, packages/server/CLAUDE.md, and inter-doc links. Drop two leftover design plan docs: - docs/ATTACHMENT_BASED_REVIEW_CONTEXT_PLAN.md - docs/plan-approval-normalization.md * docs: drop stale uppercase entries from case-insensitive rename * feat(website): power /docs from public-docs/ markdown tree Move website docs out of TSX route components and into a root-level public-docs/ directory of plain markdown files with frontmatter (title, description, nav, order). - Add packages/website/src/docs.ts loader using import.meta.glob with ?raw to compile the markdown into the bundle at build time. - Replace the 9 hand-written docs/*.tsx routes with a single $.tsx catch-all that renders any slug via react-markdown. - Drive the docs sidebar nav from frontmatter order/nav. - Auto-discover docs routes in vite.config.ts so the sitemap stays in sync without manual edits. * fix(website): bind dev server to 0.0.0.0 so port collisions trigger fallback `host: "127.0.0.1"` (or unset) lets macOS coexist with another process holding an IPv6 dual-stack `*:8082` socket, so Vite never sees EADDRINUSE and silently binds alongside it. Forcing IPv4 wildcard makes the conflict real, and Vite's default `strictPort: false` falls through to the next free port. * fix(website): restore docs page styling after markdown migration Add a .docs-prose class that mirrors the styling the original docs/*.tsx components hand-rolled (h1/h2/h3 sizes, paragraph/list spacing, link colors, code blocks, callout-style blockquotes). ReactMarkdown was emitting unstyled HTML because the previous wrapper class only had inline-code rules — headings and code blocks fell back to user-agent defaults.
This commit is contained in:
@@ -192,7 +192,7 @@ npm run db:query -- "SELECT * FROM agent_timeline_rows..."
|
||||
| File | What it covers |
|
||||
| ---------------------------------------------------------- | ------------------------------------------------ |
|
||||
| [../CLAUDE.md](../CLAUDE.md) | Repository overview, critical rules, quick start |
|
||||
| [../docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md) | System design, WebSocket protocol, data flow |
|
||||
| [../docs/CODING_STANDARDS.md](../docs/CODING_STANDARDS.md) | Type hygiene, error handling, React patterns |
|
||||
| [../docs/TESTING.md](../docs/TESTING.md) | TDD workflow, determinism, real deps over mocks |
|
||||
| [../docs/architecture.md](../docs/architecture.md) | System design, WebSocket protocol, data flow |
|
||||
| [../docs/coding-standards.md](../docs/coding-standards.md) | Type hygiene, error handling, React patterns |
|
||||
| [../docs/testing.md](../docs/testing.md) | TDD workflow, determinism, real deps over mocks |
|
||||
| [../SECURITY.md](../SECURITY.md) | Relay threat model, E2E encryption |
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
interface DocFrontmatter {
|
||||
title: string;
|
||||
description: string;
|
||||
nav: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface Doc {
|
||||
slug: string;
|
||||
href: string;
|
||||
frontmatter: DocFrontmatter;
|
||||
content: string;
|
||||
}
|
||||
|
||||
function parseFrontmatter(raw: string): { data: Record<string, string>; content: string } {
|
||||
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
||||
if (!match) return { data: {}, content: raw };
|
||||
|
||||
const data: Record<string, string> = {};
|
||||
for (const line of match[1].split("\n")) {
|
||||
const colonIdx = line.indexOf(":");
|
||||
if (colonIdx === -1) continue;
|
||||
const key = line.slice(0, colonIdx).trim();
|
||||
const value = line
|
||||
.slice(colonIdx + 1)
|
||||
.trim()
|
||||
.replace(/^["']|["']$/g, "");
|
||||
data[key] = value;
|
||||
}
|
||||
|
||||
return { data, content: match[2] };
|
||||
}
|
||||
|
||||
const docModules = import.meta.glob("../../../public-docs/**/*.md", {
|
||||
eager: true,
|
||||
query: "?raw",
|
||||
import: "default",
|
||||
}) as Record<string, string>;
|
||||
|
||||
function pathToSlug(path: string): string {
|
||||
const after = path.split("/public-docs/")[1] ?? path;
|
||||
const noExt = after.replace(/\.md$/, "");
|
||||
return noExt === "index" ? "" : noExt;
|
||||
}
|
||||
|
||||
function loadDocs(): Doc[] {
|
||||
const docs: Doc[] = [];
|
||||
|
||||
for (const [path, raw] of Object.entries(docModules)) {
|
||||
const { data, content } = parseFrontmatter(raw);
|
||||
const slug = pathToSlug(path);
|
||||
const href = slug === "" ? "/docs" : `/docs/${slug}`;
|
||||
const order = Number.parseInt(data.order ?? "999", 10);
|
||||
|
||||
docs.push({
|
||||
slug,
|
||||
href,
|
||||
frontmatter: {
|
||||
title: data.title ?? "",
|
||||
description: data.description ?? "",
|
||||
nav: data.nav ?? data.title ?? slug,
|
||||
order: Number.isFinite(order) ? order : 999,
|
||||
},
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
docs.sort((a, b) => a.frontmatter.order - b.frontmatter.order);
|
||||
return docs;
|
||||
}
|
||||
|
||||
let cached: Doc[] | undefined;
|
||||
|
||||
export function getDocs(): Doc[] {
|
||||
if (!cached) cached = loadDocs();
|
||||
return cached;
|
||||
}
|
||||
|
||||
export function getDoc(slug: string): Doc | undefined {
|
||||
return getDocs().find((d) => d.slug === slug);
|
||||
}
|
||||
@@ -20,15 +20,7 @@ import { Route as BlogRouteImport } from "./routes/blog";
|
||||
import { Route as IndexRouteImport } from "./routes/index";
|
||||
import { Route as DocsIndexRouteImport } from "./routes/docs/index";
|
||||
import { Route as BlogIndexRouteImport } from "./routes/blog/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 DocsSkillsRouteImport } from "./routes/docs/skills";
|
||||
import { Route as DocsSecurityRouteImport } from "./routes/docs/security";
|
||||
import { Route as DocsProvidersRouteImport } from "./routes/docs/providers";
|
||||
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 DocsSplatRouteImport } from "./routes/docs/$";
|
||||
import { Route as BlogSplatRouteImport } from "./routes/blog/$";
|
||||
|
||||
const PrivacyRoute = PrivacyRouteImport.update({
|
||||
@@ -86,49 +78,9 @@ const BlogIndexRoute = BlogIndexRouteImport.update({
|
||||
path: "/",
|
||||
getParentRoute: () => BlogRoute,
|
||||
} as any);
|
||||
const DocsWorktreesRoute = DocsWorktreesRouteImport.update({
|
||||
id: "/worktrees",
|
||||
path: "/worktrees",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const DocsVoiceRoute = DocsVoiceRouteImport.update({
|
||||
id: "/voice",
|
||||
path: "/voice",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const DocsUpdatesRoute = DocsUpdatesRouteImport.update({
|
||||
id: "/updates",
|
||||
path: "/updates",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const DocsSkillsRoute = DocsSkillsRouteImport.update({
|
||||
id: "/skills",
|
||||
path: "/skills",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const DocsSecurityRoute = DocsSecurityRouteImport.update({
|
||||
id: "/security",
|
||||
path: "/security",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const DocsProvidersRoute = DocsProvidersRouteImport.update({
|
||||
id: "/providers",
|
||||
path: "/providers",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const DocsConfigurationRoute = DocsConfigurationRouteImport.update({
|
||||
id: "/configuration",
|
||||
path: "/configuration",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const DocsCliRoute = DocsCliRouteImport.update({
|
||||
id: "/cli",
|
||||
path: "/cli",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const DocsBestPracticesRoute = DocsBestPracticesRouteImport.update({
|
||||
id: "/best-practices",
|
||||
path: "/best-practices",
|
||||
const DocsSplatRoute = DocsSplatRouteImport.update({
|
||||
id: "/$",
|
||||
path: "/$",
|
||||
getParentRoute: () => DocsRoute,
|
||||
} as any);
|
||||
const BlogSplatRoute = BlogSplatRouteImport.update({
|
||||
@@ -148,15 +100,7 @@ export interface FileRoutesByFullPath {
|
||||
"/opencode": typeof OpencodeRoute;
|
||||
"/privacy": typeof PrivacyRoute;
|
||||
"/blog/$": typeof BlogSplatRoute;
|
||||
"/docs/best-practices": typeof DocsBestPracticesRoute;
|
||||
"/docs/cli": typeof DocsCliRoute;
|
||||
"/docs/configuration": typeof DocsConfigurationRoute;
|
||||
"/docs/providers": typeof DocsProvidersRoute;
|
||||
"/docs/security": typeof DocsSecurityRoute;
|
||||
"/docs/skills": typeof DocsSkillsRoute;
|
||||
"/docs/updates": typeof DocsUpdatesRoute;
|
||||
"/docs/voice": typeof DocsVoiceRoute;
|
||||
"/docs/worktrees": typeof DocsWorktreesRoute;
|
||||
"/docs/$": typeof DocsSplatRoute;
|
||||
"/blog/": typeof BlogIndexRoute;
|
||||
"/docs/": typeof DocsIndexRoute;
|
||||
}
|
||||
@@ -169,15 +113,7 @@ export interface FileRoutesByTo {
|
||||
"/opencode": typeof OpencodeRoute;
|
||||
"/privacy": typeof PrivacyRoute;
|
||||
"/blog/$": typeof BlogSplatRoute;
|
||||
"/docs/best-practices": typeof DocsBestPracticesRoute;
|
||||
"/docs/cli": typeof DocsCliRoute;
|
||||
"/docs/configuration": typeof DocsConfigurationRoute;
|
||||
"/docs/providers": typeof DocsProvidersRoute;
|
||||
"/docs/security": typeof DocsSecurityRoute;
|
||||
"/docs/skills": typeof DocsSkillsRoute;
|
||||
"/docs/updates": typeof DocsUpdatesRoute;
|
||||
"/docs/voice": typeof DocsVoiceRoute;
|
||||
"/docs/worktrees": typeof DocsWorktreesRoute;
|
||||
"/docs/$": typeof DocsSplatRoute;
|
||||
"/blog": typeof BlogIndexRoute;
|
||||
"/docs": typeof DocsIndexRoute;
|
||||
}
|
||||
@@ -193,15 +129,7 @@ export interface FileRoutesById {
|
||||
"/opencode": typeof OpencodeRoute;
|
||||
"/privacy": typeof PrivacyRoute;
|
||||
"/blog/$": typeof BlogSplatRoute;
|
||||
"/docs/best-practices": typeof DocsBestPracticesRoute;
|
||||
"/docs/cli": typeof DocsCliRoute;
|
||||
"/docs/configuration": typeof DocsConfigurationRoute;
|
||||
"/docs/providers": typeof DocsProvidersRoute;
|
||||
"/docs/security": typeof DocsSecurityRoute;
|
||||
"/docs/skills": typeof DocsSkillsRoute;
|
||||
"/docs/updates": typeof DocsUpdatesRoute;
|
||||
"/docs/voice": typeof DocsVoiceRoute;
|
||||
"/docs/worktrees": typeof DocsWorktreesRoute;
|
||||
"/docs/$": typeof DocsSplatRoute;
|
||||
"/blog/": typeof BlogIndexRoute;
|
||||
"/docs/": typeof DocsIndexRoute;
|
||||
}
|
||||
@@ -218,15 +146,7 @@ export interface FileRouteTypes {
|
||||
| "/opencode"
|
||||
| "/privacy"
|
||||
| "/blog/$"
|
||||
| "/docs/best-practices"
|
||||
| "/docs/cli"
|
||||
| "/docs/configuration"
|
||||
| "/docs/providers"
|
||||
| "/docs/security"
|
||||
| "/docs/skills"
|
||||
| "/docs/updates"
|
||||
| "/docs/voice"
|
||||
| "/docs/worktrees"
|
||||
| "/docs/$"
|
||||
| "/blog/"
|
||||
| "/docs/";
|
||||
fileRoutesByTo: FileRoutesByTo;
|
||||
@@ -239,15 +159,7 @@ export interface FileRouteTypes {
|
||||
| "/opencode"
|
||||
| "/privacy"
|
||||
| "/blog/$"
|
||||
| "/docs/best-practices"
|
||||
| "/docs/cli"
|
||||
| "/docs/configuration"
|
||||
| "/docs/providers"
|
||||
| "/docs/security"
|
||||
| "/docs/skills"
|
||||
| "/docs/updates"
|
||||
| "/docs/voice"
|
||||
| "/docs/worktrees"
|
||||
| "/docs/$"
|
||||
| "/blog"
|
||||
| "/docs";
|
||||
id:
|
||||
@@ -262,15 +174,7 @@ export interface FileRouteTypes {
|
||||
| "/opencode"
|
||||
| "/privacy"
|
||||
| "/blog/$"
|
||||
| "/docs/best-practices"
|
||||
| "/docs/cli"
|
||||
| "/docs/configuration"
|
||||
| "/docs/providers"
|
||||
| "/docs/security"
|
||||
| "/docs/skills"
|
||||
| "/docs/updates"
|
||||
| "/docs/voice"
|
||||
| "/docs/worktrees"
|
||||
| "/docs/$"
|
||||
| "/blog/"
|
||||
| "/docs/";
|
||||
fileRoutesById: FileRoutesById;
|
||||
@@ -366,67 +270,11 @@ declare module "@tanstack/react-router" {
|
||||
preLoaderRoute: typeof BlogIndexRouteImport;
|
||||
parentRoute: typeof BlogRoute;
|
||||
};
|
||||
"/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/skills": {
|
||||
id: "/docs/skills";
|
||||
path: "/skills";
|
||||
fullPath: "/docs/skills";
|
||||
preLoaderRoute: typeof DocsSkillsRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/security": {
|
||||
id: "/docs/security";
|
||||
path: "/security";
|
||||
fullPath: "/docs/security";
|
||||
preLoaderRoute: typeof DocsSecurityRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/docs/providers": {
|
||||
id: "/docs/providers";
|
||||
path: "/providers";
|
||||
fullPath: "/docs/providers";
|
||||
preLoaderRoute: typeof DocsProvidersRouteImport;
|
||||
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;
|
||||
"/docs/$": {
|
||||
id: "/docs/$";
|
||||
path: "/$";
|
||||
fullPath: "/docs/$";
|
||||
preLoaderRoute: typeof DocsSplatRouteImport;
|
||||
parentRoute: typeof DocsRoute;
|
||||
};
|
||||
"/blog/$": {
|
||||
@@ -452,28 +300,12 @@ const BlogRouteChildren: BlogRouteChildren = {
|
||||
const BlogRouteWithChildren = BlogRoute._addFileChildren(BlogRouteChildren);
|
||||
|
||||
interface DocsRouteChildren {
|
||||
DocsBestPracticesRoute: typeof DocsBestPracticesRoute;
|
||||
DocsCliRoute: typeof DocsCliRoute;
|
||||
DocsConfigurationRoute: typeof DocsConfigurationRoute;
|
||||
DocsProvidersRoute: typeof DocsProvidersRoute;
|
||||
DocsSecurityRoute: typeof DocsSecurityRoute;
|
||||
DocsSkillsRoute: typeof DocsSkillsRoute;
|
||||
DocsUpdatesRoute: typeof DocsUpdatesRoute;
|
||||
DocsVoiceRoute: typeof DocsVoiceRoute;
|
||||
DocsWorktreesRoute: typeof DocsWorktreesRoute;
|
||||
DocsSplatRoute: typeof DocsSplatRoute;
|
||||
DocsIndexRoute: typeof DocsIndexRoute;
|
||||
}
|
||||
|
||||
const DocsRouteChildren: DocsRouteChildren = {
|
||||
DocsBestPracticesRoute: DocsBestPracticesRoute,
|
||||
DocsCliRoute: DocsCliRoute,
|
||||
DocsConfigurationRoute: DocsConfigurationRoute,
|
||||
DocsProvidersRoute: DocsProvidersRoute,
|
||||
DocsSecurityRoute: DocsSecurityRoute,
|
||||
DocsSkillsRoute: DocsSkillsRoute,
|
||||
DocsUpdatesRoute: DocsUpdatesRoute,
|
||||
DocsVoiceRoute: DocsVoiceRoute,
|
||||
DocsWorktreesRoute: DocsWorktreesRoute,
|
||||
DocsSplatRoute: DocsSplatRoute,
|
||||
DocsIndexRoute: DocsIndexRoute,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
import { createFileRoute, Link, Outlet } from "@tanstack/react-router";
|
||||
import { getDocs } from "~/docs";
|
||||
import "~/styles.css";
|
||||
|
||||
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: "Skills", href: "/docs/skills" },
|
||||
{ name: "Providers", href: "/docs/providers" },
|
||||
{ name: "Configuration", href: "/docs/configuration" },
|
||||
{ name: "Security", href: "/docs/security" },
|
||||
{ name: "Best practices", href: "/docs/best-practices" },
|
||||
];
|
||||
|
||||
const ACTIVE_OPTIONS_EXACT = { exact: true };
|
||||
const MOBILE_ACTIVE_PROPS = { className: "text-foreground" };
|
||||
const DESKTOP_ACTIVE_PROPS = { className: "bg-muted text-foreground" };
|
||||
|
||||
function DocsLayout() {
|
||||
const navigation = getDocs().map((doc) => ({
|
||||
name: doc.frontmatter.nav,
|
||||
href: doc.href,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Mobile header */}
|
||||
@@ -31,7 +24,7 @@ function DocsLayout() {
|
||||
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
|
||||
<span className="text-lg font-medium">Paseo</span>
|
||||
</Link>
|
||||
<nav className="flex gap-4 mt-4">
|
||||
<nav className="flex gap-4 mt-4 flex-wrap">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
@@ -67,7 +60,7 @@ function DocsLayout() {
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
<main className="flex-1 p-6 md:p-12 max-w-3xl prose">
|
||||
<main className="flex-1 p-6 md:p-12 max-w-3xl docs-prose">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { getDoc } from "~/docs";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/$")({
|
||||
head: ({ params }) => {
|
||||
const slug = params._splat ?? "";
|
||||
const doc = getDoc(slug);
|
||||
if (!doc) return { meta: pageMeta("Not Found - Paseo Docs", "Doc not found.") };
|
||||
return {
|
||||
meta: pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description),
|
||||
};
|
||||
},
|
||||
component: DocsPage,
|
||||
});
|
||||
|
||||
function DocsPage() {
|
||||
const { _splat } = Route.useParams();
|
||||
const slug = _splat ?? "";
|
||||
const doc = getDoc(slug);
|
||||
|
||||
if (!doc) {
|
||||
return <p className="text-muted-foreground">Doc not found.</p>;
|
||||
}
|
||||
|
||||
return <ReactMarkdown>{doc.content}</ReactMarkdown>;
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
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.",
|
||||
),
|
||||
}),
|
||||
component: BestPractices,
|
||||
});
|
||||
|
||||
function BestPractices() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Agents replace typing, not thinking</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Your role has changed. You're no longer the one writing code line by line.
|
||||
You're the one making decisions: what to build, how it should work, what the
|
||||
architecture looks like. The agent executes, but you direct.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
You can't just say "implement feature X" and walk away. You still have to
|
||||
do the hard part: deciding what to build, how it fits into the system, what trade-offs to
|
||||
make. Thinking is not optional. At least for now, agents replace the typing, not the
|
||||
thinking.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Verification loops</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
The agent needs a way to verify its work. TDD is one implementation of this pattern: get
|
||||
the agent to write a failing test, verify it fails for the right reasons, then tell it to
|
||||
make the test pass. The agent can loop on its own because it knows what "done"
|
||||
means.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Invest in tooling</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
It's not just test runners. For web apps, something like Playwright MCP lets the
|
||||
agent take screenshots and verify UI changes. For a SaaS app I built a CLI that wraps all
|
||||
the business logic so the agent could launch jobs, check statuses, and scrape data without
|
||||
going through the UI.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Code is cheap with coding agents. I would have never written that CLI before because it
|
||||
felt like wasted effort. Now I bootstrap tooling first. It pays off exponentially.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Agents are cheap</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Don't be shy about running multiple agents. Paseo lets you launch agents in isolated
|
||||
worktrees. Kick one off with voice while walking, then kick off another. They work
|
||||
independently. You get a notification when they're done.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Use voice extensively</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
It's much more natural to use voice to communicate ideas and pull them out of your
|
||||
brain. The agent will parse and organize your thoughts better than if you try to write the
|
||||
perfect prompt. You don't need to organize anything. Just talk.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Current speech-to-text models are really good. They catch accents, acronyms, technical
|
||||
terms. And even when they don't, the LLM will infer what you meant.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Understand the type of work</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Sometimes you need to plan: design a spec, verify it, get the agent to follow through.
|
||||
Maybe it takes a couple of agents to work through it. Other times it's
|
||||
conversational: kick off a single agent and start talking, asking questions. Match your
|
||||
approach to the task.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Iterate and refactor often</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Don't expect perfect. Expect working. Make it work, make it correct, make it
|
||||
beautiful. Each iteration gets you closer. With tests, refactoring is cheap.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
I don't let myself add too many features before stopping to refactor. Sometimes I
|
||||
kick off an agent and have it trace code paths, explain dependencies, show me how modules
|
||||
connect. I make mental notes during code review and circle back.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Use agents to check agents</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
If an agent implements something and you ask it to review its own work, it will never find
|
||||
issues. Launch a separate agent with a fresh context to review the first agent's
|
||||
code. It will catch things the first agent missed or glossed over. An agent might say
|
||||
it's done when it's not. Another agent can detect that.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Learn your agents' quirks</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
People argue about which model is better. That's the wrong question. Each model has
|
||||
strengths and weaknesses. Knowing them is more useful than chasing benchmarks. Benchmarks
|
||||
don't mean anything. You need to try the models yourself to form an opinion.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
I use Claude Code as my main driver because it's quick and uses tools well. But
|
||||
sometimes it jumps to conclusions and gives up too easily. Codex is frustratingly slow but
|
||||
goes deep, doesn't stop, and is methodical. It's also stubborn and too serious.
|
||||
These aren't good or bad traits, just differences you learn to work around. Use the
|
||||
right model for the job.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/cli")({
|
||||
head: () => ({
|
||||
meta: pageMeta(
|
||||
"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() {
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Agent orchestration callout */}
|
||||
<section className="space-y-4">
|
||||
<div className="bg-primary/10 border border-primary/30 rounded-lg p-4 text-white/80">
|
||||
<strong>Agent orchestration:</strong> You can tell coding agents to use the Paseo CLI to
|
||||
spawn and manage other agents. This enables multi-agent workflows where one agent
|
||||
delegates subtasks to others and waits for results.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Quick reference */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Quick reference</h2>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo run "fix the tests" # Start an agent
|
||||
paseo ls # List running agents
|
||||
paseo attach <id> # Stream agent output
|
||||
paseo send <id> "also fix linting" # Send follow-up task
|
||||
paseo logs <id> # View agent timeline
|
||||
paseo stop <id> # Stop an agent`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Running agents */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Running agents</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Use <code className="font-mono">paseo run</code> to start a new agent with a task:
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo run "implement user authentication"
|
||||
paseo run --provider codex "refactor the API layer"
|
||||
paseo run --detach "run the full test suite" # background
|
||||
paseo run --worktree feature-x "implement feature X"
|
||||
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.
|
||||
</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>.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
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>
|
||||
|
||||
{/* Listing agents */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Listing agents</h2>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo ls # Running agents in current directory
|
||||
paseo ls -a # Include completed/stopped agents
|
||||
paseo ls -g # All directories
|
||||
paseo ls -a -g --json # Full list as JSON`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Streaming output */}
|
||||
<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:
|
||||
</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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Sending messages */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Sending messages</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Send follow-up tasks to a running or idle agent:
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo send <id> "now run the tests"
|
||||
paseo send <id> --image screenshot.png "what's wrong here?"
|
||||
paseo send <id> --no-wait "queue this task"`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Viewing logs */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Viewing logs</h2>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo logs <id> # Full timeline
|
||||
paseo logs <id> -f # Follow (streaming)
|
||||
paseo logs <id> --tail 10 # Last 10 entries
|
||||
paseo logs <id> --filter tools # Only tool calls`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Waiting for agents */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Waiting for agents</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Block until an agent finishes its current task:
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo wait <id>
|
||||
paseo wait <id> --timeout 60 # 60 second timeout`}</pre>
|
||||
</Code>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Useful in scripts or when one agent needs to wait for another.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Permissions */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Permissions</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Agents may request permission for certain actions. Manage these from the CLI:
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo permit ls # List pending requests
|
||||
paseo permit allow <id> # Allow all pending for agent
|
||||
paseo permit deny <id> --all # Deny all pending`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Agent modes */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Agent modes</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Change an agent's operational mode (provider-specific):
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo agent mode <id> --list # Show available modes
|
||||
paseo agent mode <id> bypass # Set bypass mode
|
||||
paseo agent mode <id> plan # Set plan mode`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Daemon management */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Daemon management</h2>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo daemon start # Start the daemon
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Multi-agent workflows */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Multi-agent workflows</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
The CLI is designed to be used by agents themselves. You can instruct an agent to spawn
|
||||
sub-agents for parallel work:
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`# Agent A spawns Agent B and waits for it
|
||||
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>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`# Requires jq
|
||||
while true; do
|
||||
paseo run --provider codex "make the tests pass" >/dev/null
|
||||
|
||||
verdict=$(paseo run --provider claude --output-schema '{"type":"object","properties":{"criteria_met":{"type":"boolean"}},"required":["criteria_met"],"additionalProperties":false}' "ensure tests all pass")
|
||||
if echo "$verdict" | jq -e '.criteria_met == true' >/dev/null; then
|
||||
echo "criteria met"
|
||||
break
|
||||
fi
|
||||
done`}</pre>
|
||||
</Code>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
This pattern enables hierarchical task decomposition — a lead agent can break down work,
|
||||
delegate to specialists, and synthesize results.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Output formats */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Output formats</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Most commands support multiple output formats for scripting:
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo ls --json # JSON output
|
||||
paseo ls --format yaml # YAML output
|
||||
paseo ls -q # IDs only (quiet)`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Global options */}
|
||||
<section className="space-y-4">
|
||||
<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 <host:port></code> — connect to a different
|
||||
daemon
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">--json</code> — JSON output
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">-q, --quiet</code> — minimal output
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">--no-color</code> — disable colors
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/configuration")({
|
||||
head: () => ({
|
||||
meta: pageMeta(
|
||||
"Configuration - Paseo Docs",
|
||||
"Configure Paseo via config.json, environment variables, and CLI overrides.",
|
||||
),
|
||||
}),
|
||||
component: Configuration,
|
||||
});
|
||||
|
||||
const CUSTOM_PROVIDERS_URL = "https://github.com/getpaseo/paseo/blob/main/docs/CUSTOM-PROVIDERS.md";
|
||||
|
||||
function Configuration() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Where config lives</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
By default, Paseo uses <code className="font-mono">~/.paseo</code> as its home directory.
|
||||
The configuration file is:
|
||||
</p>
|
||||
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
|
||||
<span className="text-muted-foreground select-none">$ </span>
|
||||
<span>~/.paseo/config.json</span>
|
||||
</div>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
You can change the home directory by setting <code className="font-mono">PASEO_HOME</code>{" "}
|
||||
or passing <code className="font-mono">--home</code> to{" "}
|
||||
<code className="font-mono">paseo daemon start</code>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Precedence</h2>
|
||||
<p className="text-white/60 leading-relaxed">Paseo merges configuration in this order:</p>
|
||||
<ol className="text-white/60 space-y-2 list-decimal list-inside">
|
||||
<li>Defaults</li>
|
||||
<li>
|
||||
<code className="font-mono">config.json</code>
|
||||
</li>
|
||||
<li>Environment variables</li>
|
||||
<li>CLI flags</li>
|
||||
</ol>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Lists append across sources (for example, <code className="font-mono">hostnames</code> and
|
||||
<code className="font-mono">cors.allowedOrigins</code>).
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Example</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Minimal example that configures listening address, hostnames, 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,
|
||||
"daemon": {
|
||||
"listen": "127.0.0.1:6767",
|
||||
"hostnames": ["localhost", ".localhost"],
|
||||
"mcp": { "enabled": true }
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
<code className="font-mono">daemon.hostnames</code> is the primary field. The old{" "}
|
||||
<code className="font-mono">daemon.allowedHosts</code> name still works as a deprecated
|
||||
alias for backward compatibility.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Agent providers</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Agent providers — both the first-class ones Paseo ships with and custom entries you add
|
||||
under <code className="font-mono">agents.providers</code> — are documented on their own
|
||||
page.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
See{" "}
|
||||
<a href="/docs/providers" className="underline hover:text-white/80">
|
||||
Providers
|
||||
</a>{" "}
|
||||
for first-class providers, how to point Claude at Anthropic-compatible endpoints (Z.AI,
|
||||
Alibaba/Qwen), multiple profiles, custom binaries, ACP agents, and the{" "}
|
||||
<code className="font-mono">additionalModels</code> merge behavior. The full field
|
||||
reference lives on GitHub at{" "}
|
||||
<a
|
||||
href={CUSTOM_PROVIDERS_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
docs/CUSTOM-PROVIDERS.md
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<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{" "}
|
||||
<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>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Logging</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Daemon logging uses separate console and file sinks by default:
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
Console: <code className="font-mono">info</code> and above
|
||||
</li>
|
||||
<li>
|
||||
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,{" "}
|
||||
<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",
|
||||
"format": "pretty"
|
||||
},
|
||||
"file": {
|
||||
"level": "trace",
|
||||
"path": "daemon.log",
|
||||
"rotate": {
|
||||
"maxSize": "10m",
|
||||
"maxFiles": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
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>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Common env vars</h2>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<code className="font-mono">PASEO_HOME</code> — set Paseo home directory
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">PASEO_LISTEN</code> — override{" "}
|
||||
<code className="font-mono">daemon.listen</code>
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">PASEO_HOSTNAMES</code> — override/extend{" "}
|
||||
<code className="font-mono">daemon.hostnames</code>
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">PASEO_ALLOWED_HOSTS</code> — deprecated alias for{" "}
|
||||
<code className="font-mono">PASEO_HOSTNAMES</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>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Schema</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
For editor autocomplete/validation, set <code className="font-mono">$schema</code> to:
|
||||
</p>
|
||||
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
|
||||
<span>https://paseo.sh/schemas/paseo.config.v1.json</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,170 +1,21 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { getDoc } from "~/docs";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
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.",
|
||||
),
|
||||
}),
|
||||
component: GettingStarted,
|
||||
head: () => {
|
||||
const doc = getDoc("");
|
||||
if (!doc) return { meta: pageMeta("Docs - Paseo", "Paseo documentation.") };
|
||||
return {
|
||||
meta: pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description),
|
||||
};
|
||||
},
|
||||
component: DocsIndex,
|
||||
});
|
||||
|
||||
function GettingStarted() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium font-title mb-4">Getting Started</h1>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Paseo has three main pieces: the daemon is the local server that manages your agents, the
|
||||
app is the client you use from mobile, web, or desktop, and the CLI is the terminal
|
||||
interface that can also launch the daemon.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Prerequisites</h2>
|
||||
<p className="text-white/60">
|
||||
Paseo manages existing agent CLIs. Install at least one agent and make sure it already
|
||||
works with your credentials before you set up Paseo.
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<a
|
||||
href="https://docs.anthropic.com/en/docs/claude-code"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
Claude Code
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/openai/codex"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
Codex
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/anomalyco/opencode"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
OpenCode
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Desktop App</h2>
|
||||
<p className="text-white/60">
|
||||
Download the desktop app from{" "}
|
||||
<a
|
||||
href="https://paseo.sh/download"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
paseo.sh/download
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://github.com/getpaseo/paseo/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
GitHub releases page
|
||||
</a>
|
||||
. Open it and you're done.
|
||||
</p>
|
||||
<p className="text-white/60">
|
||||
The desktop app bundles and manages its own daemon automatically, so you do not need a
|
||||
separate CLI install on that machine unless you want it.
|
||||
</p>
|
||||
<p className="text-white/60">
|
||||
On first launch, you may briefly see a startup screen while the local server starts and
|
||||
the app connects to it. After that, connect from your phone by scanning the QR code in
|
||||
Settings if you want mobile access.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">CLI / Server</h2>
|
||||
<p className="text-white/60">
|
||||
Use this path for headless setups, servers, or remote machines where you want the daemon
|
||||
running without the desktop app.
|
||||
</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>
|
||||
</div>
|
||||
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
|
||||
<span className="text-muted-foreground select-none">$ </span>
|
||||
<span>paseo</span>
|
||||
</div>
|
||||
<p className="text-white/60">
|
||||
Paseo prints a QR code in the terminal. Scan it from the mobile app, or enter the daemon
|
||||
address manually from another client.
|
||||
</p>
|
||||
<p className="text-white/60">
|
||||
Configuration and local state live under <code>PASEO_HOME</code>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<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.
|
||||
</p>
|
||||
<p className="text-white/60">
|
||||
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
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Next</h2>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<a href="/docs/updates" className="underline hover:text-white/80">
|
||||
Updates
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/voice" className="underline hover:text-white/80">
|
||||
Voice
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/providers" className="underline hover:text-white/80">
|
||||
Providers
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/configuration" className="underline hover:text-white/80">
|
||||
Configuration
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/security" className="underline hover:text-white/80">
|
||||
Security
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
function DocsIndex() {
|
||||
const doc = getDoc("");
|
||||
if (!doc) return <p className="text-muted-foreground">Doc not found.</p>;
|
||||
return <ReactMarkdown>{doc.content}</ReactMarkdown>;
|
||||
}
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/providers")({
|
||||
head: () => ({
|
||||
meta: pageMeta(
|
||||
"Providers - Paseo Docs",
|
||||
"First-class agent providers in Paseo, and how to configure custom providers, ACP agents, and profiles.",
|
||||
),
|
||||
}),
|
||||
component: Providers,
|
||||
});
|
||||
|
||||
const CUSTOM_PROVIDERS_URL = "https://github.com/getpaseo/paseo/blob/main/docs/CUSTOM-PROVIDERS.md";
|
||||
|
||||
function Providers() {
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium font-title mb-4">Providers</h1>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
A provider is an agent CLI that Paseo knows how to launch, stream, and control. Paseo
|
||||
ships with first-class providers for the major coding agents, and lets you add your own
|
||||
through <code className="font-mono">config.json</code> — either by pointing an existing
|
||||
provider at a different API, adding extra profiles, or plugging in any{" "}
|
||||
<a
|
||||
href="https://agentclientprotocol.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
ACP
|
||||
</a>
|
||||
-compatible agent.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">First-class providers</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
These work out of the box once the underlying CLI is installed and authenticated. Paseo
|
||||
discovers them automatically, wires up modes, and exposes them in the app and CLI.
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-3 list-disc list-inside">
|
||||
<li>
|
||||
<code className="font-mono">claude</code> — Anthropic's Claude Code. Multi-tool
|
||||
assistant with MCP support, streaming, and deep reasoning.
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">codex</code> — OpenAI's Codex workspace agent with
|
||||
sandbox controls and optional network access.
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">opencode</code> — Open-source coding assistant with
|
||||
multi-provider model support.
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">copilot</code> — GitHub Copilot via ACP, with dynamic modes
|
||||
and session support.
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">pi</code> — Minimal terminal-based coding agent with
|
||||
multi-provider LLM support.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Custom providers</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Everything beyond the defaults lives under{" "}
|
||||
<code className="font-mono">agents.providers</code> in{" "}
|
||||
<code className="font-mono">~/.paseo/config.json</code>. You can:
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<strong>Extend</strong> a first-class provider to point at a different API (Z.AI,
|
||||
Alibaba/Qwen, a proxy, a self-hosted endpoint).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Add profiles</strong> — multiple entries against the same underlying provider
|
||||
with different credentials or curated model lists.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Override the binary</strong> — run a nightly build, a wrapper script, or a
|
||||
Docker image instead of the installed CLI.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Add ACP agents</strong> — Gemini CLI, Hermes, or any agent speaking the Agent
|
||||
Client Protocol over stdio.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Disable</strong> a provider you don't use.
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Provider IDs must be lowercase alphanumeric with hyphens (
|
||||
<code className="font-mono">/^[a-z][a-z0-9-]*$/</code>). Every custom entry needs{" "}
|
||||
<code className="font-mono">extends</code> (a first-class provider ID or{" "}
|
||||
<code className="font-mono">"acp"</code>) and a{" "}
|
||||
<code className="font-mono">label</code>.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
The examples below are a quick tour. The full, up-to-date reference is on GitHub:{" "}
|
||||
<a
|
||||
href={CUSTOM_PROVIDERS_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
docs/CUSTOM-PROVIDERS.md
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Extending a first-class provider</h2>
|
||||
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
|
||||
{`{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"my-claude": {
|
||||
"extends": "claude",
|
||||
"label": "My Claude",
|
||||
"env": {
|
||||
"ANTHROPIC_API_KEY": "sk-ant-...",
|
||||
"ANTHROPIC_BASE_URL": "https://my-proxy.example.com/v1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Z.AI (GLM) coding plan</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Z.AI exposes GLM models through an Anthropic-compatible endpoint. Point{" "}
|
||||
<code className="font-mono">ANTHROPIC_BASE_URL</code> at their API and use{" "}
|
||||
<code className="font-mono">ANTHROPIC_AUTH_TOKEN</code> for the key. Third-party endpoints
|
||||
don't support Anthropic's server-side tools, so disable{" "}
|
||||
<code className="font-mono">WebSearch</code>.
|
||||
</p>
|
||||
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
|
||||
{`{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"zai": {
|
||||
"extends": "claude",
|
||||
"label": "ZAI",
|
||||
"env": {
|
||||
"ANTHROPIC_AUTH_TOKEN": "<your-zai-api-key>",
|
||||
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
||||
"API_TIMEOUT_MS": "3000000"
|
||||
},
|
||||
"disallowedTools": ["WebSearch"],
|
||||
"models": [
|
||||
{ "id": "glm-5-turbo", "label": "GLM 5 Turbo", "isDefault": true },
|
||||
{ "id": "glm-5.1", "label": "GLM 5.1" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Alibaba Cloud (Qwen) coding plan</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Alibaba's coding plan routes Claude Code to Qwen models via an Anthropic-compatible
|
||||
API. Subscription keys look like <code className="font-mono">sk-sp-...</code> and must be
|
||||
created in the Singapore region.
|
||||
</p>
|
||||
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
|
||||
{`{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"qwen": {
|
||||
"extends": "claude",
|
||||
"label": "Qwen (Alibaba)",
|
||||
"env": {
|
||||
"ANTHROPIC_AUTH_TOKEN": "sk-sp-<coding-plan-key>",
|
||||
"ANTHROPIC_BASE_URL": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"
|
||||
},
|
||||
"disallowedTools": ["WebSearch"],
|
||||
"models": [
|
||||
{ "id": "qwen3.5-plus", "label": "Qwen 3.5 Plus", "isDefault": true },
|
||||
{ "id": "qwen3-coder-next", "label": "Qwen 3 Coder Next" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Multiple profiles</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Create as many entries as you want against the same first-class provider. Each one shows
|
||||
up as a separate option in the app with its own credentials and models.
|
||||
</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-work": {
|
||||
"extends": "claude",
|
||||
"label": "Claude (Work)",
|
||||
"env": { "ANTHROPIC_API_KEY": "sk-ant-work-..." }
|
||||
},
|
||||
"claude-personal": {
|
||||
"extends": "claude",
|
||||
"label": "Claude (Personal)",
|
||||
"env": { "ANTHROPIC_API_KEY": "sk-ant-personal-..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Custom binary</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
<code className="font-mono">command</code> is an array — first element is the binary, the
|
||||
rest are arguments. It fully replaces the default launch command for that provider.
|
||||
</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": {
|
||||
"command": ["/opt/claude-nightly/claude"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">ACP providers</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Any agent that speaks{" "}
|
||||
<a
|
||||
href="https://agentclientprotocol.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
ACP
|
||||
</a>{" "}
|
||||
over stdio can be added with <code className="font-mono">extends: "acp"</code>{" "}
|
||||
and a <code className="font-mono">command</code>. Paseo spawns the process, sends an{" "}
|
||||
<code className="font-mono">initialize</code> JSON-RPC request, and the agent reports its
|
||||
capabilities, modes, and models at runtime.
|
||||
</p>
|
||||
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
|
||||
{`{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"gemini": {
|
||||
"extends": "acp",
|
||||
"label": "Google Gemini",
|
||||
"command": ["gemini", "--acp"]
|
||||
},
|
||||
"hermes": {
|
||||
"extends": "acp",
|
||||
"label": "Hermes",
|
||||
"command": ["hermes", "acp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Adding or relabeling models</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
<code className="font-mono">models</code> replaces the model list entirely.{" "}
|
||||
<code className="font-mono">additionalModels</code> merges with runtime-discovered models
|
||||
(ACP) or with <code className="font-mono">models</code> — use it to add an extra entry or
|
||||
relabel a discovered one without redeclaring the full list. An entry with the same{" "}
|
||||
<code className="font-mono">id</code> as a discovered model updates it in place.
|
||||
</p>
|
||||
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
|
||||
{`{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"gemini": {
|
||||
"extends": "acp",
|
||||
"label": "Google Gemini",
|
||||
"command": ["gemini", "--acp"],
|
||||
"additionalModels": [
|
||||
{ "id": "experimental-model", "label": "Experimental", "isDefault": true },
|
||||
{ "id": "gemini-2.5-pro", "label": "Gemini 2.5 Pro (preferred)" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Disabling a provider</h2>
|
||||
<pre className="bg-card border border-border rounded-lg p-4 font-mono text-sm overflow-x-auto text-white/80">
|
||||
{`{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"copilot": { "enabled": false }
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Full reference</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
For the complete field reference (<code className="font-mono">extends</code>,{" "}
|
||||
<code className="font-mono">label</code>, <code className="font-mono">command</code>,{" "}
|
||||
<code className="font-mono">env</code>, <code className="font-mono">models</code>,{" "}
|
||||
<code className="font-mono">additionalModels</code>,{" "}
|
||||
<code className="font-mono">disallowedTools</code>,{" "}
|
||||
<code className="font-mono">enabled</code>, <code className="font-mono">order</code>),
|
||||
model and thinking-option schemas, and deeper examples for each plan, see{" "}
|
||||
<a
|
||||
href={CUSTOM_PROVIDERS_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
docs/CUSTOM-PROVIDERS.md
|
||||
</a>{" "}
|
||||
on GitHub.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
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.",
|
||||
),
|
||||
}),
|
||||
component: Security,
|
||||
});
|
||||
|
||||
function Callout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="bg-primary/10 border border-primary/30 rounded-lg p-4 text-white/80">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Security() {
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium font-title mb-4">Security</h1>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Paseo follows a client-server architecture, similar to Docker. The daemon runs on your
|
||||
machine and manages your coding agents. Clients (the mobile app, CLI, or web interface)
|
||||
connect to the daemon to monitor and control those agents.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed mt-3">
|
||||
Your code never leaves your machine. Paseo is a local-first tool that connects directly to
|
||||
your development environment.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Architecture Overview */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Architecture</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
The Paseo daemon can run anywhere you want to execute agents: your laptop, a Mac Mini, a
|
||||
VPS, or a Docker container. The daemon listens for connections and manages agent
|
||||
lifecycles.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Clients connect to the daemon over WebSocket. There are two ways to establish this
|
||||
connection:
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<strong className="text-white/80">Relay connection (recommended)</strong> — The daemon
|
||||
connects outbound to our relay server, and clients meet it there. No open ports
|
||||
required.
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">Direct connection</strong> — The daemon listens on a
|
||||
network address and clients connect directly
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Relay Connection */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Relay connections (recommended)</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
The relay is the simplest way to connect from your phone. It requires no VPN setup, no
|
||||
port forwarding, and no firewall configuration. The daemon can stay bound to localhost or
|
||||
a socket file — it connects <em>outbound</em> to the relay, and your phone meets it there.
|
||||
</p>
|
||||
|
||||
<Callout>
|
||||
<strong>The relay is designed to be untrusted.</strong> All traffic between your phone and
|
||||
daemon is end-to-end encrypted. The relay server cannot read your messages, see your code,
|
||||
or modify traffic without detection. Even if the relay is compromised, your data remains
|
||||
protected.
|
||||
</Callout>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">How it works</h3>
|
||||
<ol className="text-white/60 space-y-2 list-decimal list-inside">
|
||||
<li>
|
||||
The daemon generates a persistent ECDH keypair and stores it in{" "}
|
||||
<code className="font-mono">$PASEO_HOME/daemon-keypair.json</code>
|
||||
</li>
|
||||
<li>
|
||||
When you scan the QR code or click the pairing link, your phone receives the
|
||||
daemon's public key
|
||||
</li>
|
||||
<li>
|
||||
Your phone sends a handshake message with its own public key. The daemon will not accept
|
||||
any commands until this handshake completes.
|
||||
</li>
|
||||
<li>
|
||||
Both sides perform an ECDH key exchange to derive a shared secret. All subsequent
|
||||
messages are encrypted with AES-256-GCM.
|
||||
</li>
|
||||
</ol>
|
||||
<p className="text-white/60 leading-relaxed mt-3">
|
||||
The relay sees only: IP addresses, timing, message sizes, and session IDs. It cannot read
|
||||
message contents, forge messages, or derive encryption keys from observing the handshake.
|
||||
</p>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">Why the relay can't attack you</h3>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
The daemon requires a valid cryptographic handshake before processing any commands. A
|
||||
compromised relay cannot:
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<strong className="text-white/80">Send commands</strong> — Without your phone's
|
||||
private key, it cannot complete the handshake
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">Read your traffic</strong> — All messages are
|
||||
encrypted with AES-256-GCM after the handshake
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">Forge messages</strong> — GCM provides authenticated
|
||||
encryption; tampered messages are rejected
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">Replay old messages</strong> — Each session derives
|
||||
fresh encryption keys
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">Trust model</h3>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
The QR code or pairing link is the trust anchor. It contains the daemon's public key,
|
||||
which is required to establish the encrypted connection. Treat it like a password —
|
||||
don't share it publicly.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
If you believe a pairing offer has been compromised, restart the daemon to generate a new
|
||||
session ID and rotate the relay pairing.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Direct Connection */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Direct connections</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
By default, the daemon listens on <code className="font-mono">127.0.0.1:6767</code>{" "}
|
||||
(localhost only). This is safe for local CLI usage but not reachable from your phone or
|
||||
other devices.
|
||||
</p>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">Socket file (CLI only)</h3>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
For maximum isolation, you can configure the daemon to listen on a Unix socket file
|
||||
instead of a TCP port. This prevents any network access entirely — only processes on the
|
||||
same machine can connect. The CLI supports this mode, but the mobile app and web interface
|
||||
require a network connection.
|
||||
</p>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">VPN access</h3>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
If you prefer direct connections over the relay, you can use a VPN like{" "}
|
||||
<a
|
||||
href="https://tailscale.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
Tailscale
|
||||
</a>
|
||||
. Tailscale creates a private network between your devices, so you can access your daemon
|
||||
without exposing it to the public internet.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">To set this up:</p>
|
||||
<ol className="text-white/60 space-y-2 list-decimal list-inside">
|
||||
<li>
|
||||
Install Tailscale on your machine and phone and join them to the same{" "}
|
||||
<a
|
||||
href="https://tailscale.com/kb/1136/tailnet"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
tailnet
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
Configure the daemon to listen on your Tailscale IP (e.g.,{" "}
|
||||
<code className="font-mono">100.x.y.z:6767</code>)
|
||||
</li>
|
||||
<li>
|
||||
Add your Tailscale hostname to <code className="font-mono">hostnames</code> and{" "}
|
||||
<code className="font-mono">cors.allowedOrigins</code>
|
||||
</li>
|
||||
<li>
|
||||
Add the daemon as a direct connection in the Paseo app using the Tailscale address
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">Binding to 0.0.0.0</h3>
|
||||
<div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4 text-white/80">
|
||||
<strong>Warning:</strong> Binding to <code className="font-mono">0.0.0.0</code> makes the
|
||||
daemon reachable on all network interfaces, including public Wi-Fi and local networks.
|
||||
This can expose your daemon to unauthorized access. If you must bind to all interfaces,
|
||||
ensure you have proper firewall rules and review your{" "}
|
||||
<code className="font-mono">hostnames</code> configuration.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* DNS Rebinding Protection */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">DNS rebinding protection</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
<strong className="text-white/80">CORS is not a complete security boundary.</strong> It
|
||||
controls which browser origins can make requests, but does not prevent a malicious website
|
||||
from resolving its domain to your local machine (DNS rebinding).
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Paseo uses a host allowlist to validate the <code className="font-mono">Host</code> header
|
||||
on incoming requests. Requests with unrecognized hosts are rejected.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Configure via <code className="font-mono">daemon.hostnames</code> in{" "}
|
||||
<code className="font-mono">config.json</code>:
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
Default (<code className="font-mono">[]</code>): allow{" "}
|
||||
<code className="font-mono">localhost</code>,{" "}
|
||||
<code className="font-mono">*.localhost</code>, and all IP addresses
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">['.example.com']</code>: allow{" "}
|
||||
<code className="font-mono">example.com</code> and any subdomain, plus defaults
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">true</code>: allow any host (not recommended)
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Agent Authentication */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Agent authentication</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Paseo wraps agent CLIs (Claude Code, Codex, OpenCode) but does not manage their
|
||||
authentication. Each agent provider handles its own credentials:
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<strong className="text-white/80">Claude Code</strong> — authenticates via
|
||||
Anthropic's OAuth flow, stored in <code className="font-mono">~/.claude/</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">Codex</strong> — uses your OpenAI API key or OAuth
|
||||
session
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">OpenCode</strong> — configured via provider-specific
|
||||
API keys
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Paseo never stores or transmits provider API keys. Agents run in your user context with
|
||||
your existing credentials.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Best Practices Summary */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Recommendations</h2>
|
||||
<ul className="text-white/60 space-y-3 list-disc list-inside">
|
||||
<li>
|
||||
<strong className="text-white/80">Use the relay</strong> for mobile access — it's
|
||||
the simplest option and all traffic is end-to-end encrypted
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">Treat the QR code like a password</strong> — anyone
|
||||
with the pairing offer can connect to your daemon
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">Never bind to 0.0.0.0</strong> unless you understand
|
||||
the implications and have proper firewall rules
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white/80">Keep your daemon updated</strong> — security
|
||||
improvements are released regularly
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/skills")({
|
||||
head: () => ({
|
||||
meta: pageMeta(
|
||||
"Orchestration Skills - Paseo Docs",
|
||||
"Paseo orchestration skills: teach coding agents to spawn, coordinate, and manage other agents using slash commands.",
|
||||
),
|
||||
}),
|
||||
component: Skills,
|
||||
});
|
||||
|
||||
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 Skills() {
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium font-title mb-4">Orchestration Skills</h1>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Paseo ships orchestration skills that teach coding agents (Claude Code, Codex) how to use
|
||||
the Paseo CLI to spawn, coordinate, and manage other agents. Skills are slash commands
|
||||
your agent can invoke — they provide the prompts, context, and workflows so agents know
|
||||
how to orchestrate without you writing boilerplate. Install them from the desktop
|
||||
app's Integrations settings or via the CLI.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Installation */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Installation</h2>
|
||||
<p className="text-white/60 leading-relaxed">Two ways to install:</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<strong>Desktop app:</strong> Settings → Integrations → Install
|
||||
</li>
|
||||
<li>
|
||||
<strong>Manual:</strong>{" "}
|
||||
<code className="font-mono">npx skills add getpaseo/paseo</code> — this installs to{" "}
|
||||
<code className="font-mono">~/.agents/skills/</code> and sets up symlinks for each
|
||||
agent.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* /paseo */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">
|
||||
<code className="font-mono">/paseo</code> — CLI Reference
|
||||
</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
The foundational skill. Loaded automatically by other skills. Contains the full Paseo CLI
|
||||
command reference so agents know how to run commands.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Not typically invoked directly by users — it's a reference that other skills depend
|
||||
on.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* /paseo-handoff */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">
|
||||
<code className="font-mono">/paseo-handoff</code> — Task Handoff
|
||||
</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Hands off your current task to another agent with full context. The receiving agent gets a
|
||||
comprehensive prompt with: task description, relevant files, what's been tried,
|
||||
decisions made, and acceptance criteria.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Default provider is Codex. Can specify Claude (sonnet/opus). Supports{" "}
|
||||
<code className="font-mono">--worktree</code> for isolated git branches.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`/paseo-handoff hand off the auth fix to codex in a worktree
|
||||
/paseo-handoff hand this to claude opus for review`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* /paseo-loop */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">
|
||||
<code className="font-mono">/paseo-loop</code> — Iterative Loops
|
||||
</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Runs an agent in a loop with automatic verification until an exit condition is met. Worker
|
||||
runs, verifier checks, repeat until done or max iterations. Supports different providers
|
||||
for worker vs verifier (e.g., Codex implements, Claude verifies).
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Stop conditions: <code className="font-mono">--max-iterations</code>,{" "}
|
||||
<code className="font-mono">--max-time</code>, or verification passes.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`/paseo-loop fix the failing tests, verify with npm test, max 5 iterations
|
||||
/paseo-loop use codex to implement, claude sonnet to verify, loop until tests pass`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* /paseo-orchestrator */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">
|
||||
<code className="font-mono">/paseo-orchestrator</code> — Team Orchestration
|
||||
</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Builds and manages a team of agents coordinating through a shared chat room. You describe
|
||||
the work, it sets up roles, launches agents, and coordinates through chat. Uses a
|
||||
heartbeat schedule to check progress.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Cross-provider: typically Codex for implementation, Claude for review.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`/paseo-orchestrator spin up a team to implement the database migration, codex implements, claude reviews`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* /paseo-chat */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">
|
||||
<code className="font-mono">/paseo-chat</code> — Chat Rooms
|
||||
</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Use persistent chat rooms for asynchronous agent coordination. Create rooms, post
|
||||
messages, read history, wait for replies. Supports @mentions for specific agents or
|
||||
@everyone.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Typically used by the orchestrator skill, but can be used directly.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`/paseo-chat create a room called "backend-refactor" for coordinating the API changes
|
||||
/paseo-chat post to backend-refactor: "API endpoints are done, ready for review"`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* /paseo-committee */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">
|
||||
<code className="font-mono">/paseo-committee</code> — Committee Planning
|
||||
</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Forms a committee of two high-reasoning agents (Claude Opus + GPT 5.4) to analyze a
|
||||
problem before implementing. Both agents reason in parallel, then plans are merged. Useful
|
||||
when stuck, looping, or facing a hard architectural decision.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Agents are prevented from editing code — they only produce a plan.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`/paseo-committee why are the websocket connections dropping under load?
|
||||
/paseo-committee plan the auth system migration`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/updates")({
|
||||
head: () => ({
|
||||
meta: pageMeta(
|
||||
"Updates - Paseo Docs",
|
||||
"How to update Paseo daemon and apps across web, desktop, and mobile.",
|
||||
),
|
||||
}),
|
||||
component: UpdatesDocs,
|
||||
});
|
||||
|
||||
function UpdatesDocs() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium font-title mb-4">Updates</h1>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Keep your daemon and apps current to get the latest fixes and features.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Version compatibility</h2>
|
||||
<p className="text-white/60">
|
||||
For now, daemon and app versions should be kept in lockstep. If your daemon is version X,
|
||||
make sure your clients are also version X.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Update the daemon</h2>
|
||||
<p className="text-white/60">Install the latest CLI/daemon package globally:</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@latest</span>
|
||||
</div>
|
||||
<p className="text-white/60">Then restart the daemon:</p>
|
||||
<div className="bg-card border border-border rounded-lg p-4 font-mono text-sm">
|
||||
<span className="text-muted-foreground select-none">$ </span>
|
||||
<span>paseo daemon restart</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Web app</h2>
|
||||
<p className="text-white/60">
|
||||
<a
|
||||
href="https://app.paseo.sh"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
app.paseo.sh
|
||||
</a>{" "}
|
||||
is always up to date. No manual update needed.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Desktop app</h2>
|
||||
<p className="text-white/60">
|
||||
Download the latest desktop build from the GitHub releases page and install it over your
|
||||
current version.
|
||||
</p>
|
||||
<a
|
||||
href="https://github.com/getpaseo/paseo/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-white/80"
|
||||
>
|
||||
Paseo releases
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Mobile apps</h2>
|
||||
<p className="text-white/60">
|
||||
Mobile apps are available on the App Store and Play Store. Update through your respective
|
||||
store. Store versions may lag behind the latest release due to review processes.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/voice")({
|
||||
head: () => ({
|
||||
meta: pageMeta(
|
||||
"Voice - Paseo Docs",
|
||||
"Paseo voice architecture, local-first model execution, and provider configuration.",
|
||||
),
|
||||
}),
|
||||
component: VoiceDocs,
|
||||
});
|
||||
|
||||
function VoiceDocs() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<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.
|
||||
</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.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
This keeps credentials and execution in your environment and avoids introducing a separate
|
||||
cloud-only voice stack.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<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>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>Tooling path: MCP stdio bridge for voice tools and agent control</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<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).
|
||||
</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.
|
||||
</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" } },
|
||||
"voiceMode": {
|
||||
"llm": { "provider": "claude", "model": "haiku" },
|
||||
"stt": { "provider": "local", "model": "parakeet-tdt-0.6b-v3-int8" },
|
||||
"tts": { "provider": "local", "model": "kokoro-en-v0_19", "speakerId": 0 }
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"local": {
|
||||
"modelsDir": "~/.paseo/models/local-speech"
|
||||
}
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<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>.
|
||||
</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" } },
|
||||
"voiceMode": {
|
||||
"stt": { "provider": "openai" },
|
||||
"tts": { "provider": "openai" }
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"openai": { "apiKey": "..." }
|
||||
}
|
||||
}`}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
</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.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { pageMeta } from "~/meta";
|
||||
|
||||
export const Route = createFileRoute("/docs/worktrees")({
|
||||
head: () => ({
|
||||
meta: pageMeta(
|
||||
"Git Worktrees - Paseo Docs",
|
||||
"Run agents in isolated git worktrees with setup hooks, scripts, and long-running services.",
|
||||
),
|
||||
}),
|
||||
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() {
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-medium font-title mb-4">Git Worktrees</h1>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Each agent runs in its own git worktree — a separate directory on a separate branch — so
|
||||
parallel agents never step on each other. You configure setup, scripts, and long-running
|
||||
services through a <code className="font-mono">paseo.json</code> file at your repo root.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Layout & workflow */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Layout and workflow</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Worktrees live under <code className="font-mono">$PASEO_HOME/worktrees/</code>, grouped by
|
||||
a hash of the source checkout path. Each worktree gets a random slug; the branch name is
|
||||
chosen when you first launch an agent.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`~/.paseo/worktrees/
|
||||
└── 1vnnm9k3/ # hash of source checkout path
|
||||
├── tidy-fox/ # worktree slug (branch set on first agent)
|
||||
└── bold-owl/`}</pre>
|
||||
</Code>
|
||||
<ol className="text-white/60 space-y-2 list-decimal list-inside">
|
||||
<li>Create a worktree — Paseo runs your setup hooks</li>
|
||||
<li>Launch an agent — a branch is created or assigned</li>
|
||||
<li>Review the diff against the base branch</li>
|
||||
<li>Merge or archive — archive runs teardown and removes the directory</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
{/* paseo.json */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">paseo.json</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Drop a <code className="font-mono">paseo.json</code> in your repo root. Paseo reads it
|
||||
from the committed version of the base branch you picked, so uncommitted changes in other
|
||||
branches don't apply.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`{
|
||||
"worktree": {
|
||||
"setup": "npm ci",
|
||||
"teardown": "rm -rf .cache"
|
||||
},
|
||||
"scripts": {
|
||||
"test": { "command": "npm test" },
|
||||
"web": { "command": "npm run dev", "type": "service", "port": 3000 }
|
||||
}
|
||||
}`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Setup & teardown */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Setup and teardown</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
<code className="font-mono">setup</code> runs once after the worktree is created. A fresh
|
||||
worktree has no installed dependencies and no ignored files (like{" "}
|
||||
<code className="font-mono">.env</code>), so use setup to install and copy what you need.
|
||||
<code className="font-mono">teardown</code> runs during archive, before the directory is
|
||||
removed.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`{
|
||||
"worktree": {
|
||||
"setup": "npm ci\\ncp \\"$PASEO_SOURCE_CHECKOUT_PATH/.env\\" .env\\nnpm run db:migrate",
|
||||
"teardown": "npm run db:drop || true"
|
||||
}
|
||||
}`}</pre>
|
||||
</Code>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Both fields accept a multiline shell script or an array of commands; commands run
|
||||
sequentially either way.
|
||||
</p>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Commands run with the worktree as <code className="font-mono">cwd</code>. Use{" "}
|
||||
<code className="font-mono">$PASEO_SOURCE_CHECKOUT_PATH</code> to reach files in the
|
||||
original checkout (untracked config, local caches, etc).
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Scripts & services */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Scripts and services</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
<code className="font-mono">scripts</code> are named commands you can run inside a
|
||||
worktree on demand. Mark one as a <em>service</em> and Paseo supervises it as a
|
||||
long-running process, assigns it a port, and routes HTTP traffic to it through the
|
||||
daemon's reverse proxy.
|
||||
</p>
|
||||
|
||||
<h3 className="text-lg font-medium mt-4">Plain scripts</h3>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`{
|
||||
"scripts": {
|
||||
"test": { "command": "npm test" },
|
||||
"lint": { "command": "npm run lint" },
|
||||
"generate": { "command": "npm run codegen" }
|
||||
}
|
||||
}`}</pre>
|
||||
</Code>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">Services</h3>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`{
|
||||
"scripts": {
|
||||
"web": {
|
||||
"type": "service",
|
||||
"command": "npm run dev -- --port $PASEO_PORT",
|
||||
"port": 3000
|
||||
},
|
||||
"api": {
|
||||
"type": "service",
|
||||
"command": "npm run api -- --port $PASEO_PORT"
|
||||
}
|
||||
}
|
||||
}`}</pre>
|
||||
</Code>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Omit <code className="font-mono">port</code> to let Paseo auto-assign one. Bind your
|
||||
process to <code className="font-mono">$PASEO_PORT</code> rather than hard-coding — each
|
||||
worktree gets a distinct port so multiple copies of the same service coexist.
|
||||
</p>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">Reverse proxy</h3>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Every service is reachable through the daemon at a deterministic hostname:
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`http://<script>.<branch>.<project>.localhost:<daemon-port>
|
||||
|
||||
# on the default branch, the branch label is dropped:
|
||||
http://<script>.<project>.localhost:<daemon-port>`}</pre>
|
||||
</Code>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
<code className="font-mono">*.localhost</code> resolves to{" "}
|
||||
<code className="font-mono">127.0.0.1</code> on modern systems, so these URLs work out of
|
||||
the box. The proxy supports WebSocket upgrades.
|
||||
</p>
|
||||
|
||||
<h3 className="text-lg font-medium mt-6">Service-to-service</h3>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Services launched from the same workspace see each other's ports and proxy URLs.
|
||||
Given <code className="font-mono">web</code> and <code className="font-mono">api</code>{" "}
|
||||
above, each process gets:
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`PASEO_PORT=3000 # this service's port
|
||||
PASEO_URL=http://web.my-app.localhost:6767 # this service's proxy URL
|
||||
PASEO_SERVICE_API_PORT=51732
|
||||
PASEO_SERVICE_API_URL=http://api.my-app.localhost:6767
|
||||
PASEO_SERVICE_WEB_PORT=3000
|
||||
PASEO_SERVICE_WEB_URL=http://web.my-app.localhost:6767`}</pre>
|
||||
</Code>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Script names are upper-cased and non-alphanumerics become{" "}
|
||||
<code className="font-mono">_</code>. Point your frontend at{" "}
|
||||
<code className="font-mono">$PASEO_SERVICE_API_URL</code> instead of hard-coding a port.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Terminals */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Terminals</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Open terminals automatically when a worktree is created. Useful for tailing logs or
|
||||
leaving a REPL ready to go.
|
||||
</p>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`{
|
||||
"worktree": {
|
||||
"terminals": [
|
||||
{ "name": "logs", "command": "tail -f dev.log" },
|
||||
{ "name": "shell", "command": "bash" }
|
||||
]
|
||||
}
|
||||
}`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
|
||||
{/* Env vars */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">Environment variables</h2>
|
||||
<p className="text-white/60 leading-relaxed">
|
||||
Setup, teardown, scripts, and services all see:
|
||||
</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<code className="font-mono">$PASEO_SOURCE_CHECKOUT_PATH</code> — the original repo root
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">$PASEO_WORKTREE_PATH</code> — the worktree directory
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">$PASEO_BRANCH_NAME</code> — the worktree's branch
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">$PASEO_WORKTREE_PORT</code> — legacy per-worktree port
|
||||
(prefer <code className="font-mono">$PASEO_PORT</code> inside services)
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-white/60 leading-relaxed">Services additionally get:</p>
|
||||
<ul className="text-white/60 space-y-2 list-disc list-inside">
|
||||
<li>
|
||||
<code className="font-mono">$PASEO_PORT</code> — this service's assigned port
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">$PASEO_URL</code> — this service's proxy URL
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">$PASEO_SERVICE_<NAME>_PORT</code> /{" "}
|
||||
<code className="font-mono">_URL</code> — peer service ports and URLs
|
||||
</li>
|
||||
<li>
|
||||
<code className="font-mono">$HOST</code> — <code className="font-mono">127.0.0.1</code>{" "}
|
||||
for local-only daemons, <code className="font-mono">0.0.0.0</code> when the daemon binds
|
||||
all interfaces
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* CLI */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium">CLI</h2>
|
||||
<Code>
|
||||
<pre className="text-white/80">{`paseo run --worktree feature-auth --base main "implement auth"
|
||||
paseo worktree ls
|
||||
paseo worktree archive feature-auth`}</pre>
|
||||
</Code>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -84,6 +84,134 @@
|
||||
color: rgba(250, 250, 250, 0.8);
|
||||
}
|
||||
|
||||
/* Docs page prose styles — mirrors the original docs/*.tsx components */
|
||||
.docs-prose {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
line-height: 1.625;
|
||||
}
|
||||
|
||||
.docs-prose > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.docs-prose h1 {
|
||||
color: var(--color-foreground);
|
||||
font-size: 1.875rem;
|
||||
line-height: 1.25;
|
||||
font-weight: 500;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.docs-prose h2 {
|
||||
color: var(--color-foreground);
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.4;
|
||||
font-weight: 500;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.docs-prose h3 {
|
||||
color: var(--color-foreground);
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.4;
|
||||
font-weight: 500;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.docs-prose p {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.docs-prose a {
|
||||
color: var(--color-foreground);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.docs-prose a:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.docs-prose ul,
|
||||
.docs-prose ol {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.docs-prose ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.docs-prose ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.docs-prose li {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.docs-prose strong {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.docs-prose blockquote {
|
||||
background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--color-primary) 30%, transparent);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.docs-prose blockquote > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.docs-prose blockquote > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.docs-prose pre {
|
||||
background-color: var(--color-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.docs-prose pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.docs-prose :not(pre) > code {
|
||||
background-color: var(--color-muted);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.docs-prose hr {
|
||||
border-color: var(--color-border);
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Blog post prose styles — adapted from moboudra.com */
|
||||
.blog-prose {
|
||||
color: rgba(250, 250, 250, 0.82);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { defineConfig, type UserConfig } from "vite";
|
||||
import tsConfigPaths from "vite-tsconfig-paths";
|
||||
@@ -8,24 +9,37 @@ import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "../..");
|
||||
const siteHost = "https://paseo.sh";
|
||||
|
||||
function discoverDocsRoutes(): string[] {
|
||||
const docsDir = path.join(repoRoot, "public-docs");
|
||||
if (!fs.existsSync(docsDir)) return ["/docs"];
|
||||
const routes = new Set<string>(["/docs"]);
|
||||
const walk = (dir: string) => {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(full);
|
||||
continue;
|
||||
}
|
||||
if (!entry.name.endsWith(".md")) continue;
|
||||
const rel = path.relative(docsDir, full).replace(/\.md$/, "");
|
||||
if (rel === "index") continue;
|
||||
routes.add(`/docs/${rel.split(path.sep).join("/")}`);
|
||||
}
|
||||
};
|
||||
walk(docsDir);
|
||||
return [...routes].sort();
|
||||
}
|
||||
|
||||
const sitemapPages = [
|
||||
"/",
|
||||
"/changelog",
|
||||
"/claude-code",
|
||||
"/codex",
|
||||
"/docs",
|
||||
"/download",
|
||||
"/opencode",
|
||||
"/privacy",
|
||||
"/docs/best-practices",
|
||||
"/docs/cli",
|
||||
"/docs/configuration",
|
||||
"/docs/providers",
|
||||
"/docs/skills",
|
||||
"/docs/security",
|
||||
"/docs/updates",
|
||||
"/docs/voice",
|
||||
"/docs/worktrees",
|
||||
...discoverDocsRoutes(),
|
||||
].map((routePath) => ({
|
||||
path: routePath,
|
||||
}));
|
||||
@@ -33,7 +47,9 @@ const sitemapPages = [
|
||||
export default defineConfig((): UserConfig => {
|
||||
return {
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 8082,
|
||||
strictPort: false,
|
||||
fs: {
|
||||
allow: [repoRoot],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user