Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

120 linesCodeBlameRaw
1import { Check, Copy, ExternalLink } from "lucide-react";
2import { useMemo, useState, useSyncExternalStore } from "react";
3import { useRouteLoaderData } from "react-router";
4
5import type { loader as rootLoader } from "../root";
6
7import {
8 AGENT_IDS,
9 type AgentId,
10 DEFAULT_AGENT,
11 agentsFor,
12 cursorInstallLink,
13 getAgentChoice,
14 setAgentChoice,
15 subscribeAgentChoice,
16} from "../lib/agent-setup";
17import { useAddresses } from "../lib/addresses";
18import { SelectField } from "./ui/select";
19import { Button } from "./ui/button";
20import { Card } from "./ui/card";
21
22/** The agent this browser chose last; every toggle on the page shares it. */
23export function useAgentChoice(): [AgentId, (id: AgentId) => void] {
24 const root = useRouteLoaderData<typeof rootLoader>("root");
25 const fallback = root?.agentDefault ?? DEFAULT_AGENT;
26 const id = useSyncExternalStore(subscribeAgentChoice, () => getAgentChoice(fallback), () => root?.agentChoice ?? fallback);
27 return [id, setAgentChoice];
28}
29
30/**
31 * How to connect g1t to a coding agent: a client picker, the command or
32 * config for the one chosen, and a copy button. Choosing an agent switches
33 * every block on the page and is remembered.
34 */
35export function AgentSetup({ hint = true, className }: { hint?: boolean; className?: string }) {
36 const [chosen, choose] = useAgentChoice();
37 const [copied, setCopied] = useState(false);
38 const { mcp } = useAddresses();
39 const agents = useMemo(() => agentsFor(mcp), [mcp]);
40 const agent = agents[chosen];
41
42 const copy = () => {
43 void navigator.clipboard?.writeText(agent.code).then(
44 () => {
45 setCopied(true);
46 setTimeout(() => setCopied(false), 1500);
47 },
48 () => undefined,
49 );
50 };
51
52 return (
53 <div className={className}>
54 <Card radius="lg" className="@container overflow-hidden">
55 <div className="flex items-center justify-between gap-3 border-b border-line px-3.5 py-2">
56 <span className="text-xs text-muted">Connect with</span>
57 <SelectField
58 aria-label="AI client"
59 value={chosen}
60 onValueChange={(id) => { setCopied(false); choose(id as AgentId); }}
61 options={AGENT_IDS.map((id) => ({ value: id, label: agents[id].label }))}
62 size="sm"
63 className="w-44"
64 />
65 </div>
66 <div className="relative" role="region" aria-label={`${agent.label} setup`}>
67 {agent.file && (
68 <p className="px-3.5 pt-2.5 font-mono text-[0.6875rem] text-faint">{agent.file}</p>
69 )}
70 <pre className="overflow-x-auto py-2.5 pr-11 pl-3.5 font-mono text-[0.8125rem] leading-relaxed">
71 <code>
72 {agent.lang === "sh"
73 ? agent.code.split("\n").map((line, index) => (
74 <span key={index} className="block whitespace-pre-wrap wrap-anywhere">
75 <span className="mr-2 text-faint select-none">$</span>
76 {line}
77 </span>
78 ))
79 : agent.code}
80 </code>
81 </pre>
82 <Button
83 type="button"
84 aria-label={copied ? "Copied" : `Copy the ${agent.label} ${agent.file ? "config" : agent.lang === "text" ? "URL" : "command"}`}
85 onClick={copy}
86 variant="ghost"
87 size="inline"
88 className="absolute top-1.5 right-1.5 p-1.5 text-faint"
89 >
90 {copied ? <Check size={14} className="text-success" /> : <Copy size={14} />}
91 </Button>
92 </div>
93 </Card>
94 {hint && (
95 <p className="mt-2 text-xs leading-5 text-muted">
96 {agent.then.split("`").map((part, index) =>
97 index % 2 ? (
98 <code key={index} className="font-mono text-fg-soft">
99 {part}
100 </code>
101 ) : (
102 part
103 ),
104 )}
105 {chosen === "codex" && mcp === "https://mcp.g1t.sh" && (
106 <> {" "}<a href="https://docs.g1t.sh/guides/bring-your-own-agent/#codex-marketplace" className="text-fg underline underline-offset-4">Install the Codex plugin</a> for issue and session workflows.</>
107 )}
108 {chosen === "cursor" && (
109 <>
110 {" "}
111 <a href={cursorInstallLink(mcp)} className="inline-flex items-center gap-1 text-fg underline underline-offset-4">
112 Add to Cursor <ExternalLink size={11} />
113 </a>
114 </>
115 )}
116 </p>
117 )}
118 </div>
119 );
120}