flagon-io/g1t

public

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

g1t/packages/contracts/src/identity.ts

353 lines13,802 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 type { RepoPath } from "./repos";
Initial g1t: services, event bus, intents and attempts2import type { Result } from "./result";
3
Email verification, password reset, and Git for AI scale positioning4export type User = {
5 id: string;
6 username: string;
7 /**
Agents as a team: lifecycle, merge queue, billing and a new shell8 * `workspace` when a workspace is acting through one of its own access
9 * tokens: `id` is then the workspace's and `username` its slug. Absent
10 * means `user`.
11 */
12 kind?: "user" | "workspace" | "agent";
13 /**
Email verification, password reset, and Git for AI scale positioning14 * Whether the account's email address is confirmed. Only set on users
15 * resolved from credentials; unverified accounts cannot change anything.
16 */
17 verified?: boolean;
Workspaces own repositories18 /**
19 * The workspaces this user belongs to. Set on users resolved from
20 * credentials, so any service can authorize from it.
21 */
22 workspaces?: Membership[];
Workspace names and icons, and a component kit for every control23 /**
24 * The person's uploaded avatar: the SHA-256 of its bytes, served at
25 * `/avatars/<avatar>`. Only set on the signed-in person; absent means
26 * the generated letter avatar.
27 */
28 avatar?: string;
Email verification, password reset, and Git for AI scale positioning29};
Initial g1t: services, event bus, intents and attempts30
Workspaces own repositories31/** What a member may do: an owner also manages the workspace's members. */
32export type Role = "owner" | "member";
33
Workspace names and icons, and a component kit for every control34export type Membership = {
35 /** The workspace's name in URLs: `g1t.sh/<slug>`. */
36 slug: string;
37 role: Role;
38 /** Its display name. Set on users resolved from credentials. */
39 name?: string;
40 /** Its uploaded icon, as `Workspace.avatar`. */
41 avatar?: string;
42};
Workspaces own repositories43
Agents and memory, checks and conflicts, profiles, slug renames, custom domains44/** How long an old workspace slug redirects, and stays reserved for it, after a rename. */
45export const SLUG_HOLD_DAYS = 90;
46
47/** How long a workspace must wait between renames. */
48export const RENAME_COOLDOWN_HOURS = 24;
49
Workspace names and icons, and a component kit for every control50/** The largest avatar that can be uploaded, in bytes. */
51export const MAX_AVATAR_BYTES = 1024 * 1024;
52
Workspaces own repositories53/**
54 * A workspace: the owner of repositories, and the first segment of their
55 * URLs. A person's own space and a team's are the same thing.
56 */
57export type Workspace = {
58 id: string;
59 slug: string;
60 name: string;
Agents as a team: lifecycle, merge queue, billing and a new shell61 /** One line saying what the workspace is for. */
62 description: string | null;
Workspaces own repositories63 /** RFC 3339. */
64 createdAt: string;
65 memberCount: number;
Workspace names and icons, and a component kit for every control66 /**
67 * The workspace's uploaded icon: the SHA-256 of its bytes, served at
68 * `/avatars/<avatar>`. Null means the generated letter avatar.
69 */
70 avatar: string | null;
Workspaces own repositories71};
72
73export type Member = { username: string; role: Role };
74
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace75/** An owner of a workspace, as staff see them. */
76export type AdminOwner = { username: string; email: string | null };
77
78/** A workspace as staff see it. Mirrors `AdminWorkspace` in `crates/contracts/src/identity.rs`. */
79export type AdminWorkspace = {
80 slug: string;
81 name: string;
82 /** RFC 3339. */
83 createdAt: string;
84 owners: AdminOwner[];
85 memberCount: number;
86};
87
88/** A member of a workspace, as staff see them. */
89export type AdminMember = { username: string; email: string | null; role: Role; /** RFC 3339. */ joined: string };
90
91export type AdminWorkspaceDetail = {
92 slug: string;
93 name: string;
94 description: string | null;
95 /** RFC 3339. */
96 createdAt: string;
97 /** Owners first, then by username. */
98 members: AdminMember[];
99};
100
101/** The most workspaces one `workspaces` call returns. */
102export const ADMIN_WORKSPACES_LIMIT = 500;
103
104/**
105 * Staff-only identity, for sudo.g1t.sh. It takes no viewer and checks no
106 * membership: only sudo calls it, over its service binding, once Cloudflare
107 * Access and its staff list have let someone in. Never call it on behalf of
108 * a customer.
109 */
110export interface IdentityAdminApi {
111 /** Every workspace, newest first, at most 500; `query` matches slug, name, or an owner's username or email. */
112 workspaces(query?: string): Promise<AdminWorkspace[]>;
113 /** One workspace with all its members, or null. */
114 workspace(slug: string): Promise<AdminWorkspaceDetail | null>;
115}
116
Initial g1t: services, event bus, intents and attempts117/** Who is asking. Every read and write in every service takes one. */
118export type Viewer = User | null;
119
120export type SshKey = {
121 id: string;
122 title: string;
123 fingerprint: string;
RFC 3339 timestamps in identity and repos124 /** RFC 3339. */
125 createdAt: string;
Initial g1t: services, event bus, intents and attempts126};
127
Agents as a team: lifecycle, merge queue, billing and a new shell128export type AccessToken = {
129 id: string;
130 name: string;
131 /** RFC 3339. */
132 createdAt: string;
133 /** RFC 3339, to within a few minutes. Null until it is first used. */
134 lastUsedAt: string | null;
135 /**
136 * For a workspace's token, the username of the member who made it. Null
137 * once that account is gone, and on personal tokens.
138 */
139 createdBy: string | null;
140};
Initial g1t: services, event bus, intents and attempts141
Device sign-in replaces registering and minting tokens over the API142export type DeviceStart = {
143 /** Secret held by the tool and exchanged for a token once approved. */
144 deviceCode: string;
145 /** Short code shown to the person, e.g. `WDJB-MJHT`. */
146 userCode: string;
147 /** Seconds until both codes stop working. */
148 expiresIn: number;
149 /** Seconds the tool should wait between polls. */
150 interval: number;
151};
152
153export type DeviceRequest = { userCode: string; clientName: string };
154
155export type DeviceClaim =
156 | { status: "pending" | "denied" | "expired" }
157 | { status: "approved"; token: string; user: User };
158
OAuth 2.1 sign-in for MCP clients and other applications159/** What the site passes on once a person has approved an application. */
160export type OAuthApproval = {
161 clientId: string;
162 /** Shown wherever the application's access is listed. */
163 clientName: string;
164 redirectUri: string;
165 /** PKCE challenge, method S256. */
166 codeChallenge: string;
167};
168
169export type OAuthTokens = {
170 accessToken: string;
171 /** Works once; using it returns the next one. */
172 refreshToken: string;
173 /** Seconds until the access token stops working. */
174 expiresIn: number;
175};
176
177/** An application a person has signed in to. */
178export type OAuthGrant = {
179 id: string;
180 clientName: string;
181 /** RFC 3339. */
182 createdAt: string;
183 /** RFC 3339. */
184 lastUsedAt: string;
185};
186
Initial g1t: services, event bus, intents and attempts187/** Accounts, credentials and sessions. */
188export interface IdentityApi {
API and MCP server, Rust identity service, registration, site redesign189 /** Creates an account and signs it in. */
190 register(username: string, email: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>;
Initial g1t: services, event bus, intents and attempts191 /** Verifies a username and password for website sign-in. */
192 signIn(username: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>;
193 signOut(sessionToken: string): Promise<void>;
Email verification, password reset, and Git for AI scale positioning194
195 /** Sends the confirmation email again. */
196 resendVerification(user: User): Promise<Result<boolean>>;
197 /** Confirms the address the emailed token was sent to. */
198 verifyEmail(token: string): Promise<Result<User>>;
199 /** Emails a reset link if the address has an account. Always resolves. */
200 requestPasswordReset(email: string): Promise<boolean>;
201 /** Sets a new password from an emailed token and ends every session. */
202 resetPassword(token: string, password: string): Promise<Result<User>>;
203
Device sign-in replaces registering and minting tokens over the API204 /**
205 * Device sign-in (RFC 8628). A tool starts a request, a person approves
206 * its short code in a browser, and the tool claims an access token.
207 */
208 deviceStart(clientName: string): Promise<DeviceStart>;
209 /** What a user code is asking for, or null if it is not valid. */
210 deviceLookup(userCode: string): Promise<DeviceRequest | null>;
211 deviceResolve(userCode: string, user: User, approve: boolean): Promise<Result<boolean>>;
212 deviceClaim(deviceCode: string): Promise<DeviceClaim>;
213
OAuth 2.1 sign-in for MCP clients and other applications214 /**
215 * OAuth 2.1 for applications that sign a person in through the browser.
216 * The caller has checked the client and its redirect address; this
217 * returns the one-time code the application exchanges for tokens.
218 */
219 oauthAuthorize(user: User, approval: OAuthApproval): Promise<{ code: string }>;
220 /** Redeems a code. It works once, for that client, with the PKCE verifier. */
221 oauthExchange(code: string, codeVerifier: string, clientId: string, redirectUri: string): Promise<Result<OAuthTokens>>;
222 /** Trades a refresh token for new tokens; the old ones stop working. */
223 oauthRefresh(refreshToken: string, clientId: string): Promise<Result<OAuthTokens>>;
224 /** Applications the user has signed in to, most recently used first. */
225 listOAuthGrants(user: User): Promise<OAuthGrant[]>;
226 /** Signs an application out. */
227 revokeOAuthGrant(user: User, id: string): Promise<void>;
228
Workspaces own repositories229 createWorkspace(user: User, slug: string, name: string): Promise<Result<Workspace>>;
230 /** Public details of a workspace, or null. */
231 getWorkspace(slug: string): Promise<Workspace | null>;
232 /** Members only. */
233 listMembers(slug: string, viewer: Viewer): Promise<Result<Member[]>>;
234 /** Owners only. */
235 addMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
236 /** Owners only. */
237 removeMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
Agents as a team: lifecycle, merge queue, billing and a new shell238 /** Owners only. An empty name falls back to the slug. */
239 updateWorkspace(actor: User, slug: string, details: { name: string; description: string }): Promise<Result<Workspace>>;
Workspace names and icons, and a component kit for every control240 /**
Agents and memory, checks and conflicts, profiles, slug renames, custom domains241 * Owners only. Changes the slug, the first segment of the workspace's
242 * URLs; the display name is untouched. The old slug redirects to the new
243 * one, and stays reserved for this workspace, for `SLUG_HOLD_DAYS`.
244 * Publishes `workspace.renamed`.
245 */
246 renameWorkspace(actor: User, slug: string, newSlug: string): Promise<Result<Workspace>>;
247 /** Whether `renameWorkspace` would be allowed, changing nothing. */
248 checkWorkspaceRename(actor: User, slug: string, newSlug: string): Promise<Result<boolean>>;
249 /**
250 * The workspace's current slug when `slug` is one it was renamed from
251 * within `SLUG_HOLD_DAYS`; null otherwise, including for a slug in use.
252 */
253 resolveSlug(slug: string): Promise<string | null>;
254 /**
Workspace names and icons, and a component kit for every control255 * Owners only. `image` is the file in base64: PNG, JPEG, WebP or GIF, at
256 * most `MAX_AVATAR_BYTES`, checked by its bytes. Null removes the icon.
257 */
258 setWorkspaceAvatar(actor: User, slug: string, image: string | null): Promise<Result<Workspace>>;
259 /** A person's own avatar, as `setWorkspaceAvatar`: the new one, or null. */
260 setUserAvatar(user: User, image: string | null): Promise<Result<string | null>>;
Workspaces own repositories261
Agents as a team: lifecycle, merge queue, billing and a new shell262 /**
263 * A workspace's own access tokens. They belong to the workspace, act as
264 * it, and keep working when the member who made one leaves. Members only.
265 */
266 listWorkspaceTokens(slug: string, viewer: Viewer): Promise<Result<AccessToken[]>>;
267 /** Owners only. The plaintext token is returned once and never stored. */
268 createWorkspaceToken(actor: User, slug: string, name: string): Promise<Result<{ token: string; info: AccessToken }>>;
269 /** Owners only. */
270 removeWorkspaceToken(actor: User, slug: string, id: string): Promise<Result<boolean>>;
271
Initial g1t: services, event bus, intents and attempts272 userForSession(sessionToken: string): Promise<Viewer>;
273
274 /** Verifies git credentials: the account password or an access token. */
275 userForGitCredentials(username: string, secret: string): Promise<Viewer>;
API and MCP server, Rust identity service, registration, site redesign276 /** Resolves a `g1t_…` access token, as sent to the API and MCP server. */
277 userForAccessToken(token: string): Promise<Viewer>;
Initial g1t: services, event bus, intents and attempts278 userForSshKey(fingerprint: string): Promise<Viewer>;
279 userByUsername(username: string): Promise<Viewer>;
What happened across an outcome, as a feed beside its graph280 /** The names behind account and workspace ids; unknown ids are left out. */
281 usernames(ids: string[]): Promise<Record<string, string>>;
Initial g1t: services, event bus, intents and attempts282
Agents and memory, checks and conflicts, profiles, slug renames, custom domains283 /** A person's public profile, or null if there is no such account. Never an email address. */
284 profile(username: string): Promise<Profile | null>;
285 /** A person changes their own profile. Every field is replaced; an empty one is cleared. */
286 updateProfile(actor: User, fields: ProfileFields): Promise<Result<Profile>>;
287 /**
288 * The workspaces a profile shows `viewer`: those the viewer belongs to
289 * as well, and those of `publicIn` (where the person made a public
290 * project) that the person really belongs to. Nothing else.
291 */
292 profileWorkspaces(username: string, viewer: Viewer, publicIn: string[]): Promise<ProfileWorkspace[]>;
293
Initial g1t: services, event bus, intents and attempts294 listSshKeys(user: User): Promise<SshKey[]>;
295 /** Takes one line in OpenSSH public key format. */
296 addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>>;
297 removeSshKey(user: User, id: string): Promise<void>;
298
299 listAccessTokens(user: User): Promise<AccessToken[]>;
Account dropdown, llms.txt onboarding, hosted agent runner (not yet deployed)300 /**
Agents as a team: lifecycle, merge queue, billing and a new shell301 * The plaintext token is returned once and never stored. With
302 * `ttlSeconds` the token expires and is left out of token lists; that
303 * form is used for hosted agents. A token made for a workspace acting
304 * through a token of its own belongs to that workspace too.
Account dropdown, llms.txt onboarding, hosted agent runner (not yet deployed)305 */
306 createAccessToken(user: User, name: string, ttlSeconds?: number): Promise<{ token: string; info: AccessToken }>;
Agents as a team: lifecycle, merge queue, billing and a new shell307 /**
308 * A token for a g1t agent working for `onBehalfOf`: it acts as
309 * `g1t-agent`, in `scope.repo` only, and only for `scope.operations`.
310 */
311 createAgentToken(
312 onBehalfOf: User,
313 scope: AgentScope,
314 ttlSeconds: number,
315 ): Promise<{ token: string; info: AccessToken }>;
Initial g1t: services, event bus, intents and attempts316 removeAccessToken(user: User, id: string): Promise<void>;
317}
Agents as a team: lifecycle, merge queue, billing and a new shell318
319
320/** What an agent's token may do: these operations, in this repository. */
321export type AgentScope = { repo: RepoPath; operations: string[] };
Agents and memory, checks and conflicts, profiles, slug renames, custom domains322
323/** The most characters each profile field takes. Mirrors `crates/contracts/src/identity.rs`. */
324export const PROFILE_LIMITS = { name: 80, bio: 160, location: 80, website: 200, pronouns: 40 } as const;
325
326/** What anyone may see about a person, at `g1t.sh/u/<username>`. */
327export type Profile = {
328 username: string;
329 /** The name they go by, if they gave one. */
330 name: string | null;
331 bio: string | null;
332 location: string | null;
333 /** Always an `https://` address. */
334 website: string | null;
335 pronouns: string | null;
336 /** The uploaded avatar's hash, served at `/avatars/<avatar>`. */
337 avatar: string | null;
338 /** When the account was made. RFC 3339. */
339 createdAt: string;
340};
341
342/** What a person may change on their profile. Empty clears a field. */
343export type ProfileFields = {
344 name: string;
345 bio: string;
346 location: string;
347 /** `https://…`; a bare `example.com` is taken as `https://example.com`. */
348 website: string;
349 pronouns: string;
350};
351
352/** A workspace on a person's profile. */
353export type ProfileWorkspace = { slug: string; name: string; avatar: string | null };