Skip to content
1,135 linesCodeBlameRaw

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.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1import type { AccessClient, BasePermission, RepoGrant } from "./access";
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers2import type { Permissions, ScopeLevel, ScopeResource } from "./scopes";
Merge main (membership, two-factor, GitHub repo roles) into tokens3import type { MemberPrivileges, OrgRole, PolicyHold } from "./members";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API4import type { Acting, CreateRunCredentialInput, RunBinding } from "./audit";
Webhooks: every event, to your own addresses, signed and retried5import type { RepoPath } from "./repos";
6import type { Result } from "./result";
Merge branch 'worktree-agent-ad7c6d88d93adc817'7import type { TeamCreation, TeamsClient } from "./teams";
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca8import type { DeployKeysClient } from "./deploy-keys";
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)9import type { EmailConfirmed } from "./accounts";
Webhooks: every event, to your own addresses, signed and retried10
11export type User = {
12 id: string;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers13 /** Lowercased: what the person is found, linked and mentioned by. */
Webhooks: every event, to your own addresses, signed and retried14 username: string;
15 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers16 * The username as its owner wrote it (`Ana`), when that differs from
17 * `username`: what pages show (`shownUsername`). Set on the signed-in
18 * person and on people looked up by name.
19 */
20 display_username?: string;
21 /**
Webhooks: every event, to your own addresses, signed and retried22 * `workspace` when a workspace is acting through one of its own access
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily23 * tokens: `id` is then the workspace's and `username` its slug. `system`
24 * is g1t itself doing platform work, such as a security update
25 * (`username` `g1t`). Absent means `user`.
Webhooks: every event, to your own addresses, signed and retried26 */
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily27 kind?: "user" | "workspace" | "agent" | "system";
Webhooks: every event, to your own addresses, signed and retried28 /**
29 * Whether the account's email address is confirmed. Only set on users
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)30 * resolved from credentials. An account that has not confirmed it can
31 * only confirm it: see `awaitsConfirmation`.
Webhooks: every event, to your own addresses, signed and retried32 */
33 verified?: boolean;
34 /**
35 * The workspaces this user belongs to. Set on users resolved from
36 * credentials, so any service can authorize from it.
37 */
38 workspaces?: Membership[];
Workspace names and icons, and a component kit for every control39 /**
40 * The person's uploaded avatar: the SHA-256 of its bytes, served at
41 * `/avatars/<avatar>`. Only set on the signed-in person; absent means
42 * the generated letter avatar.
43 */
44 avatar?: string;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API45 /**
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent46 * Set on an agent resolved from its token: who it acts for ("g1t
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API47 * on behalf of syntaqx"), with which credential, and what it may do.
48 */
49 acting?: Acting;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look50 /**
51 * The repositories this user has a role on directly, whether or not they
52 * belong to its workspace. Set with `workspaces`; see `access.ts`.
53 */
54 grants?: RepoGrant[];
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step55 /**
56 * Set on a user resolved from an access token: its scopes (null for full
57 * access) and the workspaces or repositories it reaches. See scopes.ts.
58 */
59 token?: {
60 token_id: string;
61 scopes?: string[] | null;
62 legacy?: boolean;
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens63 /** The token's name, as its owner gave it. */
64 name?: string;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers65 /**
66 * A token narrowed to one workspace (or none): its workspace and
67 * repositories. The key is kept from before tokens were one kind.
68 */
TS access mirrors the workspace token cap and fine-grained reach69 fine_grained?: {
70 workspace?: string | null;
71 repositories?: "all" | "selected" | "public";
72 repo_ids?: string[];
73 };
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers74 /** A workspace's own token with Repositories: admin. */
TS access mirrors the workspace token cap and fine-grained reach75 admin?: boolean;
76 /** Set on what a deploy key resolves to. */
77 deploy_key?: string;
78 /** The one repository a job's token or a deploy key reaches. */
79 repo?: string;
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts80 /** Set on a workflow job's token (`G1T_TOKEN`): the run and job it was made for. */
81 job?: { run_id: string; job_id: string; pull_requests?: boolean };
Merge main into Artifacts Phase 282 /**
83 * A person's token whose owner let it use the website as them, sent as
84 * `Authorization: Bearer` (apps/web, lib/website-token.ts). Not a scope.
85 */
86 website?: boolean;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step87 };
Merge main (membership, two-factor, GitHub repo roles) into tokens88 /**
89 * Workspaces the person belongs to but cannot use until they meet its
90 * policy, such as turning on two-factor authentication. Left out of
91 * `workspaces` and `grants` meanwhile.
92 */
93 held?: PolicyHold[];
Webhooks: every event, to your own addresses, signed and retried94};
95
96/** What a member may do: an owner also manages the workspace's members. */
97export type Role = "owner" | "member";
98
Workspace names and icons, and a component kit for every control99export type Membership = {
100 /** The workspace's name in URLs: `g1t.sh/<slug>`. */
101 slug: string;
102 role: Role;
103 /** Its display name. Set on users resolved from credentials. */
104 name?: string;
105 /** Its uploaded icon, as `Workspace.avatar`. */
106 avatar?: string;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look107 /** The workspace's base permission: what members get on every repository. Absent means `write`. */
108 base_permission?: BasePermission;
Merge branch 'worktree-agent-ad7c6d88d93adc817'109 /** Who may create its teams. Absent means any member. */
110 team_creation?: TeamCreation;
Merge main (membership, two-factor, GitHub repo roles) into tokens111 /** The roles held besides owner or member. */
112 org_roles?: OrgRole[];
113 /** What the workspace lets its members do. Absent means the defaults. */
114 privileges?: MemberPrivileges;
Chat and workspace agents: channels, DMs and named agents you talk to115 /**
116 * Whether this member uses Code: repositories, issues, pull requests,
117 * checks, deploys. False for people who only use Chat, Docs and agents
118 * (support, sales, finance): they see no repository, whatever the base
119 * permission, and agents treat them as unable to change code. Absent
120 * means true.
121 */
122 code_access?: boolean;
Workspace names and icons, and a component kit for every control123};
Webhooks: every event, to your own addresses, signed and retried124
Chat and workspace agents: channels, DMs and named agents you talk to125/** Whether a member uses Code. See `Membership.code_access`. */
126export function hasCodeAccess(membership: Pick<Membership, "code_access"> | null | undefined): boolean {
127 return membership?.code_access !== false;
128}
129
Agents and memory, checks and conflicts, profiles, slug renames, custom domains130/** How long an old workspace slug redirects, and stays reserved for it, after a rename. */
131export const SLUG_HOLD_DAYS = 90;
132
133/** How long a workspace must wait between renames. */
134export const RENAME_COOLDOWN_HOURS = 24;
135
Workspace names and icons, and a component kit for every control136/** The largest avatar that can be uploaded, in bytes. */
137export const MAX_AVATAR_BYTES = 1024 * 1024;
138
Webhooks: every event, to your own addresses, signed and retried139/**
Merge branch 'worktree-agent-a2013627e5ea4ab13'140 * Where a workspace keeps its repositories' git data: anywhere g1t stores
141 * it (the default), or in the EU only. It applies to repositories made
142 * after it is set.
143 */
144export type DataResidency = "anywhere" | "eu";
145
146/**
Webhooks: every event, to your own addresses, signed and retried147 * A workspace: the owner of repositories, and the first segment of their
148 * URLs. A person's own space and a team's are the same thing.
149 */
150export type Workspace = {
151 id: string;
152 slug: string;
153 name: string;
154 /** One line saying what the workspace is for. */
155 description: string | null;
156 /** RFC 3339. */
157 createdAt: string;
158 memberCount: number;
Workspace names and icons, and a component kit for every control159 /**
160 * The workspace's uploaded icon: the SHA-256 of its bytes, served at
161 * `/avatars/<avatar>`. Null means the generated letter avatar.
162 */
163 avatar: string | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look164 /** What every member gets on each repository; owners have Admin. */
165 basePermission?: BasePermission;
Merge branch 'worktree-agent-ad7c6d88d93adc817'166 /** Who may create its teams. Absent means any member. */
167 teamCreation?: TeamCreation;
Merge main (membership, two-factor, GitHub repo roles) into tokens168 /** Whether members and outside collaborators need two-factor authentication. */
169 twoFactorRequirementEnabled?: boolean;
170} & Partial<MemberPrivileges>;
Webhooks: every event, to your own addresses, signed and retried171
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look172export type Member = {
173 username: string;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar174 /** The username as its owner wrote it (`Ana`), when that differs from `username`. */
175 display_username?: string;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look176 role: Role;
Merge main (membership, two-factor, GitHub repo roles) into tokens177 /** The roles they hold besides `role`. */
178 org_roles?: OrgRole[];
179 /** Whether two-factor authentication is on; owners only, null for anyone else. */
180 two_factor?: boolean | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look181 /** Their display name, when they set one. */
182 name?: string | null;
183 /** Their uploaded avatar's hash, served at `/avatars/<avatar>`; null for the generated letter avatar. */
184 avatar?: string | null;
185};
Webhooks: every event, to your own addresses, signed and retried186
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace187/** An owner of a workspace, as staff see them. */
188export type AdminOwner = { username: string; email: string | null };
189
190/** A workspace as staff see it. Mirrors `AdminWorkspace` in `crates/contracts/src/identity.rs`. */
191export type AdminWorkspace = {
192 slug: string;
193 name: string;
194 /** RFC 3339. */
195 createdAt: string;
196 owners: AdminOwner[];
197 memberCount: number;
198};
199
200/** A member of a workspace, as staff see them. */
201export type AdminMember = { username: string; email: string | null; role: Role; /** RFC 3339. */ joined: string };
202
203export type AdminWorkspaceDetail = {
204 slug: string;
205 name: string;
206 description: string | null;
207 /** RFC 3339. */
208 createdAt: string;
209 /** Owners first, then by username. */
210 members: AdminMember[];
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member211 /** It can never be deleted, by anyone (identity's `PROTECTED_WORKSPACES`). */
212 protected: boolean;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace213};
214
215/** The most workspaces one `workspaces` call returns. */
216export const ADMIN_WORKSPACES_LIMIT = 500;
217
218/**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look219 * Whether anyone may make an account, or only someone with an invite.
220 * Identity's `REGISTRATION_MODE`; unset means `invite`.
221 */
222export type RegistrationMode = "invite" | "open";
223
224/** How many invites a person may have out at once, unless identity's `INVITES_PER_USER` says otherwise. */
225export const INVITES_PER_USER = 5;
226/** How long an invite works, unless identity's `INVITE_TTL_DAYS` says otherwise. */
227export const INVITE_TTL_DAYS = 30;
228
229/** Only a pending invite can be used or revoked. Revoked and expired ones never used give the invite back. */
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)230/**
231 * `awaiting_confirmation`: used to make an account that has not confirmed its
232 * email address yet; what it gives is joined when the address is confirmed,
233 * unless it is revoked first.
234 */
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)235/**
236 * `awaiting_answer`: the account it made is confirmed, and the workspace it
237 * names waits for the person to accept or decline. `declined`: they said no.
238 */
239export type InviteStatus =
240 | "pending"
241 | "awaiting_confirmation"
242 | "awaiting_answer"
243 | "redeemed"
244 | "declined"
245 | "expired"
246 | "revoked";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look247
248/** One invite. Mirrors `Invite` in `crates/contracts/src/identity.rs`. */
249export type Invite = {
250 id: string;
251 /** `g1t-k7m2-…`: returned when it is made, and to its maker while pending. */
252 code: string | null;
253 /** The code's first group, such as `g1t-k7m2`. */
254 hint: string;
255 /** Only this address can use it. */
256 email: string | null;
257 /** `account` makes an account; `workspace` joins an existing one to `workspace`. */
258 kind: "account" | "workspace";
259 /** The workspace using it joins. */
260 workspace: string | null;
261 status: InviteStatus;
262 /** Whose allowance it used. */
263 chargedTo: "user" | "workspace" | "none";
264 /** Its maker's username; null when g1t staff made it. */
265 invitedBy: string | null;
266 /** The account that used it. */
267 redeemedBy: string | null;
268 /** RFC 3339. */
269 createdAt: string;
270 /** RFC 3339. */
271 expiresAt: string;
272 redeemedAt: string | null;
273 revokedAt: string | null;
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)274 /** The account a workspace invitation is for, by username: someone invited by username, or the account the invite made. */
275 invitee?: string | null;
276 /** The role `workspace` is joined with; null when it names none. */
277 role?: Role | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look278 /** The staff member who minted it; only in staff views. */
279 staff?: string | null;
280};
281
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)282/**
283 * A workspace invitation waiting for its person's answer, as they see it.
284 * Mirrors `WorkspaceInvitation` in `crates/contracts/src/identity.rs`.
285 */
286export type WorkspaceInvitation = {
287 id: string;
288 workspace: ProfileWorkspace;
289 /** The role accepting joins with. */
290 role: Role;
291 /** Null when g1t staff sent it. */
292 invitedBy: { username: string; name: string | null; avatar: string | null } | null;
293 createdAt: string;
294 expiresAt: string;
295};
296
297/** Someone to invite, as `findPeople` finds them: never an email address. */
298export type PersonMatch = { username: string; name: string | null; avatar: string | null };
299
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look300/** How many invites someone may have out. `limit` and `remaining` are null for no limit. */
301export type Allowance = { limit: number | null; used: number; remaining: number | null };
302
303export type InvitesOverview = {
304 mode: RegistrationMode;
305 allowance: Allowance;
306 /** Workspaces the person owns that were granted invites to share. */
307 workspaces: { slug: string; allowance: Allowance }[];
308 invites: Invite[];
309};
310
311/** What a valid code is for, before it is used. */
312export type InvitePreview = {
313 kind: "account" | "workspace";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas314 /** Pending, unless `anyStatus` asked about a code that is spent. */
315 status: InviteStatus;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look316 /** Null when g1t staff sent it. */
317 invitedBy: { username: string; name: string | null; avatar: string | null } | null;
318 workspace: ProfileWorkspace | null;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas319 /** The repository it accepts an invitation to, such as `{ name: "flagon-io/g1t", role: "write" }`. */
320 repository: { name: string; role: string } | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look321 /** Partly hidden, such as `a•••@example.com`. */
322 email: string | null;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas323 /** The bound address in full, while the invite is pending: it fills in and locks the sign-up form. */
324 address: string | null;
325 /** Whether the bound address has a g1t account already: sign in to accept. */
326 hasAccount: boolean;
327 /** With a viewer: whether it is theirs (for one of their confirmed addresses, or used by them). */
328 forViewer: boolean | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look329 expiresAt: string;
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)330 /** A shared invite link's group, such as `Cloudflare judges`; null for a one-person invite. Not secret. */
331 sharedLabel: string | null;
332 /** The email domains a shared invite link is limited to; empty for any address. */
333 sharedDomains: string[];
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm334 /**
335 * Whether the page was opened from this pending invite's own email (its
336 * `proof` checked out): the account made with it starts with `address`
337 * confirmed. False without a proof, with a wrong one, or for an invite
338 * bound to no address.
339 */
340 emailProven: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look341};
342
343export type WaitlistStatus = "waiting" | "invited" | "dismissed";
344
345export type WaitlistEntry = {
346 id: string;
347 email: string;
348 about: string | null;
349 status: WaitlistStatus;
350 inviteId: string | null;
351 decidedBy: string | null;
352 decidedAt: string | null;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas353 /** What staff wrote when approving; it went in the invite email. */
354 note: string | null;
355 /** The account made with the invite, once it was used. */
356 joinedAs: string | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look357 /** When they first asked. */
358 createdAt: string;
359 /** When they last asked. */
360 updatedAt: string;
361};
362
363export type InviteGrant = { amount: number; note: string | null; grantedBy: string; createdAt: string };
364export type InviteTreeNode = { username: string; joinedAt: string; invited: InviteTreeNode[] };
365
366/** Where a person came from and whom they brought. For a workspace, `username` is its slug. */
367export type InviteTree = {
368 username: string;
369 /** Who invited them, then who invited that person, and so on. */
370 invitedBy: string[];
371 /** The staff member who minted their invite, when staff did. */
372 staff: string | null;
373 allowance: Allowance;
374 grants: InviteGrant[];
375 invites: Invite[];
376 /** Whom they invited, three levels down. */
377 invited: InviteTreeNode[];
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)378 /** The shared invite link the account was made with, if it was. */
379 shared: SharedInviteSource | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look380};
381
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)382// --- Shared invite links, staff only ---------------------------------------------------
383//
384// One link for a group (a conference's judges, a post, a community): up to
385// `maxUses` new accounts, until it expires or staff revoke it, optionally only
386// for addresses at some domains. Each use makes a new account, which makes its
387// own workspace; it never joins an existing one and uses nobody's allowance.
388// The link is `https://g1t.sh/register?invite=<code>`. Mirrors the shared
389// invite types in `crates/contracts/src/identity.rs`.
390
391/** How long a shared invite link works when staff give no date. */
392export const SHARED_INVITE_TTL_DAYS = 14;
393/** The furthest ahead a shared invite link's last day may be set. */
394export const SHARED_INVITE_MAX_DAYS = 365;
395/** The most accounts one shared invite link makes. */
396export const MAX_SHARED_INVITE_USES = 1000;
397/** The most characters a shared invite link's label keeps. */
398export const MAX_SHARED_INVITE_LABEL = 80;
399/** The most email domains one shared invite link may be limited to. */
400export const MAX_SHARED_INVITE_DOMAINS = 10;
401
402/** Only a live link makes accounts; `used_up`: every use is taken. */
403export type SharedInviteStatus = "live" | "used_up" | "expired" | "revoked";
404
405/** The shared invite link an account was made with. */
406export type SharedInviteSource = { id: string; label: string };
407
408/** One shared invite link, as staff see it. */
409export type SharedInvite = {
410 /** `sinv_…`. */
411 id: string;
412 /** Whom it is for, such as `Cloudflare judges`. */
413 label: string;
414 /** The code, while it is live. */
415 code: string | null;
416 /** The code's first group, such as `g1t-k7m2`. */
417 hint: string;
418 maxUses: number;
419 /** Accounts made with it so far. */
420 uses: number;
421 /** Only addresses at these domains may use it; empty for any. */
422 domains: string[];
423 status: SharedInviteStatus;
424 /** The staff member who made it, by email. */
425 staff: string;
426 createdAt: string;
427 expiresAt: string;
428 revokedAt: string | null;
429 revokedBy: string | null;
430 /** The accounts made with it, oldest first; `username` is null once one is purged. */
431 accounts: { username: string | null; joinedAt: string }[];
432};
433
434/** What staff make a shared invite link from. */
435export type NewSharedInvite = {
436 label: string;
437 /** 1 to 1000. */
438 maxUses: number;
439 /** The last day it works, `YYYY-MM-DD` (UTC); null for 14 days from now. */
440 expiresOn: string | null;
441 /** Email domains it is limited to, such as `cloudflare.com`; empty for any address. */
442 domains: string[];
443};
444
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look445/** The most rows one staff listing of invites or the waitlist returns. */
446export const ADMIN_INVITES_LIMIT = 500;
447
448/**
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace449 * Staff-only identity, for sudo.g1t.sh. It takes no viewer and checks no
450 * membership: only sudo calls it, over its service binding, once Cloudflare
451 * Access and its staff list have let someone in. Never call it on behalf of
452 * a customer.
453 */
454export interface IdentityAdminApi {
455 /** Every workspace, newest first, at most 500; `query` matches slug, name, or an owner's username or email. */
456 workspaces(query?: string): Promise<AdminWorkspace[]>;
457 /** One workspace with all its members, or null. */
458 workspace(slug: string): Promise<AdminWorkspaceDetail | null>;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look459
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas460 /** The waitlist, newest first; `query` matches the address or what they said. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look461 waitlist(query?: string | null, status?: WaitlistStatus | null): Promise<WaitlistEntry[]>;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas462 /** How many requests are waiting, for the navigation's badge. */
463 waitlistPending(): Promise<number>;
464 /**
465 * Approving mints an invite bound to the address and emails it, with
466 * `note` (up to 500 characters) if given; dismissing only marks it.
467 */
468 decideWaitlist(id: string, approve: boolean, staff: string, note?: string | null): Promise<Result<WaitlistEntry>>;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look469 /** Invites, newest first; `query` is a code's start, or part of an email, inviter or redeemer. */
470 invites(query?: string | null): Promise<Invite[]>;
471 revokeInvite(id: string, staff: string): Promise<Result<Invite>>;
472 /** An invite that uses nobody's allowance, optionally bound to (and emailed to) `email`. */
473 mintInvite(email: string | null, staff: string): Promise<Result<Invite>>;
474 /** More invites (or fewer, with a negative amount) for a person or a workspace. */
475 grantInvites(
476 target: "user" | "workspace",
477 name: string,
478 amount: number,
479 note: string,
480 staff: string,
481 ): Promise<Result<Allowance>>;
482 /** Where a person came from and whom they brought, or null. */
483 inviteTree(username: string): Promise<InviteTree | null>;
484 /** A workspace's granted invites and the invites made for it, or null. */
485 workspaceInvites(slug: string): Promise<InviteTree | null>;
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)486 /** Shared invite links, newest first, each with the accounts it made. */
487 sharedInvites(): Promise<SharedInvite[]>;
488 /** Makes a shared invite link; the result carries its code. Recorded in the audit log. */
489 createSharedInvite(link: NewSharedInvite, staff: string): Promise<Result<SharedInvite>>;
490 /** Stops a shared invite link making more accounts; those it made stay. Recorded in the audit log. */
491 revokeSharedInvite(id: string, staff: string): Promise<Result<SharedInvite>>;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member492
493 /** Workspaces owners deleted that are not purged yet, newest first. */
494 deletedWorkspaces(): Promise<DeletedWorkspace[]>;
495 /**
496 * Brings a deleted workspace back, with its members, tokens and what went
497 * with it, while it is still restorable. Publishes `workspace.restored`.
498 */
499 restoreWorkspace(workspaceId: string, staff: string): Promise<Result<boolean>>;
500 /**
501 * Purges a deleted workspace now rather than at `purgeAfter`. `confirm` is
502 * its slug, typed out. Refused for a protected workspace. Publishes
503 * `workspace.deleted`.
504 */
505 purgeWorkspace(workspaceId: string, staff: string, confirm: string): Promise<Result<boolean>>;
Merge branch 'worktree-agent-a8385d293d42c913a'506
507 /** Every workspace alias, by name. */
508 aliases(): Promise<WorkspaceAlias[]>;
509 /**
510 * Points `alias` at the workspace whose slug is `workspace`. Refused for
511 * one of the site's routes, anyone's username, a workspace's slug (deleted
512 * or held after a rename) and an existing alias. `note` says why.
513 */
514 setAlias(alias: string, workspace: string, note: string, staff: string): Promise<Result<WorkspaceAlias>>;
515 /** Removes an alias; `reason` goes in sudo's audit log. */
516 removeAlias(alias: string, reason: string, staff: string): Promise<Result<boolean>>;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace517}
518
Merge branch 'worktree-agent-a8385d293d42c913a'519/**
520 * A name g1t's staff point at a workspace, so its addresses lead there under
521 * the workspace's own name: `g1t`, the product, leads to `flagon-io`, Flagon,
522 * Inc. Staff-managed only; it follows the workspace through renames.
523 */
524export type WorkspaceAlias = {
525 alias: string;
526 workspaceId: string;
527 /** The workspace's slug and name now. */
528 workspace: string;
529 workspaceName: string;
530 /** Why it exists. */
531 note: string;
532 /** The staff member who set it, or `migration`. */
533 createdBy: string;
534 /** RFC 3339. */
535 createdAt: string;
536};
537
Webhooks: every event, to your own addresses, signed and retried538/** Who is asking. Every read and write in every service takes one. */
539export type Viewer = User | null;
540
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)541/**
542 * Whether this is a person whose account has not confirmed its email
543 * address. Such an account can only confirm it, change it, or sign out.
544 */
545export function awaitsConfirmation(user: Pick<User, "kind" | "verified"> | null | undefined): boolean {
546 return !!user && (user.kind ?? "user") === "user" && !user.verified;
547}
548
Webhooks: every event, to your own addresses, signed and retried549export type SshKey = {
550 id: string;
551 title: string;
552 fingerprint: string;
553 /** RFC 3339. */
554 createdAt: string;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca555 /** When it last signed in over SSH, RFC 3339, to within 5 minutes; null when it never has. */
556 lastUsedAt: string | null;
Webhooks: every event, to your own addresses, signed and retried557};
558
559export type AccessToken = {
560 id: string;
561 name: string;
562 /** RFC 3339. */
563 createdAt: string;
564 /** RFC 3339, to within a few minutes. Null until it is first used. */
565 lastUsedAt: string | null;
566 /**
567 * For a workspace's token, the username of the member who made it. Null
568 * once that account is gone, and on personal tokens.
569 */
570 createdBy: string | null;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers571 /** Its scopes, as `resource:level`, the highest of each resource. Null: full access. */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step572 scopes: string[] | null;
573 /** Made before tokens had scopes: full access until someone narrows it. */
574 legacy: boolean;
575 /** RFC 3339. Null: it does not expire. */
576 expiresAt: string | null;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers577 /** Its scopes as permissions: each resource it may use, at the highest level. */
578 permissions?: Permissions;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules579 /** What it is for, as its owner wrote it. */
580 description?: string | null;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers581 /**
582 * A personal token's reach: the workspace it is made for; null for every
583 * workspace you belong to (or, with `repositorySelection` public, none).
584 * Null on a workspace's own token, which reaches its workspace.
585 */
586 workspace?: string | null;
587 /** Which repositories of that workspace it reaches. */
588 repositorySelection?: RepositorySelection;
589 /** With `selected`: the repositories, as `owner/name`, that you can see. */
590 repositories?: string[];
591 /** Whether a token made for a workspace that approves tokens may be used there yet. */
592 status?: TokenStatus;
593 /** Why an owner denied or revoked it. */
594 reviewReason?: string | null;
595 /** A workspace's own token, acting as the workspace. */
596 workspaceOwned?: boolean;
597 /** A workspace's own token with Repositories: admin, an admin of its repositories. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules598 admin?: boolean;
Merge main into Artifacts Phase 2599 /** A personal token its owner let use the website as them. */
600 website?: boolean;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules601};
602
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers603/** Which repositories a token reaches in its workspace: all, the selected ones, or public ones only. */
604export type RepositorySelection = "all" | "selected" | "public";
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules605
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers606/** Whether a token made for a workspace may be used there yet. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules607export type TokenStatus = "active" | "pending" | "denied" | "revoked";
608
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers609/** A new access token: a person's, or (with `owner`) a workspace's. */
610export type TokenInput = {
611 /** A workspace's slug to make that workspace's token; null for your own. */
612 owner?: string | null;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules613 name: string;
614 description?: string | null;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers615 /** 1 to 366 days; null for no expiry, where the workspaces it reaches allow that. */
616 ttlSeconds: number | null;
617 /**
618 * A personal token's reach: a workspace's slug, or null for every
619 * workspace you belong to (with `repositorySelection` public: none).
620 */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules621 workspace: string | null;
622 repositorySelection: RepositorySelection;
623 /** With `selected`: `owner/name` or names in the workspace. */
624 repositories: string[];
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers625 /** Each resource's level; left out is no access. */
626 permissions: Partial<Record<ScopeResource, ScopeLevel>>;
Merge main into Artifacts Phase 2627 /** A personal token: whether it may use the website as you. Off unless set. */
628 website?: boolean;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules629};
630
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers631/** A change to a token; what is left out stays. */
Merge main into Artifacts Phase 2632export type TokenChange = Partial<Pick<TokenInput, "name" | "description" | "repositorySelection" | "repositories" | "permissions" | "website">>;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers633
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules634/** A workspace's rules for personal access tokens. */
635export type TokenPolicy = {
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers636 /** A token made for every workspace of its owner reaches this one. */
637 allowTokensForAllWorkspaces: boolean;
638 /** A token may be made for this workspace alone. */
639 allowTokensForThisWorkspace: boolean;
640 /** A token made for this workspace waits for an owner's approval. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules641 requireApproval: boolean;
642 /** Null: no limit. */
643 maxLifetimeDays: number | null;
644 forbidNoExpiry: boolean;
645 updatedBy?: string | null;
646 updatedAt?: string | null;
647};
648
649/** A member's personal token that reaches a workspace, as its owners see it. */
650export type MemberToken = {
651 owner: string;
652 token: AccessToken;
653 /** Whether it reaches the workspace now. */
654 reaches: boolean;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers655 /** Why not: pending approval, denied, revoked, tokens for all workspaces not allowed, tokens made for this workspace not allowed, lasts too long, never expires. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules656 blockedBy?: string | null;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step657};
658
659/** What a new or changed token may do. */
660export type TokenGrant = {
661 /** Null: full access. */
662 scopes: string[] | null;
Webhooks: every event, to your own addresses, signed and retried663};
664
665export type DeviceStart = {
666 /** Secret held by the tool and exchanged for a token once approved. */
667 deviceCode: string;
668 /** Short code shown to the person, e.g. `WDJB-MJHT`. */
669 userCode: string;
670 /** Seconds until both codes stop working. */
671 expiresIn: number;
672 /** Seconds the tool should wait between polls. */
673 interval: number;
674};
675
676export type DeviceRequest = { userCode: string; clientName: string };
677
678export type DeviceClaim =
679 | { status: "pending" | "denied" | "expired" }
680 | { status: "approved"; token: string; user: User };
681
682/** What the site passes on once a person has approved an application. */
683export type OAuthApproval = {
684 clientId: string;
685 /** Shown wherever the application's access is listed. */
686 clientName: string;
687 redirectUri: string;
688 /** PKCE challenge, method S256. */
689 codeChallenge: string;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step690 /** What the person granted. Null: full access. */
691 scopes: string[] | null;
Webhooks: every event, to your own addresses, signed and retried692};
693
694export type OAuthTokens = {
695 accessToken: string;
696 /** Works once; using it returns the next one. */
697 refreshToken: string;
698 /** Seconds until the access token stops working. */
699 expiresIn: number;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step700 /** The scopes granted, space-separated, or `*` for full access. */
701 scope?: string | null;
Webhooks: every event, to your own addresses, signed and retried702};
703
704/** An application a person has signed in to. */
705export type OAuthGrant = {
706 id: string;
707 clientName: string;
708 /** RFC 3339. */
709 createdAt: string;
710 /** RFC 3339. */
711 lastUsedAt: string;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step712 /** What the person granted. Null: full access. */
713 scopes: string[] | null;
714 /** Signed in before applications had scopes: full access until narrowed. */
715 legacy: boolean;
Webhooks: every event, to your own addresses, signed and retried716};
717
718/** Accounts, credentials and sessions. */
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member719/**
720 * What deleting a workspace takes with it, and what stands in the way:
721 * nothing does while `billing` is null and it is not `protected`.
722 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look723export type WorkspaceDeletion = {
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member724 /** Its live repositories, deleted with it. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look725 repositories: number;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member726 /** Its projects, hidden with it. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look727 projects: number;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member728 members: number;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look729 /** Why billing cannot close it yet, in words for its owner. */
730 billing: string | null;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member731 /** It can never be deleted, by anyone. */
732 protected: boolean;
733};
734
735/** How long a deleted workspace is kept, for g1t's staff to restore, before it is purged. */
736export const WORKSPACE_RESTORE_DAYS = 30;
737
738/**
739 * Workspaces nobody can delete, whatever identity's `PROTECTED_WORKSPACES`
740 * says: Flagon's, which runs g1t. Services that act on `workspace.deleting`
741 * check it too, so one published for it by mistake changes nothing.
742 */
743export const ALWAYS_PROTECTED_WORKSPACES: readonly string[] = ["flagon-io"];
744
745/** Whether `slug` is one of `ALWAYS_PROTECTED_WORKSPACES`, in any case. */
746export function isProtectedWorkspace(slug: string): boolean {
747 return ALWAYS_PROTECTED_WORKSPACES.includes(slug.trim().toLowerCase());
748}
749
750/** A workspace an owner deleted, kept until `purgeAfter` for staff to restore. */
751export type DeletedWorkspace = {
752 workspaceId: string;
753 slug: string;
754 name: string;
755 /** RFC 3339. */
756 deletedAt: string;
Merge sudo: delete an account with the workspaces it alone owns, purge each757 /** The username of the owner who deleted it, or the staff member who deleted it with the account that alone owned it. */
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member758 deletedBy: string;
759 /** RFC 3339: when it is purged unless restored first. */
760 purgeAfter: string;
761 /** What went with it, counted when it was deleted. */
762 went: WorkspaceDeletion;
763 /** Whether staff can still restore it. */
764 restorable: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look765};
766
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca767export interface IdentityApi extends AccessClient, TeamsClient, DeployKeysClient {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look768 /**
769 * Creates an account and signs it in. While registration is invite-only,
770 * `inviteCode` must be an unused, unexpired invite (and, when it names an
771 * email, that address); it is ignored while registration is open.
772 */
773 register(
774 username: string,
775 email: string,
776 password: string,
777 inviteCode?: string | null,
778 /** Who is asking, such as the visitor's IP address, for rate limits. */
779 client?: string | null,
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm780 /**
781 * The `proof` from the invite email's link. When it is the invite's own
782 * and `email` is the address it was sent to, the account starts with that
783 * address confirmed; otherwise it is ignored.
784 */
785 emailProof?: string | null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look786 ): Promise<Result<{ user: User; sessionToken: string }>>;
Webhooks: every event, to your own addresses, signed and retried787 /** Verifies a username and password for website sign-in. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look788 /**
789 * Verifies a username, or any confirmed address of the account, and its
790 * password. Wrong passwords are counted against the account and `client`
791 * (the visitor's IP address); past a limit nothing is checked for a while.
792 */
Merge main (membership, two-factor, GitHub repo roles) into tokens793 signIn(
794 username: string,
795 password: string,
796 client?: string | null,
797 ): Promise<Result<{ user: User; sessionToken: string; twoFactorChallenge?: string | null }>>;
798 /**
799 * The second step of signing in, for an account with two-factor
800 * authentication: the challenge `signIn` returned, and a code from the
801 * app or a recovery code.
802 */
803 twoFactorSignIn(challenge: string, code: string, client?: string | null): Promise<Result<{ user: User; sessionToken: string }>>;
Webhooks: every event, to your own addresses, signed and retried804 signOut(sessionToken: string): Promise<void>;
805
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)806 /**
807 * Sends a new confirmation code and link to the primary of an account
808 * that has not confirmed it, at most once a minute.
809 */
Webhooks: every event, to your own addresses, signed and retried810 resendVerification(user: User): Promise<Result<boolean>>;
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)811 /** Confirms the address the emailed link was sent to, signed in or not; ends the code sent with it. */
812 verifyEmail(token: string): Promise<Result<EmailConfirmed>>;
Webhooks: every event, to your own addresses, signed and retried813 /** Emails a reset link if the address has an account. Always resolves. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look814 /**
815 * Any confirmed address of an account works; the link goes to it, and the
816 * primary and backup are told. A few an hour per address and per `client`.
817 */
818 requestPasswordReset(email: string, client?: string | null): Promise<boolean>;
Webhooks: every event, to your own addresses, signed and retried819 /** Sets a new password from an emailed token and ends every session. */
820 resetPassword(token: string, password: string): Promise<Result<User>>;
821
822 /**
823 * Device sign-in (RFC 8628). A tool starts a request, a person approves
824 * its short code in a browser, and the tool claims an access token.
825 */
826 deviceStart(clientName: string): Promise<DeviceStart>;
827 /** What a user code is asking for, or null if it is not valid. */
828 deviceLookup(userCode: string): Promise<DeviceRequest | null>;
829 deviceResolve(userCode: string, user: User, approve: boolean): Promise<Result<boolean>>;
830 deviceClaim(deviceCode: string): Promise<DeviceClaim>;
831
832 /**
833 * OAuth 2.1 for applications that sign a person in through the browser.
834 * The caller has checked the client and its redirect address; this
835 * returns the one-time code the application exchanges for tokens.
836 */
837 oauthAuthorize(user: User, approval: OAuthApproval): Promise<{ code: string }>;
838 /** Redeems a code. It works once, for that client, with the PKCE verifier. */
839 oauthExchange(code: string, codeVerifier: string, clientId: string, redirectUri: string): Promise<Result<OAuthTokens>>;
840 /** Trades a refresh token for new tokens; the old ones stop working. */
841 oauthRefresh(refreshToken: string, clientId: string): Promise<Result<OAuthTokens>>;
842 /** Applications the user has signed in to, most recently used first. */
843 listOAuthGrants(user: User): Promise<OAuthGrant[]>;
844 /** Signs an application out. */
845 revokeOAuthGrant(user: User, id: string): Promise<void>;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step846 /** Changes what an application may do, at once and when it refreshes. */
847 updateOAuthGrant(user: User, id: string, grant: TokenGrant): Promise<Result<OAuthGrant>>;
Webhooks: every event, to your own addresses, signed and retried848
849 createWorkspace(user: User, slug: string, name: string): Promise<Result<Workspace>>;
850 /** Public details of a workspace, or null. */
851 getWorkspace(slug: string): Promise<Workspace | null>;
Merge branch 'worktree-agent-a2013627e5ea4ab13'852 /** Where a workspace keeps its repositories' git data; null when there is no such workspace. */
853 workspaceResidency(slug: string): Promise<DataResidency | null>;
854 /**
855 * Owners only. Applies to repositories made from then on. Offer `eu`
856 * only when the repos service's `storageOptions()` says it is available.
857 */
858 setWorkspaceResidency(actor: User, slug: string, residency: DataResidency): Promise<Result<DataResidency>>;
Webhooks: every event, to your own addresses, signed and retried859 /** Members only. */
860 listMembers(slug: string, viewer: Viewer): Promise<Result<Member[]>>;
861 /** Owners only. */
862 addMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
Merge main (membership, two-factor, GitHub repo roles) into tokens863 /** Owners only; your own username is leaving. Never the last owner. */
Webhooks: every event, to your own addresses, signed and retried864 removeMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
Merge main (membership, two-factor, GitHub repo roles) into tokens865 /** Owners only: owner or member, and the roles held besides it. Never leaves no owner. */
866 updateMember(actor: User, slug: string, username: string, change: { role?: Role; org_roles?: OrgRole[] }): Promise<Result<Member>>;
867 /** Owners only: `username` becomes an owner, and you a member. */
868 transferOwnership(actor: User, slug: string, username: string): Promise<Result<boolean>>;
869 /** You leave the workspace. Never the last owner. */
870 leaveWorkspace(user: User, slug: string): Promise<Result<boolean>>;
871 /** Owners only: change some member privileges; returns all of them. */
872 setMemberPrivileges(actor: User, slug: string, change: Partial<MemberPrivileges>): Promise<Result<MemberPrivileges>>;
873 /** Owners only, with two-factor on themselves: require it of everyone. */
874 setTwoFactorRequirement(actor: User, slug: string, required: boolean): Promise<Result<boolean>>;
Webhooks: every event, to your own addresses, signed and retried875 /** Owners only. An empty name falls back to the slug. */
876 updateWorkspace(actor: User, slug: string, details: { name: string; description: string }): Promise<Result<Workspace>>;
Workspace names and icons, and a component kit for every control877 /**
Agents and memory, checks and conflicts, profiles, slug renames, custom domains878 * Owners only. Changes the slug, the first segment of the workspace's
879 * URLs; the display name is untouched. The old slug redirects to the new
880 * one, and stays reserved for this workspace, for `SLUG_HOLD_DAYS`.
881 * Publishes `workspace.renamed`.
882 */
883 renameWorkspace(actor: User, slug: string, newSlug: string): Promise<Result<Workspace>>;
884 /** Whether `renameWorkspace` would be allowed, changing nothing. */
885 checkWorkspaceRename(actor: User, slug: string, newSlug: string): Promise<Result<boolean>>;
886 /**
887 * The workspace's current slug when `slug` is one it was renamed from
Merge branch 'worktree-agent-a8385d293d42c913a'888 * within `SLUG_HOLD_DAYS`, or when `slug` is an alias staff set for it
889 * (`WorkspaceAlias`); null otherwise, including for a slug in use.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains890 */
891 resolveSlug(slug: string): Promise<string | null>;
892 /**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look893 * Owners only, a person only. `confirm` is the slug, typed out. Refused
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member894 * for a protected workspace, and while billing cannot settle it. Its
895 * repositories, projects and apps go with it; it is kept for
896 * `WORKSPACE_RESTORE_DAYS`, when g1t's staff can restore it, then purged.
897 * Its slug is never given to anyone else; the person whose username it is
898 * may make it again once it is purged. Publishes `workspace.deleting`.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look899 */
900 deleteWorkspace(actor: User, slug: string, confirm: string): Promise<Result<boolean>>;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member901 /** What `deleteWorkspace` would take with it, and what stands in its way, changing nothing. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look902 checkWorkspaceDeletion(actor: User, slug: string): Promise<Result<WorkspaceDeletion>>;
903 /**
Workspace names and icons, and a component kit for every control904 * Owners only. `image` is the file in base64: PNG, JPEG, WebP or GIF, at
905 * most `MAX_AVATAR_BYTES`, checked by its bytes. Null removes the icon.
906 */
907 setWorkspaceAvatar(actor: User, slug: string, image: string | null): Promise<Result<Workspace>>;
908 /** A person's own avatar, as `setWorkspaceAvatar`: the new one, or null. */
909 setUserAvatar(user: User, image: string | null): Promise<Result<string | null>>;
Webhooks: every event, to your own addresses, signed and retried910
911 /**
912 * A workspace's own access tokens. They belong to the workspace, act as
913 * it, and keep working when the member who made one leaves. Members only.
914 */
915 listWorkspaceTokens(slug: string, viewer: Viewer): Promise<Result<AccessToken[]>>;
916 /** Owners only. */
917 removeWorkspaceToken(actor: User, slug: string, id: string): Promise<Result<boolean>>;
918
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules919 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers920 * An access token: yours, or (with `input.owner`, owners only) a
921 * workspace's. People only, signed in. The plaintext token is returned
922 * once and never stored. A personal token made for a workspace that asks
923 * for approval starts pending unless you are an owner there.
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules924 */
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers925 createToken(actor: User, input: TokenInput): Promise<Result<{ token: string; info: AccessToken }>>;
926 /**
927 * Changes a token of yours, or (with `owner`, owners only) a
928 * workspace's; what is left out stays. Widening a token made for a
929 * workspace that approves tokens asks for approval again.
930 */
931 updateToken(actor: User, id: string, change: TokenChange, owner?: string | null): Promise<Result<AccessToken>>;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules932 /** A workspace's rules for personal access tokens. Members only. */
933 getTokenPolicy(slug: string, viewer: Viewer): Promise<Result<TokenPolicy>>;
934 /** Owners only, as people. `maxLifetimeDays` of 0 removes the limit. */
935 setTokenPolicy(
936 actor: User,
937 slug: string,
938 change: Partial<Omit<TokenPolicy, "updatedBy" | "updatedAt">>,
939 ): Promise<Result<TokenPolicy>>;
940 /** The members' tokens that can reach a workspace. Owners only. */
941 listMemberTokens(
942 actor: User,
943 slug: string,
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers944 filter?: { status?: TokenStatus },
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules945 ): Promise<Result<MemberToken[]>>;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers946 /** Approve or deny a token waiting for approval. Owners only. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules947 reviewTokenRequest(actor: User, slug: string, id: string, approve: boolean, reason?: string | null): Promise<Result<MemberToken>>;
948 /** Take a member's token out of the workspace. Owners only. */
949 revokeMemberToken(actor: User, slug: string, id: string, reason?: string | null): Promise<Result<boolean>>;
950
Webhooks: every event, to your own addresses, signed and retried951 userForSession(sessionToken: string): Promise<Viewer>;
952
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look953 /** Whether registration is invite-only. */
954 registration(): Promise<RegistrationMode>;
955 /** A person's invites and what they have left. */
956 listInvites(user: User): Promise<InvitesOverview>;
957 /**
958 * A person makes an invite, optionally for one address (emailed to it),
959 * using one of theirs or, with `workspace`, one the workspace was granted.
960 * People only: never an agent or a workspace's token.
961 */
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)962 createInvite(
963 user: User,
Merge two kinds of invite, kept apart: an invite to g1t (Settings, invite-only only, no workspace unless asked) and an invitation to a workspace (its People page)964 options?: { email?: string | null; workspace?: string | null; join?: string | null; joinRole?: "owner" | "member" | null },
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)965 ): Promise<Result<Invite>>;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look966 /** Its maker, or an owner of its workspace, revokes a pending invite; the invite comes back. */
967 revokeInvite(user: User, id: string): Promise<Result<Invite>>;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas968 /**
969 * What a code is for. Unknown, used, revoked and expired codes all get the
970 * same answer, unless `anyStatus`: then a real code that is spent is
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm971 * described, with its `status`. `viewer` sets `forViewer`; `emailProof`,
972 * the `proof` from the invite email's link, sets `emailProven`.
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas973 */
974 checkInvite(
975 code: string,
976 client?: string | null,
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm977 options?: { viewer?: User | null; anyStatus?: boolean; emailProof?: string | null },
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas978 ): Promise<Result<InvitePreview>>;
979 /**
980 * A signed-in person uses a workspace invite sent to their address, or one
981 * sent with a repository invitation; returns the workspace's slug, or
982 * `workspace/repo`.
983 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look984 acceptInvite(user: User, code: string): Promise<Result<string>>;
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)985 /**
986 * Owners only. Invites someone into a workspace by address (always with an
987 * invite bound to it) or by `username`: a workspace invitation they accept
988 * or decline. Nobody joins without saying yes. `role` is what they join as.
989 */
990 inviteMember(
991 actor: User,
992 slug: string,
993 who: { email?: string | null; username?: string | null; role?: Role | null },
994 ): Promise<Result<Invite>>;
995 /** The workspace invitations waiting for the person's answer, newest first. */
996 listInvitations(user: User): Promise<WorkspaceInvitation[]>;
997 /** Joins the invitation's workspace with its role; returns the workspace's slug. */
998 acceptInvitation(user: User, id: string): Promise<Result<string>>;
999 /** Declines it; whoever sent it is told in their inbox. */
1000 declineInvitation(user: User, id: string): Promise<Result<boolean>>;
1001 /** People to invite, by username prefix or name: a username, a name and an avatar each. */
1002 findPeople(query: string, limit?: number): Promise<PersonMatch[]>;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1003 /** Owners only: the workspace's invites, newest first. */
1004 workspaceInvites(slug: string, viewer: Viewer): Promise<Result<Invite[]>>;
1005 /** Owners only. */
1006 revokeWorkspaceInvite(actor: User, slug: string, id: string): Promise<Result<Invite>>;
1007 /** Someone without an invite asks for one. Always the same answer for a valid address. */
1008 requestAccess(email: string, about: string, client?: string | null): Promise<Result<boolean>>;
1009
Webhooks: every event, to your own addresses, signed and retried1010 /** Verifies git credentials: the account password or an access token. */
1011 userForGitCredentials(username: string, secret: string): Promise<Viewer>;
1012 /** Resolves a `g1t_…` access token, as sent to the API and MCP server. */
1013 userForAccessToken(token: string): Promise<Viewer>;
1014 userForSshKey(fingerprint: string): Promise<Viewer>;
1015 userByUsername(username: string): Promise<Viewer>;
1016 /** The names behind account and workspace ids; unknown ids are left out. */
1017 usernames(ids: string[]): Promise<Record<string, string>>;
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)1018 /**
1019 * Internal: the people behind these ids (at most 50) with their
1020 * workspaces, roles and repository grants, as a signed-in viewer has
1021 * them. For the agents service's audience checks only. Ids of no live
1022 * account are left out.
1023 */
1024 usersForAudience(ids: string[]): Promise<User[]>;
Webhooks: every event, to your own addresses, signed and retried1025
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1026 /** A person's public profile, or null if there is no such account. Never an email address. */
1027 profile(username: string): Promise<Profile | null>;
1028 /** A person changes their own profile. Every field is replaced; an empty one is cleared. */
1029 updateProfile(actor: User, fields: ProfileFields): Promise<Result<Profile>>;
1030 /**
1031 * The workspaces a profile shows `viewer`: those the viewer belongs to
1032 * as well, and those of `publicIn` (where the person made a public
1033 * project) that the person really belongs to. Nothing else.
1034 */
1035 profileWorkspaces(username: string, viewer: Viewer, publicIn: string[]): Promise<ProfileWorkspace[]>;
1036
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.1037 /**
1038 * The apps `user` pinned to their dock in the workspace `workspace` (a
1039 * slug), in the order they set; null when they never saved any there or
1040 * are not one of its members. Kept with the account, so every device
1041 * shows the same dock.
1042 */
1043 dockPins(user: User, workspace: string): Promise<string[] | null>;
1044 /**
1045 * Replaces `user`'s dock pins in `workspace` with `apps`, in that order:
1046 * keys of lowercase letters, digits and hyphens, repeats dropped, at
1047 * most `MAX_DOCK_PINS`. Which keys are real apps is the caller's to check.
1048 */
1049 setDockPins(user: User, workspace: string, apps: string[]): Promise<Result<string[]>>;
1050
Webhooks: every event, to your own addresses, signed and retried1051 listSshKeys(user: User): Promise<SshKey[]>;
1052 /** Takes one line in OpenSSH public key format. */
1053 addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>>;
1054 removeSshKey(user: User, id: string): Promise<void>;
1055
1056 listAccessTokens(user: User): Promise<AccessToken[]>;
1057 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers1058 * A credential minted for a person or workspace by another service. The
1059 * plaintext token is returned once and never stored. With `ttlSeconds`
1060 * the token expires and is left out of token lists; that form is used
1061 * for hosted agents. Tokens people make use `createToken`.
Webhooks: every event, to your own addresses, signed and retried1062 */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1063 createAccessToken(
1064 user: User,
1065 name: string,
1066 ttlSeconds?: number,
1067 grant?: TokenGrant & { listed?: boolean },
1068 ): Promise<{ token: string; info: AccessToken }>;
Webhooks: every event, to your own addresses, signed and retried1069 /**
1070 * A token for a g1t agent working for `onBehalfOf`: it acts as
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1071 * `g1t`, in `scope.repo` only, and only for `scope.operations`.
Webhooks: every event, to your own addresses, signed and retried1072 */
1073 createAgentToken(
1074 onBehalfOf: User,
1075 scope: AgentScope,
1076 ttlSeconds: number,
1077 ): Promise<{ token: string; info: AccessToken }>;
1078 removeAccessToken(user: User, id: string): Promise<void>;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1079 /**
1080 * A token for one sandbox run: it acts as the agent on behalf of
1081 * `onBehalfOf`, can do only what the run's kind needs in `repo`, and
1082 * expires after `ttlSeconds`. See `audit.ts`.
1083 */
1084 createRunCredential(input: CreateRunCredentialInput): Promise<{ token: string; info: AccessToken }>;
1085 /** Ties tokens, by the SHA-256 of their text in hex, to the agent run their sandbox recorded. */
1086 bindRunCredentials(tokenHashes: string[], runId: string): Promise<boolean>;
1087 /** Ends a sandbox's run credentials, by hash or by run. Never touches another token. */
1088 revokeRunCredentials(target: { tokenHashes?: string[]; runId?: string | null }): Promise<boolean>;
Webhooks: every event, to your own addresses, signed and retried1089}
1090
1091
1092/** What an agent's token may do: these operations, in this repository. */
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1093export type AgentScope = { repo: RepoPath; operations: string[]; run?: RunBinding };
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1094
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.1095/** The most apps a person pins in one workspace. Mirrors `MAX_DOCK_PINS` in `crates/contracts/src/identity.rs`. */
1096export const MAX_DOCK_PINS = 24;
1097
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1098/** The most characters each profile field takes. Mirrors `crates/contracts/src/identity.rs`. */
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)1099export const PROFILE_LIMITS = { name: 80, bio: 160, location: 80, website: 200, pronouns: 40, timezone: 64 } as const;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1100
1101/** What anyone may see about a person, at `g1t.sh/u/<username>`. */
1102export type Profile = {
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers1103 /** Lowercased: what the profile is found and linked by. */
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1104 username: string;
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers1105 /** The username as its owner wrote it (`Ana`), when that differs: what the page shows. */
1106 displayUsername?: string | null;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1107 /** The name they go by, if they gave one. */
1108 name: string | null;
1109 bio: string | null;
1110 location: string | null;
1111 /** Always an `https://` address. */
1112 website: string | null;
1113 pronouns: string | null;
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)1114 /** The time zone they are in, an IANA name such as `America/Denver`. */
1115 timezone: string | null;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1116 /** The uploaded avatar's hash, served at `/avatars/<avatar>`. */
1117 avatar: string | null;
1118 /** When the account was made. RFC 3339. */
1119 createdAt: string;
1120};
1121
1122/** What a person may change on their profile. Empty clears a field. */
1123export type ProfileFields = {
1124 name: string;
1125 bio: string;
1126 location: string;
1127 /** `https://…`; a bare `example.com` is taken as `https://example.com`. */
1128 website: string;
1129 pronouns: string;
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)1130 /** An IANA time zone name, such as `America/Denver`; empty clears it. */
1131 timezone: string;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1132};
1133
1134/** A workspace on a person's profile. */
1135export type ProfileWorkspace = { slug: string; name: string; avatar: string | null };

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