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

206 lines7,892 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.

Agents as a team: lifecycle, merge queue, billing and a new shell1import { Form } from "react-router";
2
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step3import { presetScopes } from "@g1t/contracts";
4
Agents as a team: lifecycle, merge queue, billing and a new shell5import type { Route } from "./+types/tokens";
Social cards for every page: og.g1t.sh6import { page } from "../../lib/meta";
Merge branch 'worktree-agent-aaf03bdceac799c89'7import { useAddresses } from "../../lib/addresses";
Agents as a team: lifecycle, merge queue, billing and a new shell8import {
9 Button,
10 CopyLine,
11 EmptyState,
12 ErrorText,
13 Field,
14 Input,
15 TimeAgo,
16} from "../../components/ui";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step17import { AccessSummary, ExpiryField, ScopeChecklist } from "../../components/token-scopes";
Agents as a team: lifecycle, merge queue, billing and a new shell18import { 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 step19import { describeExpiry, expiryTtl, grantFromForm } from "../../lib/token-scopes";
Agents as a team: lifecycle, merge queue, billing and a new shell20import {
21 assertSameOrigin,
22 getViewer,
23 requireUser,
24 roleIn,
25 unwrap,
26} from "../../lib/session.server";
27
Social cards for every page: og.g1t.sh28export function meta({ params, ...args }: Route.MetaArgs) {
29 return page(args, { title: `Access tokens · ${params.owner} · g1t` });
Agents as a team: lifecycle, merge queue, billing and a new shell30}
31
32export async function loader({ params, context }: Route.LoaderArgs) {
33 const viewer = getViewer(context);
34 return {
35 slug: params.owner.toLowerCase(),
36 role: roleIn(viewer, params.owner),
37 tokens: unwrap(await identity.listWorkspaceTokens(params.owner, viewer)),
38 };
39}
40
41export async function action({ request, params, context }: Route.ActionArgs) {
42 assertSameOrigin(request);
43 const user = requireUser(context, request);
44 const form = await request.formData();
45 if (form.get("action") === "delete") {
46 const removed = await identity.removeWorkspaceToken(
47 user,
48 params.owner,
49 String(form.get("id") ?? ""),
50 );
51 return { token: null, error: removed.ok ? null : removed.error.message };
52 }
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step53 const grant = grantFromForm(form);
54 if (!grant.ok) return { token: null, error: grant.error };
Agents as a team: lifecycle, merge queue, billing and a new shell55 const created = await identity.createWorkspaceToken(
56 user,
57 params.owner,
58 String(form.get("label") ?? ""),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step59 { ...grant.value, ttlSeconds: expiryTtl(form.get("expires")) },
Agents as a team: lifecycle, merge queue, billing and a new shell60 );
61 return created.ok
62 ? { token: created.value, error: null }
63 : { token: null, error: created.error.message };
64}
65
66export default function WorkspaceTokens({ loaderData, actionData }: Route.ComponentProps) {
67 const { slug, role, tokens } = loaderData;
68 const created = actionData?.token;
Merge branch 'worktree-agent-aaf03bdceac799c89'69 const { site, api } = useAddresses();
70 // With git, the token is the password in the clone address, after the scheme.
71 const [scheme, rest] = site.split("://");
Agents as a team: lifecycle, merge queue, billing and a new shell72 return (
73 <div className="grid gap-10 lg:grid-cols-[1fr_20rem]">
74 <div className="min-w-0">
75
76 {created && (
77 <div className="mt-5 rounded-xl border border-accent/40 bg-surface p-4">
78 <p className="text-sm">
79 <span className="font-medium">{created.info.name}</span> is ready. Copy
80 it now; it will not be shown again.
81 </p>
82 <div className="mt-3">
83 <CopyLine text={created.token} />
84 </div>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step85 <AccessSummary holder={created.info} className="mt-3" />
86 <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 shell87 </div>
88 )}
89
90 <div className="mt-5">
91 {tokens.length === 0 ? (
92 <EmptyState title="No access tokens yet">
93 {role === "owner"
94 ? "Create one below and give it to whatever needs to act for this workspace."
95 : "An owner can create one."}
96 </EmptyState>
97 ) : (
98 <ul className="divide-y divide-line rounded-xl border border-line">
99 {tokens.map((token) => (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step100 <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 shell101 <div className="min-w-0 grow">
102 <p className="truncate text-sm font-medium">{token.name}</p>
103 <p className="mt-0.5 text-xs text-faint">
104 Created <TimeAgo at={token.createdAt} />
105 {token.createdBy ? (
106 <>
107 {" "}
108 by <span className="font-mono">{token.createdBy}</span>
109 </>
110 ) : (
111 " by someone who has since left g1t"
112 )}{" "}
113 ·{" "}
114 {token.lastUsedAt ? (
115 <>
116 last used <TimeAgo at={token.lastUsedAt} />
117 </>
118 ) : (
119 "never used"
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step120 )}{" "}
121 ·{" "}
122 <span className={describeExpiry(token.expiresAt) === "Expired" ? "text-danger" : undefined}>
123 {describeExpiry(token.expiresAt)}
124 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell125 </p>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step126 <AccessSummary holder={token} />
127 {token.legacy && token.scopes === null && (
128 <p className="mt-1.5 text-xs text-warn">
129 Made before tokens had scopes, so it can do everything a member can here.
130 Replace it with a narrower one.
131 </p>
132 )}
Agents as a team: lifecycle, merge queue, billing and a new shell133 </div>
134 {role === "owner" && (
135 <Form method="post">
136 <input type="hidden" name="action" value="delete" />
137 <input type="hidden" name="id" value={token.id} />
138 <Button variant="quiet" type="submit">
139 Delete
140 </Button>
141 </Form>
142 )}
143 </li>
144 ))}
145 </ul>
146 )}
147 </div>
148
149 {role === "owner" ? (
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step150 <Form method="post" className="mt-6 space-y-5 rounded-xl border border-line p-4 sm:p-5">
151 <h2 className="font-medium">New token</h2>
152 <div className="grid gap-4 sm:grid-cols-[1fr_11rem]">
153 <Field label="Name" hint="Name it after what will use it.">
Agents as a team: lifecycle, merge queue, billing and a new shell154 <Input name="label" required maxLength={100} placeholder="deploy pipeline" />
155 </Field>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step156 <ExpiryField />
Agents as a team: lifecycle, merge queue, billing and a new shell157 </div>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step158 <ScopeChecklist initial={presetScopes("ci")} />
159 <Button type="submit">Create token</Button>
Agents as a team: lifecycle, merge queue, billing and a new shell160 </Form>
161 ) : (
162 <p className="mt-4 text-sm text-muted">
163 Only owners can create or delete a workspace's tokens.
164 </p>
165 )}
166 <ErrorText>{actionData?.error}</ErrorText>
167 </div>
168
169 <aside className="space-y-5 text-sm">
170 <section className="rounded-xl border border-line bg-surface p-5">
171 <h3 className="font-medium">What a token can do</h3>
172 <ul className="mt-2 list-disc space-y-1.5 pl-4 text-muted">
173 <li>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step174 What its scopes allow, in this workspace only, and never more
175 than a member can: push, open and merge pull requests, manage
176 issues.
Agents as a team: lifecycle, merge queue, billing and a new shell177 </li>
178 <li>
179 It acts as <span className="font-mono text-fg">{slug}</span>, so
180 what it does is shown as the workspace's doing.
181 </li>
182 <li>It keeps working when the person who made it leaves.</li>
183 <li>It cannot manage people, tokens or other workspaces.</li>
184 </ul>
185 </section>
186 <section>
187 <h3 className="font-medium">Using one</h3>
188 <p className="mt-2 text-muted">With git, as the password:</p>
189 <div className="mt-2">
190 <CopyLine
191 prompt
Merge branch 'worktree-agent-aaf03bdceac799c89'192 text={`git clone ${scheme}://${slug}:$G1T_TOKEN@${rest}/${slug}/<repo>.git`}
Agents as a team: lifecycle, merge queue, billing and a new shell193 />
194 </div>
195 <p className="mt-4 text-muted">With the API and the MCP server:</p>
196 <div className="mt-2">
197 <CopyLine
198 prompt
Merge branch 'worktree-agent-aaf03bdceac799c89'199 text={`curl -H "Authorization: Bearer $G1T_TOKEN" ${api}/user`}
Agents as a team: lifecycle, merge queue, billing and a new shell200 />
201 </div>
202 </section>
203 </aside>
204 </div>
205 );
206}

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