flagon-io/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

g1t/apps/web/app/routes/settings.tsx

215 lines7,720 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

API and MCP server, Rust identity service, registration, site redesign1import { identity } from "../lib/services.server";
Agents as a team: lifecycle, merge queue, billing and a new shell2import { Form, Link } from "react-router";
Initial g1t: services, event bus, intents and attempts3
4import type { Route } from "./+types/settings";
OAuth 2.1 sign-in for MCP clients and other applications5import { Button, ErrorText, Field, Input, TimeAgo } from "../components/ui";
Initial g1t: services, event bus, intents and attempts6import { assertSameOrigin, requireUser } from "../lib/session.server";
7
8export function meta({}: Route.MetaArgs) {
9 return [{ title: "Settings · g1t" }];
10}
11
12export async function loader({ request, context }: Route.LoaderArgs) {
13 const user = requireUser(context, request);
OAuth 2.1 sign-in for MCP clients and other applications14 const [keys, tokens, applications] = await Promise.all([
API and MCP server, Rust identity service, registration, site redesign15 identity.listSshKeys(user),
16 identity.listAccessTokens(user),
OAuth 2.1 sign-in for MCP clients and other applications17 identity.listOAuthGrants(user),
Initial g1t: services, event bus, intents and attempts18 ]);
OAuth 2.1 sign-in for MCP clients and other applications19 return { user, keys, tokens, applications };
Initial g1t: services, event bus, intents and attempts20}
21
22export async function action({ request, context }: Route.ActionArgs) {
23 assertSameOrigin(request);
24 const user = requireUser(context, request);
25 const form = await request.formData();
26 const id = String(form.get("id") ?? "");
27
28 switch (form.get("intent")) {
29 case "add-key": {
API and MCP server, Rust identity service, registration, site redesign30 const result = await identity.addSshKey(
Initial g1t: services, event bus, intents and attempts31 user,
32 String(form.get("title") ?? ""),
33 String(form.get("key") ?? ""),
34 );
35 return result.ok ? null : { keyError: result.error.message };
36 }
37 case "delete-key":
API and MCP server, Rust identity service, registration, site redesign38 await identity.removeSshKey(user, id);
Initial g1t: services, event bus, intents and attempts39 return null;
40 case "add-token": {
API and MCP server, Rust identity service, registration, site redesign41 const created = await identity.createAccessToken(
Initial g1t: services, event bus, intents and attempts42 user,
Agents as a team: lifecycle, merge queue, billing and a new shell43 String(form.get("label") ?? ""),
Initial g1t: services, event bus, intents and attempts44 );
45 return { newToken: created.token };
46 }
47 case "delete-token":
API and MCP server, Rust identity service, registration, site redesign48 await identity.removeAccessToken(user, id);
Initial g1t: services, event bus, intents and attempts49 return null;
OAuth 2.1 sign-in for MCP clients and other applications50 case "sign-out-application":
51 await identity.revokeOAuthGrant(user, id);
52 return null;
Initial g1t: services, event bus, intents and attempts53 }
54 return null;
55}
56
OAuth 2.1 sign-in for MCP clients and other applications57function DeleteButton({
58 intent,
59 id,
60 label = "Delete",
61}: {
62 intent: string;
63 id: string;
64 label?: string;
65}) {
Initial g1t: services, event bus, intents and attempts66 return (
67 <Form method="post" className="ml-auto">
68 <input type="hidden" name="intent" value={intent} />
69 <input type="hidden" name="id" value={id} />
70 <Button variant="quiet" type="submit">
OAuth 2.1 sign-in for MCP clients and other applications71 {label}
Initial g1t: services, event bus, intents and attempts72 </Button>
73 </Form>
74 );
75}
76
77export default function Settings({
78 loaderData,
79 actionData,
80}: Route.ComponentProps) {
OAuth 2.1 sign-in for MCP clients and other applications81 const { user, keys, tokens, applications } = loaderData;
Initial g1t: services, event bus, intents and attempts82 return (
Account settings in the same shape as a workspace's83 <main className="mx-auto max-w-5xl px-4 py-10 sm:px-8">
84 <header className="mb-8 border-b border-line pb-6">
85 <h1 className="text-2xl font-semibold tracking-tight">Account</h1>
86 <p className="mt-1.5 text-sm text-muted">
87 How <span className="font-mono text-fg">{user.username}</span> signs in from git, tools and agents.
88 </p>
89 </header>
90 <div className="max-w-2xl space-y-12">
91 <section id="ssh-keys" className="scroll-mt-20">
Initial g1t: services, event bus, intents and attempts92 <h2 className="font-medium">SSH keys</h2>
93 <ul className="mt-4 divide-y divide-line rounded-md border border-line empty:hidden">
94 {keys.map((key) => (
95 <li key={key.id} className="flex items-center gap-4 px-4 py-3">
96 <div className="min-w-0">
97 <p className="text-sm">{key.title}</p>
98 <p className="truncate font-mono text-xs text-muted">
99 {key.fingerprint}
100 </p>
101 </div>
102 <DeleteButton intent="delete-key" id={key.id} />
103 </li>
104 ))}
105 </ul>
106 <Form method="post" className="mt-4 space-y-3">
107 <input type="hidden" name="intent" value="add-key" />
108 <Field label="Title (optional)">
109 <Input name="title" maxLength={100} />
110 </Field>
111 <Field label="Public key">
112 <Input name="key" placeholder="ssh-ed25519 AAAA…" required />
113 </Field>
114 <ErrorText>{actionData?.keyError}</ErrorText>
115 <Button type="submit">Add SSH key</Button>
116 </Form>
117 </section>
118
Account settings in the same shape as a workspace's119 <section id="tokens" className="scroll-mt-20">
Initial g1t: services, event bus, intents and attempts120 <h2 className="font-medium">Access tokens</h2>
121 <p className="mt-1 text-sm text-muted">
122 Use a token as the password when git asks for one over HTTPS, and to
Agents as a team: lifecycle, merge queue, billing and a new shell123 authenticate agents and the API. A token here acts as you.
Initial g1t: services, event bus, intents and attempts124 </p>
Agents as a team: lifecycle, merge queue, billing and a new shell125 {(user.workspaces ?? []).length > 0 && (
126 <p className="mt-1 text-sm text-muted">
127 For CI and integrations that work for a team, use a workspace's
128 own tokens instead:{" "}
129 {(user.workspaces ?? []).map((membership, i) => (
130 <span key={membership.slug}>
131 {i > 0 && ", "}
132 <Link
133 to={`/${membership.slug}/-/tokens`}
134 className="font-mono text-fg underline underline-offset-4"
135 >
136 {membership.slug}
137 </Link>
138 </span>
139 ))}
140 .
141 </p>
142 )}
Initial g1t: services, event bus, intents and attempts143 {actionData?.newToken && (
144 <div className="mt-4 rounded-md border border-accent/40 bg-surface p-4">
145 <p className="text-sm">Copy it now. It will not be shown again.</p>
146 <pre className="mt-2 overflow-x-auto font-mono text-sm text-accent">
147 {actionData.newToken}
148 </pre>
149 </div>
150 )}
151 <ul className="mt-4 divide-y divide-line rounded-md border border-line empty:hidden">
152 {tokens.map((token) => (
153 <li key={token.id} className="flex items-center gap-4 px-4 py-3">
Agents as a team: lifecycle, merge queue, billing and a new shell154 <div className="min-w-0">
155 <p className="truncate text-sm">{token.name}</p>
156 <p className="text-xs text-faint">
157 Created <TimeAgo at={token.createdAt} /> ·{" "}
158 {token.lastUsedAt ? (
159 <>
160 last used <TimeAgo at={token.lastUsedAt} />
161 </>
162 ) : (
163 "never used"
164 )}
165 </p>
166 </div>
Initial g1t: services, event bus, intents and attempts167 <DeleteButton intent="delete-token" id={token.id} />
168 </li>
169 ))}
170 </ul>
171 <Form method="post" className="mt-4 flex items-end gap-3">
172 <input type="hidden" name="intent" value="add-token" />
173 <div className="grow">
174 <Field label="Name">
Agents as a team: lifecycle, merge queue, billing and a new shell175 <Input name="label" maxLength={100} placeholder="laptop" />
Initial g1t: services, event bus, intents and attempts176 </Field>
177 </div>
178 <Button type="submit">Create token</Button>
179 </Form>
180 </section>
OAuth 2.1 sign-in for MCP clients and other applications181
Account settings in the same shape as a workspace's182 <section id="applications" className="scroll-mt-20">
OAuth 2.1 sign-in for MCP clients and other applications183 <h2 className="font-medium">Connected applications</h2>
184 <p className="mt-1 text-sm text-muted">
185 Applications you signed in to through your browser, such as an agent
186 connected to the g1t MCP server. Signing one out ends its access at
187 once.
188 </p>
189 {applications.length === 0 ? (
190 <p className="mt-4 text-sm text-faint">None yet.</p>
191 ) : (
192 <ul className="mt-4 divide-y divide-line rounded-md border border-line">
193 {applications.map((application) => (
194 <li key={application.id} className="flex items-center gap-4 px-4 py-3">
195 <div>
196 <p className="text-sm">{application.clientName}</p>
197 <p className="text-xs text-faint">
198 Connected <TimeAgo at={application.createdAt} /> · last used{" "}
199 <TimeAgo at={application.lastUsedAt} />
200 </p>
201 </div>
202 <DeleteButton
203 intent="sign-out-application"
204 id={application.id}
205 label="Sign out"
206 />
207 </li>
208 ))}
209 </ul>
210 )}
211 </section>
Account settings in the same shape as a workspace's212 </div>
Initial g1t: services, event bus, intents and attempts213 </main>
214 );
215}