Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions apps/host-selfhost/web/auth-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { ReactNode } from "react";

import { Wordmark } from "@executor-js/react/components/wordmark";

// Split auth layout for the chromeless pages (setup, login, join): a promo
// panel on the left, the form on the right. The panel follows design.md's
// registry-minimal rules — graph-paper texture, mono eyebrow + index numerals,
// grayscale only — so the first screen a person ever sees speaks the same
// language as the app behind it. Collapses to form-only below lg.
const PANEL_POINTS: ReadonlyArray<{ index: string; title: string; body: string }> = [
{
index: "01",
title: "Connect",
body: "OpenAPI, GraphQL, and MCP sources become tools your agent can call.",
},
{
index: "02",
title: "Control",
body: "Policies decide which tools run, which ask first, and which are blocked.",
},
{
index: "03",
title: "Audit",
body: "Every invocation is recorded, with approvals where they matter.",
},
];

export function AuthLayout(props: { readonly children: ReactNode }) {
return (
<div className="grid min-h-screen bg-background lg:grid-cols-[1.1fr_1fr]">
<aside className="relative hidden flex-col justify-between overflow-hidden border-r border-border bg-sidebar p-12 lg:flex">
{/* Graph-paper texture, faded toward the bottom (design.md signature). */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 text-foreground opacity-[0.05]"
style={{
backgroundImage:
"linear-gradient(currentColor 1px, transparent 1px), linear-gradient(90deg, currentColor 1px, transparent 1px)",
backgroundSize: "32px 32px",
maskImage: "linear-gradient(to bottom, black 30%, transparent 85%)",
}}
/>

<div className="relative">
<Wordmark />
</div>

<div className="relative max-w-md space-y-10">
<div className="space-y-4">
<p className="font-mono text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground">
The integration layer for AI agents
</p>
<h2 className="text-4xl font-semibold tracking-[-0.04em] text-foreground">
Every tool your agent needs, behind one endpoint.
</h2>
<p className="text-sm leading-6 text-muted-foreground">
Connect your APIs once. Any MCP-compatible agent gets the whole catalog, governed by
your policies.
</p>
</div>

<ul className="space-y-5">
{PANEL_POINTS.map((point) => (
<li key={point.index} className="flex gap-4">
<span className="font-mono text-[11px] font-medium tracking-[0.08em] text-muted-foreground/70 pt-0.5">
{point.index}
</span>
<div>
<p className="text-sm font-medium text-foreground">{point.title}</p>
<p className="text-sm leading-6 text-muted-foreground">{point.body}</p>
</div>
</li>
))}
</ul>
</div>

<p className="relative font-mono text-[11px] tracking-[0.08em] text-muted-foreground">
self-hosted
</p>
</aside>

<main className="flex flex-col items-center justify-center gap-6 p-6">
<div className="lg:hidden">
<Wordmark />
</div>
{props.children}
</main>
</div>
);
}
15 changes: 10 additions & 5 deletions apps/host-selfhost/web/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Input } from "@executor-js/react/components/input";
import { Label } from "@executor-js/react/components/label";

import { authClient } from "./auth-client";
import { AuthLayout } from "./auth-layout";
import { mcpAuthorizeResumeTarget, safeReturnTo } from "../src/auth/return-to";

// Self-host login: email + password sign-in via Better Auth. On success we
Expand Down Expand Up @@ -54,12 +55,16 @@ export const LoginPage = () => {
};

return (
<div className="flex min-h-screen items-center justify-center bg-background p-6">
<AuthLayout>
<div className="w-full max-w-sm space-y-4 rounded-xl border border-border bg-card p-6 shadow-sm">
<div className="space-y-1 text-center">
<h1 className="font-mono text-2xl tracking-tight text-foreground">Executor</h1>
<div className="space-y-1">
<h1 className="text-xl font-semibold tracking-tight text-foreground">
{mode === "signin" ? "Sign in" : "Join this instance"}
</h1>
<p className="text-sm text-muted-foreground">
{mode === "signin" ? "Sign in to your instance" : "Join with your invite code"}
{mode === "signin"
? "Welcome back. Use your instance account."
: "Enter the invite code you were given."}
</p>
</div>

Expand Down Expand Up @@ -127,6 +132,6 @@ export const LoginPage = () => {
</Button>
</div>
</div>
</div>
</AuthLayout>
);
};
11 changes: 6 additions & 5 deletions apps/host-selfhost/web/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Input } from "@executor-js/react/components/input";
import { Label } from "@executor-js/react/components/label";

import { authClient } from "./auth-client";
import { AuthLayout } from "./auth-layout";

// First-run setup. A fresh instance has no users, so the first visitor creates
// the admin account here. The server admits the first signup into the empty org
Expand Down Expand Up @@ -32,15 +33,15 @@ export const SetupPage = () => {
};

return (
<div className="flex min-h-screen items-center justify-center bg-background p-6">
<AuthLayout>
<form
onSubmit={submit}
className="w-full max-w-sm space-y-4 rounded-xl border border-border bg-card p-6 shadow-sm"
>
<div className="space-y-1 text-center">
<h1 className="font-mono text-2xl tracking-tight text-foreground">Set up Executor</h1>
<div className="space-y-1">
<h1 className="text-xl font-semibold tracking-tight text-foreground">Set up Executor</h1>
<p className="text-sm text-muted-foreground">
Create the admin account for this instance.
Create the admin account for this instance. You can invite your team once you're in.
</p>
</div>

Expand Down Expand Up @@ -87,6 +88,6 @@ export const SetupPage = () => {
{busy ? "Creating…" : "Create admin account"}
</Button>
</form>
</div>
</AuthLayout>
);
};
5 changes: 3 additions & 2 deletions packages/react/src/components/mcp-install-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export function McpInstallCard(props: { className?: string }) {
? isDev
? "Uses the repo-local dev CLI from any agent working directory."
: "Requires the executor CLI on your PATH."
: "Connect to executor as a remote MCP server over streamable HTTP.";
: "Paste this into Claude Code, Cursor, or any MCP client, and your agent gets every tool you connect here.";

const advancedControls = (
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
Expand Down Expand Up @@ -224,9 +224,10 @@ export function McpInstallCard(props: { className?: string }) {
key={key}
title={label}
aria-label={label}
role="img"
style={{ zIndex: SUPPORTED_AGENTS.length - index }}
className={cn(
"flex h-6 items-center justify-center rounded-md border border-border/60 bg-background px-1.5 transition-[margin] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)]",
"flex h-6 items-center justify-center rounded-md border border-border/60 bg-background px-1.5 text-muted-foreground transition-[margin] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)]",
index > 0 && "-ml-2 group-hover/agents:ml-1",
)}
>
Expand Down
Loading