'use client'; import { useEffect, useState } from 'react'; import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; import { ShieldCheck, GitFork, Activity, ExternalLink, ChevronDown, ChevronRight } from 'lucide-react'; import { apiFetch } from '@/lib/api'; import { Logo } from '@/components/logo'; import { Button } from '@/components/ui/button'; import { CodeBlock } from '@/components/code-block'; interface Tool { name: string; description: string; inputSchema: Record; } interface SecretHint { key: string; description: string; howToGetUrl?: string; } interface TemplateDetail { id: string; slug: string; title: string; shortDescription: string; longDescription: string | null; category: string; verified: boolean; forkCount: number; activeDeployments: number; toolsSchema: Tool[]; generatedCode: string; requiredSecrets: SecretHint[]; scopes: string[]; ownerName: string | null; ownerOrgName: string | null; createdAt: string; } export default function TemplateDetail() { const params = useParams<{ slug: string }>(); const router = useRouter(); const [template, setTemplate] = useState(null); const [error, setError] = useState(null); const [showCode, setShowCode] = useState(false); useEffect(() => { apiFetch<{ template: TemplateDetail }>(`/v1/templates/${params.slug}`) .then((r) => setTemplate(r.template)) .catch((e) => { const detail = (e as { detail?: { error?: string } }).detail; setError(detail?.error ?? (e as Error).message); }); }, [params.slug]); function useTemplate() { if (!template) return; router.push(`/servers/new?template=${template.slug}`); } if (error) { return (

Template not found.

← Back to marketplace
); } if (!template) { return (
Loading…
); } return (
/ templates / {template.slug}
Start building
{template.category} {template.verified && ( verified )}

{template.title}

{template.shortDescription}

{template.longDescription && (

{template.longDescription}

)}

Tools ({template.toolsSchema.length})

{template.toolsSchema.map((tool) => (
{tool.name} {Object.keys(tool.inputSchema ?? {}).length} param {Object.keys(tool.inputSchema ?? {}).length === 1 ? '' : 's'}

{tool.description}

{Object.keys(tool.inputSchema ?? {}).length > 0 && (
)}
))}
{template.requiredSecrets.length > 0 && (

Credentials you'll need

When you fork, the wizard asks you for these. Your values stay in your container — the template author never sees them.

{template.requiredSecrets.map((s) => (
{s.key} {s.howToGetUrl && ( How to get one )}

{s.description}

))}
)}

Audit before you fork. We re-scan every published template for banned patterns (eval, child_process, prompt-injection markers).

{showCode && (
)}
); } function Row({ label, value, mono, icon, }: { label: string; value: string | number; mono?: boolean; icon?: React.ReactNode; }) { return (
{label} {icon} {value}
); }