Skip to content

g1t/apps/web/app/routes/workspace/tokens.tsx

238 lines10,013 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.

Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules1import { TriangleAlert } from "lucide-react";
2import { Form, Link } from "react-router";
Agents as a team: lifecycle, merge queue, billing and a new shell3
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step4import { presetScopes } from "@g1t/contracts";
5
Agents as a team: lifecycle, merge queue, billing and a new shell6import type { Route } from "./+types/tokens";
Social cards for every page: og.g1t.sh7import { page } from "../../lib/meta";
Merge branch 'worktree-agent-aaf03bdceac799c89'8import { useAddresses } from "../../lib/addresses";
Agents as a team: lifecycle, merge queue, billing and a new shell9import {
10 CopyLine,
11 EmptyState,
12 ErrorText,
13 Field,
14 Input,
Merge branch 'worktree-agent-a985c01f43538af01'15 SubmitButton,
Agents as a team: lifecycle, merge queue, billing and a new shell16 TimeAgo,
17} from "../../components/ui";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step18import { AccessSummary, ExpiryField, ScopeChecklist } from "../../components/token-scopes";
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules19import { Badge } from "../../components/ui/badge";
Agents as a team: lifecycle, merge queue, billing and a new shell20import { identity } from "../../lib/services.server";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step21import { describeExpiry, expiryTtl, grantFromForm } from "../../lib/token-scopes";
Agents as a team: lifecycle, merge queue, billing and a new shell22import {
23 assertSameOrigin,
24 getViewer,
25 requireUser,
26 roleIn,
27 unwrap,
28} from "../../lib/session.server";
29
Social cards for every page: og.g1t.sh30export function meta({ params, ...args }: Route.MetaArgs) {
31 return page(args, { title: `Access tokens · ${params.owner} · g1t` });
Agents as a team: lifecycle, merge queue, billing and a new shell32}
33
34export async function loader({ params, context }: Route.LoaderArgs) {
35 const viewer = getViewer(context);
36 return {
37 slug: params.owner.toLowerCase(),
38 role: roleIn(viewer, params.owner),
39 tokens: unwrap(await identity.listWorkspaceTokens(params.owner, viewer)),
40 };
41}
42
43export async function action({ request, params, context }: Route.ActionArgs) {
44 assertSameOrigin(request);
45 const user = requireUser(context, request);
46 const form = await request.formData();
47 if (form.get("action") === "delete") {
48 const removed = await identity.removeWorkspaceToken(
49 user,
50 params.owner,
51 String(form.get("id") ?? ""),
52 );
53 return { token: null, error: removed.ok ? null : removed.error.message };
54 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step55 const grant = grantFromForm(form);
56 if (!grant.ok) return { token: null, error: grant.error };
Agents as a team: lifecycle, merge queue, billing and a new shell57 const created = await identity.createWorkspaceToken(
58 user,
59 params.owner,
60 String(form.get("label") ?? ""),
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules61 { ...grant.value, ttlSeconds: expiryTtl(form.get("expires")), admin: form.get("admin") === "on" },
Agents as a team: lifecycle, merge queue, billing and a new shell62 );
63 return created.ok
64 ? { token: created.value, error: null }
65 : { token: null, error: created.error.message };
66}
67
68export default function WorkspaceTokens({ loaderData, actionData }: Route.ComponentProps) {
69 const { slug, role, tokens } = loaderData;
70 const created = actionData?.token;
Merge branch 'worktree-agent-aaf03bdceac799c89'71 const { site, api } = useAddresses();
72 // With git, the token is the password in the clone address, after the scheme.
73 const [scheme, rest] = site.split("://");
Agents as a team: lifecycle, merge queue, billing and a new shell74 return (
75 <div className="grid gap-10 lg:grid-cols-[1fr_20rem]">
76 <div className="min-w-0">
77
78 {created && (
79 <div className="mt-5 rounded-xl border border-accent/40 bg-surface p-4">
80 <p className="text-sm">
81 <span className="font-medium">{created.info.name}</span> is ready. Copy
82 it now; it will not be shown again.
83 </p>
84 <div className="mt-3">
85 <CopyLine text={created.token} />
86 </div>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step87 <AccessSummary holder={created.info} className="mt-3" />
88 <p className="mt-1.5 text-xs text-faint">{describeExpiry(created.info.expiresAt)}</p>
Agents as a team: lifecycle, merge queue, billing and a new shell89 </div>
90 )}
91
92 <div className="mt-5">
93 {tokens.length === 0 ? (
94 <EmptyState title="No access tokens yet">
95 {role === "owner"
96 ? "Create one below and give it to whatever needs to act for this workspace."
97 : "An owner can create one."}
98 </EmptyState>
99 ) : (
100 <ul className="divide-y divide-line rounded-xl border border-line">
101 {tokens.map((token) => (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step102 <li key={token.id} className="flex items-start gap-4 px-4 py-3">
Agents as a team: lifecycle, merge queue, billing and a new shell103 <div className="min-w-0 grow">
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules104 <p className="flex min-w-0 items-center gap-2 text-sm font-medium">
105 <span className="truncate">{token.name}</span>
106 <Badge tone={token.admin ? "danger" : "neutral"}>{token.admin ? "Admin" : "Write"}</Badge>
107 </p>
Agents as a team: lifecycle, merge queue, billing and a new shell108 <p className="mt-0.5 text-xs text-faint">
109 Created <TimeAgo at={token.createdAt} />
110 {token.createdBy ? (
111 <>
112 {" "}
113 by <span className="font-mono">{token.createdBy}</span>
114 </>
115 ) : (
116 " by someone who has since left g1t"
117 )}{" "}
118 ·{" "}
119 {token.lastUsedAt ? (
120 <>
121 last used <TimeAgo at={token.lastUsedAt} />
122 </>
123 ) : (
124 "never used"
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step125 )}{" "}
126 ·{" "}
127 <span className={describeExpiry(token.expiresAt) === "Expired" ? "text-danger" : undefined}>
128 {describeExpiry(token.expiresAt)}
129 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell130 </p>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step131 <AccessSummary holder={token} />
132 {token.legacy && token.scopes === null && (
133 <p className="mt-1.5 text-xs text-warn">
134 Made before tokens had scopes, so it can do everything a member can here.
135 Replace it with a narrower one.
136 </p>
137 )}
Agents as a team: lifecycle, merge queue, billing and a new shell138 </div>
139 {role === "owner" && (
140 <Form method="post">
141 <input type="hidden" name="action" value="delete" />
142 <input type="hidden" name="id" value={token.id} />
Merge branch 'worktree-agent-a985c01f43538af01'143 <SubmitButton variant="quiet" match={{ action: "delete", id: token.id }} pending="Deleting…">
Agents as a team: lifecycle, merge queue, billing and a new shell144 Delete
Merge branch 'worktree-agent-a985c01f43538af01'145 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell146 </Form>
147 )}
148 </li>
149 ))}
150 </ul>
151 )}
152 </div>
153
154 {role === "owner" ? (
Merge branch 'worktree-agent-a985c01f43538af01'155 // Empty again once the token is made; kept as filled in when it failed.
156 <Form method="post" key={created?.info.id ?? ""} className="mt-6 space-y-5 rounded-xl border border-line p-4 sm:p-5">
157 <input type="hidden" name="action" value="create" />
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step158 <h2 className="font-medium">New token</h2>
159 <div className="grid gap-4 sm:grid-cols-[1fr_11rem]">
160 <Field label="Name" hint="Name it after what will use it.">
Agents as a team: lifecycle, merge queue, billing and a new shell161 <Input name="label" required maxLength={100} placeholder="deploy pipeline" />
162 </Field>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step163 <ExpiryField />
Agents as a team: lifecycle, merge queue, billing and a new shell164 </div>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step165 <ScopeChecklist initial={presetScopes("ci")} />
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules166 <label className="flex cursor-pointer items-start gap-2.5 rounded-md border border-danger/30 px-3 py-2.5">
167 <input type="checkbox" name="admin" className="mt-0.5 size-4 shrink-0 accent-danger" />
168 <span className="min-w-0">
169 <span className="block text-sm text-fg">Admin on the workspace's repositories</span>
170 <span className="mt-0.5 flex items-start gap-1.5 text-xs text-faint">
171 <TriangleAlert size={13} className="mt-px shrink-0 text-danger" />
172 Without it the token has Write, as a member does. With it, the token can also manage webhooks,
173 secrets, deploy keys and who has access, and act as an owner on teams, as far as its scopes allow.
174 </span>
175 </span>
176 </label>
Merge branch 'worktree-agent-a985c01f43538af01'177 <SubmitButton match={{ action: "create" }} pending="Creating…">
178 Create token
179 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell180 </Form>
181 ) : (
182 <p className="mt-4 text-sm text-muted">
183 Only owners can create or delete a workspace's tokens.
184 </p>
185 )}
186 <ErrorText>{actionData?.error}</ErrorText>
187 </div>
188
189 <aside className="space-y-5 text-sm">
190 <section className="rounded-xl border border-line bg-surface p-5">
191 <h3 className="font-medium">What a token can do</h3>
192 <ul className="mt-2 list-disc space-y-1.5 pl-4 text-muted">
193 <li>
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules194 What its scopes allow, in this workspace only, with Write on its
195 repositories, as a member: push, open and merge pull requests,
196 manage issues. Admin only when an owner gives it that.
Agents as a team: lifecycle, merge queue, billing and a new shell197 </li>
198 <li>
199 It acts as <span className="font-mono text-fg">{slug}</span>, so
200 what it does is shown as the workspace's doing.
201 </li>
202 <li>It keeps working when the person who made it leaves.</li>
203 <li>It cannot manage people, tokens or other workspaces.</li>
204 </ul>
205 </section>
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules206 {role === "owner" && (
207 <section className="rounded-xl border border-line p-5">
208 <h3 className="font-medium">Your members' own tokens</h3>
209 <p className="mt-2 text-muted">
210 Which personal tokens may reach {slug}, how long they may last, and approving fine-grained ones:{" "}
211 <Link to={`/${slug}/-/personal-access-tokens`} className="text-fg underline underline-offset-4">
212 Personal access tokens
213 </Link>
214 .
215 </p>
216 </section>
217 )}
Agents as a team: lifecycle, merge queue, billing and a new shell218 <section>
219 <h3 className="font-medium">Using one</h3>
220 <p className="mt-2 text-muted">With git, as the password:</p>
221 <div className="mt-2">
222 <CopyLine
223 prompt
Merge branch 'worktree-agent-aaf03bdceac799c89'224 text={`git clone ${scheme}://${slug}:$G1T_TOKEN@${rest}/${slug}/<repo>.git`}
Agents as a team: lifecycle, merge queue, billing and a new shell225 />
226 </div>
227 <p className="mt-4 text-muted">With the API and the MCP server:</p>
228 <div className="mt-2">
229 <CopyLine
230 prompt
Merge branch 'worktree-agent-aaf03bdceac799c89'231 text={`curl -H "Authorization: Bearer $G1T_TOKEN" ${api}/user`}
Agents as a team: lifecycle, merge queue, billing and a new shell232 />
233 </div>
234 </section>
235 </aside>
236 </div>
237 );
238}

This file's history is long; its oldest lines are credited to the oldest commit read.