diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts new file mode 100644 index 000000000..9101fca90 --- /dev/null +++ b/apps/cloud/src/edge/marketing.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "@effect/vitest"; + +import { isMarketingPath } from "./marketing"; + +// On executor.sh the marketing middleware proxies an allow-list of paths to the +// `executor-marketing` worker; everything else falls through to the auth-gated +// cloud app (the sign-in page). `/blog` and `/llms.txt` are public content, so +// they must be on the allow-list: without it an unauthenticated visit redirects +// to `/login?returnTo=...` and the reader bounces. +describe("isMarketingPath", () => { + const marketing = [ + "/home", + "/privacy", + "/terms", + "/blog", + "/blog/", + "/blog/some-post", + "/llms.txt", + "/og-image.png", + "/_astro/app.css", + ]; + for (const pathname of marketing) { + it(`proxies ${pathname} to marketing`, () => { + expect(isMarketingPath(pathname)).toBe(true); + }); + } + + // App-owned routes must reach the Effect handler, not marketing. `/blogger` + // guards against a bare `startsWith("/blog")` swallowing unrelated words. + const notMarketing = ["/", "/login", "/cloud", "/mcp", "/dashboard", "/blogger"]; + for (const pathname of notMarketing) { + it(`leaves ${pathname} alone`, () => { + expect(isMarketingPath(pathname)).toBe(false); + }); + } +}); diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 9b8b579a3..708155d0c 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -18,13 +18,15 @@ const MARKETING_PATHS = [ "/setup", "/privacy", "/terms", + "/blog", + "/llms.txt", "/api/detect", "/_astro", "/og-image.png", "/pattern-graph-paper.svg", ]; -const isMarketingPath = (pathname: string) => +export const isMarketingPath = (pathname: string) => MARKETING_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`)); const getMarketingWorker = () => env.MARKETING as { fetch: typeof fetch } | undefined; diff --git a/apps/marketing/public/llms.txt b/apps/marketing/public/llms.txt new file mode 100644 index 000000000..d98199e40 --- /dev/null +++ b/apps/marketing/public/llms.txt @@ -0,0 +1,32 @@ +# Executor + +> Executor is an open-source integration layer for AI agents. Configure every integration once (MCP servers, OpenAPI specs, GraphQL APIs) with authentication and per-tool policies, then use that one catalog from any MCP-compatible agent. + +Every agent you use (Claude Code, Cursor, ChatGPT, and the rest) otherwise needs its own copy of every integration: the same API keys pasted in three places, the same MCP servers wired up again, no shared idea of what each tool is allowed to do. Executor is the layer in between: add a tool once, give it credentials once, set its policy once, and every agent shares it over MCP. + +How it works: + +- Add an integration: an MCP server, an OpenAPI spec, a GraphQL API, or a Google Discovery document. +- Create a connection: one configured (optionally authenticated) instance of that integration. An integration can have many connections. +- Set policies: decide whether each tool is always allowed, needs approval, or is blocked. +- Use it from any MCP-compatible agent: one catalog of tools, shared across every client. + +Run it your way: local CLI, a desktop app, hosted Executor Cloud, or self-hosted on Docker or Cloudflare. + +## Docs + +- [Documentation](https://executor.sh/docs): guides for adding integrations, connections, policies, and connecting agents over MCP. + +## Product + +- [Website](https://executor.sh): product overview and getting started. +- [Pricing](https://executor.sh/#pricing): plans for Executor Cloud. +- [Install](https://executor.sh/#install): install the CLI and connect your first agent. + +## Source + +- [GitHub](https://github.com/UsefulSoftwareCo/executor): source for the integration layer, plugins, and hosts. + +## Community + +- [Discord](https://discord.gg/eF29HBHwM6): support and discussion.