mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
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:
@@ -207,7 +207,7 @@
|
||||
|
||||
### Added
|
||||
|
||||
- Provider profiles — define custom providers in your Paseo config that appear alongside built-ins. Override a built-in's binary, env, or models, or create entirely new providers. See the [configuration guide](https://github.com/getpaseo/paseo/blob/main/docs/CUSTOM-PROVIDERS.md).
|
||||
- Provider profiles — define custom providers in your Paseo config that appear alongside built-ins. Override a built-in's binary, env, or models, or create entirely new providers. See the [configuration guide](https://github.com/getpaseo/paseo/blob/main/docs/custom-providers.md).
|
||||
- ACP agent support — add any ACP-compatible agent to Paseo with `extends: "acp"` in your provider config. No code changes needed.
|
||||
- Choose provider and model when creating scheduled agents.
|
||||
- Max reasoning effort option for Opus 4.6 models.
|
||||
|
||||
18
CLAUDE.md
18
CLAUDE.md
@@ -19,14 +19,14 @@ This is an npm workspace monorepo:
|
||||
|
||||
| Doc | What's in it |
|
||||
| ---------------------------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | System design, package layering, WebSocket protocol, agent lifecycle, data flow |
|
||||
| [docs/CODING_STANDARDS.md](docs/CODING_STANDARDS.md) | Type hygiene, error handling, state design, React patterns, file organization |
|
||||
| [docs/TESTING.md](docs/TESTING.md) | TDD workflow, determinism, real dependencies over mocks, test organization |
|
||||
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP |
|
||||
| [docs/RELEASE.md](docs/RELEASE.md) | Release playbook, draft releases, completion checklist |
|
||||
| [docs/CUSTOM-PROVIDERS.md](docs/CUSTOM-PROVIDERS.md) | Custom provider config: Z.AI, Alibaba/Qwen, ACP agents, profiles, custom binaries |
|
||||
| [docs/ANDROID.md](docs/ANDROID.md) | App variants, local/cloud builds, EAS workflows |
|
||||
| [docs/DESIGN.md](docs/DESIGN.md) | How to design features before implementation |
|
||||
| [docs/architecture.md](docs/architecture.md) | System design, package layering, WebSocket protocol, agent lifecycle, data flow |
|
||||
| [docs/coding-standards.md](docs/coding-standards.md) | Type hygiene, error handling, state design, React patterns, file organization |
|
||||
| [docs/testing.md](docs/testing.md) | TDD workflow, determinism, real dependencies over mocks, test organization |
|
||||
| [docs/development.md](docs/development.md) | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP |
|
||||
| [docs/release.md](docs/release.md) | Release playbook, draft releases, completion checklist |
|
||||
| [docs/custom-providers.md](docs/custom-providers.md) | Custom provider config: Z.AI, Alibaba/Qwen, ACP agents, profiles, custom binaries |
|
||||
| [docs/android.md](docs/android.md) | App variants, local/cloud builds, EAS workflows |
|
||||
| [docs/design.md](docs/design.md) | How to design features before implementation |
|
||||
| [SECURITY.md](SECURITY.md) | Relay threat model, E2E encryption, DNS rebinding, agent auth |
|
||||
|
||||
## Quick start
|
||||
@@ -41,7 +41,7 @@ npm run format # Auto-format with Biome
|
||||
npm run format:check # Check formatting without writing
|
||||
```
|
||||
|
||||
See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for full setup, build sync requirements, and debugging.
|
||||
See [docs/development.md](docs/development.md) for full setup, build sync requirements, and debugging.
|
||||
|
||||
## Critical rules
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ If you want to propose a direction change, start a conversation.
|
||||
Please read these first:
|
||||
|
||||
- [README.md](README.md)
|
||||
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
||||
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)
|
||||
- [docs/CODING_STANDARDS.md](docs/CODING_STANDARDS.md)
|
||||
- [docs/TESTING.md](docs/TESTING.md)
|
||||
- [docs/architecture.md](docs/architecture.md)
|
||||
- [docs/development.md](docs/development.md)
|
||||
- [docs/coding-standards.md](docs/coding-standards.md)
|
||||
- [docs/testing.md](docs/testing.md)
|
||||
- [CLAUDE.md](CLAUDE.md)
|
||||
|
||||
## What is most helpful
|
||||
@@ -105,7 +105,7 @@ npm run dev:website
|
||||
npm run cli -- ls -a -g
|
||||
```
|
||||
|
||||
Read [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for build-sync gotchas, local state, ports, and daemon details.
|
||||
Read [docs/development.md](docs/development.md) for build-sync gotchas, local state, ports, and daemon details.
|
||||
|
||||
## Multi-platform testing
|
||||
|
||||
@@ -132,7 +132,7 @@ If you touch protocol or shared client/server behavior, read the compatibility n
|
||||
|
||||
Paseo has explicit standards. Follow them.
|
||||
|
||||
The full guide lives in [docs/CODING_STANDARDS.md](docs/CODING_STANDARDS.md).
|
||||
The full guide lives in [docs/coding-standards.md](docs/coding-standards.md).
|
||||
|
||||
## PR checklist
|
||||
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
# Attachment-Based Review Context Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Use structured attachments as the source of truth for review context during agent creation.
|
||||
|
||||
This covers two related behaviors:
|
||||
|
||||
1. Provider-facing context for the agent prompt
|
||||
2. Worktree checkout behavior when creating a worktree from a review item
|
||||
|
||||
The design should stay compatible with future forge support such as GitLab.
|
||||
|
||||
## Core Direction
|
||||
|
||||
- Do not parse the prompt to discover issue/PR intent.
|
||||
- Do not introduce deeply nested transport objects for this flow.
|
||||
- Make the attachment itself the discriminated union.
|
||||
- Keep forge-specific checkout logic below the attachment layer.
|
||||
- Let each agent provider translate attachments into its own prompt/input format.
|
||||
|
||||
## Initial Attachment Types
|
||||
|
||||
Two initial structured attachment types:
|
||||
|
||||
- `github_pr`
|
||||
- `github_issue`
|
||||
|
||||
With separate MIME types:
|
||||
|
||||
- `application/github-pr`
|
||||
- `application/github-issue`
|
||||
|
||||
Proposed wire shape:
|
||||
|
||||
```ts
|
||||
type AgentAttachment =
|
||||
| {
|
||||
type: "github_pr";
|
||||
mimeType: "application/github-pr";
|
||||
number: number;
|
||||
title: string;
|
||||
url: string;
|
||||
body?: string | null;
|
||||
baseRefName?: string | null;
|
||||
headRefName?: string | null;
|
||||
}
|
||||
| {
|
||||
type: "github_issue";
|
||||
mimeType: "application/github-issue";
|
||||
number: number;
|
||||
title: string;
|
||||
url: string;
|
||||
body?: string | null;
|
||||
};
|
||||
```
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- Add new optional `attachments` fields; keep existing `images` fields.
|
||||
- Unknown attachment discriminators must be dropped during schema normalization instead of failing the full request.
|
||||
- Malformed attachment entries should also be ignored safely.
|
||||
- Old clients continue working because `attachments` is optional.
|
||||
- New clients can send `attachments` without breaking older flows that still rely on `images`.
|
||||
|
||||
## Request-Level Changes
|
||||
|
||||
Add optional `attachments` to:
|
||||
|
||||
- `create_agent_request`
|
||||
- `send_agent_message_request`
|
||||
|
||||
Existing `initialPrompt` remains plain user text.
|
||||
|
||||
The selected GitHub PR/issue becomes a structured attachment instead of being injected into prompt text as the primary source of truth.
|
||||
|
||||
## App Responsibilities
|
||||
|
||||
When the user selects a GitHub item:
|
||||
|
||||
- If it is a PR, create a `github_pr` attachment
|
||||
- If it is an issue, create a `github_issue` attachment
|
||||
- Include the attachment in the create-agent request
|
||||
|
||||
The UI can still render friendly labels and previews from the same data.
|
||||
|
||||
The app should stop treating PR/issue context as prompt-only metadata.
|
||||
|
||||
## Worktree Creation Behavior
|
||||
|
||||
During agent creation, if worktree creation is requested:
|
||||
|
||||
- Inspect normalized attachments
|
||||
- If a `github_pr` attachment is present, use it to drive checkout for the worktree
|
||||
|
||||
The important rule:
|
||||
|
||||
- attachment type identifies the review object
|
||||
- server-side git logic decides how to check it out
|
||||
|
||||
This keeps the attachment contract simple while allowing fork-safe implementation details.
|
||||
|
||||
## Checkout Resolution
|
||||
|
||||
The attachment itself should not encode the full checkout strategy.
|
||||
|
||||
Instead, the server should resolve checkout from the `github_pr` attachment using forge-aware logic.
|
||||
|
||||
Examples of possible implementation strategies:
|
||||
|
||||
- `gh pr checkout`
|
||||
- `git fetch origin refs/pull/<number>/head:<local-branch>`
|
||||
- another GitHub-aware resolver if needed later
|
||||
|
||||
This is intentionally a server implementation detail, not part of the attachment schema.
|
||||
|
||||
## Provider Responsibilities
|
||||
|
||||
Each provider adapter should receive normalized attachments and decide how to represent them for the model.
|
||||
|
||||
Examples:
|
||||
|
||||
- Claude: render attachment into text blocks
|
||||
- Codex: inject as blocks or text
|
||||
- OpenCode: send as file/resource-like input where appropriate
|
||||
- ACP: convert to resource/text forms as supported
|
||||
|
||||
The session layer should not hardcode one translation strategy for all providers.
|
||||
|
||||
## Suggested Implementation Order
|
||||
|
||||
1. Add tolerant attachment schema and normalization in shared/server message handling.
|
||||
2. Thread `attachments` through app -> daemon client -> session layer.
|
||||
3. Update create-agent UI flows to emit `github_pr` / `github_issue` attachments from GitHub selection.
|
||||
4. Teach worktree creation to inspect attachments and special-case `github_pr`.
|
||||
5. Update provider adapters to translate attachments into provider-specific prompt/input forms.
|
||||
6. Optionally migrate image transport into the same attachment mechanism later.
|
||||
|
||||
## Files Likely Involved
|
||||
|
||||
- `packages/server/src/shared/messages.ts`
|
||||
- `packages/server/src/client/daemon-client.ts`
|
||||
- `packages/server/src/server/session.ts`
|
||||
- `packages/server/src/server/worktree-session.ts`
|
||||
- `packages/server/src/server/agent/agent-sdk-types.ts`
|
||||
- `packages/server/src/server/agent/providers/claude-agent.ts`
|
||||
- `packages/server/src/server/agent/providers/codex-app-server-agent.ts`
|
||||
- `packages/server/src/server/agent/providers/opencode-agent.ts`
|
||||
- `packages/server/src/server/agent/providers/acp-agent.ts`
|
||||
- `packages/app/src/screens/new-workspace-screen.tsx`
|
||||
- `packages/app/src/screens/agent/draft-agent-screen.tsx`
|
||||
- `packages/app/src/contexts/session-context.tsx`
|
||||
|
||||
## Notes
|
||||
|
||||
- The current image path can remain in place initially.
|
||||
- The plan intentionally keeps checkout concerns separate from provider prompt translation.
|
||||
- Future forge support should add new attachment discriminators rather than expanding GitHub-only nested fields.
|
||||
@@ -1,4 +1,4 @@
|
||||
# DESIGN-SYSTEM.md
|
||||
# Design system
|
||||
|
||||
Tokens — every color, font size, weight, spacing step, radius, icon size — live in `packages/app/src/styles/theme.ts`.
|
||||
|
||||
@@ -17,7 +17,7 @@ Before designing anything new, understand what exists:
|
||||
|
||||
- Where does similar functionality live?
|
||||
- What patterns does the codebase already use?
|
||||
- What layers exist? (See [ARCHITECTURE.md](./ARCHITECTURE.md))
|
||||
- What layers exist? (See [architecture.md](./architecture.md))
|
||||
- What types and data shapes are already defined?
|
||||
|
||||
New features rarely mean only new code. Usually they require modifying existing interfaces, extending existing types, or refactoring to accommodate the new functionality. Identify what needs to change, not just what needs to be added.
|
||||
@@ -38,7 +38,7 @@ If you can't define verification, you don't understand the feature well enough y
|
||||
|
||||
- What types are needed?
|
||||
- Use discriminated unions — make impossible states impossible
|
||||
- One canonical type per concept (see [CODING_STANDARDS.md](./CODING_STANDARDS.md))
|
||||
- One canonical type per concept (see [coding-standards.md](./coding-standards.md))
|
||||
|
||||
### Layers
|
||||
|
||||
@@ -261,4 +261,4 @@ xcrun simctl ui booted appearance dark # set dark
|
||||
xcrun simctl ui booted appearance light # set light
|
||||
```
|
||||
|
||||
Expo dev server logs are in the tmux pane running `npm run dev`. Daemon logs are at `$PASEO_HOME/daemon.log` (see [DEVELOPMENT.md](DEVELOPMENT.md)).
|
||||
Expo dev server logs are in the tmux pane running `npm run dev`. Daemon logs are at `$PASEO_HOME/daemon.log` (see [development.md](development.md)).
|
||||
@@ -1,68 +0,0 @@
|
||||
# Plan Approval Normalization
|
||||
|
||||
## Goal
|
||||
|
||||
Normalize plan approval across providers so the UI renders one consistent plan approval card and action row, while each provider keeps its own execution quirks behind the session permission interface.
|
||||
|
||||
## Compatibility Constraints
|
||||
|
||||
- Older clients must remain compatible with newer daemons.
|
||||
- All new wire fields must be optional.
|
||||
- Existing plan permissions without action metadata must still render and work.
|
||||
- Existing question permissions must keep their current behavior.
|
||||
|
||||
## Design
|
||||
|
||||
### Shared abstraction
|
||||
|
||||
Add optional permission action definitions to the shared permission request/response types.
|
||||
|
||||
- Permission requests may include `actions`.
|
||||
- Permission responses may include `selectedActionId`.
|
||||
- `kind: "plan"` remains the normalized concept for plan approval.
|
||||
- The UI renders actions from the permission request instead of hardcoding provider-specific buttons.
|
||||
|
||||
### Claude
|
||||
|
||||
Keep Claude's plan permission flow, but enrich it with explicit action definitions.
|
||||
|
||||
- Always expose `Reject`.
|
||||
- Always expose `Implement`.
|
||||
- If the agent entered plan mode from a more permissive mode like `bypassPermissions`, also expose `Implement with <previous mode>`.
|
||||
- Resolve the selected action entirely inside `respondToPermission()`.
|
||||
|
||||
### Codex
|
||||
|
||||
Synthesize a normalized `kind: "plan"` permission after a Codex plan-mode turn completes with a plan result.
|
||||
|
||||
- Emit a plan permission with `Reject` and `Implement` actions.
|
||||
- On `Implement`, disable `plan_mode`, disable `fast_mode`, and automatically start a follow-up implementation turn.
|
||||
- On `Reject`, resolve without starting a follow-up turn.
|
||||
- Keep the implementation prompt and state transitions inside the Codex provider.
|
||||
|
||||
### Manager and state sync
|
||||
|
||||
After permission resolution, refresh provider-derived state so the UI sees internal mode/feature changes without knowing provider quirks.
|
||||
|
||||
- Refresh current mode
|
||||
- Refresh pending permissions
|
||||
- Refresh runtime info
|
||||
- Refresh features
|
||||
- Persist refreshed state
|
||||
|
||||
### UI
|
||||
|
||||
Render plan permissions through the existing plan card, but generate buttons from normalized permission actions.
|
||||
|
||||
- If `actions` are absent, fall back to legacy buttons.
|
||||
- Plan cards should use `Implement` as the default primary label.
|
||||
- Do not add provider-specific rendering branches.
|
||||
|
||||
## Verification
|
||||
|
||||
1. Shared schema/type tests for optional `actions` and `selectedActionId`
|
||||
2. App tests for generic plan-action rendering
|
||||
3. Claude tests for third action when resuming from a more permissive mode
|
||||
4. Codex tests for synthetic plan approval and automatic implementation follow-up
|
||||
5. Manager tests for post-permission state refresh
|
||||
6. `npm run typecheck`
|
||||
@@ -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 |
|
||||
|
||||
81
packages/website/src/docs.ts
Normal file
81
packages/website/src/docs.ts
Normal file
@@ -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>
|
||||
|
||||
28
packages/website/src/routes/docs/$.tsx
Normal file
28
packages/website/src/routes/docs/$.tsx
Normal file
@@ -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],
|
||||
},
|
||||
|
||||
56
public-docs/best-practices.md
Normal file
56
public-docs/best-practices.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Best practices
|
||||
description: Tips for getting the most out of Paseo and mobile-first agent workflows.
|
||||
nav: Best practices
|
||||
order: 10
|
||||
---
|
||||
|
||||
# Best practices
|
||||
|
||||
What I've learned from using Paseo daily. Not rules, just patterns that have worked for me.
|
||||
|
||||
## Agents replace typing, not thinking
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Verification loops
|
||||
|
||||
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.
|
||||
|
||||
## Invest in tooling
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Agents are cheap
|
||||
|
||||
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.
|
||||
|
||||
## Use voice extensively
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Understand the type of work
|
||||
|
||||
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.
|
||||
|
||||
## Iterate and refactor often
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Use agents to check agents
|
||||
|
||||
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.
|
||||
|
||||
## Learn your agents' quirks
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
166
public-docs/cli.md
Normal file
166
public-docs/cli.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: CLI
|
||||
description: "Paseo CLI reference: manage agents, daemons, permissions, and worktrees from your terminal."
|
||||
nav: CLI
|
||||
order: 5
|
||||
---
|
||||
|
||||
# CLI
|
||||
|
||||
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.
|
||||
|
||||
> **Agent orchestration:** 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.
|
||||
|
||||
## Quick reference
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
## Running agents
|
||||
|
||||
Use `paseo run` to start a new agent with a task:
|
||||
|
||||
```bash
|
||||
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"
|
||||
```
|
||||
|
||||
The `--worktree` flag creates the agent in an isolated git worktree, useful for parallel feature development.
|
||||
|
||||
Use `--output-schema` 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 `--detach`.
|
||||
|
||||
By default, `paseo run` waits for completion. Use `--detach` to run in the background.
|
||||
|
||||
## Listing agents
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
## Streaming output
|
||||
|
||||
Use `paseo attach` to stream an agent's output in real-time:
|
||||
|
||||
```bash
|
||||
paseo attach abc123 # Attach to agent (Ctrl+C to detach)
|
||||
```
|
||||
|
||||
Agent IDs can be shortened — `abc` works if it's unambiguous.
|
||||
|
||||
## Sending messages
|
||||
|
||||
Send follow-up tasks to a running or idle agent:
|
||||
|
||||
```bash
|
||||
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"
|
||||
```
|
||||
|
||||
## Viewing logs
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
## Waiting for agents
|
||||
|
||||
Block until an agent finishes its current task:
|
||||
|
||||
```bash
|
||||
paseo wait <id>
|
||||
paseo wait <id> --timeout 60 # 60 second timeout
|
||||
```
|
||||
|
||||
Useful in scripts or when one agent needs to wait for another.
|
||||
|
||||
## Permissions
|
||||
|
||||
Agents may request permission for certain actions. Manage these from the CLI:
|
||||
|
||||
```bash
|
||||
paseo permit ls # List pending requests
|
||||
paseo permit allow <id> # Allow all pending for agent
|
||||
paseo permit deny <id> --all # Deny all pending
|
||||
```
|
||||
|
||||
## Agent modes
|
||||
|
||||
Change an agent's operational mode (provider-specific):
|
||||
|
||||
```bash
|
||||
paseo agent mode <id> --list # Show available modes
|
||||
paseo agent mode <id> bypass # Set bypass mode
|
||||
paseo agent mode <id> plan # Set plan mode
|
||||
```
|
||||
|
||||
## Daemon management
|
||||
|
||||
```bash
|
||||
paseo daemon start # Start the daemon
|
||||
paseo daemon status # Check status
|
||||
paseo daemon stop # Stop the daemon
|
||||
```
|
||||
|
||||
Use `PASEO_HOME` to run multiple isolated daemon instances.
|
||||
|
||||
## Multi-agent workflows
|
||||
|
||||
The CLI is designed to be used by agents themselves. You can instruct an agent to spawn sub-agents for parallel work:
|
||||
|
||||
```bash
|
||||
# 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
|
||||
```
|
||||
|
||||
Simple implement + verify loop:
|
||||
|
||||
```bash
|
||||
# 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
|
||||
```
|
||||
|
||||
This pattern enables hierarchical task decomposition — a lead agent can break down work, delegate to specialists, and synthesize results.
|
||||
|
||||
## Output formats
|
||||
|
||||
Most commands support multiple output formats for scripting:
|
||||
|
||||
```bash
|
||||
paseo ls --json # JSON output
|
||||
paseo ls --format yaml # YAML output
|
||||
paseo ls -q # IDs only (quiet)
|
||||
```
|
||||
|
||||
## Global options
|
||||
|
||||
- `--host <host:port>` — connect to a different daemon
|
||||
- `--json` — JSON output
|
||||
- `-q, --quiet` — minimal output
|
||||
- `--no-color` — disable colors
|
||||
118
public-docs/configuration.md
Normal file
118
public-docs/configuration.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: Configuration
|
||||
description: Configure Paseo via config.json, environment variables, and CLI overrides.
|
||||
nav: Configuration
|
||||
order: 8
|
||||
---
|
||||
|
||||
# Configuration
|
||||
|
||||
Paseo loads configuration from a single JSON file in your Paseo home directory, with optional environment variable and CLI overrides.
|
||||
|
||||
## Where config lives
|
||||
|
||||
By default, Paseo uses `~/.paseo` as its home directory. The configuration file is:
|
||||
|
||||
```bash
|
||||
~/.paseo/config.json
|
||||
```
|
||||
|
||||
You can change the home directory by setting `PASEO_HOME` or passing `--home` to `paseo daemon start`.
|
||||
|
||||
## Precedence
|
||||
|
||||
Paseo merges configuration in this order:
|
||||
|
||||
1. Defaults
|
||||
2. `config.json`
|
||||
3. Environment variables
|
||||
4. CLI flags
|
||||
|
||||
Lists append across sources (for example, `hostnames` and `cors.allowedOrigins`).
|
||||
|
||||
## Example
|
||||
|
||||
Minimal example that configures listening address, hostnames, and MCP:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://paseo.sh/schemas/paseo.config.v1.json",
|
||||
"version": 1,
|
||||
"daemon": {
|
||||
"listen": "127.0.0.1:6767",
|
||||
"hostnames": ["localhost", ".localhost"],
|
||||
"mcp": { "enabled": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`daemon.hostnames` is the primary field. The old `daemon.allowedHosts` name still works as a deprecated alias for backward compatibility.
|
||||
|
||||
## Agent providers
|
||||
|
||||
Agent providers — both the first-class ones Paseo ships with and custom entries you add under `agents.providers` — are documented on their own page.
|
||||
|
||||
See [Providers](/docs/providers) for first-class providers, how to point Claude at Anthropic-compatible endpoints (Z.AI, Alibaba/Qwen), multiple profiles, custom binaries, ACP agents, and the `additionalModels` merge behavior. The full field reference lives on GitHub at [docs/custom-providers.md](https://github.com/getpaseo/paseo/blob/main/docs/custom-providers.md).
|
||||
|
||||
## Voice
|
||||
|
||||
Voice is configured through `features.dictation` and `features.voiceMode`, with provider credentials under `providers`.
|
||||
|
||||
For voice philosophy, architecture, and complete local/OpenAI setup examples, see [Voice docs](/docs/voice).
|
||||
|
||||
## Logging
|
||||
|
||||
Daemon logging uses separate console and file sinks by default:
|
||||
|
||||
- Console: `info` and above
|
||||
- File (`$PASEO_HOME/daemon.log`): `trace` and above
|
||||
- File rotation: `10m` max file size, `2` retained files total (active + 1 rotated)
|
||||
|
||||
```json
|
||||
{
|
||||
"log": {
|
||||
"console": {
|
||||
"level": "info",
|
||||
"format": "pretty"
|
||||
},
|
||||
"file": {
|
||||
"level": "trace",
|
||||
"path": "daemon.log",
|
||||
"rotate": {
|
||||
"maxSize": "10m",
|
||||
"maxFiles": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Legacy fields `log.level` and `log.format` are still supported and map to the new destination settings.
|
||||
|
||||
## Common env vars
|
||||
|
||||
- `PASEO_HOME` — set Paseo home directory
|
||||
- `PASEO_LISTEN` — override `daemon.listen`
|
||||
- `PASEO_HOSTNAMES` — override/extend `daemon.hostnames`
|
||||
- `PASEO_ALLOWED_HOSTS` — deprecated alias for `PASEO_HOSTNAMES`
|
||||
- `PASEO_LOG_CONSOLE_LEVEL` — override `log.console.level`
|
||||
- `PASEO_LOG_FILE_LEVEL` — override `log.file.level`
|
||||
- `PASEO_LOG_FILE_PATH` — override `log.file.path`
|
||||
- `PASEO_LOG_FILE_ROTATE_SIZE` — override `log.file.rotate.maxSize`
|
||||
- `PASEO_LOG_FILE_ROTATE_COUNT` — override `log.file.rotate.maxFiles`
|
||||
- `PASEO_LOG`, `PASEO_LOG_FORMAT` — legacy log overrides (still supported)
|
||||
- `OPENAI_API_KEY` — override OpenAI provider key
|
||||
- `PASEO_VOICE_LLM_PROVIDER` — override voice LLM provider (`claude`, `codex`, `opencode`)
|
||||
- `PASEO_DICTATION_STT_PROVIDER`, `PASEO_VOICE_STT_PROVIDER`, `PASEO_VOICE_TTS_PROVIDER` — override voice provider selection (`local` or `openai`)
|
||||
- `PASEO_LOCAL_MODELS_DIR` — control local model directory
|
||||
- `PASEO_DICTATION_LOCAL_STT_MODEL` — override local dictation STT model
|
||||
- `PASEO_VOICE_LOCAL_STT_MODEL`, `PASEO_VOICE_LOCAL_TTS_MODEL` — override local voice STT/TTS models
|
||||
- `PASEO_VOICE_LOCAL_TTS_SPEAKER_ID`, `PASEO_VOICE_LOCAL_TTS_SPEED` — optional local voice TTS tuning
|
||||
|
||||
## Schema
|
||||
|
||||
For editor autocomplete/validation, set `$schema` to:
|
||||
|
||||
```
|
||||
https://paseo.sh/schemas/paseo.config.v1.json
|
||||
```
|
||||
58
public-docs/index.md
Normal file
58
public-docs/index.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Getting started
|
||||
description: Learn how to set up and use Paseo to manage your coding agents from anywhere.
|
||||
nav: Getting started
|
||||
order: 1
|
||||
---
|
||||
|
||||
# Getting started
|
||||
|
||||
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.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Paseo manages existing agent CLIs. Install at least one agent and make sure it already works with your credentials before you set up Paseo.
|
||||
|
||||
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
||||
- [Codex](https://github.com/openai/codex)
|
||||
- [OpenCode](https://github.com/anomalyco/opencode)
|
||||
|
||||
## Desktop App
|
||||
|
||||
Download the desktop app from [paseo.sh/download](https://paseo.sh/download) or the [GitHub releases page](https://github.com/getpaseo/paseo/releases). Open it and you're done.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## CLI / Server
|
||||
|
||||
Use this path for headless setups, servers, or remote machines where you want the daemon running without the desktop app.
|
||||
|
||||
```bash
|
||||
npm install -g @getpaseo/cli
|
||||
```
|
||||
|
||||
```bash
|
||||
paseo
|
||||
```
|
||||
|
||||
Paseo prints a QR code in the terminal. Scan it from the mobile app, or enter the daemon address manually from another client.
|
||||
|
||||
Configuration and local state live under `PASEO_HOME`.
|
||||
|
||||
## Voice Setup
|
||||
|
||||
Paseo includes first-class voice support with a local-first architecture and configurable speech providers.
|
||||
|
||||
For architecture, local model behavior, and provider configuration, see the Voice docs page.
|
||||
|
||||
[Voice docs](/docs/voice)
|
||||
|
||||
## Next
|
||||
|
||||
- [Updates](/docs/updates)
|
||||
- [Voice](/docs/voice)
|
||||
- [Providers](/docs/providers)
|
||||
- [Configuration](/docs/configuration)
|
||||
- [Security](/docs/security)
|
||||
206
public-docs/providers.md
Normal file
206
public-docs/providers.md
Normal file
@@ -0,0 +1,206 @@
|
||||
---
|
||||
title: Providers
|
||||
description: First-class agent providers in Paseo, and how to configure custom providers, ACP agents, and profiles.
|
||||
nav: Providers
|
||||
order: 7
|
||||
---
|
||||
|
||||
# Providers
|
||||
|
||||
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 `config.json` — either by pointing an existing provider at a different API, adding extra profiles, or plugging in any [ACP](https://agentclientprotocol.com)-compatible agent.
|
||||
|
||||
## First-class providers
|
||||
|
||||
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.
|
||||
|
||||
- `claude` — Anthropic's Claude Code. Multi-tool assistant with MCP support, streaming, and deep reasoning.
|
||||
- `codex` — OpenAI's Codex workspace agent with sandbox controls and optional network access.
|
||||
- `opencode` — Open-source coding assistant with multi-provider model support.
|
||||
- `copilot` — GitHub Copilot via ACP, with dynamic modes and session support.
|
||||
- `pi` — Minimal terminal-based coding agent with multi-provider LLM support.
|
||||
|
||||
## Custom providers
|
||||
|
||||
Everything beyond the defaults lives under `agents.providers` in `~/.paseo/config.json`. You can:
|
||||
|
||||
- **Extend** a first-class provider to point at a different API (Z.AI, Alibaba/Qwen, a proxy, a self-hosted endpoint).
|
||||
- **Add profiles** — multiple entries against the same underlying provider with different credentials or curated model lists.
|
||||
- **Override the binary** — run a nightly build, a wrapper script, or a Docker image instead of the installed CLI.
|
||||
- **Add ACP agents** — Gemini CLI, Hermes, or any agent speaking the Agent Client Protocol over stdio.
|
||||
- **Disable** a provider you don't use.
|
||||
|
||||
Provider IDs must be lowercase alphanumeric with hyphens (`/^[a-z][a-z0-9-]*$/`). Every custom entry needs `extends` (a first-class provider ID or `"acp"`) and a `label`.
|
||||
|
||||
The examples below are a quick tour. The full, up-to-date reference is on GitHub: [docs/custom-providers.md](https://github.com/getpaseo/paseo/blob/main/docs/custom-providers.md).
|
||||
|
||||
## Extending a first-class provider
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"my-claude": {
|
||||
"extends": "claude",
|
||||
"label": "My Claude",
|
||||
"env": {
|
||||
"ANTHROPIC_API_KEY": "sk-ant-...",
|
||||
"ANTHROPIC_BASE_URL": "https://my-proxy.example.com/v1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Z.AI (GLM) coding plan
|
||||
|
||||
Z.AI exposes GLM models through an Anthropic-compatible endpoint. Point `ANTHROPIC_BASE_URL` at their API and use `ANTHROPIC_AUTH_TOKEN` for the key. Third-party endpoints don't support Anthropic's server-side tools, so disable `WebSearch`.
|
||||
|
||||
```json
|
||||
{
|
||||
"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" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Alibaba Cloud (Qwen) coding plan
|
||||
|
||||
Alibaba's coding plan routes Claude Code to Qwen models via an Anthropic-compatible API. Subscription keys look like `sk-sp-...` and must be created in the Singapore region.
|
||||
|
||||
```json
|
||||
{
|
||||
"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" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple profiles
|
||||
|
||||
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.
|
||||
|
||||
```json
|
||||
{
|
||||
"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-..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Custom binary
|
||||
|
||||
`command` is an array — first element is the binary, the rest are arguments. It fully replaces the default launch command for that provider.
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"claude": {
|
||||
"command": ["/opt/claude-nightly/claude"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ACP providers
|
||||
|
||||
Any agent that speaks [ACP](https://agentclientprotocol.com) over stdio can be added with `extends: "acp"` and a `command`. Paseo spawns the process, sends an `initialize` JSON-RPC request, and the agent reports its capabilities, modes, and models at runtime.
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"gemini": {
|
||||
"extends": "acp",
|
||||
"label": "Google Gemini",
|
||||
"command": ["gemini", "--acp"]
|
||||
},
|
||||
"hermes": {
|
||||
"extends": "acp",
|
||||
"label": "Hermes",
|
||||
"command": ["hermes", "acp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Adding or relabeling models
|
||||
|
||||
`models` replaces the model list entirely. `additionalModels` merges with runtime-discovered models (ACP) or with `models` — use it to add an extra entry or relabel a discovered one without redeclaring the full list. An entry with the same `id` as a discovered model updates it in place.
|
||||
|
||||
```json
|
||||
{
|
||||
"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)" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Disabling a provider
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"copilot": { "enabled": false }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Full reference
|
||||
|
||||
For the complete field reference (`extends`, `label`, `command`, `env`, `models`, `additionalModels`, `disallowedTools`, `enabled`, `order`), model and thinking-option schemas, and deeper examples for each plan, see [docs/custom-providers.md](https://github.com/getpaseo/paseo/blob/main/docs/custom-providers.md) on GitHub.
|
||||
103
public-docs/security.md
Normal file
103
public-docs/security.md
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
title: Security
|
||||
description: "Security model for Paseo: architecture overview, connection methods, relay encryption, and best practices."
|
||||
nav: Security
|
||||
order: 9
|
||||
---
|
||||
|
||||
# Security
|
||||
|
||||
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.
|
||||
|
||||
Your code never leaves your machine. Paseo is a local-first tool that connects directly to your development environment.
|
||||
|
||||
## Architecture
|
||||
|
||||
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.
|
||||
|
||||
Clients connect to the daemon over WebSocket. There are two ways to establish this connection:
|
||||
|
||||
- **Relay connection (recommended)** — The daemon connects outbound to our relay server, and clients meet it there. No open ports required.
|
||||
- **Direct connection** — The daemon listens on a network address and clients connect directly.
|
||||
|
||||
## Relay connections (recommended)
|
||||
|
||||
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 _outbound_ to the relay, and your phone meets it there.
|
||||
|
||||
> **The relay is designed to be untrusted.** 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.
|
||||
|
||||
### How it works
|
||||
|
||||
1. The daemon generates a persistent ECDH keypair and stores it in `$PASEO_HOME/daemon-keypair.json`
|
||||
2. When you scan the QR code or click the pairing link, your phone receives the daemon's public key
|
||||
3. Your phone sends a handshake message with its own public key. The daemon will not accept any commands until this handshake completes.
|
||||
4. Both sides perform an ECDH key exchange to derive a shared secret. All subsequent messages are encrypted with AES-256-GCM.
|
||||
|
||||
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.
|
||||
|
||||
### Why the relay can't attack you
|
||||
|
||||
The daemon requires a valid cryptographic handshake before processing any commands. A compromised relay cannot:
|
||||
|
||||
- **Send commands** — Without your phone's private key, it cannot complete the handshake
|
||||
- **Read your traffic** — All messages are encrypted with AES-256-GCM after the handshake
|
||||
- **Forge messages** — GCM provides authenticated encryption; tampered messages are rejected
|
||||
- **Replay old messages** — Each session derives fresh encryption keys
|
||||
|
||||
### Trust model
|
||||
|
||||
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.
|
||||
|
||||
If you believe a pairing offer has been compromised, restart the daemon to generate a new session ID and rotate the relay pairing.
|
||||
|
||||
## Direct connections
|
||||
|
||||
By default, the daemon listens on `127.0.0.1:6767` (localhost only). This is safe for local CLI usage but not reachable from your phone or other devices.
|
||||
|
||||
### Socket file (CLI only)
|
||||
|
||||
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.
|
||||
|
||||
### VPN access
|
||||
|
||||
If you prefer direct connections over the relay, you can use a VPN like [Tailscale](https://tailscale.com). Tailscale creates a private network between your devices, so you can access your daemon without exposing it to the public internet.
|
||||
|
||||
To set this up:
|
||||
|
||||
1. Install Tailscale on your machine and phone and join them to the same [tailnet](https://tailscale.com/kb/1136/tailnet)
|
||||
2. Configure the daemon to listen on your Tailscale IP (e.g., `100.x.y.z:6767`)
|
||||
3. Add your Tailscale hostname to `hostnames` and `cors.allowedOrigins`
|
||||
4. Add the daemon as a direct connection in the Paseo app using the Tailscale address
|
||||
|
||||
### Binding to 0.0.0.0
|
||||
|
||||
> **Warning:** Binding to `0.0.0.0` 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 `hostnames` configuration.
|
||||
|
||||
## DNS rebinding protection
|
||||
|
||||
**CORS is not a complete security boundary.** 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).
|
||||
|
||||
Paseo uses a host allowlist to validate the `Host` header on incoming requests. Requests with unrecognized hosts are rejected.
|
||||
|
||||
Configure via `daemon.hostnames` in `config.json`:
|
||||
|
||||
- Default (`[]`): allow `localhost`, `*.localhost`, and all IP addresses
|
||||
- `['.example.com']`: allow `example.com` and any subdomain, plus defaults
|
||||
- `true`: allow any host (not recommended)
|
||||
|
||||
## Agent authentication
|
||||
|
||||
Paseo wraps agent CLIs (Claude Code, Codex, OpenCode) but does not manage their authentication. Each agent provider handles its own credentials:
|
||||
|
||||
- **Claude Code** — authenticates via Anthropic's OAuth flow, stored in `~/.claude/`
|
||||
- **Codex** — uses your OpenAI API key or OAuth session
|
||||
- **OpenCode** — configured via provider-specific API keys
|
||||
|
||||
Paseo never stores or transmits provider API keys. Agents run in your user context with your existing credentials.
|
||||
|
||||
## Recommendations
|
||||
|
||||
- **Use the relay** for mobile access — it's the simplest option and all traffic is end-to-end encrypted
|
||||
- **Treat the QR code like a password** — anyone with the pairing offer can connect to your daemon
|
||||
- **Never bind to 0.0.0.0** unless you understand the implications and have proper firewall rules
|
||||
- **Keep your daemon updated** — security improvements are released regularly
|
||||
77
public-docs/skills.md
Normal file
77
public-docs/skills.md
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Orchestration skills
|
||||
description: "Paseo orchestration skills: teach coding agents to spawn, coordinate, and manage other agents using slash commands."
|
||||
nav: Skills
|
||||
order: 6
|
||||
---
|
||||
|
||||
# Orchestration skills
|
||||
|
||||
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.
|
||||
|
||||
## Installation
|
||||
|
||||
Two ways to install:
|
||||
|
||||
- **Desktop app:** Settings → Integrations → Install
|
||||
- **Manual:** `npx skills add getpaseo/paseo` — this installs to `~/.agents/skills/` and sets up symlinks for each agent.
|
||||
|
||||
## `/paseo` — CLI Reference
|
||||
|
||||
The foundational skill. Loaded automatically by other skills. Contains the full Paseo CLI command reference so agents know how to run commands.
|
||||
|
||||
Not typically invoked directly by users — it's a reference that other skills depend on.
|
||||
|
||||
## `/paseo-handoff` — Task Handoff
|
||||
|
||||
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.
|
||||
|
||||
Default provider is Codex. Can specify Claude (sonnet/opus). Supports `--worktree` for isolated git branches.
|
||||
|
||||
```
|
||||
/paseo-handoff hand off the auth fix to codex in a worktree
|
||||
/paseo-handoff hand this to claude opus for review
|
||||
```
|
||||
|
||||
## `/paseo-loop` — Iterative Loops
|
||||
|
||||
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).
|
||||
|
||||
Stop conditions: `--max-iterations`, `--max-time`, or verification passes.
|
||||
|
||||
```
|
||||
/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
|
||||
```
|
||||
|
||||
## `/paseo-orchestrator` — Team Orchestration
|
||||
|
||||
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.
|
||||
|
||||
Cross-provider: typically Codex for implementation, Claude for review.
|
||||
|
||||
```
|
||||
/paseo-orchestrator spin up a team to implement the database migration, codex implements, claude reviews
|
||||
```
|
||||
|
||||
## `/paseo-chat` — Chat Rooms
|
||||
|
||||
Use persistent chat rooms for asynchronous agent coordination. Create rooms, post messages, read history, wait for replies. Supports @mentions for specific agents or @everyone.
|
||||
|
||||
Typically used by the orchestrator skill, but can be used directly.
|
||||
|
||||
```
|
||||
/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"
|
||||
```
|
||||
|
||||
## `/paseo-committee` — Committee Planning
|
||||
|
||||
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.
|
||||
|
||||
Agents are prevented from editing code — they only produce a plan.
|
||||
|
||||
```
|
||||
/paseo-committee why are the websocket connections dropping under load?
|
||||
/paseo-committee plan the auth system migration
|
||||
```
|
||||
42
public-docs/updates.md
Normal file
42
public-docs/updates.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Updates
|
||||
description: How to update Paseo daemon and apps across web, desktop, and mobile.
|
||||
nav: Updates
|
||||
order: 2
|
||||
---
|
||||
|
||||
# Updates
|
||||
|
||||
Keep your daemon and apps current to get the latest fixes and features.
|
||||
|
||||
## Version compatibility
|
||||
|
||||
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.
|
||||
|
||||
## Update the daemon
|
||||
|
||||
Install the latest CLI/daemon package globally:
|
||||
|
||||
```bash
|
||||
npm install -g @getpaseo/cli@latest
|
||||
```
|
||||
|
||||
Then restart the daemon:
|
||||
|
||||
```bash
|
||||
paseo daemon restart
|
||||
```
|
||||
|
||||
## Web app
|
||||
|
||||
[app.paseo.sh](https://app.paseo.sh) is always up to date. No manual update needed.
|
||||
|
||||
## Desktop app
|
||||
|
||||
Download the latest desktop build from the GitHub releases page and install it over your current version.
|
||||
|
||||
[Paseo releases](https://github.com/getpaseo/paseo/releases)
|
||||
|
||||
## Mobile apps
|
||||
|
||||
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.
|
||||
81
public-docs/voice.md
Normal file
81
public-docs/voice.md
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: Voice
|
||||
description: Paseo voice architecture, local-first model execution, and provider configuration.
|
||||
nav: Voice
|
||||
order: 3
|
||||
---
|
||||
|
||||
# Voice
|
||||
|
||||
Paseo has first-class voice support for dictation and realtime conversations with your coding environment.
|
||||
|
||||
## Philosophy
|
||||
|
||||
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.
|
||||
|
||||
This keeps credentials and execution in your environment and avoids introducing a separate cloud-only voice stack.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Speech I/O: STT and TTS providers per feature (`local` or `openai`)
|
||||
- Local speech runtime: ONNX models executed on CPU by default
|
||||
- Voice LLM orchestration: hidden agent session using your configured provider (`claude`, `codex`, or `opencode`)
|
||||
- Tooling path: MCP stdio bridge for voice tools and agent control
|
||||
|
||||
## Local Speech
|
||||
|
||||
Local speech defaults to model IDs `parakeet-tdt-0.6b-v3-int8` (STT) and `kokoro-en-v0_19` (TTS, speaker 0 / voice 00).
|
||||
|
||||
Missing models are downloaded at daemon startup into `$PASEO_HOME/models/local-speech`. Downloads happen only for missing files.
|
||||
|
||||
```json
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## OpenAI Speech Option
|
||||
|
||||
You can switch dictation, voice STT, and voice TTS to OpenAI by setting provider fields to `openai` and providing `OPENAI_API_KEY`.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"features": {
|
||||
"dictation": { "stt": { "provider": "openai" } },
|
||||
"voiceMode": {
|
||||
"stt": { "provider": "openai" },
|
||||
"tts": { "provider": "openai" }
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"openai": { "apiKey": "..." }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `OPENAI_API_KEY` — OpenAI speech credentials
|
||||
- `PASEO_VOICE_LLM_PROVIDER` — voice agent provider override
|
||||
- `PASEO_LOCAL_MODELS_DIR` — local model storage directory
|
||||
- `PASEO_DICTATION_LOCAL_STT_MODEL` — local dictation STT model ID
|
||||
- `PASEO_VOICE_LOCAL_STT_MODEL`, `PASEO_VOICE_LOCAL_TTS_MODEL` — local voice STT/TTS model IDs
|
||||
- `PASEO_VOICE_LOCAL_TTS_SPEAKER_ID`, `PASEO_VOICE_LOCAL_TTS_SPEED` — optional local voice TTS tuning
|
||||
|
||||
## Operational Notes
|
||||
|
||||
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.
|
||||
163
public-docs/worktrees.md
Normal file
163
public-docs/worktrees.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
title: Git worktrees
|
||||
description: Run agents in isolated git worktrees with setup hooks, scripts, and long-running services.
|
||||
nav: Git worktrees
|
||||
order: 4
|
||||
---
|
||||
|
||||
# Git worktrees
|
||||
|
||||
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 `paseo.json` file at your repo root.
|
||||
|
||||
## Layout and workflow
|
||||
|
||||
Worktrees live under `$PASEO_HOME/worktrees/`, 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.
|
||||
|
||||
```
|
||||
~/.paseo/worktrees/
|
||||
└── 1vnnm9k3/ # hash of source checkout path
|
||||
├── tidy-fox/ # worktree slug (branch set on first agent)
|
||||
└── bold-owl/
|
||||
```
|
||||
|
||||
1. Create a worktree — Paseo runs your setup hooks
|
||||
2. Launch an agent — a branch is created or assigned
|
||||
3. Review the diff against the base branch
|
||||
4. Merge or archive — archive runs teardown and removes the directory
|
||||
|
||||
## paseo.json
|
||||
|
||||
Drop a `paseo.json` 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.
|
||||
|
||||
```json
|
||||
{
|
||||
"worktree": {
|
||||
"setup": "npm ci",
|
||||
"teardown": "rm -rf .cache"
|
||||
},
|
||||
"scripts": {
|
||||
"test": { "command": "npm test" },
|
||||
"web": { "command": "npm run dev", "type": "service", "port": 3000 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Setup and teardown
|
||||
|
||||
`setup` runs once after the worktree is created. A fresh worktree has no installed dependencies and no ignored files (like `.env`), so use setup to install and copy what you need. `teardown` runs during archive, before the directory is removed.
|
||||
|
||||
```json
|
||||
{
|
||||
"worktree": {
|
||||
"setup": "npm ci\ncp \"$PASEO_SOURCE_CHECKOUT_PATH/.env\" .env\nnpm run db:migrate",
|
||||
"teardown": "npm run db:drop || true"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Both fields accept a multiline shell script or an array of commands; commands run sequentially either way.
|
||||
|
||||
Commands run with the worktree as `cwd`. Use `$PASEO_SOURCE_CHECKOUT_PATH` to reach files in the original checkout (untracked config, local caches, etc).
|
||||
|
||||
## Scripts and services
|
||||
|
||||
`scripts` are named commands you can run inside a worktree on demand. Mark one as a _service_ 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.
|
||||
|
||||
### Plain scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": { "command": "npm test" },
|
||||
"lint": { "command": "npm run lint" },
|
||||
"generate": { "command": "npm run codegen" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Services
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"web": {
|
||||
"type": "service",
|
||||
"command": "npm run dev -- --port $PASEO_PORT",
|
||||
"port": 3000
|
||||
},
|
||||
"api": {
|
||||
"type": "service",
|
||||
"command": "npm run api -- --port $PASEO_PORT"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Omit `port` to let Paseo auto-assign one. Bind your process to `$PASEO_PORT` rather than hard-coding — each worktree gets a distinct port so multiple copies of the same service coexist.
|
||||
|
||||
### Reverse proxy
|
||||
|
||||
Every service is reachable through the daemon at a deterministic hostname:
|
||||
|
||||
```
|
||||
http://<script>.<branch>.<project>.localhost:<daemon-port>
|
||||
|
||||
# on the default branch, the branch label is dropped:
|
||||
http://<script>.<project>.localhost:<daemon-port>
|
||||
```
|
||||
|
||||
`*.localhost` resolves to `127.0.0.1` on modern systems, so these URLs work out of the box. The proxy supports WebSocket upgrades.
|
||||
|
||||
### Service-to-service
|
||||
|
||||
Services launched from the same workspace see each other's ports and proxy URLs. Given `web` and `api` above, each process gets:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
Script names are upper-cased and non-alphanumerics become `_`. Point your frontend at `$PASEO_SERVICE_API_URL` instead of hard-coding a port.
|
||||
|
||||
## Terminals
|
||||
|
||||
Open terminals automatically when a worktree is created. Useful for tailing logs or leaving a REPL ready to go.
|
||||
|
||||
```json
|
||||
{
|
||||
"worktree": {
|
||||
"terminals": [
|
||||
{ "name": "logs", "command": "tail -f dev.log" },
|
||||
{ "name": "shell", "command": "bash" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
Setup, teardown, scripts, and services all see:
|
||||
|
||||
- `$PASEO_SOURCE_CHECKOUT_PATH` — the original repo root
|
||||
- `$PASEO_WORKTREE_PATH` — the worktree directory
|
||||
- `$PASEO_BRANCH_NAME` — the worktree's branch
|
||||
- `$PASEO_WORKTREE_PORT` — legacy per-worktree port (prefer `$PASEO_PORT` inside services)
|
||||
|
||||
Services additionally get:
|
||||
|
||||
- `$PASEO_PORT` — this service's assigned port
|
||||
- `$PASEO_URL` — this service's proxy URL
|
||||
- `$PASEO_SERVICE_<NAME>_PORT` / `_URL` — peer service ports and URLs
|
||||
- `$HOST` — `127.0.0.1` for local-only daemons, `0.0.0.0` when the daemon binds all interfaces
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
paseo run --worktree feature-auth --base main "implement auth"
|
||||
paseo worktree ls
|
||||
paseo worktree archive feature-auth
|
||||
```
|
||||
Reference in New Issue
Block a user