docs: mdx, light/dark mode, ask (#400)

This commit is contained in:
Chris Tate
2026-02-09 11:16:21 -06:00
committed by GitHub
parent 4d8097a56f
commit e8ceafcbe1
36 changed files with 4221 additions and 1258 deletions
+18
View File
@@ -0,0 +1,18 @@
export type NavItem = {
name: string;
href: string;
};
export const allDocsPages: NavItem[] = [
{ name: "Introduction", href: "/" },
{ name: "Installation", href: "/installation" },
{ name: "Quick Start", href: "/quick-start" },
{ name: "Commands", href: "/commands" },
{ name: "Selectors", href: "/selectors" },
{ name: "Sessions", href: "/sessions" },
{ name: "Snapshots", href: "/snapshots" },
{ name: "Streaming", href: "/streaming" },
{ name: "CDP Mode", href: "/cdp-mode" },
{ name: "iOS Simulator", href: "/ios" },
{ name: "Changelog", href: "/changelog" },
];
+54
View File
@@ -0,0 +1,54 @@
/**
* Converts raw MDX content to clean Markdown suitable for AI agents.
*
* Transformations:
* - Remove `export` statements (metadata, etc.)
* - Remove `import` statements
* - Strip standalone JSX divs with className attributes
* - Pass everything else through as-is (already valid Markdown)
*/
export function mdxToCleanMarkdown(raw: string): string {
const lines = raw.split("\n");
const out: string[] = [];
let inJsxBlock = false;
let jsxDepth = 0;
for (const line of lines) {
const trimmed = line.trim();
// Skip export and import statements
if (trimmed.startsWith("export ") || trimmed.startsWith("import ")) {
continue;
}
// Track JSX blocks (like callout divs) and skip them
if (
!inJsxBlock &&
trimmed.startsWith("<div ") &&
trimmed.includes("className=")
) {
inJsxBlock = true;
jsxDepth = 1;
continue;
}
if (inJsxBlock) {
// Count opening/closing div tags to handle nesting
const opens = (line.match(/<div[\s>]/g) || []).length;
const closes = (line.match(/<\/div>/g) || []).length;
jsxDepth += opens - closes;
if (jsxDepth <= 0) {
inJsxBlock = false;
jsxDepth = 0;
}
continue;
}
out.push(line);
}
// Clean up leading blank lines
let result = out.join("\n");
result = result.replace(/^\n+/, "\n").trim();
return result;
}
+57
View File
@@ -0,0 +1,57 @@
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
// Lazy initialization to avoid errors when Redis env vars are not configured
let _minuteRateLimit: Ratelimit | null = null;
let _dailyRateLimit: Ratelimit | null = null;
function getRedis(): Redis | null {
const url = process.env.KV_REST_API_URL;
const token = process.env.KV_REST_API_TOKEN;
if (!url || !token) {
return null;
}
return new Redis({ url, token });
}
// No-op rate limiter for when Redis is not configured
const noopRateLimiter = {
limit: async () => ({ success: true, limit: 0, remaining: 0, reset: 0 }),
};
const MINUTE_LIMIT = Number(process.env.RATE_LIMIT_PER_MINUTE) || 10;
const DAILY_LIMIT = Number(process.env.RATE_LIMIT_PER_DAY) || 100;
// Requests per minute (sliding window)
export const minuteRateLimit = {
limit: async (identifier: string) => {
if (!_minuteRateLimit) {
const redis = getRedis();
if (!redis) return noopRateLimiter.limit();
_minuteRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(MINUTE_LIMIT, "1 m"),
prefix: "ratelimit:minute",
});
}
return _minuteRateLimit.limit(identifier);
},
};
// Requests per day (fixed window)
export const dailyRateLimit = {
limit: async (identifier: string) => {
if (!_dailyRateLimit) {
const redis = getRedis();
if (!redis) return noopRateLimiter.limit();
_dailyRateLimit = new Ratelimit({
redis,
limiter: Ratelimit.fixedWindow(DAILY_LIMIT, "1 d"),
prefix: "ratelimit:daily",
});
}
return _dailyRateLimit.limit(identifier);
},
};