website: add /cloud design partners page and shared shell
- New /cloud page with signup form (email, name, company, role, message) posting to a Discord webhook via a TanStack Start server function on the Cloudflare Worker. Webhook URL read from DISCORD_WEBHOOK_URL secret. - Extract SiteShell, SiteFooter, FAQItem from landing-page. - Migrate /privacy, /download, /blog, /changelog, /cloud to SiteShell so they share header + footer with consistent width. - Add /cloud to header and footer nav. - Make /docs sidebar full-width; bump footer text from xs to sm. - Add Cloudflare Workers types and ignore .dev.vars.
This commit is contained in:
@@ -13,6 +13,8 @@ out/
|
|||||||
.env.local
|
.env.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env*.local
|
.env*.local
|
||||||
|
.dev.vars
|
||||||
|
**/.dev.vars
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { env } from "cloudflare:workers";
|
||||||
|
import { createServerFn } from "@tanstack/react-start";
|
||||||
|
|
||||||
|
export interface CloudSignupInput {
|
||||||
|
email: string;
|
||||||
|
name?: string;
|
||||||
|
company?: string;
|
||||||
|
role?: string;
|
||||||
|
message: string;
|
||||||
|
honeypot?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
|
function asString(v: unknown, max: number): string {
|
||||||
|
if (typeof v !== "string") throw new Error("expected string");
|
||||||
|
const trimmed = v.trim();
|
||||||
|
if (trimmed.length > max) throw new Error("field too long");
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(raw: unknown): CloudSignupInput {
|
||||||
|
if (typeof raw !== "object" || raw === null) throw new Error("invalid input");
|
||||||
|
const r = raw as Record<string, unknown>;
|
||||||
|
|
||||||
|
const email = asString(r.email, 320);
|
||||||
|
if (!EMAIL_RE.test(email)) throw new Error("invalid email");
|
||||||
|
|
||||||
|
const message = asString(r.message, 4000);
|
||||||
|
if (message.length === 0) throw new Error("message required");
|
||||||
|
|
||||||
|
const name = r.name === undefined || r.name === "" ? undefined : asString(r.name, 200);
|
||||||
|
const company =
|
||||||
|
r.company === undefined || r.company === "" ? undefined : asString(r.company, 200);
|
||||||
|
const role = r.role === undefined || r.role === "" ? undefined : asString(r.role, 200);
|
||||||
|
const honeypot = typeof r.honeypot === "string" ? r.honeypot : "";
|
||||||
|
|
||||||
|
return { email, name, company, role, message, honeypot };
|
||||||
|
}
|
||||||
|
|
||||||
|
function truncate(s: string, max: number): string {
|
||||||
|
return s.length <= max ? s : `${s.slice(0, max - 1)}…`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEmbed(input: CloudSignupInput) {
|
||||||
|
const fields: Array<{ name: string; value: string; inline?: boolean }> = [
|
||||||
|
{ name: "Email", value: truncate(input.email, 1024), inline: true },
|
||||||
|
];
|
||||||
|
if (input.name) {
|
||||||
|
fields.push({ name: "Name", value: truncate(input.name, 1024), inline: true });
|
||||||
|
}
|
||||||
|
if (input.company) {
|
||||||
|
fields.push({ name: "Company", value: truncate(input.company, 1024), inline: true });
|
||||||
|
}
|
||||||
|
if (input.role) {
|
||||||
|
fields.push({ name: "Role", value: truncate(input.role, 1024), inline: true });
|
||||||
|
}
|
||||||
|
fields.push({ name: "Message", value: truncate(input.message, 1024) });
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: "Paseo Cloud signup",
|
||||||
|
color: 0x5865f2,
|
||||||
|
fields,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const submitCloudSignup = createServerFn({ method: "POST" })
|
||||||
|
.inputValidator(validate)
|
||||||
|
.handler(async ({ data }) => {
|
||||||
|
if (data.honeypot) return { ok: true };
|
||||||
|
|
||||||
|
const url = (env as { DISCORD_WEBHOOK_URL?: string }).DISCORD_WEBHOOK_URL;
|
||||||
|
if (!url) throw new Error("webhook not configured");
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ embeds: [buildEmbed(data)] }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error(`webhook ${res.status}`);
|
||||||
|
return { ok: true };
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
export function FAQItem({ question, children }: { question: string; children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<details className="group">
|
||||||
|
<summary className="font-medium text-sm cursor-pointer list-none flex items-start gap-2 -ml-4">
|
||||||
|
<span className="font-mono text-white/40 flex-shrink-0 group-open:hidden">+</span>
|
||||||
|
<span className="font-mono text-white/40 flex-shrink-0 hidden group-open:inline">−</span>
|
||||||
|
{question}
|
||||||
|
</summary>
|
||||||
|
<div className="text-sm text-muted-foreground space-y-2 mt-2 prose">{children}</div>
|
||||||
|
</details>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -48,6 +48,8 @@ import { useRelease } from "~/routes/__root";
|
|||||||
import { Mic } from "lucide-react";
|
import { Mic } from "lucide-react";
|
||||||
import { HeroMockup } from "~/components/hero-mockup";
|
import { HeroMockup } from "~/components/hero-mockup";
|
||||||
import { ClaudeIcon } from "~/components/mockup";
|
import { ClaudeIcon } from "~/components/mockup";
|
||||||
|
import { FAQItem } from "~/components/faq-item";
|
||||||
|
import { SiteFooter } from "~/components/site-footer";
|
||||||
import { SiteHeader } from "~/components/site-header";
|
import { SiteHeader } from "~/components/site-header";
|
||||||
import "~/styles.css";
|
import "~/styles.css";
|
||||||
|
|
||||||
@@ -97,126 +99,7 @@ export function LandingPage({ title, subtitle }: LandingPageProps) {
|
|||||||
<SponsorCTA />
|
<SponsorCTA />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer className="p-6 md:p-20 md:pt-0 max-w-5xl mx-auto">
|
<SiteFooter />
|
||||||
<div className="border-t border-white/10 pt-8 pb-4 grid grid-cols-2 sm:grid-cols-4 gap-8 text-xs">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<p className="text-white/60 font-medium">Product</p>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<a
|
|
||||||
href="/blog"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Blog
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/docs"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Docs
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/changelog"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Changelog
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/docs/cli"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
CLI
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/privacy"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Privacy
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<p className="text-white/60 font-medium">Agents</p>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<a
|
|
||||||
href="/claude-code"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Claude Code
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/codex"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Codex
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="/opencode"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
OpenCode
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<p className="text-white/60 font-medium">Community</p>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<a
|
|
||||||
href="https://discord.gg/jz8T2uahpH"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Discord
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/getpaseo/paseo"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<p className="text-white/60 font-medium">Download</p>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<a
|
|
||||||
href={appStoreUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
App Store
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href={playStoreUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Google Play
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/getpaseo/paseo/releases"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Desktop
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href={webAppUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
|
||||||
>
|
|
||||||
Web App
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
</CursorFieldProvider>
|
</CursorFieldProvider>
|
||||||
);
|
);
|
||||||
@@ -1685,16 +1568,3 @@ function SponsorCTA() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FAQItem({ question, children }: { question: string; children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<details className="group">
|
|
||||||
<summary className="font-medium text-sm cursor-pointer list-none flex items-start gap-2 -ml-4">
|
|
||||||
<span className="font-mono text-white/40 flex-shrink-0 group-open:hidden">+</span>
|
|
||||||
<span className="font-mono text-white/40 flex-shrink-0 hidden group-open:inline">−</span>
|
|
||||||
{question}
|
|
||||||
</summary>
|
|
||||||
<div className="text-sm text-muted-foreground space-y-2 mt-2 prose">{children}</div>
|
|
||||||
</details>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { appStoreUrl, playStoreUrl, webAppUrl } from "~/downloads";
|
||||||
|
|
||||||
|
interface SiteFooterProps {
|
||||||
|
width?: "default" | "prose";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||||
|
const widthClasses =
|
||||||
|
width === "prose" ? "max-w-prose p-6 md:p-12 md:pt-0" : "max-w-5xl p-6 md:p-20 md:pt-0";
|
||||||
|
return (
|
||||||
|
<footer className={`${widthClasses} mx-auto`}>
|
||||||
|
<div className="border-t border-white/10 pt-8 pb-4 grid grid-cols-2 sm:grid-cols-4 gap-8 text-sm">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-white/60 font-medium">Product</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<a href="/blog" className="block text-white/40 hover:text-white/60 transition-colors">
|
||||||
|
Blog
|
||||||
|
</a>
|
||||||
|
<a href="/docs" className="block text-white/40 hover:text-white/60 transition-colors">
|
||||||
|
Docs
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/changelog"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
Changelog
|
||||||
|
</a>
|
||||||
|
<a href="/cloud" className="block text-white/40 hover:text-white/60 transition-colors">
|
||||||
|
Cloud
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/docs/cli"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
CLI
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/privacy"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
Privacy
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-white/60 font-medium">Agents</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<a
|
||||||
|
href="/claude-code"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
Claude Code
|
||||||
|
</a>
|
||||||
|
<a href="/codex" className="block text-white/40 hover:text-white/60 transition-colors">
|
||||||
|
Codex
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/opencode"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
OpenCode
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-white/60 font-medium">Community</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/jz8T2uahpH"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/getpaseo/paseo"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-white/60 font-medium">Download</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<a
|
||||||
|
href={appStoreUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
App Store
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={playStoreUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
Google Play
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/getpaseo/paseo/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
Desktop
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={webAppUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||||
|
>
|
||||||
|
Web App
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,6 +28,12 @@ export function SiteHeader() {
|
|||||||
>
|
>
|
||||||
Changelog
|
Changelog
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
href="/cloud"
|
||||||
|
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
Cloud
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
href="/download"
|
href="/download"
|
||||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { SiteFooter } from "~/components/site-footer";
|
||||||
|
import { SiteHeader } from "~/components/site-header";
|
||||||
|
|
||||||
|
interface SiteShellProps {
|
||||||
|
children: ReactNode;
|
||||||
|
width?: "default" | "prose";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SiteShell({ children, width = "default" }: SiteShellProps) {
|
||||||
|
const mainClasses =
|
||||||
|
width === "prose" ? "max-w-prose p-6 md:p-12 mx-auto" : "max-w-5xl p-6 md:p-20 mx-auto";
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<main className={mainClasses}>
|
||||||
|
<div className="mb-12">
|
||||||
|
<SiteHeader />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<SiteFooter width={width} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { Route as OpencodeRouteImport } from "./routes/opencode";
|
|||||||
import { Route as DownloadRouteImport } from "./routes/download";
|
import { Route as DownloadRouteImport } from "./routes/download";
|
||||||
import { Route as DocsRouteImport } from "./routes/docs";
|
import { Route as DocsRouteImport } from "./routes/docs";
|
||||||
import { Route as CodexRouteImport } from "./routes/codex";
|
import { Route as CodexRouteImport } from "./routes/codex";
|
||||||
|
import { Route as CloudRouteImport } from "./routes/cloud";
|
||||||
import { Route as ClaudeCodeRouteImport } from "./routes/claude-code";
|
import { Route as ClaudeCodeRouteImport } from "./routes/claude-code";
|
||||||
import { Route as ChangelogRouteImport } from "./routes/changelog";
|
import { Route as ChangelogRouteImport } from "./routes/changelog";
|
||||||
import { Route as BlogRouteImport } from "./routes/blog";
|
import { Route as BlogRouteImport } from "./routes/blog";
|
||||||
@@ -48,6 +49,11 @@ const CodexRoute = CodexRouteImport.update({
|
|||||||
path: "/codex",
|
path: "/codex",
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any);
|
} as any);
|
||||||
|
const CloudRoute = CloudRouteImport.update({
|
||||||
|
id: "/cloud",
|
||||||
|
path: "/cloud",
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any);
|
||||||
const ClaudeCodeRoute = ClaudeCodeRouteImport.update({
|
const ClaudeCodeRoute = ClaudeCodeRouteImport.update({
|
||||||
id: "/claude-code",
|
id: "/claude-code",
|
||||||
path: "/claude-code",
|
path: "/claude-code",
|
||||||
@@ -94,6 +100,7 @@ export interface FileRoutesByFullPath {
|
|||||||
"/blog": typeof BlogRouteWithChildren;
|
"/blog": typeof BlogRouteWithChildren;
|
||||||
"/changelog": typeof ChangelogRoute;
|
"/changelog": typeof ChangelogRoute;
|
||||||
"/claude-code": typeof ClaudeCodeRoute;
|
"/claude-code": typeof ClaudeCodeRoute;
|
||||||
|
"/cloud": typeof CloudRoute;
|
||||||
"/codex": typeof CodexRoute;
|
"/codex": typeof CodexRoute;
|
||||||
"/docs": typeof DocsRouteWithChildren;
|
"/docs": typeof DocsRouteWithChildren;
|
||||||
"/download": typeof DownloadRoute;
|
"/download": typeof DownloadRoute;
|
||||||
@@ -108,6 +115,7 @@ export interface FileRoutesByTo {
|
|||||||
"/": typeof IndexRoute;
|
"/": typeof IndexRoute;
|
||||||
"/changelog": typeof ChangelogRoute;
|
"/changelog": typeof ChangelogRoute;
|
||||||
"/claude-code": typeof ClaudeCodeRoute;
|
"/claude-code": typeof ClaudeCodeRoute;
|
||||||
|
"/cloud": typeof CloudRoute;
|
||||||
"/codex": typeof CodexRoute;
|
"/codex": typeof CodexRoute;
|
||||||
"/download": typeof DownloadRoute;
|
"/download": typeof DownloadRoute;
|
||||||
"/opencode": typeof OpencodeRoute;
|
"/opencode": typeof OpencodeRoute;
|
||||||
@@ -123,6 +131,7 @@ export interface FileRoutesById {
|
|||||||
"/blog": typeof BlogRouteWithChildren;
|
"/blog": typeof BlogRouteWithChildren;
|
||||||
"/changelog": typeof ChangelogRoute;
|
"/changelog": typeof ChangelogRoute;
|
||||||
"/claude-code": typeof ClaudeCodeRoute;
|
"/claude-code": typeof ClaudeCodeRoute;
|
||||||
|
"/cloud": typeof CloudRoute;
|
||||||
"/codex": typeof CodexRoute;
|
"/codex": typeof CodexRoute;
|
||||||
"/docs": typeof DocsRouteWithChildren;
|
"/docs": typeof DocsRouteWithChildren;
|
||||||
"/download": typeof DownloadRoute;
|
"/download": typeof DownloadRoute;
|
||||||
@@ -140,6 +149,7 @@ export interface FileRouteTypes {
|
|||||||
| "/blog"
|
| "/blog"
|
||||||
| "/changelog"
|
| "/changelog"
|
||||||
| "/claude-code"
|
| "/claude-code"
|
||||||
|
| "/cloud"
|
||||||
| "/codex"
|
| "/codex"
|
||||||
| "/docs"
|
| "/docs"
|
||||||
| "/download"
|
| "/download"
|
||||||
@@ -154,6 +164,7 @@ export interface FileRouteTypes {
|
|||||||
| "/"
|
| "/"
|
||||||
| "/changelog"
|
| "/changelog"
|
||||||
| "/claude-code"
|
| "/claude-code"
|
||||||
|
| "/cloud"
|
||||||
| "/codex"
|
| "/codex"
|
||||||
| "/download"
|
| "/download"
|
||||||
| "/opencode"
|
| "/opencode"
|
||||||
@@ -168,6 +179,7 @@ export interface FileRouteTypes {
|
|||||||
| "/blog"
|
| "/blog"
|
||||||
| "/changelog"
|
| "/changelog"
|
||||||
| "/claude-code"
|
| "/claude-code"
|
||||||
|
| "/cloud"
|
||||||
| "/codex"
|
| "/codex"
|
||||||
| "/docs"
|
| "/docs"
|
||||||
| "/download"
|
| "/download"
|
||||||
@@ -184,6 +196,7 @@ export interface RootRouteChildren {
|
|||||||
BlogRoute: typeof BlogRouteWithChildren;
|
BlogRoute: typeof BlogRouteWithChildren;
|
||||||
ChangelogRoute: typeof ChangelogRoute;
|
ChangelogRoute: typeof ChangelogRoute;
|
||||||
ClaudeCodeRoute: typeof ClaudeCodeRoute;
|
ClaudeCodeRoute: typeof ClaudeCodeRoute;
|
||||||
|
CloudRoute: typeof CloudRoute;
|
||||||
CodexRoute: typeof CodexRoute;
|
CodexRoute: typeof CodexRoute;
|
||||||
DocsRoute: typeof DocsRouteWithChildren;
|
DocsRoute: typeof DocsRouteWithChildren;
|
||||||
DownloadRoute: typeof DownloadRoute;
|
DownloadRoute: typeof DownloadRoute;
|
||||||
@@ -228,6 +241,13 @@ declare module "@tanstack/react-router" {
|
|||||||
preLoaderRoute: typeof CodexRouteImport;
|
preLoaderRoute: typeof CodexRouteImport;
|
||||||
parentRoute: typeof rootRouteImport;
|
parentRoute: typeof rootRouteImport;
|
||||||
};
|
};
|
||||||
|
"/cloud": {
|
||||||
|
id: "/cloud";
|
||||||
|
path: "/cloud";
|
||||||
|
fullPath: "/cloud";
|
||||||
|
preLoaderRoute: typeof CloudRouteImport;
|
||||||
|
parentRoute: typeof rootRouteImport;
|
||||||
|
};
|
||||||
"/claude-code": {
|
"/claude-code": {
|
||||||
id: "/claude-code";
|
id: "/claude-code";
|
||||||
path: "/claude-code";
|
path: "/claude-code";
|
||||||
@@ -316,6 +336,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
BlogRoute: BlogRouteWithChildren,
|
BlogRoute: BlogRouteWithChildren,
|
||||||
ChangelogRoute: ChangelogRoute,
|
ChangelogRoute: ChangelogRoute,
|
||||||
ClaudeCodeRoute: ClaudeCodeRoute,
|
ClaudeCodeRoute: ClaudeCodeRoute,
|
||||||
|
CloudRoute: CloudRoute,
|
||||||
CodexRoute: CodexRoute,
|
CodexRoute: CodexRoute,
|
||||||
DocsRoute: DocsRouteWithChildren,
|
DocsRoute: DocsRouteWithChildren,
|
||||||
DownloadRoute: DownloadRoute,
|
DownloadRoute: DownloadRoute,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||||
import { SiteHeader } from "~/components/site-header";
|
import { SiteShell } from "~/components/site-shell";
|
||||||
|
|
||||||
export const Route = createFileRoute("/blog")({
|
export const Route = createFileRoute("/blog")({
|
||||||
component: BlogLayout,
|
component: BlogLayout,
|
||||||
@@ -7,13 +7,8 @@ export const Route = createFileRoute("/blog")({
|
|||||||
|
|
||||||
function BlogLayout() {
|
function BlogLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<SiteShell>
|
||||||
<div className="max-w-prose mx-auto p-6 md:p-12">
|
<Outlet />
|
||||||
<div className="mb-8">
|
</SiteShell>
|
||||||
<SiteHeader />
|
|
||||||
</div>
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import changelogMarkdown from "../../../../CHANGELOG.md?raw";
|
import changelogMarkdown from "../../../../CHANGELOG.md?raw";
|
||||||
|
import { SiteShell } from "~/components/site-shell";
|
||||||
import { pageMeta } from "~/meta";
|
import { pageMeta } from "~/meta";
|
||||||
import { SiteHeader } from "~/components/site-header";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/changelog")({
|
export const Route = createFileRoute("/changelog")({
|
||||||
head: () => ({
|
head: () => ({
|
||||||
@@ -16,15 +16,10 @@ export const Route = createFileRoute("/changelog")({
|
|||||||
|
|
||||||
function Changelog() {
|
function Changelog() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<SiteShell>
|
||||||
<div className="max-w-4xl mx-auto p-6 md:p-12">
|
<article className="changelog-markdown rounded-xl border border-border bg-card/40 p-6 md:p-8">
|
||||||
<div className="mb-8">
|
<ReactMarkdown>{changelogMarkdown}</ReactMarkdown>
|
||||||
<SiteHeader />
|
</article>
|
||||||
</div>
|
</SiteShell>
|
||||||
<article className="changelog-markdown rounded-xl border border-border bg-card/40 p-6 md:p-8">
|
|
||||||
<ReactMarkdown>{changelogMarkdown}</ReactMarkdown>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useCallback, useState, type FormEvent } from "react";
|
||||||
|
import { submitCloudSignup, type CloudSignupInput } from "~/cloud-signup";
|
||||||
|
import { FAQItem } from "~/components/faq-item";
|
||||||
|
import { SiteShell } from "~/components/site-shell";
|
||||||
|
import { pageMeta } from "~/meta";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/cloud")({
|
||||||
|
head: () => ({
|
||||||
|
meta: pageMeta(
|
||||||
|
"Paseo Cloud - Design Partners",
|
||||||
|
"Paseo across machines, with a team, or inside a company. Looking for design partners.",
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
component: Cloud,
|
||||||
|
});
|
||||||
|
|
||||||
|
const INPUT_CLASS =
|
||||||
|
"block w-full rounded-md bg-white/5 border border-white/10 px-3.5 py-2.5 text-sm text-white placeholder:text-white/30 focus:outline-none focus:border-white/30 transition-colors";
|
||||||
|
|
||||||
|
type Status = "idle" | "submitting" | "success" | "error";
|
||||||
|
|
||||||
|
function Cloud() {
|
||||||
|
return (
|
||||||
|
<SiteShell>
|
||||||
|
<h1 className="text-3xl font-medium mb-3">Paseo Cloud</h1>
|
||||||
|
<p className="text-white/70 leading-relaxed mb-10">
|
||||||
|
For using Paseo across machines, with a team, or inside a company. Looking for design
|
||||||
|
partners.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-20">
|
||||||
|
<SignupForm />
|
||||||
|
<FaqSection />
|
||||||
|
</div>
|
||||||
|
</SiteShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignupForm() {
|
||||||
|
const [status, setStatus] = useState<Status>("idle");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(async (event: FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setStatus("submitting");
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const form = new FormData(event.currentTarget);
|
||||||
|
const data: CloudSignupInput = {
|
||||||
|
email: String(form.get("email") ?? ""),
|
||||||
|
name: form.get("name") ? String(form.get("name")) : undefined,
|
||||||
|
company: form.get("company") ? String(form.get("company")) : undefined,
|
||||||
|
role: form.get("role") ? String(form.get("role")) : undefined,
|
||||||
|
message: String(form.get("message") ?? ""),
|
||||||
|
honeypot: form.get("website") ? String(form.get("website")) : "",
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await submitCloudSignup({ data });
|
||||||
|
setStatus("success");
|
||||||
|
} catch (err) {
|
||||||
|
setStatus("error");
|
||||||
|
setError(err instanceof Error ? err.message : "Something went wrong.");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (status === "success") {
|
||||||
|
return (
|
||||||
|
<section className="space-y-3">
|
||||||
|
<p className="text-white/70">
|
||||||
|
Got it. I'll be in touch. If you don't hear back within a week, ping me on{" "}
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/jz8T2uahpH"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-white/80"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitting = status === "submitting";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6" noValidate>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||||
|
<Field label="Email" required>
|
||||||
|
<input type="email" name="email" required autoComplete="email" className={INPUT_CLASS} />
|
||||||
|
</Field>
|
||||||
|
<Field label="Name">
|
||||||
|
<input type="text" name="name" autoComplete="name" className={INPUT_CLASS} />
|
||||||
|
</Field>
|
||||||
|
<Field label="Company">
|
||||||
|
<input type="text" name="company" autoComplete="organization" className={INPUT_CLASS} />
|
||||||
|
</Field>
|
||||||
|
<Field label="Role">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="role"
|
||||||
|
autoComplete="organization-title"
|
||||||
|
className={INPUT_CLASS}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Field label="Message" required>
|
||||||
|
<textarea
|
||||||
|
name="message"
|
||||||
|
required
|
||||||
|
rows={5}
|
||||||
|
placeholder="A bit about you and what you'd want from Paseo Cloud."
|
||||||
|
className={`${INPUT_CLASS} resize-y`}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="website"
|
||||||
|
tabIndex={-1}
|
||||||
|
autoComplete="off"
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute left-[-9999px] w-px h-px opacity-0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p className="text-sm text-red-400">
|
||||||
|
{error === "webhook not configured"
|
||||||
|
? "The form isn't wired up yet. Try Discord for now."
|
||||||
|
: "Something went wrong. Try again or DM me on Discord."}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 pt-4">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={submitting}
|
||||||
|
className="rounded-md bg-white text-black px-4 py-2 text-sm font-medium hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
{submitting ? "Sending…" : "Send"}
|
||||||
|
</button>
|
||||||
|
<p className="text-sm text-white/50">
|
||||||
|
Or{" "}
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/jz8T2uahpH"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-white/80"
|
||||||
|
>
|
||||||
|
DM me on Discord
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Field({
|
||||||
|
label,
|
||||||
|
required,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
required?: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<label className="block">
|
||||||
|
<span className="block text-sm font-medium text-white/80 pb-2">
|
||||||
|
{label}
|
||||||
|
{required && <span className="text-white/40"> *</span>}
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FaqSection() {
|
||||||
|
return (
|
||||||
|
<section className="space-y-6">
|
||||||
|
<h2 className="text-3xl font-medium">FAQ</h2>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<FAQItem question="What is Paseo Cloud?">
|
||||||
|
An optional layer on top of Paseo for running daemons across machines, syncing config
|
||||||
|
between them, and using Paseo with a team or company. Think shared runners, permissions,
|
||||||
|
audit, managed daemons, and org controls.
|
||||||
|
</FAQItem>
|
||||||
|
<FAQItem question="Will Paseo stay free and open source?">
|
||||||
|
Yes. The whole stack stays free and open source: app, daemon, CLI, protocols, and the
|
||||||
|
Cloud control plane. Managed Cloud is the optional paid layer for people who don't
|
||||||
|
want to host it themselves.
|
||||||
|
</FAQItem>
|
||||||
|
<FAQItem question="Self-hosted or managed?">
|
||||||
|
Both. The control plane will live in the Paseo monorepo so you can host it yourself.
|
||||||
|
Managed is for people and teams who don't want to.
|
||||||
|
</FAQItem>
|
||||||
|
<FAQItem question="Does my code go through Paseo?">
|
||||||
|
No. Daemons run on your machines and talk to agent providers directly. Cloud handles
|
||||||
|
registration, config sync, permissions, and orchestration. Code and model traffic stay on
|
||||||
|
your machines.
|
||||||
|
</FAQItem>
|
||||||
|
<FAQItem question="When will it be available?">
|
||||||
|
Early access for design partners now. No public date. The app and daemon come first.
|
||||||
|
</FAQItem>
|
||||||
|
<FAQItem question="How does pricing work?">
|
||||||
|
Not set yet. Design partners help shape it.
|
||||||
|
</FAQItem>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -39,14 +39,14 @@ function DocsLayout() {
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="max-w-5xl mx-auto flex">
|
<div className="flex">
|
||||||
{/* Desktop sidebar */}
|
{/* Desktop sidebar */}
|
||||||
<aside className="hidden md:block w-56 shrink-0 border-r border-border p-6 sticky top-0 h-screen">
|
<aside className="hidden md:block w-56 shrink-0 border-r border-border p-6 sticky top-0 h-screen">
|
||||||
<Link to="/" className="flex items-center gap-3 mb-8">
|
<Link to="/" className="flex items-center gap-3 mb-8">
|
||||||
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
|
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
|
||||||
<span className="text-lg font-medium">Paseo</span>
|
<span className="text-lg font-medium">Paseo</span>
|
||||||
</Link>
|
</Link>
|
||||||
<nav className="space-y-1">
|
<nav className="space-y-1 -ml-3">
|
||||||
{navigation.map((item) => (
|
{navigation.map((item) => (
|
||||||
<Link
|
<Link
|
||||||
key={item.href}
|
key={item.href}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Link, createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { CodeBlock } from "~/components/code-block";
|
import { CodeBlock } from "~/components/code-block";
|
||||||
|
import { SiteShell } from "~/components/site-shell";
|
||||||
import { pageMeta } from "~/meta";
|
import { pageMeta } from "~/meta";
|
||||||
import {
|
import {
|
||||||
downloadUrls,
|
downloadUrls,
|
||||||
@@ -32,232 +33,176 @@ function Download() {
|
|||||||
const urls = downloadUrls(release);
|
const urls = downloadUrls(release);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<SiteShell>
|
||||||
<div className="max-w-3xl mx-auto p-6 md:p-12">
|
<h1 className="text-3xl md:text-4xl font-semibold tracking-tight mb-2">Download</h1>
|
||||||
<header className="flex items-center justify-between gap-4 mb-12">
|
<p className="text-muted-foreground mb-10">v{version}</p>
|
||||||
<Link to="/" className="flex items-center gap-3">
|
|
||||||
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
|
{/* Desktop */}
|
||||||
<span className="text-lg font-medium">Paseo</span>
|
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
|
||||||
</Link>
|
<div className="flex items-start justify-between mb-8">
|
||||||
<div className="flex items-center gap-4">
|
<div>
|
||||||
<Link
|
<h2 className="text-2xl font-semibold">Desktop</h2>
|
||||||
to="/docs"
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
Recommended, bundles everything you need
|
||||||
>
|
</p>
|
||||||
Docs
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/changelog"
|
|
||||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
||||||
>
|
|
||||||
Changelog
|
|
||||||
</Link>
|
|
||||||
<a
|
|
||||||
href="https://discord.gg/jz8T2uahpH"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
aria-label="Discord"
|
|
||||||
className="text-muted-foreground hover:text-foreground transition-colors inline-flex items-center"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
role="img"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/getpaseo/paseo"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
aria-label="GitHub"
|
|
||||||
className="text-muted-foreground hover:text-foreground transition-colors inline-flex items-center"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path d="M12 0C5.37 0 0 5.484 0 12.252c0 5.418 3.438 10.013 8.205 11.637.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.738-4.042-1.61-4.042-1.61-.546-1.403-1.333-1.776-1.333-1.776-1.089-.756.084-.741.084-.741 1.205.087 1.838 1.262 1.838 1.262 1.07 1.87 2.809 1.33 3.495 1.017.108-.79.417-1.33.76-1.636-2.665-.31-5.467-1.35-5.467-6.005 0-1.327.465-2.413 1.235-3.262-.124-.31-.535-1.556.117-3.243 0 0 1.008-.33 3.3 1.248a11.2 11.2 0 0 1 3.003-.404c1.02.005 2.045.138 3.003.404 2.29-1.578 3.297-1.248 3.297-1.248.653 1.687.242 2.933.118 3.243.77.85 1.233 1.935 1.233 3.262 0 4.667-2.807 5.692-5.48 5.995.43.38.823 1.133.823 2.285 0 1.65-.015 2.98-.015 3.386 0 .315.218.694.825.576C20.565 22.26 24 17.667 24 12.252 24 5.484 18.627 0 12 0z" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
<MonitorIcon className="h-5 w-5 text-muted-foreground mt-1.5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1 className="text-3xl md:text-4xl font-semibold tracking-tight mb-2">Download</h1>
|
<div className="divide-y divide-border">
|
||||||
<p className="text-muted-foreground mb-10">v{version}</p>
|
{/* macOS */}
|
||||||
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
{/* Desktop */}
|
<div className="flex items-center gap-3">
|
||||||
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
|
<AppleIcon className="h-5 w-5 text-foreground" />
|
||||||
<div className="flex items-start justify-between mb-8">
|
<span className="font-medium">macOS</span>
|
||||||
<div>
|
</div>
|
||||||
<h2 className="text-2xl font-semibold">Desktop</h2>
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<p className="text-sm text-muted-foreground mt-1">Recommended, bundles the server</p>
|
<DownloadPill href={urls.macAppleSilicon} label="Apple Silicon" />
|
||||||
|
<DownloadPill href={urls.macIntel} label="Intel" />
|
||||||
</div>
|
</div>
|
||||||
<MonitorIcon className="h-5 w-5 text-muted-foreground mt-1.5" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="divide-y divide-border">
|
{/* Homebrew */}
|
||||||
{/* macOS */}
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-3">
|
<TerminalIcon className="h-5 w-5 text-foreground" />
|
||||||
<AppleIcon className="h-5 w-5 text-foreground" />
|
<span className="font-medium">Homebrew</span>
|
||||||
<span className="font-medium">macOS</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
<DownloadPill href={urls.macAppleSilicon} label="Apple Silicon" />
|
|
||||||
<DownloadPill href={urls.macIntel} label="Intel" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<CodeBlock size="sm">brew install --cask paseo</CodeBlock>
|
||||||
{/* Homebrew */}
|
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<TerminalIcon className="h-5 w-5 text-foreground" />
|
|
||||||
<span className="font-medium">Homebrew</span>
|
|
||||||
</div>
|
|
||||||
<CodeBlock size="sm">brew install --cask paseo</CodeBlock>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Windows */}
|
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<WindowsIcon className="h-5 w-5 text-foreground" />
|
|
||||||
<span className="font-medium">Windows</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<DownloadPill
|
|
||||||
href={urls.windowsExeX64}
|
|
||||||
label={urls.windowsExeArm64 ? "Intel / x64" : "Download"}
|
|
||||||
/>
|
|
||||||
{urls.windowsExeArm64 && <DownloadPill href={urls.windowsExeArm64} label="ARM64" />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Linux */}
|
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<LinuxIcon className="h-5 w-5 text-foreground" />
|
|
||||||
<span className="font-medium">Linux</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<DownloadPill href={urls.linuxAppImage} label="AppImage" />
|
|
||||||
<DownloadPill href={urls.linuxDeb} label="DEB" />
|
|
||||||
<DownloadPill href={urls.linuxRpm} label="RPM" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Mobile */}
|
|
||||||
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
|
|
||||||
<div className="flex items-center justify-between mb-8">
|
|
||||||
<h2 className="text-2xl font-semibold">Mobile</h2>
|
|
||||||
<PhoneIcon className="h-5 w-5 text-muted-foreground" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="divide-y divide-border">
|
{/* Windows */}
|
||||||
{/* Android */}
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-3">
|
<WindowsIcon className="h-5 w-5 text-foreground" />
|
||||||
<AndroidIcon className="h-5 w-5 text-foreground" />
|
<span className="font-medium">Windows</span>
|
||||||
<span className="font-medium">Android</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<DownloadPill href={playStoreUrl} label="Play Store" external />
|
|
||||||
<DownloadPill href={urls.androidApk} label="APK" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
{/* iOS */}
|
<DownloadPill
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
href={urls.windowsExeX64}
|
||||||
<div className="flex items-center gap-3">
|
label={urls.windowsExeArm64 ? "Intel / x64" : "Download"}
|
||||||
<AppleIcon className="h-5 w-5 text-foreground" />
|
/>
|
||||||
<span className="font-medium">iOS</span>
|
{urls.windowsExeArm64 && <DownloadPill href={urls.windowsExeArm64} label="ARM64" />}
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<DownloadPill href={appStoreUrl} label="App Store" external />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Web */}
|
{/* Linux */}
|
||||||
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex items-start justify-between mb-8">
|
<div className="flex items-center gap-3">
|
||||||
<div>
|
<LinuxIcon className="h-5 w-5 text-foreground" />
|
||||||
<h2 className="text-2xl font-semibold">Web</h2>
|
<span className="font-medium">Linux</span>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
|
||||||
Connect to a server from any browser
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<GlobeIcon className="h-5 w-5 text-muted-foreground mt-1.5" />
|
<div className="flex flex-wrap gap-2">
|
||||||
</div>
|
<DownloadPill href={urls.linuxAppImage} label="AppImage" />
|
||||||
|
<DownloadPill href={urls.linuxDeb} label="DEB" />
|
||||||
<div className="divide-y divide-border">
|
<DownloadPill href={urls.linuxRpm} label="RPM" />
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<GlobeIcon className="h-5 w-5 text-foreground" />
|
|
||||||
<span className="font-medium">Web App</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<DownloadPill href={webAppUrl} label="Open" external />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Server */}
|
{/* Mobile */}
|
||||||
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8">
|
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
|
||||||
<div className="flex items-start justify-between mb-8">
|
<div className="flex items-center justify-between mb-8">
|
||||||
<div>
|
<h2 className="text-2xl font-semibold">Mobile</h2>
|
||||||
<h2 className="text-2xl font-semibold">Server</h2>
|
<PhoneIcon className="h-5 w-5 text-muted-foreground" />
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
</div>
|
||||||
Run the Paseo server anywhere, connect from any client
|
|
||||||
</p>
|
<div className="divide-y divide-border">
|
||||||
|
{/* Android */}
|
||||||
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<AndroidIcon className="h-5 w-5 text-foreground" />
|
||||||
|
<span className="font-medium">Android</span>
|
||||||
</div>
|
</div>
|
||||||
<TerminalIcon className="h-5 w-5 text-muted-foreground mt-1.5" />
|
<div className="flex flex-wrap gap-2">
|
||||||
</div>
|
<DownloadPill href={playStoreUrl} label="Play Store" external />
|
||||||
|
<DownloadPill href={urls.androidApk} label="APK" />
|
||||||
<div className="divide-y divide-border">
|
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<TerminalIcon className="h-5 w-5 text-foreground" />
|
|
||||||
<span className="font-medium">npm</span>
|
|
||||||
</div>
|
|
||||||
<CodeBlock size="sm">npm install -g @getpaseo/cli && paseo</CodeBlock>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<TerminalIcon className="h-5 w-5 text-foreground" />
|
|
||||||
<span className="font-medium">Nix</span>
|
|
||||||
</div>
|
|
||||||
<CodeBlock size="sm">nix run github:getpaseo/paseo</CodeBlock>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<p className="text-center text-xs text-muted-foreground mt-8">
|
{/* iOS */}
|
||||||
All releases are available on{" "}
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<a
|
<div className="flex items-center gap-3">
|
||||||
href="https://github.com/getpaseo/paseo/releases"
|
<AppleIcon className="h-5 w-5 text-foreground" />
|
||||||
target="_blank"
|
<span className="font-medium">iOS</span>
|
||||||
rel="noopener noreferrer"
|
</div>
|
||||||
className="underline hover:text-foreground transition-colors"
|
<div className="flex flex-wrap gap-2">
|
||||||
>
|
<DownloadPill href={appStoreUrl} label="App Store" external />
|
||||||
GitHub
|
</div>
|
||||||
</a>
|
</div>
|
||||||
.
|
</div>
|
||||||
</p>
|
</section>
|
||||||
</div>
|
|
||||||
</div>
|
{/* Web */}
|
||||||
|
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
|
||||||
|
<div className="flex items-start justify-between mb-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold">Web</h2>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Connect to a server from any browser
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<GlobeIcon className="h-5 w-5 text-muted-foreground mt-1.5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="divide-y divide-border">
|
||||||
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<GlobeIcon className="h-5 w-5 text-foreground" />
|
||||||
|
<span className="font-medium">Web App</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<DownloadPill href={webAppUrl} label="Open" external />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Server */}
|
||||||
|
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8">
|
||||||
|
<div className="flex items-start justify-between mb-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold">Server</h2>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Run the Paseo server anywhere, connect from any client
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<TerminalIcon className="h-5 w-5 text-muted-foreground mt-1.5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="divide-y divide-border">
|
||||||
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<TerminalIcon className="h-5 w-5 text-foreground" />
|
||||||
|
<span className="font-medium">npm</span>
|
||||||
|
</div>
|
||||||
|
<CodeBlock size="sm">npm install -g @getpaseo/cli && paseo</CodeBlock>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<TerminalIcon className="h-5 w-5 text-foreground" />
|
||||||
|
<span className="font-medium">Nix</span>
|
||||||
|
</div>
|
||||||
|
<CodeBlock size="sm">nix run github:getpaseo/paseo</CodeBlock>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p className="text-center text-xs text-muted-foreground mt-8">
|
||||||
|
All releases are available on{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/getpaseo/paseo/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</SiteShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { SiteShell } from "~/components/site-shell";
|
||||||
import { pageMeta } from "~/meta";
|
import { pageMeta } from "~/meta";
|
||||||
|
|
||||||
export const Route = createFileRoute("/privacy")({
|
export const Route = createFileRoute("/privacy")({
|
||||||
@@ -13,83 +14,81 @@ export const Route = createFileRoute("/privacy")({
|
|||||||
|
|
||||||
function Privacy() {
|
function Privacy() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<SiteShell>
|
||||||
<div className="max-w-3xl mx-auto p-6 md:p-12">
|
<h1 className="text-3xl font-medium mb-8">Privacy Policy</h1>
|
||||||
<h1 className="text-3xl font-medium mb-8">Privacy Policy</h1>
|
|
||||||
|
|
||||||
<div className="space-y-6 text-white/70 leading-relaxed">
|
<div className="space-y-6 text-white/70 leading-relaxed">
|
||||||
|
<p>
|
||||||
|
Paseo is a self-hosted tool for managing coding agents. Your code and data stay on your
|
||||||
|
machine.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section className="space-y-3">
|
||||||
|
<h2 className="text-xl font-medium text-white">What we collect</h2>
|
||||||
|
<p>Nothing. Paseo runs on your machine and doesn't send us any data.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="space-y-3">
|
||||||
|
<h2 className="text-xl font-medium text-white">The relay server</h2>
|
||||||
<p>
|
<p>
|
||||||
Paseo is a self-hosted tool for managing coding agents. Your code and data stay on your
|
If you use the optional encrypted relay to connect your phone to your daemon, the relay
|
||||||
machine.
|
sees:
|
||||||
</p>
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 ml-4">
|
||||||
|
<li>IP addresses and connection timing</li>
|
||||||
|
<li>Message sizes</li>
|
||||||
|
<li>Session IDs</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
All messages between your phone and daemon are end-to-end encrypted with AES-256-GCM.
|
||||||
|
The relay cannot read your messages, see your code, or decrypt your traffic.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<h2 className="text-xl font-medium text-white">What we collect</h2>
|
<h2 className="text-xl font-medium text-white">Analytics and tracking</h2>
|
||||||
<p>Nothing. Paseo runs on your machine and doesn't send us any data.</p>
|
<p>
|
||||||
</section>
|
We don't use analytics, tracking pixels, cookies, or ads. The app doesn't
|
||||||
|
phone home.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<h2 className="text-xl font-medium text-white">The relay server</h2>
|
<h2 className="text-xl font-medium text-white">Third-party services</h2>
|
||||||
<p>
|
<p>
|
||||||
If you use the optional encrypted relay to connect your phone to your daemon, the
|
Paseo wraps agent providers like Claude Code, Codex, and OpenCode. Those tools
|
||||||
relay sees:
|
communicate with their own APIs (Anthropic, OpenAI, etc.) using your credentials. Paseo
|
||||||
</p>
|
doesn't manage or intercept those API calls.
|
||||||
<ul className="list-disc list-inside space-y-1 ml-4">
|
</p>
|
||||||
<li>IP addresses and connection timing</li>
|
<p>
|
||||||
<li>Message sizes</li>
|
If you use voice features with cloud providers (OpenAI speech), your voice data is sent
|
||||||
<li>Session IDs</li>
|
to those services according to their privacy policies.
|
||||||
</ul>
|
</p>
|
||||||
<p>
|
</section>
|
||||||
All messages between your phone and daemon are end-to-end encrypted with AES-256-GCM.
|
|
||||||
The relay cannot read your messages, see your code, or decrypt your traffic.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<h2 className="text-xl font-medium text-white">Analytics and tracking</h2>
|
<h2 className="text-xl font-medium text-white">We don't sell your data</h2>
|
||||||
<p>
|
<p>We don't have your data to sell. Paseo is self-hosted and local-first.</p>
|
||||||
We don't use analytics, tracking pixels, cookies, or ads. The app doesn't
|
</section>
|
||||||
phone home.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<h2 className="text-xl font-medium text-white">Third-party services</h2>
|
<h2 className="text-xl font-medium text-white">Questions</h2>
|
||||||
<p>
|
<p>
|
||||||
Paseo wraps agent providers like Claude Code, Codex, and OpenCode. Those tools
|
If you have questions about privacy, open an issue on{" "}
|
||||||
communicate with their own APIs (Anthropic, OpenAI, etc.) using your credentials.
|
<a
|
||||||
Paseo doesn't manage or intercept those API calls.
|
href="https://github.com/getpaseo/paseo"
|
||||||
</p>
|
target="_blank"
|
||||||
<p>
|
rel="noopener noreferrer"
|
||||||
If you use voice features with cloud providers (OpenAI speech), your voice data is
|
className="underline hover:text-white/90"
|
||||||
sent to those services according to their privacy policies.
|
>
|
||||||
</p>
|
GitHub
|
||||||
</section>
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="space-y-3">
|
<p className="text-sm text-white/50 pt-6">Last updated: February 2025</p>
|
||||||
<h2 className="text-xl font-medium text-white">We don't sell your data</h2>
|
|
||||||
<p>We don't have your data to sell. Paseo is self-hosted and local-first.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="space-y-3">
|
|
||||||
<h2 className="text-xl font-medium text-white">Questions</h2>
|
|
||||||
<p>
|
|
||||||
If you have questions about privacy, open an issue on{" "}
|
|
||||||
<a
|
|
||||||
href="https://github.com/getpaseo/paseo"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline hover:text-white/90"
|
|
||||||
>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<p className="text-sm text-white/50 pt-6">Last updated: February 2025</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SiteShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
"types": ["@cloudflare/workers-types"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"]
|
"~/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const sitemapPages = [
|
|||||||
"/",
|
"/",
|
||||||
"/changelog",
|
"/changelog",
|
||||||
"/claude-code",
|
"/claude-code",
|
||||||
|
"/cloud",
|
||||||
"/codex",
|
"/codex",
|
||||||
"/download",
|
"/download",
|
||||||
"/opencode",
|
"/opencode",
|
||||||
|
|||||||
Reference in New Issue
Block a user