Merge branch 'main' into dev

Surgical merge of 74 commits from main into dev. Key features ported:

- Provider visibility gating (appVersion filtering for Pi/Copilot)
- Pi agent provider + Copilot re-enabled
- Provider-declared features system (Codex fast mode)
- Workspace dedup by worktree root (adapted to integer IDs)
- Bulk close archiving fix for stored agents
- Agent creation timeout increase to 60s
- Audio/voice crash fixes (external buffer copies)
- Multi-host setup fix
- Reload agent tab action

Dev architecture preserved: SQLite storage, service proxy,
workspace setup dialog, hover cards, removed launcher tabs.
This commit is contained in:
Mohamed Boudra
2026-04-05 12:59:07 +07:00
173 changed files with 8873 additions and 2316 deletions
@@ -6,13 +6,14 @@ import {
appStoreUrl,
playStoreUrl,
webAppUrl,
downloadOptions,
getDownloadOptions,
useDetectedPlatform,
AppleIcon,
AndroidIcon,
TerminalIcon,
GlobeIcon,
} from "~/downloads";
import { useRelease } from "~/routes/__root";
import { Mic } from "lucide-react";
import { HeroMockup } from "~/components/hero-mockup";
import { ClaudeIcon } from "~/components/mockup";
@@ -297,6 +298,8 @@ function MultiProviderSection() {
{ name: "Claude Code", icon: <ClaudeIcon size={28} /> },
{ name: "Codex", icon: <CodexIcon className="w-7 h-7" /> },
{ name: "OpenCode", icon: <OpenCodeIcon className="w-7 h-7" /> },
{ name: "Copilot", icon: <CopilotIcon className="w-7 h-7" /> },
{ name: "Pi", icon: <PiIcon className="w-7 h-7" /> },
];
return (
@@ -305,7 +308,18 @@ function MultiProviderSection() {
description="Run multiple providers from a single interface. Paseo runs the native agent harness as you'd normally run it, with your skills, config and MCP servers intact."
>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{providers.map((p) => (
{providers.slice(0, 3).map((p) => (
<div
key={p.name}
className="flex items-center justify-center gap-3 rounded-xl border border-white/10 bg-white/[0.03] px-5 py-4"
>
<span className="text-white/80">{p.icon}</span>
<span className="font-medium">{p.name}</span>
</div>
))}
</div>
<div className="grid grid-cols-2 gap-4 sm:w-2/3">
{providers.slice(3).map((p) => (
<div
key={p.name}
className="flex items-center justify-center gap-3 rounded-xl border border-white/10 bg-white/[0.03] px-5 py-4"
@@ -819,8 +833,9 @@ function GetStarted() {
}
function DownloadButton() {
const { version } = useRelease();
const detectedPlatform = useDetectedPlatform();
const primary = downloadOptions.find((o) => o.platform === detectedPlatform)!;
const primary = getDownloadOptions(version).find((o) => o.platform === detectedPlatform)!;
const PrimaryIcon = primary.icon;
return (
@@ -906,6 +921,42 @@ function OpenCodeIcon(props: React.SVGProps<SVGSVGElement>) {
);
}
function CopilotIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 416"
fill="currentColor"
aria-hidden="true"
{...props}
>
<path
d="M181.33 266.143c0-11.497 9.32-20.818 20.818-20.818 11.498 0 20.819 9.321 20.819 20.818v38.373c0 11.497-9.321 20.818-20.819 20.818-11.497 0-20.818-9.32-20.818-20.818v-38.373zM308.807 245.325c-11.477 0-20.798 9.321-20.798 20.818v38.373c0 11.497 9.32 20.818 20.798 20.818 11.497 0 20.818-9.32 20.818-20.818v-38.373c0-11.497-9.32-20.818-20.818-20.818z"
fillRule="evenodd"
/>
<path d="M512.002 246.393v57.384c-.02 7.411-3.696 14.638-9.67 19.011C431.767 374.444 344.695 416 256 416c-98.138 0-196.379-56.542-246.33-93.21-5.975-4.374-9.65-11.6-9.671-19.012v-57.384a35.347 35.347 0 016.857-20.922l15.583-21.085c8.336-11.312 20.757-14.31 33.98-14.31 4.988-56.953 16.794-97.604 45.024-127.354C155.194 5.77 226.56 0 256 0c29.441 0 100.807 5.77 154.557 62.722 28.19 29.75 40.036 70.401 45.025 127.354 13.263 0 25.602 2.936 33.958 14.31l15.583 21.127c4.476 6.077 6.878 13.345 6.878 20.88zm-97.666-26.075c-.677-13.058-11.292-18.19-22.338-21.824-11.64 7.309-25.848 10.183-39.46 10.183-14.454 0-41.432-3.47-63.872-25.869-5.667-5.625-9.527-14.454-12.155-24.247a212.902 212.902 0 00-20.469-1.088c-6.098 0-13.099.349-20.551 1.088-2.628 9.793-6.509 18.622-12.155 24.247-22.4 22.4-49.418 25.87-63.872 25.87-13.612 0-27.86-2.855-39.501-10.184-11.005 3.613-21.558 8.828-22.277 21.824-1.17 24.555-1.272 49.11-1.375 73.645-.041 12.318-.082 24.658-.288 36.976.062 7.166 4.374 13.818 10.882 16.774 52.97 24.124 103.045 36.278 149.137 36.278 46.01 0 96.085-12.154 149.014-36.278 6.508-2.956 10.84-9.608 10.881-16.774.637-36.832.124-73.809-1.642-110.62h.041zM107.521 168.97c8.643 8.623 24.966 14.392 42.56 14.392 13.448 0 39.03-2.874 60.156-24.329 9.28-8.951 15.05-31.35 14.413-54.079-.657-18.231-5.769-33.28-13.448-39.665-8.315-7.371-27.203-10.574-48.33-8.644-22.399 2.238-41.267 9.588-50.875 19.833-20.798 22.728-16.323 80.317-4.476 92.492zm130.556-56.008c.637 3.51.965 7.35 1.273 11.517 0 2.875 0 5.77-.308 8.952 6.406-.636 11.847-.636 16.959-.636s10.553 0 16.959.636c-.329-3.182-.329-6.077-.329-8.952.329-4.167.657-8.007 1.294-11.517-6.735-.637-12.812-.965-17.924-.965s-11.21.328-17.924.965zm49.275-8.008c-.637 22.728 5.133 45.128 14.413 54.08 21.105 21.454 46.708 24.328 60.155 24.328 17.596 0 33.918-5.769 42.561-14.392 11.847-12.175 16.322-69.764-4.476-92.492-9.608-10.245-28.476-17.595-50.875-19.833-21.127-1.93-40.015 1.273-48.33 8.644-7.679 6.385-12.791 21.434-13.448 39.665z" />
</svg>
);
}
function PiIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 800 800"
fill="currentColor"
aria-hidden="true"
{...props}
>
<path
d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"
fillRule="evenodd"
/>
<path d="M517.36 400 H634.72 V634.72 H517.36 Z" />
</svg>
);
}
function AppStoreIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
@@ -2,12 +2,12 @@ import "~/styles.css";
export function SiteHeader() {
return (
<header className="flex items-center justify-between gap-4">
<header className="flex flex-col items-center gap-4 md:flex-row md:justify-between">
<a href="/" className="flex items-center gap-3">
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
<span className="text-lg font-medium">Paseo</span>
</a>
<div className="flex items-center gap-4">
<div className="flex flex-wrap items-center justify-center gap-4">
<a
href="/blog"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
+44 -36
View File
@@ -1,16 +1,21 @@
import * as React from "react";
import websitePackage from "../package.json";
declare const __DESKTOP_VERSION__: string | undefined;
export function releaseBase(version: string) {
return `https://github.com/getpaseo/paseo/releases/download/v${version}`;
}
export const desktopVersion =
typeof __DESKTOP_VERSION__ !== "undefined" ? __DESKTOP_VERSION__ : websitePackage.version;
export const releaseBase = `https://github.com/getpaseo/paseo/releases/download/v${desktopVersion}`;
export const macAppleSiliconDownloadUrl = `${releaseBase}/Paseo-${desktopVersion}-arm64.dmg`;
export const macIntelDownloadUrl = `${releaseBase}/Paseo-${desktopVersion}-x64.dmg`;
export const linuxAppImageDownloadUrl = `${releaseBase}/Paseo-${desktopVersion}-x86_64.AppImage`;
export const linuxDebDownloadUrl = `${releaseBase}/Paseo-${desktopVersion}-amd64.deb`;
export const linuxRpmDownloadUrl = `${releaseBase}/Paseo-${desktopVersion}-x86_64.rpm`;
export function downloadUrls(version: string) {
const base = releaseBase(version);
return {
macAppleSilicon: `${base}/Paseo-${version}-arm64.dmg`,
macIntel: `${base}/Paseo-${version}-x64.dmg`,
linuxAppImage: `${base}/Paseo-${version}-x86_64.AppImage`,
linuxDeb: `${base}/Paseo-${version}-amd64.deb`,
linuxRpm: `${base}/Paseo-${version}-x86_64.rpm`,
windowsExe: `${base}/Paseo-Setup-${version}.exe`,
androidApk: `${base}/paseo-v${version}-android.apk`,
};
}
export const appStoreUrl = "https://apps.apple.com/app/paseo-pocket-engineer/id6758887924";
export const playStoreUrl = "https://play.google.com/store/apps/details?id=sh.paseo";
@@ -25,32 +30,35 @@ export interface DownloadOption {
icon: (props: React.SVGProps<SVGSVGElement>) => React.ReactElement;
}
export const downloadOptions: DownloadOption[] = [
{
platform: "mac-silicon",
label: "Mac",
href: macAppleSiliconDownloadUrl,
icon: AppleIcon,
},
{
platform: "mac-intel",
label: "Mac Intel",
href: macIntelDownloadUrl,
icon: AppleIcon,
},
{
platform: "windows",
label: "Windows",
href: `${releaseBase}/Paseo-Setup-${desktopVersion}.exe`,
icon: WindowsIcon,
},
{
platform: "linux",
label: "Linux",
href: linuxAppImageDownloadUrl,
icon: LinuxIcon,
},
];
export function getDownloadOptions(version: string): DownloadOption[] {
const urls = downloadUrls(version);
return [
{
platform: "mac-silicon",
label: "Mac",
href: urls.macAppleSilicon,
icon: AppleIcon,
},
{
platform: "mac-intel",
label: "Mac Intel",
href: urls.macIntel,
icon: AppleIcon,
},
{
platform: "windows",
label: "Windows",
href: urls.windowsExe,
icon: WindowsIcon,
},
{
platform: "linux",
label: "Linux",
href: urls.linuxAppImage,
icon: LinuxIcon,
},
];
}
export function useDetectedPlatform(): Platform {
const [platform, setPlatform] = React.useState<Platform>("mac-silicon");
+69
View File
@@ -0,0 +1,69 @@
import { createServerFn } from "@tanstack/react-start";
import websitePackage from "../package.json";
interface GitHubAsset {
name: string;
}
interface GitHubRelease {
tag_name: string;
assets: GitHubAsset[];
prerelease: boolean;
draft: boolean;
}
const REQUIRED_ASSET_PATTERNS = [
/Paseo-.*-arm64\.dmg$/, // Mac Apple Silicon
/Paseo-.*-x86_64\.AppImage$/, // Linux AppImage
/Paseo-Setup-.*\.exe$/, // Windows
];
function hasRequiredAssets(release: GitHubRelease): boolean {
return REQUIRED_ASSET_PATTERNS.every((pattern) =>
release.assets.some((asset) => pattern.test(asset.name)),
);
}
function versionFromTag(tag: string): string {
return tag.replace(/^v/, "");
}
const GITHUB_RELEASES_URL =
"https://api.github.com/repos/getpaseo/paseo/releases?per_page=10";
async function fetchLatestReadyRelease(): Promise<string> {
const fallback = websitePackage.version.replace(/-.*$/, "");
try {
const res = await fetch(GITHUB_RELEASES_URL, {
headers: {
Accept: "application/vnd.github+json",
"User-Agent": "paseo-website",
},
// Cloudflare Workers: cache the upstream response with stale-while-revalidate.
// Fresh for 60s, serve stale for up to 300s while revalidating in background.
// In non-Workers environments this is ignored.
cf: {
cacheEverything: true,
cacheTtl: 60,
cacheKey: "github-releases-latest",
},
} as RequestInit);
if (!res.ok) return fallback;
const releases = (await res.json()) as GitHubRelease[];
const ready = releases.find(
(r) => !r.prerelease && !r.draft && hasRequiredAssets(r),
);
return ready ? versionFromTag(ready.tag_name) : fallback;
} catch {
return fallback;
}
}
export const getLatestRelease = createServerFn({ method: "GET" }).handler(
async () => {
const version = await fetchLatestReadyRelease();
return { version };
},
);
+21
View File
@@ -25,6 +25,7 @@ import { Route as DocsVoiceRouteImport } from './routes/docs/voice'
import { Route as DocsUpdatesRouteImport } from './routes/docs/updates'
import { Route as DocsSecurityRouteImport } from './routes/docs/security'
import { Route as DocsConfigurationRouteImport } from './routes/docs/configuration'
import { Route as DocsSkillsRouteImport } from './routes/docs/skills'
import { Route as DocsCliRouteImport } from './routes/docs/cli'
import { Route as DocsBestPracticesRouteImport } from './routes/docs/best-practices'
import { Route as BlogSplatRouteImport } from './routes/blog/$'
@@ -109,6 +110,11 @@ const DocsConfigurationRoute = DocsConfigurationRouteImport.update({
path: '/configuration',
getParentRoute: () => DocsRoute,
} as any)
const DocsSkillsRoute = DocsSkillsRouteImport.update({
id: '/skills',
path: '/skills',
getParentRoute: () => DocsRoute,
} as any)
const DocsCliRoute = DocsCliRouteImport.update({
id: '/cli',
path: '/cli',
@@ -140,6 +146,7 @@ export interface FileRoutesByFullPath {
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/skills': typeof DocsSkillsRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
@@ -159,6 +166,7 @@ export interface FileRoutesByTo {
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/skills': typeof DocsSkillsRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
@@ -181,6 +189,7 @@ export interface FileRoutesById {
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/skills': typeof DocsSkillsRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
@@ -204,6 +213,7 @@ export interface FileRouteTypes {
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/skills'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
@@ -223,6 +233,7 @@ export interface FileRouteTypes {
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/skills'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
@@ -244,6 +255,7 @@ export interface FileRouteTypes {
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/skills'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
@@ -377,6 +389,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DocsConfigurationRouteImport
parentRoute: typeof DocsRoute
}
'/docs/skills': {
id: '/docs/skills'
path: '/skills'
fullPath: '/docs/skills'
preLoaderRoute: typeof DocsSkillsRouteImport
parentRoute: typeof DocsRoute
}
'/docs/cli': {
id: '/docs/cli'
path: '/cli'
@@ -418,6 +437,7 @@ interface DocsRouteChildren {
DocsCliRoute: typeof DocsCliRoute
DocsConfigurationRoute: typeof DocsConfigurationRoute
DocsSecurityRoute: typeof DocsSecurityRoute
DocsSkillsRoute: typeof DocsSkillsRoute
DocsUpdatesRoute: typeof DocsUpdatesRoute
DocsVoiceRoute: typeof DocsVoiceRoute
DocsWorktreesRoute: typeof DocsWorktreesRoute
@@ -429,6 +449,7 @@ const DocsRouteChildren: DocsRouteChildren = {
DocsCliRoute: DocsCliRoute,
DocsConfigurationRoute: DocsConfigurationRoute,
DocsSecurityRoute: DocsSecurityRoute,
DocsSkillsRoute: DocsSkillsRoute,
DocsUpdatesRoute: DocsUpdatesRoute,
DocsVoiceRoute: DocsVoiceRoute,
DocsWorktreesRoute: DocsWorktreesRoute,
+21 -3
View File
@@ -1,7 +1,22 @@
import type { ReactNode } from "react";
import { createContext, useContext } from "react";
import { Outlet, createRootRoute, HeadContent, Scripts } from "@tanstack/react-router";
import { getLatestRelease } from "~/release";
interface ReleaseContext {
version: string;
}
const ReleaseCtx = createContext<ReleaseContext>({ version: "" });
export function useRelease(): ReleaseContext {
return useContext(ReleaseCtx);
}
export const Route = createRootRoute({
loader: async () => {
return getLatestRelease();
},
head: () => ({
meta: [
{ charSet: "utf-8" },
@@ -23,10 +38,13 @@ export const Route = createRootRoute({
});
function RootComponent() {
const release = Route.useLoaderData();
return (
<RootDocument>
<Outlet />
</RootDocument>
<ReleaseCtx value={release}>
<RootDocument>
<Outlet />
</RootDocument>
</ReleaseCtx>
);
}
+1
View File
@@ -11,6 +11,7 @@ const navigation = [
{ name: "Voice", href: "/docs/voice" },
{ name: "Git worktrees", href: "/docs/worktrees" },
{ name: "CLI", href: "/docs/cli" },
{ name: "Skills", href: "/docs/skills" },
{ name: "Configuration", href: "/docs/configuration" },
{ name: "Security", href: "/docs/security" },
{ name: "Best practices", href: "/docs/best-practices" },
+162
View File
@@ -0,0 +1,162 @@
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>
);
}
+13 -15
View File
@@ -2,13 +2,7 @@ import { Link, createFileRoute } from "@tanstack/react-router";
import { CommandDialog } from "~/components/command-dialog";
import { pageMeta } from "~/meta";
import {
desktopVersion,
linuxAppImageDownloadUrl,
linuxDebDownloadUrl,
linuxRpmDownloadUrl,
releaseBase,
macAppleSiliconDownloadUrl,
macIntelDownloadUrl,
downloadUrls,
appStoreUrl,
playStoreUrl,
webAppUrl,
@@ -19,6 +13,7 @@ import {
TerminalIcon,
GlobeIcon,
} from "~/downloads";
import { useRelease } from "~/routes/__root";
import "~/styles.css";
export const Route = createFileRoute("/download")({
@@ -32,6 +27,9 @@ export const Route = createFileRoute("/download")({
});
function Download() {
const { version } = useRelease();
const urls = downloadUrls(version);
return (
<div className="min-h-screen bg-background">
<div className="max-w-3xl mx-auto p-6 md:p-12">
@@ -93,7 +91,7 @@ function Download() {
<h1 className="text-3xl md:text-4xl font-semibold tracking-tight mb-2">Download</h1>
<p className="text-muted-foreground mb-10">
v{desktopVersion}
v{version}
</p>
{/* Desktop */}
@@ -111,8 +109,8 @@ function Download() {
<span className="font-medium">macOS</span>
</div>
<div className="flex flex-wrap items-center gap-2">
<DownloadPill href={macAppleSiliconDownloadUrl} label="Apple Silicon" />
<DownloadPill href={macIntelDownloadUrl} label="Intel" />
<DownloadPill href={urls.macAppleSilicon} label="Apple Silicon" />
<DownloadPill href={urls.macIntel} label="Intel" />
<CommandDialog
trigger={
<span className="inline-flex items-center justify-center rounded-full bg-foreground px-4 py-1.5 text-sm font-medium text-background hover:bg-foreground/85 transition-colors">
@@ -133,7 +131,7 @@ function Download() {
</div>
<div className="flex flex-wrap gap-2">
<DownloadPill
href={`${releaseBase}/Paseo-Setup-${desktopVersion}.exe`}
href={urls.windowsExe}
label="Download"
/>
</div>
@@ -147,11 +145,11 @@ function Download() {
</div>
<div className="flex flex-wrap gap-2">
<DownloadPill
href={linuxAppImageDownloadUrl}
href={urls.linuxAppImage}
label="AppImage"
/>
<DownloadPill href={linuxDebDownloadUrl} label="DEB" />
<DownloadPill href={linuxRpmDownloadUrl} label="RPM" />
<DownloadPill href={urls.linuxDeb} label="DEB" />
<DownloadPill href={urls.linuxRpm} label="RPM" />
</div>
</div>
</div>
@@ -178,7 +176,7 @@ function Download() {
external
/>
<DownloadPill
href={`${releaseBase}/paseo-v${desktopVersion}-android.apk`}
href={urls.androidApk}
label="APK"
/>
</div>