"use client"; import { useEffect, useMemo, useState, type ChangeEvent, type FormEvent } from "react"; import { useTheme } from "next-themes"; import { Copy, FileJson, Globe, Search, Shield, AlertTriangle, Terminal, Settings2, Info, BookOpen, Key, Moon, Sun } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; const PRESET_SNIPPETS = [ { label: "Skip forms", text: "The agent MUST NOT fill or submit any forms.", }, { label: "Read-only", text: "Limit the agent to reading content and following public navigation links only.", }, { label: "Respect rate limits", text: "The agent SHOULD throttle requests and avoid more than one page fetch per second.", }, ]; type ExistingPolicy = { url: string; status: number; body?: unknown; raw?: string; } | null; type CrawlSummary = { totalPages: number; pagesWithForms: number; pagesWithLogins: number; robots: { present: boolean; crawlDelay?: number | null; }; }; type CrawlPage = { url: string; title: string | null; textContent: string; htmlContent: string; isTextTruncated: boolean; isHtmlTruncated: boolean; wordCount: number; hasForms: boolean; hasSearch: boolean; containsLogin: boolean; }; type ProcessLogEntry = { step: string; detail?: string; elapsedMs: number; }; type ApiResponse = { input: { url: string; instructions: string; mode: string; }; notes: string[]; existingPolicy: ExistingPolicy; llm: { model: string; raw: string; policy: unknown | null; error?: string; }; processLog: ProcessLogEntry[]; crawlSummary: CrawlSummary; crawlPages: CrawlPage[]; }; type QuickBuilderState = { siteName: string; allowReadContent: boolean; allowReadMetadata: boolean; allowNavigation: boolean; allowForms: boolean; requireHumanForForms: boolean; blockLogin: boolean; rateLimit: "gentle" | "standard" | "open"; allowDownloads: boolean; }; type ParsedPolicy = { error?: string; metadata?: { schema_version?: string; last_updated?: string; author?: string; }; resourceRules: Array<{ verb: string; selector: string; allowed: boolean; modifiers?: { burst?: number; rate_limit?: { max_requests?: number; window_seconds?: number }; time_window?: string; human_in_the_loop?: boolean; }; }>; actionGuidelines: Array<{ directive: string; description: string; exceptions?: string; }>; }; type TabKey = "quick" | "generator" | "parser"; const DEFAULT_QUICK_STATE: QuickBuilderState = { siteName: "", allowReadContent: true, allowReadMetadata: true, allowNavigation: true, allowForms: true, requireHumanForForms: false, blockLogin: false, rateLimit: "open", allowDownloads: true, }; export default function Home() { const [activeTab, setActiveTab] = useState("quick"); const { theme, setTheme } = useTheme(); // State for mounting to avoid hydration mismatch const [mounted, setMounted] = useState(false); const [url, setUrl] = useState(""); const [instructions, setInstructions] = useState(""); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [result, setResult] = useState(null); const [processLog, setProcessLog] = useState([]); const [quickState, setQuickState] = useState(DEFAULT_QUICK_STATE); const [parserInput, setParserInput] = useState(`{ "metadata": { "schema_version": "1.0.0", "last_updated": "2025-01-01T00:00:00Z", "author": "Example Corp" }, "resource_rules": [ { "verb": "read_content", "selector": "*", "allowed": true }, { "verb": "follow_link", "selector": "nav a, main a", "allowed": true } ], "action_guidelines": [ { "directive": "MUST NOT", "description": "Submit credentials or attempt to log in." } ] }`); const quickPolicy = useMemo(() => buildQuickPolicy(quickState), [quickState]); const parsedPolicy = useMemo(() => parsePolicy(parserInput), [parserInput]); useEffect(() => { setMounted(true); }, []); const handleSubmit = async (event: FormEvent) => { event.preventDefault(); setSubmitting(true); setError(null); setResult(null); setProcessLog([{ step: "Submitting request", detail: url, elapsedMs: 0 }]); const started = Date.now(); try { const response = await fetch("/api/generate?stream=1", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url, instructions, mode: "static", }), }); if (!response.ok || !response.body) { const payload = (await response.json().catch(() => null)) as { error?: string } | null; throw new Error(payload?.error ?? "Request failed."); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; const flushChunk = (chunk: string) => { const parts = chunk.split("\n").filter(Boolean); if (!parts.length) return; const eventLine = parts.find((line) => line.startsWith("event:")); const dataLine = parts.find((line) => line.startsWith("data:")); const eventType = eventLine ? eventLine.replace("event:", "").trim() : "message"; const dataRaw = dataLine ? dataLine.replace("data:", "").trim() : ""; let parsed: unknown = null; if (dataRaw) { try { parsed = JSON.parse(dataRaw); } catch { parsed = null; } } if (eventType === "log" && parsed && typeof parsed === "object") { setProcessLog((prev) => [...prev, parsed as ProcessLogEntry]); } else if (eventType === "result" && parsed) { const payload = parsed as ApiResponse; setResult(payload); setProcessLog(payload.processLog ?? []); } else if (eventType === "error") { const message = (parsed as { message?: string } | null)?.message ?? "Request failed."; throw new Error(message); } }; while (true) { const { done, value } = await reader.read(); if (done) { if (buffer.trim()) { flushChunk(buffer); } break; } buffer += decoder.decode(value, { stream: true }); let boundary = buffer.indexOf("\n\n"); while (boundary !== -1) { const chunk = buffer.slice(0, boundary); flushChunk(chunk); buffer = buffer.slice(boundary + 2); boundary = buffer.indexOf("\n\n"); } } } catch (err) { setResult(null); setProcessLog((prev) => [ ...prev, { step: "Request failed", detail: err instanceof Error ? err.message : "Unknown error", elapsedMs: Date.now() - started, }, ]); setError(err instanceof Error ? err.message : "Unknown error."); } finally { setSubmitting(false); } }; const handlePreset = (snippet: string) => { setInstructions((current) => current.includes(snippet) ? current : current.length > 0 ? `${current.trim()}\n${snippet}` : snippet, ); }; const hasResults = Boolean(result); const activeTimeline = result?.processLog?.length ? result.processLog : processLog; if (!mounted) { return null; } return (

agent-permissions.json

Tools to help you define what AI agents can and cannot do on your website.

setActiveTab(v as TabKey)} className="w-full"> Quick Builder Generator Parser
Configure Permissions Rapidly create a policy by toggling common permissions.
setQuickState((prev) => ({ ...prev, siteName: e.target.value }))} />

Let agents read visible text on the page.

setQuickState((prev) => ({ ...prev, allowReadContent: c }))} />

Permit reading titles, meta tags, and structured data.

setQuickState((prev) => ({ ...prev, allowReadMetadata: c }))} />

Let agents follow links inside the page context.

setQuickState((prev) => ({ ...prev, allowNavigation: c }))} />

Permit inputs and form submissions.

setQuickState((prev) => ({ ...prev, allowForms: c }))} />

Require human approval for form actions.

setQuickState((prev) => ({ ...prev, requireHumanForForms: c }))} disabled={!quickState.allowForms} />

Explicitly forbid authentication attempts.

setQuickState((prev) => ({ ...prev, blockLogin: c }))} />

Permit file downloads.

setQuickState((prev) => ({ ...prev, allowDownloads: c }))} />
Preview
                      {quickPolicy}
                    
AI Policy Generator Enter a URL to analyze the site and generate a tailored policy.
setUrl(e.target.value)} />