-
Notifications
You must be signed in to change notification settings - Fork 1
feat(marketing): add agent-friendly page representations #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rortan134
wants to merge
4
commits into
main
Choose a base branch
from
codex/agent-friendly-homepage-sitemap
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
144f8de
feat(marketing): add agent-friendly page representations
rortan134 c417167
fix(marketing): address agent-friendly page review feedback
rortan134 6bf80ac
Update proxy.ts
rortan134 a4e5881
refactor(marketing): simplify agent-friendly page routes
rortan134 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { describe, expect, test } from "bun:test"; | ||
|
|
||
| import { GET } from "./route"; | ||
|
|
||
| function getMarkdown(locale?: string[]): Promise<Response> { | ||
| return GET(new Request("https://www.cachd.app/api/markdown/home"), { | ||
| params: Promise.resolve({ locale }), | ||
| }); | ||
| } | ||
|
|
||
| describe("GET /api/markdown/home", () => { | ||
| test("defaults to English when no locale is provided", async () => { | ||
| const response = await getMarkdown(); | ||
|
|
||
| expect(response.status).toBe(200); | ||
| expect(await response.text()).toContain( | ||
| "The AI bookmark manager for busy people" | ||
| ); | ||
| }); | ||
|
|
||
| test("returns the requested supported locale", async () => { | ||
| const response = await getMarkdown(["es-ES"]); | ||
|
|
||
| expect(response.status).toBe(200); | ||
| expect(await response.text()).toContain( | ||
| "El gestor de marcadores con IA" | ||
| ); | ||
| }); | ||
|
|
||
| test("rejects unsupported and extra locale segments", async () => { | ||
| for (const locale of [["fr-FR"], ["en-US", "extra"]]) { | ||
| const response = await getMarkdown(locale); | ||
|
|
||
| expect(response.status).toBe(404); | ||
| } | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| import { | ||
| BASE_URL, | ||
| DEFAULT_LOCALE, | ||
| MIME_TYPES, | ||
| SUPPORTED_LOCALES, | ||
| type SupportedLocale, | ||
| } from "@/lib/common/constants"; | ||
| import { MARKETING_CACHE_CONTROL_HEADER } from "@/lib/marketing/constants"; | ||
| import { z } from "zod"; | ||
|
|
||
| const LOCALE_PARAMS_SCHEMA = z | ||
| .array(z.enum(SUPPORTED_LOCALES)) | ||
| .max(1) | ||
| .optional(); | ||
|
|
||
| const HOME_MARKDOWN = { | ||
| "en-US": `# Cache | ||
|
|
||
| > The AI bookmark manager for busy people. Collect, organize, and rediscover everything you've saved across platforms. | ||
|
|
||
| ## What Cache does | ||
|
|
||
| Cache brings your saved content into a single, searchable, actionable library. | ||
|
|
||
| - **Curate a personal library** — Search saved links, notes, recipes, lessons, and ideas when you need them. | ||
| - **Import everything you've already saved** — Sync bookmarks from Chrome, Instagram, TikTok, YouTube, X/Twitter, GitHub, Pinterest, and Google Photos. | ||
| - **Build useful collections** — Organize items into named collections, add priorities, and share collections publicly. | ||
| - **Make saving a habit** — Use automations and review workflows to bring important content back before it gets buried. | ||
| - **Search and read quickly** — Use full-text search, command shortcuts, and distraction-free Quick Look reading. | ||
| - **Keep the useful, drop the stale** — Smart Collections help separate actionable material from inspiration, duplicates, and broken links. | ||
|
|
||
| ## AI features | ||
|
|
||
| - AI-powered smart collections organize new items automatically. | ||
| - AI summaries help you synthesize collections and sections. | ||
| - Cache's AI agent can help you find and brainstorm from your saved knowledge. | ||
|
|
||
| ## Agent access | ||
|
|
||
| Cache exposes a Model Context Protocol (MCP) server so AI agents can read and write your library securely. The full setup and tool reference is available at [llms.txt](${BASE_URL}/llms.txt). | ||
|
|
||
| ## Pricing | ||
|
|
||
| Cache has a free tier. Pro is available from $8/month with unlimited bookmarks, unlimited AI quota, and priority support. | ||
|
|
||
| ## Links | ||
|
|
||
| - [Open Cache](${BASE_URL}) | ||
| - [Install the Chrome extension](https://chromewebstore.google.com/detail/fibhdcjlclheehonialdpealhemmoikn) | ||
| - [GitHub repository](https://github.com/rortan134/cache-app) | ||
| - [Full agent context](${BASE_URL}/llms.txt) | ||
| `, | ||
| "es-ES": `# Cache | ||
|
|
||
| > El gestor de marcadores con IA para personas ocupadas. Recopila, organiza y vuelve a descubrir todo lo que has guardado en distintas plataformas. | ||
|
|
||
| ## Qué hace Cache | ||
|
|
||
| Cache reúne todo tu contenido guardado en una biblioteca única, consultable y útil. | ||
|
|
||
| - **Crea una biblioteca personal** — Busca enlaces, notas, recetas, lecciones e ideas guardadas cuando las necesites. | ||
| - **Importa todo lo que ya has guardado** — Sincroniza marcadores de Chrome, Instagram, TikTok, YouTube, X/Twitter, GitHub, Pinterest y Google Photos. | ||
| - **Crea colecciones útiles** — Organiza elementos en colecciones, añade prioridades y comparte colecciones públicamente. | ||
| - **Convierte el guardado en un hábito** — Usa automatizaciones y revisiones para recuperar contenido importante antes de que se pierda. | ||
| - **Busca y lee rápidamente** — Usa la búsqueda de texto completo, los atajos de comandos y la lectura sin distracciones de Quick Look. | ||
| - **Conserva lo útil y elimina lo obsoleto** — Las Colecciones inteligentes ayudan a separar lo accionable de la inspiración, los duplicados y los enlaces rotos. | ||
|
|
||
| ## Funciones de IA | ||
|
|
||
| - Las Colecciones inteligentes con IA organizan automáticamente los elementos nuevos. | ||
| - Los resúmenes con IA ayudan a sintetizar colecciones y secciones. | ||
| - El agente de IA de Cache puede ayudarte a encontrar ideas y generar lluvias de ideas a partir de tu conocimiento guardado. | ||
|
|
||
| ## Acceso para agentes | ||
|
|
||
| Cache ofrece un servidor de Model Context Protocol (MCP) para que los agentes de IA puedan leer y escribir en tu biblioteca de forma segura. La configuración completa y la referencia de herramientas están disponibles en [llms.txt](${BASE_URL}/llms.txt). | ||
|
|
||
| ## Precios | ||
|
|
||
| Cache tiene un nivel gratuito. Pro está disponible desde 8 $/mes e incluye marcadores ilimitados, cuota de IA ilimitada y soporte prioritario. | ||
|
|
||
| ## Enlaces | ||
|
|
||
| - [Abrir Cache](${BASE_URL}) | ||
| - [Instalar la extensión de Chrome](https://chromewebstore.google.com/detail/fibhdcjlclheehonialdpealhemmoikn) | ||
| - [Repositorio en GitHub](https://github.com/rortan134/cache-app) | ||
| - [Contexto completo para agentes](${BASE_URL}/llms.txt) | ||
| `, | ||
| } satisfies Record<SupportedLocale, string>; | ||
|
|
||
| export async function GET( | ||
| _request: Request, | ||
| { params }: { params: Promise<{ locale?: string[] }> } | ||
| ): Promise<Response> { | ||
| const parsedLocale = LOCALE_PARAMS_SCHEMA.safeParse((await params).locale); | ||
| if (!parsedLocale.success) { | ||
| return new Response("Not Found\n", { | ||
| headers: { | ||
| "Content-Type": `${MIME_TYPES.text}; charset=utf-8`, | ||
| }, | ||
| status: 404, | ||
| }); | ||
| } | ||
|
|
||
| const locale = parsedLocale.data?.[0] ?? DEFAULT_LOCALE; | ||
| const markdown = HOME_MARKDOWN[locale]; | ||
|
|
||
| return new Response(markdown, { | ||
| headers: { | ||
| "Cache-Control": MARKETING_CACHE_CONTROL_HEADER, | ||
| "Content-Type": `${MIME_TYPES.markdown}; charset=utf-8`, | ||
| Vary: "Accept", | ||
| }, | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { MIME_TYPES } from "@/lib/common/constants"; | ||
| import { MARKETING_CACHE_CONTROL_HEADER } from "@/lib/marketing/constants"; | ||
| import { buildPublicSitemapRoutes } from "@/lib/marketing/site-map"; | ||
| import { getDefaultLocale, getLocales } from "gt-next"; | ||
|
|
||
| const MARKDOWN_SITEMAP_HEADER = `# Cache sitemap | ||
|
|
||
| Public pages and agent resources for Cache. The canonical page URLs below return Markdown when requested with \`Accept: text/markdown\`. | ||
|
|
||
| ## Public pages | ||
| `; | ||
|
|
||
| const MARKDOWN_SITEMAP = buildMarkdownSitemap(getDefaultLocale(), getLocales()); | ||
|
|
||
| export function GET(): Response { | ||
| return new Response(MARKDOWN_SITEMAP, { | ||
| headers: { | ||
| "Cache-Control": MARKETING_CACHE_CONTROL_HEADER, | ||
| "Content-Type": `${MIME_TYPES.markdown}; charset=utf-8`, | ||
| Vary: "Accept", | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| function buildMarkdownSitemap( | ||
| defaultLocale: string, | ||
| locales: readonly string[] | ||
| ): string { | ||
| const pages = buildPublicSitemapRoutes(defaultLocale, locales) | ||
| .map((route) => { | ||
| const alternateLocales = Object.entries(route.alternates) | ||
| .filter(([locale]) => locale !== defaultLocale) | ||
| .map( | ||
| ([locale, url]) => | ||
| ` - [${route.title} (${locale})](${url})` | ||
| ); | ||
|
|
||
| return [ | ||
| `- [${route.title}](${route.url}) — ${route.description}`, | ||
| ...alternateLocales, | ||
| ].join("\n"); | ||
| }) | ||
| .join("\n"); | ||
|
|
||
| return `${MARKDOWN_SITEMAP_HEADER}\n${pages}\n\n## Agent resources\n\n- [Full agent context](/llms.txt)\n- [MCP server endpoint](/mcp)\n`; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { describe, expect, test } from "bun:test"; | ||
|
|
||
| import { GET } from "./route"; | ||
|
|
||
| describe("GET /api/sitemap", () => { | ||
| test("returns a text/xml sitemap", async () => { | ||
| const response = GET(); | ||
|
|
||
| expect(response.status).toBe(200); | ||
| expect(response.headers.get("Content-Type")).toBe( | ||
| "text/xml; charset=utf-8" | ||
| ); | ||
| expect(await response.text()).toContain( | ||
| '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' | ||
| ); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { MIME_TYPES } from "@/lib/common/constants"; | ||
| import { MARKETING_CACHE_CONTROL_HEADER } from "@/lib/marketing/constants"; | ||
| import { | ||
| buildPublicSitemapEntries, | ||
| renderSitemapXml, | ||
| } from "@/lib/marketing/site-map"; | ||
| import { getDefaultLocale, getLocales } from "gt-next"; | ||
|
|
||
| export function GET(): Response { | ||
| const sitemap = renderSitemapXml( | ||
| buildPublicSitemapEntries(getDefaultLocale(), getLocales()) | ||
| ); | ||
|
|
||
| return new Response(sitemap, { | ||
| headers: { | ||
| "Cache-Control": MARKETING_CACHE_CONTROL_HEADER, | ||
| "Content-Type": `${MIME_TYPES.textXml}; charset=utf-8`, | ||
| Vary: "Accept", | ||
| }, | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,50 +1,12 @@ | ||
| import { BASE_URL } from "@/lib/common/constants"; | ||
| import { normalizeURL } from "@/lib/common/url"; | ||
| import { buildPublicSitemapEntries } from "@/lib/marketing/site-map"; | ||
| import { getDefaultLocale, getLocales } from "gt-next"; | ||
| import type { MetadataRoute } from "next"; | ||
|
|
||
| interface SitemapRoute { | ||
| path: `/${string}`; | ||
| priority: number; | ||
| } | ||
|
|
||
| /** | ||
| * Public static routes that do not require authentication. | ||
| * Authenticated-only routes (e.g. /library) are intentionally excluded | ||
| * because they redirect anonymous users and should not be indexed. | ||
| */ | ||
| const PUBLIC_STATIC_ROUTES = [ | ||
| { path: "/", priority: 1 }, | ||
| { path: "/library", priority: 0.85 }, | ||
| { path: "/changelog", priority: 0.7 }, | ||
| { path: "/security", priority: 0.7 }, | ||
| { path: "/legal", priority: 0.7 }, | ||
| { path: "/legal/terms-of-service", priority: 0.7 }, | ||
| { path: "/legal/privacy-policy", priority: 0.7 }, | ||
| ] satisfies SitemapRoute[]; | ||
|
|
||
| function getLocalizedUrl(locale: string, path: SitemapRoute["path"]) { | ||
| return normalizeURL( | ||
| path === "/" ? `${BASE_URL}/${locale}` : `${BASE_URL}/${locale}${path}` | ||
| ); | ||
| } | ||
|
|
||
| export default function sitemap(): MetadataRoute.Sitemap { | ||
| const locales = getLocales(); | ||
| const defaultLocale = getDefaultLocale(); | ||
|
|
||
| return PUBLIC_STATIC_ROUTES.map((entry) => ({ | ||
| alternates: { | ||
| languages: Object.fromEntries( | ||
| locales.map((locale) => [ | ||
| locale, | ||
| getLocalizedUrl(locale, entry.path), | ||
| ]) | ||
| ), | ||
| }, | ||
| changeFrequency: "weekly", | ||
| lastModified: new Date(), | ||
| priority: entry.priority, | ||
| url: getLocalizedUrl(defaultLocale, entry.path), | ||
| })); | ||
| return buildPublicSitemapEntries(getDefaultLocale(), getLocales()).map( | ||
| ({ alternates, ...entry }) => ({ | ||
| ...entry, | ||
| alternates: { languages: alternates }, | ||
| }) | ||
| ); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.