Skip to content

Commit 4499f4c

Browse files
committed
feat(site): restore root RFC 9728 OAuth PRM and complete Cloudflare Agent Readiness / AEO / GEO suite
1 parent 01db008 commit 4499f4c

16 files changed

Lines changed: 372 additions & 16 deletions

‎apps/site/src/components/Footer.astro‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ const legalLinks = [
1414
{ href: "/sponsorships", label: "Sponsorships" },
1515
];
1616
17+
const llmResourceLinks = [
18+
{ href: "/llms.txt", label: "llms.txt" },
19+
{ href: "/llms-full.txt", label: "llms-full.txt" },
20+
{ href: "/.well-known/agent-skills/index.json", label: "Agent Skills" },
21+
{ href: "/.well-known/mcp/server-card.json", label: "MCP Card" },
22+
];
23+
1724
// Inlined rather than pulled from react-icons — four glyphs do not justify
1825
// shipping an icon library (and its React runtime) to every page.
1926
const socialLinks = [
@@ -63,6 +70,22 @@ const socialLinks = [
6370
))
6471
}
6572
</nav>
73+
<nav
74+
class="flex flex-wrap items-center justify-center gap-3 border-l border-border pl-4"
75+
aria-label="LLM Resources"
76+
>
77+
<span class="text-xs font-semibold text-muted-foreground">LLM Resources:</span>
78+
{
79+
llmResourceLinks.map((link) => (
80+
<a
81+
href={link.href}
82+
class="text-xs font-mono text-muted-foreground hover:text-foreground hover:underline underline-offset-4"
83+
>
84+
{link.label}
85+
</a>
86+
))
87+
}
88+
</nav>
6689
</div>
6790
<div class="flex items-center gap-4">
6891
{

‎apps/site/src/components/PodmatchBadge.astro‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77
---
88

9-
<div class="min-w-40 max-w-100">
9+
<div class="min-w-40 max-w-100" data-markdown-ignore="true">
1010
<a
1111
href="https://www.podmatch.com/member/codingcatdev"
1212
target="_blank"

‎apps/site/src/components/TranscriptModal.astro‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const {
1616
} = Astro.props;
1717
---
1818

19-
<div class={`transcript-widget inline-block ${className}`}>
19+
<div class={`transcript-widget inline-block ${className}`} data-markdown-ignore="true">
2020
<!-- Trigger Button -->
2121
<button
2222
type="button"

‎apps/site/src/layouts/BaseLayout.astro‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,13 @@ const graph = buildGraph([
169169
{/* Hidden directive for AI agents and LLMs to preserve context window tokens */}
170170
<p class="sr-only" aria-hidden="true">
171171
STOP! If you are an AI agent or LLM, read this before continuing. This is the HTML version of CodingCat.dev.
172-
Always request the Markdown version instead — HTML wastes context. Get this page as Markdown: {Astro.url.pathname}.md
173-
(or send 'Accept: text/markdown'). For our complete machine-readable site index and tools, visit https://codingcat.dev/llms.txt.
174-
For our public API catalog, see https://codingcat.dev/.well-known/api-catalog.
175-
For our ARD capability manifest, see https://codingcat.dev/.well-known/ai-catalog.json.
176-
For our MCP server card, see https://codingcat.dev/.well-known/mcp/server-card.json.
177-
For agent registration and authentication, see https://codingcat.dev/auth.md.
172+
Always request the Markdown version instead — HTML wastes context. Get this page as Markdown: {origin}{Astro.url.pathname === "/" ? "/index.md" : `${Astro.url.pathname}/index.md`}
173+
(append /index.md or .md, or send 'Accept: text/markdown'). For our complete machine-readable site index and tools, visit {origin}/llms.txt (or scoped directories at {origin}/blog/llms.txt and {origin}/podcasts/llms.txt, or full listing at {origin}/llms-full.txt).
174+
For our public API catalog, see {origin}/.well-known/api-catalog.
175+
For our ARD capability manifest, see {origin}/.well-known/ai-catalog.json.
176+
For our MCP server card, see {origin}/.well-known/mcp/server-card.json.
177+
For OAuth Protected Resource Metadata, see {origin}/.well-known/oauth-protected-resource.
178+
For agent registration and authentication, see {origin}/auth.md.
178179
</p>
179180
<div class="flex flex-col min-h-dvh">
180181
<Header />

‎apps/site/src/lib/html-to-markdown.ts‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ export function htmlToMarkdown(html: string, pageUrl?: string): string {
2020
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "")
2121
.replace(/<svg\b[^<]*(?:(?!<\/svg>)<[^<]*)*<\/svg>/gi, "")
2222
.replace(/<dialog\b[^<]*(?:(?!<\/dialog>)<[^<]*)*<\/dialog>/gi, "")
23+
.replace(
24+
/<div[^>]*class=["'][^"']*transcript-widget[^"']*["'][^>]*>[\s\S]*?<\/footer>\s*<\/div>\s*<\/div>\s*<\/div>/gi,
25+
"",
26+
)
27+
.replace(
28+
/<a[^>]*href=["']https:\/\/www\.podmatch\.com\/member\/codingcatdev["'][^>]*>[\s\S]*?<\/a>/gi,
29+
"",
30+
)
31+
.replace(/<button\b[^<]*(?:(?!<\/button>)<[^<]*)*<\/button>/gi, "")
2332
.replace(/<nav\b[^<]*(?:(?!<\/nav>)<[^<]*)*<\/nav>/gi, "")
2433
.replace(/<header\b[^<]*(?:(?!<\/header>)<[^<]*)*<\/header>/gi, "")
2534
.replace(/<footer\b[^<]*(?:(?!<\/footer>)<[^<]*)*<\/footer>/gi, "");
@@ -79,7 +88,8 @@ export function htmlToMarkdown(html: string, pageUrl?: string): string {
7988
);
8089

8190
// 7. Bold, Italic, Strikethrough
82-
text = text.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
91+
text = text.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "**$2** ");
92+
text = text.replace(/\*\* +([.,!?:;])/g, "**$1");
8393
text = text.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
8494
text = text.replace(/<(s|del|strike)[^>]*>([\s\S]*?)<\/\1>/gi, "~~$2~~");
8595

@@ -108,7 +118,7 @@ export function htmlToMarkdown(html: string, pageUrl?: string): string {
108118

109119
// 10. Lists
110120
text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_match, item) => {
111-
return `\n- ${stripTags(item).trim()}`;
121+
return `\n- ${stripTags(item).replace(/\s+/g, " ").trim()}`;
112122
});
113123
text = text.replace(/<\/(ul|ol)>/gi, "\n\n");
114124

‎apps/site/src/lib/sanity/queries/shared.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const contentFields = `
5656
durationSeconds,
5757
status,
5858
summary,
59+
fullText,
5960
chapters,
6061
cues,
6162
statistics

‎apps/site/src/lib/structured-data.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export function videoObjectSchema(
176176
thumbnailUrl?: string;
177177
youtubeUrl?: string | null;
178178
durationSeconds?: number | null;
179+
transcript?: string | null;
179180
},
180181
path: string,
181182
): Node | null {
@@ -211,6 +212,7 @@ export function videoObjectSchema(
211212
...(video.thumbnailUrl ? { thumbnailUrl: [video.thumbnailUrl] } : {}),
212213
...(video.uploadDate ? { uploadDate: video.uploadDate } : {}),
213214
...(durationIso ? { duration: durationIso } : {}),
215+
...(video.transcript ? { transcript: video.transcript } : {}),
214216
contentUrl: video.youtubeUrl,
215217
embedUrl: video.youtubeUrl
216218
.replace("watch?v=", "embed/")
@@ -310,6 +312,7 @@ interface PodcastEpisodeInput {
310312
season?: number | null;
311313
episode?: number | null;
312314
audioUrl?: string | null;
315+
transcript?: string | null;
313316
}
314317

315318
export function podcastEpisodeSchema(
@@ -336,6 +339,7 @@ export function podcastEpisodeSchema(
336339
},
337340
}
338341
: {}),
342+
...(content.transcript ? { transcript: content.transcript } : {}),
339343
partOfSeries: {
340344
"@type": "PodcastSeries",
341345
name: "CodingCat.dev Podcast",
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { APIRoute } from "astro";
2+
import { getAuth } from "@/lib/auth";
3+
4+
export const prerender = false;
5+
6+
/**
7+
* Web Bot Auth / HTTP Message Signatures Directory (RFC 9421 / draft-meunier-web-bot-auth-architecture)
8+
* Publishes a JWKS with public Ed25519 keys for verifying signed bot/agent requests.
9+
*/
10+
const FALLBACK_JWKS = {
11+
keys: [
12+
{
13+
alg: "EdDSA",
14+
crv: "Ed25519",
15+
kty: "OKP",
16+
use: "sig",
17+
x: "2KZd5-3bT1SibLVO26SW5o6IxfbpnhJAiHDpRl2eX_o",
18+
kid: "IFc7GiWLhvkCMoy5wasQWXr3coWets0F",
19+
},
20+
],
21+
};
22+
23+
export const GET: APIRoute = async ({ request, locals }) => {
24+
const origin = locals.siteUrl.origin;
25+
const auth = getAuth(origin);
26+
27+
try {
28+
const req = new Request(`${origin}/jwks`, request);
29+
const res = await auth.handler(req);
30+
if (res.ok) {
31+
const data = (await res.json()) as { keys?: unknown[] };
32+
if (Array.isArray(data?.keys) && data.keys.length > 0) {
33+
return new Response(JSON.stringify(data, null, 2), {
34+
headers: {
35+
"content-type":
36+
"application/http-message-signatures-directory+json; charset=utf-8",
37+
"access-control-allow-origin": "*",
38+
"cache-control": "public, max-age=3600, s-maxage=86400",
39+
},
40+
});
41+
}
42+
}
43+
} catch {
44+
// Fall through to deterministic public verification key
45+
}
46+
47+
return new Response(JSON.stringify(FALLBACK_JWKS, null, 2), {
48+
headers: {
49+
"content-type":
50+
"application/http-message-signatures-directory+json; charset=utf-8",
51+
"access-control-allow-origin": "*",
52+
"cache-control": "public, max-age=3600, s-maxage=86400",
53+
},
54+
});
55+
};

‎apps/site/src/pages/.well-known/oauth-protected-resource.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const GET: APIRoute = async ({ locals }) => {
99
const origin = locals.siteUrl.origin;
1010

1111
const protectedResource = {
12-
resource: `${origin}/mcp`,
12+
resource: origin,
1313
authorization_servers: [origin],
1414
scopes_supported: [
1515
"openid",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { APIRoute } from "astro";
2+
import { defineQuery } from "groq";
3+
4+
export const prerender = false;
5+
6+
const blogLlmsQuery = defineQuery(`
7+
*[_type == "post" && defined(slug.current)] | order(coalesce(date, _createdAt) desc) [0...100] {
8+
"title": coalesce(title, "Untitled"),
9+
"slug": slug.current,
10+
excerpt,
11+
"date": coalesce(date, _createdAt),
12+
"authors": author[]->title
13+
}
14+
`);
15+
16+
export const GET: APIRoute = async ({ locals }) => {
17+
const origin = locals.siteUrl.origin;
18+
19+
let posts: Array<{
20+
title: string;
21+
slug: string;
22+
excerpt?: string | null;
23+
date?: string | null;
24+
authors?: Array<string | null> | null;
25+
}> = [];
26+
27+
try {
28+
posts = (await locals.sanity.fetchPublished(blogLlmsQuery)) ?? [];
29+
} catch (err) {
30+
console.warn("Failed to fetch posts for /blog/llms.txt:", err);
31+
}
32+
33+
let content = `# CodingCat.dev Blog — Tutorials & Architecture Guides
34+
35+
> Scoped LLM directory of CodingCat.dev blog posts, tutorials, and technical articles. Each URL supports \`/index.md\`, \`.md\`, or \`Accept: text/markdown\`. For the site-wide index see ${origin}/llms.txt.
36+
37+
## Tutorials & Articles
38+
`;
39+
40+
for (const post of posts) {
41+
const url = `${origin}/post/${post.slug}/index.md`;
42+
const desc = post.excerpt
43+
? `: ${post.excerpt.replace(/\s+/g, " ").trim()}`
44+
: "";
45+
content += `- [${post.title}](${url})${desc}\n`;
46+
}
47+
48+
return new Response(content, {
49+
headers: {
50+
"content-type": "text/plain; charset=utf-8",
51+
"cache-control": "public, max-age=3600, s-maxage=86400",
52+
"access-control-allow-origin": "*",
53+
Vary: "Accept",
54+
Link: `<${origin}/llms.txt>; rel="up", <${origin}/blog/rss.xml>; rel="alternate"; type="application/rss+xml"`,
55+
},
56+
});
57+
};

0 commit comments

Comments
 (0)