"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { ArrowRight, Eye, EyeOff, Github, Gitlab, KeyRound, Sparkles, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/lib/api"; // ─── Templates (no-op for now) ────────────────────────────────────────────── // Display-only cards; clicking pre-fills the URL bar with a known starter // repo. Wiring real template cloning is a later story. type Template = { badge: string; title: string; description: string; // repoUrl is the URL we pre-fill on click. `null` means the template // isn't wired yet — the card renders disabled with a SOON pill. repoUrl: string | null; }; const TEMPLATES: Template[] = [ { badge: "LANGGRAPH", title: "LangGraph quickstart", description: "Stateful agent graph with tool calls + memory.", repoUrl: "https://github.com/patel-lyzr/langraph-agent", }, { badge: "CREWAI", title: "CrewAI starter", description: "Multi-agent crew with role-based collaboration.", repoUrl: null, }, { badge: "LANGCHAIN", title: "LangChain agent", description: "Classic ReAct agent with retrievers and tools.", repoUrl: null, }, ]; export default function NewAgentPage() { const router = useRouter(); const [repoUrl, setRepoUrl] = useState(""); const [pat, setPat] = useState(""); const [showPat, setShowPat] = useState(false); const [showPatField, setShowPatField] = useState(false); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const urlInputRef = useRef(null); const patInputRef = useRef(null); // Focus the URL bar after either provider/template card click so the user // immediately has a clear next action. useEffect(() => { if (showPatField) { // Give the input a beat to render before focusing. requestAnimationFrame(() => patInputRef.current?.focus()); } }, [showPatField]); function pickTemplate(t: Template) { if (!t.repoUrl) return; setRepoUrl(t.repoUrl); setShowPatField(true); requestAnimationFrame(() => urlInputRef.current?.focus()); } function continueWithGitHub() { setShowPatField(true); if (!repoUrl.trim()) { requestAnimationFrame(() => urlInputRef.current?.focus()); } else { requestAnimationFrame(() => patInputRef.current?.focus()); } } async function onSave() { setSaving(true); setError(null); try { const url = repoUrl.trim(); if (!url) throw new Error("Repository URL is required"); await api.createAgent({ repoUrl: url, pat: pat.trim() || undefined, }); router.push("/agents"); } catch (e) { setError(e instanceof Error ? e.message : "save failed"); } finally { setSaving(false); } } return (

Register an agent

One agent = one repo + one PAT + the pipelines that ship it.

{/* URL bar -------------------------------------------------------- */}
setRepoUrl(e.target.value)} placeholder="Paste a GitHub repo URL — or pick a template below" spellCheck={false} autoComplete="off" className="h-10 border-0 bg-transparent text-sm focus-visible:ring-0" />
{/* Two-column: provider + templates ------------------------------- */}
{/* Import Git Repository ---------------------------------------- */}
Import Git Repository

Pick a provider. We use a PAT to install a webhook on your repo so push/PR events trigger pipelines.

Only GitHub is wired up in v0. GitLab and Bitbucket are next.

{/* Clone Template ----------------------------------------------- */}
Clone Template
Framework starters
{TEMPLATES.map((t) => { const disabled = !t.repoUrl; return ( ); })}
More framework starters coming soon. Want one added? Open an issue on the Langship repo.
{/* PAT entry + Save (revealed after a provider/template is chosen) - */} {showPatField && (
Connect repo

HTTPS URL above; PAT below. Public repos can leave the PAT blank.

setRepoUrl(e.target.value)} placeholder="https://github.com/org/agent-repo.git" spellCheck={false} autoComplete="off" />

Agent name is auto-derived from the repo path (e.g.{" "} org/repo).

setPat(e.target.value)} placeholder="ghp_… or glpat_…" spellCheck={false} autoComplete="new-password" className="pr-10 font-mono text-xs" />

Required for private repos. Scope:{" "} repo read access is enough. Stored encrypted server-side; never returned by the API after save.

{error && (

{error}

)}

After saving, open the agent to attach pipelines and (if you’ll use the Deploy node) override credentials.

)} {/* Empty agent --------------------------------------------------- */}
Empty agent

Skip git for now and configure the connection later. Coming soon — for now an agent must be registered with a repo + PAT.

); } function ProviderDisabled({ icon: Icon, label, }: { icon: React.ComponentType<{ className?: string }>; label: string; }) { return (
{label} SOON
); } // Bitbucket isn't in lucide-react. Tiny inline SVG to match the visual. function BitbucketIcon({ className }: { className?: string }) { return ( ); }