commit 15fa8be09a1fddd89e519e661d03dd360b40b32d
parent 1f285f61c0182a962eeaf92f4ea6f72dc327d306
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 30 Sep 2026 18:57:59 -0400
common: AI_DOC_URL — corpus.json's useWithAi and llms.txt's Use with AI name the homepage's AI and MCP doc; the sitemap drops /use-with-ai
A site (and the hub) has no /use-with-ai page any more. corpus.json keeps the useWithAi key with
the doc's URL; llms.txt's Ask AI section lists the site's /ask/ chat and the doc as two lines.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
5 files changed, 23 insertions(+), 150 deletions(-)
diff --git a/common/bin/compose-site.ts b/common/bin/compose-site.ts
@@ -179,7 +179,7 @@ async function emitAiFiles(paths: ReturnType<typeof getPaths>): Promise<void> {
// siteUrl; otherwise clear any stale copy from a previous build.
const sitemapPath = path.join(paths.exportPublicDir, "sitemap.xml");
if (descriptor.siteUrl) {
- const routes = ["/", "/use-with-ai", "/changelog"];
+ const routes = ["/", "/changelog"];
if (hasArchives) routes.push("/downloads");
if (await exists(path.join(paths.exportPublicDir, DUPLICATES_FILENAME))) {
routes.push("/duplicates");
diff --git a/common/lib/corpus.test.ts b/common/lib/corpus.test.ts
@@ -207,8 +207,8 @@ test("renderRobotsTxt: sitemap line only with an absolute siteUrl", () => {
test("renderSitemapXml: one loc per route", () => {
const xml = renderSitemapXml({
siteUrl: "https://demo.example",
- routes: ["/", "/use-with-ai"],
+ routes: ["/", "/changelog"],
});
assert.match(xml, /<loc>https:\/\/demo\.example\/<\/loc>/);
- assert.match(xml, /<loc>https:\/\/demo\.example\/use-with-ai<\/loc>/);
+ assert.match(xml, /<loc>https:\/\/demo\.example\/changelog<\/loc>/);
});
diff --git a/common/lib/corpus.ts b/common/lib/corpus.ts
@@ -1,5 +1,5 @@
import type { PublicSiteDescriptor } from "./siteDescriptor";
-import { PROJECT_GENERATOR } from "./project";
+import { AI_DOC_URL, PROJECT_GENERATOR } from "./project";
import { TAGS_FILENAME } from "./curatedTags";
import {
CONTRACT,
@@ -190,7 +190,9 @@ export type SiteCorpus = {
tags?: { url: string; videoField: "curatedTags"; description: string };
// Present when this build ships bulk-download archives (whole-channel zips).
bulkArchives?: { manifest: string; note: string };
- // Pointer to the human page and BYO-key chat.
+ // Pointer to the human page on using the archive with AI: the homepage's AI
+ // and MCP doc (AI_DOC_URL; the site's own /use-with-ai page until release
+ // 16). The BYO-key chat is the site's /ask/.
useWithAi: string;
};
@@ -270,7 +272,7 @@ export function buildSiteCorpus(
totals: { channels: channels.length, videos },
channels,
shardScheme: SHARD_SCHEME,
- useWithAi: join(base, "/use-with-ai"),
+ useWithAi: AI_DOC_URL,
};
// Only advertise the post scheme when this site actually ships posts, so a
// pure-video site's corpus.json is unchanged apart from the spec bump.
@@ -337,7 +339,7 @@ export function buildHubCorpus(
"the whole federation, fetch each site's corpus.json and follow its " +
"shardScheme; results can be merged client-side.",
},
- useWithAi: join(opts.hubUrl, "/use-with-ai"),
+ useWithAi: AI_DOC_URL,
};
}
@@ -362,8 +364,11 @@ export function renderSiteLlmsTxt(corpus: SiteCorpus): string {
out.push("");
out.push("## Ask AI");
out.push(
- `- [Use with AI](${join(base, "/use-with-ai")}): in-browser chat (bring ` +
- `your own API key) and MCP-server setup for Claude Code, Cursor, and other tools.`,
+ `- [Ask AI](${join(base, "/ask/")}): in-browser chat (bring your own API key).`,
+ );
+ out.push(
+ `- [Use with AI](${AI_DOC_URL}): MCP-server setup for Claude Code, Cursor, ` +
+ `and other tools.`,
);
out.push("");
out.push("## Corpus");
@@ -426,9 +431,10 @@ export function renderHubLlmsTxt(corpus: HubCorpus): string {
out.push("");
out.push("## Ask AI");
out.push(
- `- [Use with AI](${join(base, "/use-with-ai")}): ask across the whole ` +
- `federation (bring your own API key) or wire up the MCP server.`,
+ `- [Ask AI](${join(base, "/ask/")}): ask across the whole federation ` +
+ `(bring your own API key).`,
);
+ out.push(`- [Use with AI](${AI_DOC_URL}): wire up the MCP server.`);
out.push("");
out.push("## Federation");
out.push(
diff --git a/common/lib/project.ts b/common/lib/project.ts
@@ -44,6 +44,12 @@ export const PROJECT_TAGLINE = "Self-hosted, searchable video-transcript archive
// homepage/app/source); this is its no-git alternative.
export const PROJECT_DOWNLOADS_URL = `${PROJECT_URL}/downloads/`;
+// The homepage's AI and MCP doc (homepage/content/docs/ai-and-mcp.md): the
+// published contract, the MCP server and its setup, in one place. Every
+// archive's "Use with AI" link goes here, in the same tab (release 16 slice
+// DX), as do corpus.json's `useWithAi` and llms.txt's line of that name.
+export const AI_DOC_URL = `${PROJECT_URL}/docs/ai-and-mcp/`;
+
// The `generator` string stamped into corpus.json and llms.txt, so anything
// that reads an archive machine-side can find the software that built it.
// Shape mirrors the HTML <meta name="generator"> convention.
diff --git a/export/app/use-with-ai/page.tsx b/export/app/use-with-ai/page.tsx
@@ -1,139 +0,0 @@
-import type { Metadata } from "next";
-import Link from "next/link";
-import { currentSite } from "../lib/site";
-import { instanceMode } from "../lib/mode";
-import { hasArchives } from "../lib/archives";
-
-export const metadata: Metadata = { title: "Use with AI" };
-
-// A human-facing hub for the "bring your own AI" surface: the in-browser chat,
-// the machine-readable discovery files (llms.txt / corpus.json), and the MCP
-// server. Hub-aware — on a hub build it frames everything as federation-wide.
-// Fully static server component; no data fetching.
-export default function UseWithAiPage() {
- const site = currentSite();
- const isHub = instanceMode() === "hub";
- const base = site.siteUrl?.replace(/\/+$/, "") ?? "";
- const abs = (p: string) => (base ? `${base}${p}` : p);
- const scope = isHub ? "the whole federation" : "this archive";
- const envVar = isHub ? "TRANSCRIPT_HUB_URL" : "TRANSCRIPT_SITE_URL";
- const target = base || (isHub ? "https://your-hub.example" : "https://your-site.example");
-
- const mcpSnippet = `{
- "mcpServers": {
- "${site.siteId}": {
- "command": "pnpm",
- "args": [
- "-C", "/path/to/yt-dlp-transcript-browser",
- "--filter", "yt-dlp-transcript-mcp",
- "exec", "tsx", "src/index.ts"
- ],
- "env": { "${envVar}": "${target}" }
- }
- }
-}`;
-
- return (
- <div className="mx-auto flex max-w-3xl flex-col gap-10">
- <header className="flex flex-col gap-3 border-b border-border pb-6">
- <p className="font-mono text-xs uppercase tracking-[0.18em] text-brand">
- Use with AI · {site.headerTitle}
- </p>
- <h1 className="font-display text-3xl font-semibold leading-tight text-foreground">
- Ask an AI about {scope}
- </h1>
- <p className="max-w-prose text-sm text-muted-foreground">
- Nothing is hosted or paid for here — you bring your own AI. Chat in your
- browser with your own API key, or point a tool like Claude Code at the
- machine-readable index and let it browse the transcripts itself.
- </p>
- </header>
-
- <section className="flex flex-col gap-3">
- <h2 className="font-mono text-xs uppercase tracking-[0.14em] text-muted-foreground">
- Chat in your browser
- </h2>
- <div className="flex flex-col gap-3 rounded-lg border border-border bg-card/40 p-5">
- <p className="text-sm text-muted-foreground">
- A retrieval-augmented chat that searches the transcripts and answers
- with citations. It runs entirely in your browser and calls your own
- provider (Anthropic, OpenAI, or Google Gemini) with a key you supply —
- the key stays on your device and requests go straight to the provider.
- </p>
- <div>
- <Link
- href="/ask"
- className="inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-brand-strong"
- >
- Open the chat →
- </Link>
- </div>
- </div>
- </section>
-
- <section className="flex flex-col gap-3">
- <h2 className="font-mono text-xs uppercase tracking-[0.14em] text-muted-foreground">
- For coding agents & LLM tools
- </h2>
- <div className="flex flex-col gap-3 rounded-lg border border-border bg-card/40 p-5">
- <p className="text-sm text-muted-foreground">
- {scope[0].toUpperCase() + scope.slice(1)} publishes a small, fixed set
- of discovery files. An agent (e.g. Claude Code via <code className="font-mono">WebFetch</code>)
- can read these and navigate every transcript without any per-video
- pages — the paginated shard scheme is documented inline.
- </p>
- <ul className="flex flex-col gap-2 text-sm">
- <li>
- <a href={abs("/llms.txt")} className="font-mono text-brand hover:underline">
- /llms.txt
- </a>
- <span className="text-muted-foreground"> — an LLM-readable overview and link map.</span>
- </li>
- <li>
- <a href={abs("/corpus.json")} className="font-mono text-brand hover:underline">
- /corpus.json
- </a>
- <span className="text-muted-foreground">
- {" "}— the channel index and exactly how to fetch any transcript
- from the JSON shards{isHub ? " across every member site" : ""}.
- </span>
- </li>
- </ul>
- </div>
- </section>
-
- <section className="flex flex-col gap-3">
- <h2 className="font-mono text-xs uppercase tracking-[0.14em] text-muted-foreground">
- MCP server
- </h2>
- <div className="flex flex-col gap-3 rounded-lg border border-border bg-card/40 p-5">
- <p className="text-sm text-muted-foreground">
- The repo ships an MCP server that exposes {scope} to Claude Code,
- Claude Desktop, Cursor, and other MCP clients as tools
- (<code className="font-mono">search_transcripts</code>,
- {" "}<code className="font-mono">get_transcript</code>, …). It reads the
- same static shards — over HTTP or from a local build
- {isHub ? ", federating every member site" : ""}. Add it to a client:
- </p>
- <pre className="overflow-x-auto rounded-md border border-border bg-muted/50 p-3 font-mono text-xs text-foreground">
- <code>{mcpSnippet}</code>
- </pre>
- <p className="text-xs text-muted-foreground/80">
- Setup details and the <code className="font-mono">claude mcp add</code>{" "}
- command are in <code className="font-mono">mcp/README.md</code>.
- </p>
- </div>
- </section>
-
- {hasArchives() && (
- <p className="text-xs text-muted-foreground/70">
- Ingesting in bulk instead? The{" "}
- <a href="/downloads" className="text-brand hover:underline">
- Downloads page
- </a>{" "}
- has whole-channel transcript zips.
- </p>
- )}
- </div>
- );
-}