Unified · Split
| @@ -0,0 +1,90 @@ | |||
| 1 | import type { ReactNode } from "react"; | ||
| 2 | |||
| 3 | import { Wordmark } from "@executor-js/react/components/wordmark"; | ||
| 4 | |||
| 5 | // Split auth layout for the chromeless pages (setup, login, join): a promo | ||
| 6 | // panel on the left, the form on the right. The panel follows design.md's | ||
| 7 | // registry-minimal rules — graph-paper texture, mono eyebrow + index numerals, | ||
| 8 | // grayscale only — so the first screen a person ever sees speaks the same | ||
| 9 | // language as the app behind it. Collapses to form-only below lg. | ||
| 10 | const PANEL_POINTS: ReadonlyArray<{ index: string; title: string; body: string }> = [ | ||
| 11 | { | ||
| 12 | index: "01", | ||
| 13 | title: "Connect", | ||
| 14 | body: "OpenAPI, GraphQL, and MCP sources become tools your agent can call.", | ||
| 15 | }, | ||
| 16 | { | ||
| 17 | index: "02", | ||
| 18 | title: "Control", | ||
| 19 | body: "Policies decide which tools run, which ask first, and which are blocked.", | ||
| 20 | }, | ||
| 21 | { | ||
| 22 | index: "03", | ||
| 23 | title: "Audit", | ||
| 24 | body: "Every invocation is recorded, with approvals where they matter.", | ||
| 25 | }, | ||
| 26 | ]; | ||
| 27 | |||
| 28 | export function AuthLayout(props: { readonly children: ReactNode }) { | ||
| 29 | return ( | ||
| 30 | <div className="grid min-h-screen bg-background lg:grid-cols-[1.1fr_1fr]"> | ||
| 31 | <aside className="relative hidden flex-col justify-between overflow-hidden border-r border-border bg-sidebar p-12 lg:flex"> | ||
| 32 | {/* Graph-paper texture, faded toward the bottom (design.md signature). */} | ||
| 33 | <div | ||
| 34 | aria-hidden | ||
| 35 | className="pointer-events-none absolute inset-0 text-foreground opacity-[0.05]" | ||
| 36 | style={{ | ||
| 37 | backgroundImage: | ||
| 38 | "linear-gradient(currentColor 1px, transparent 1px), linear-gradient(90deg, currentColor 1px, transparent 1px)", | ||
| 39 | backgroundSize: "32px 32px", | ||
| 40 | maskImage: "linear-gradient(to bottom, black 30%, transparent 85%)", | ||
| 41 | }} | ||
| 42 | /> | ||
| 43 | |||
| 44 | <div className="relative"> | ||
| 45 | <Wordmark /> | ||
| 46 | </div> | ||
| 47 | |||
| 48 | <div className="relative max-w-md space-y-10"> | ||
| 49 | <div className="space-y-4"> | ||
| 50 | <p className="font-mono text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground"> | ||
| 51 | The integration layer for AI agents | ||
| 52 | </p> | ||
| 53 | <h2 className="text-4xl font-semibold tracking-[-0.04em] text-foreground"> | ||
| 54 | Every tool your agent needs, behind one endpoint. | ||
| 55 | </h2> | ||
| 56 | <p className="text-sm leading-6 text-muted-foreground"> | ||
| 57 | Connect your APIs once. Any MCP-compatible agent gets the whole catalog, governed by | ||
| 58 | your policies. | ||
| 59 | </p> | ||
| 60 | </div> | ||
| 61 | |||
| 62 | <ul className="space-y-5"> | ||
| 63 | {PANEL_POINTS.map((point) => ( | ||
| 64 | <li key={point.index} className="flex gap-4"> | ||
| 65 | <span className="font-mono text-[11px] font-medium tracking-[0.08em] text-muted-foreground/70 pt-0.5"> | ||
| 66 | {point.index} | ||
| 67 | </span> | ||
| 68 | <div> | ||
| 69 | <p className="text-sm font-medium text-foreground">{point.title}</p> | ||
| 70 | <p className="text-sm leading-6 text-muted-foreground">{point.body}</p> | ||
| 71 | </div> | ||
| 72 | </li> | ||
| 73 | ))} | ||
| 74 | </ul> | ||
| 75 | </div> | ||
| 76 | |||
| 77 | <p className="relative font-mono text-[11px] tracking-[0.08em] text-muted-foreground"> | ||
| 78 | self-hosted | ||
| 79 | </p> | ||
| 80 | </aside> | ||
| 81 | |||
| 82 | <main className="flex flex-col items-center justify-center gap-6 p-6"> | ||
| 83 | <div className="lg:hidden"> | ||
| 84 | <Wordmark /> | ||
| 85 | </div> | ||
| 86 | {props.children} | ||
| 87 | </main> | ||
| 88 | </div> | ||
| 89 | ); | ||
| 90 | } | ||
| @@ -5,6 +5,7 @@ import { Input } from "@executor-js/react/components/input"; · 4 unmodified lines | |||
| 5 | import { Label } from "@executor-js/react/components/label"; | 5 | import { Label } from "@executor-js/react/components/label"; |
| 6 | 6 | ||
| 7 | import { authClient } from "./auth-client"; | 7 | import { authClient } from "./auth-client"; |
| 8 | import { AuthLayout } from "./auth-layout"; | ||
| 8 | import { mcpAuthorizeResumeTarget, safeReturnTo } from "../src/auth/return-to"; | 9 | import { mcpAuthorizeResumeTarget, safeReturnTo } from "../src/auth/return-to"; |
| 9 | 10 | ||
| 10 | // Self-host login: email + password sign-in via Better Auth. On success we | 11 | // Self-host login: email + password sign-in via Better Auth. On success we |
| @@ -54,12 +55,16 @@ export const LoginPage = () => { · 43 unmodified lines | |||
| 54 | }; | 55 | }; |
| 55 | 56 | ||
| 56 | return ( | 57 | return ( |
| 57 | <div className="flex min-h-screen items-center justify-center bg-background p-6"> | 58 | <AuthLayout> |
| 58 | <div className="w-full max-w-sm space-y-4 rounded-xl border border-border bg-card p-6 shadow-sm"> | 59 | <div className="w-full max-w-sm space-y-4 rounded-xl border border-border bg-card p-6 shadow-sm"> |
| 59 | <div className="space-y-1 text-center"> | 60 | <div className="space-y-1"> |
| 60 | <h1 className="font-mono text-2xl tracking-tight text-foreground">Executor</h1> | 61 | <h1 className="text-xl font-semibold tracking-tight text-foreground"> |
| 62 | {mode === "signin" ? "Sign in" : "Join this instance"} | ||
| 63 | </h1> | ||
| 61 | <p className="text-sm text-muted-foreground"> | 64 | <p className="text-sm text-muted-foreground"> |
| 62 | {mode === "signin" ? "Sign in to your instance" : "Join with your invite code"} | 65 | {mode === "signin" |
| 66 | ? "Welcome back. Use your instance account." | ||
| 67 | : "Enter the invite code you were given."} | ||
| 63 | </p> | 68 | </p> |
| 64 | </div> | 69 | </div> |
| 65 | 70 | ||
| @@ -127,6 +132,6 @@ export const LoginPage = () => { · 61 unmodified lines | |||
| 127 | </Button> | 132 | </Button> |
| 128 | </div> | 133 | </div> |
| 129 | </div> | 134 | </div> |
| 130 | </div> | 135 | </AuthLayout> |
| 131 | ); | 136 | ); |
| 132 | }; | 137 | }; |
| @@ -5,6 +5,7 @@ import { Input } from "@executor-js/react/components/input"; · 4 unmodified lines | |||
| 5 | import { Label } from "@executor-js/react/components/label"; | 5 | import { Label } from "@executor-js/react/components/label"; |
| 6 | 6 | ||
| 7 | import { authClient } from "./auth-client"; | 7 | import { authClient } from "./auth-client"; |
| 8 | import { AuthLayout } from "./auth-layout"; | ||
| 8 | 9 | ||
| 9 | // First-run setup. A fresh instance has no users, so the first visitor creates | 10 | // First-run setup. A fresh instance has no users, so the first visitor creates |
| 10 | // the admin account here. The server admits the first signup into the empty org | 11 | // the admin account here. The server admits the first signup into the empty org |
| @@ -32,15 +33,15 @@ export const SetupPage = () => { · 21 unmodified lines | |||
| 32 | }; | 33 | }; |
| 33 | 34 | ||
| 34 | return ( | 35 | return ( |
| 35 | <div className="flex min-h-screen items-center justify-center bg-background p-6"> | 36 | <AuthLayout> |
| 36 | <form | 37 | <form |
| 37 | onSubmit={submit} | 38 | onSubmit={submit} |
| 38 | className="w-full max-w-sm space-y-4 rounded-xl border border-border bg-card p-6 shadow-sm" | 39 | className="w-full max-w-sm space-y-4 rounded-xl border border-border bg-card p-6 shadow-sm" |
| 39 | > | 40 | > |
| 40 | <div className="space-y-1 text-center"> | 41 | <div className="space-y-1"> |
| 41 | <h1 className="font-mono text-2xl tracking-tight text-foreground">Set up Executor</h1> | 42 | <h1 className="text-xl font-semibold tracking-tight text-foreground">Set up Executor</h1> |
| 42 | <p className="text-sm text-muted-foreground"> | 43 | <p className="text-sm text-muted-foreground"> |
| 43 | Create the admin account for this instance. | 44 | Create the admin account for this instance. You can invite your team once you're in. |
| 44 | </p> | 45 | </p> |
| 45 | </div> | 46 | </div> |
| 46 | 47 | ||
| @@ -87,6 +88,6 @@ export const SetupPage = () => { · 40 unmodified lines | |||
| 87 | {busy ? "Creating…" : "Create admin account"} | 88 | {busy ? "Creating…" : "Create admin account"} |
| 88 | </Button> | 89 | </Button> |
| 89 | </form> | 90 | </form> |
| 90 | </div> | 91 | </AuthLayout> |
| 91 | ); | 92 | ); |
| 92 | }; | 93 | }; |
| @@ -170,7 +170,7 @@ export function McpInstallCard(props: { className?: string }) { · 169 unmodified lines | |||
| 170 | ? isDev | 170 | ? isDev |
| 171 | ? "Uses the repo-local dev CLI from any agent working directory." | 171 | ? "Uses the repo-local dev CLI from any agent working directory." |
| 172 | : "Requires the executor CLI on your PATH." | 172 | : "Requires the executor CLI on your PATH." |
| 173 | : "Connect to executor as a remote MCP server over streamable HTTP."; | 173 | : "Paste this into Claude Code, Cursor, or any MCP client, and your agent gets every tool you connect here."; |
| 174 | 174 | ||
| 175 | const advancedControls = ( | 175 | const advancedControls = ( |
| 176 | <Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}> | 176 | <Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}> |
| @@ -224,9 +224,10 @@ export function McpInstallCard(props: { className?: string }) { · 47 unmodified lines | |||
| 224 | key={key} | 224 | key={key} |
| 225 | title={label} | 225 | title={label} |
| 226 | aria-label={label} | 226 | aria-label={label} |
| 227 | role="img" | ||
| 227 | style={{ zIndex: SUPPORTED_AGENTS.length - index }} | 228 | style={{ zIndex: SUPPORTED_AGENTS.length - index }} |
| 228 | className={cn( | 229 | className={cn( |
| 229 | "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)]", | 230 | "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)]", |
| 230 | index > 0 && "-ml-2 group-hover/agents:ml-1", | 231 | index > 0 && "-ml-2 group-hover/agents:ml-1", |
| 231 | )} | 232 | )} |
| 232 | > | 233 | > |