Skip to content
1,136 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";
Sidebar: the panels really slide5import type { RepoPath } from "./repos";
6import type { Result } from "./result";
Merge branch 'worktree-agent-ad7c6d88d93adc817'7import type { TeamCreation, TeamsClient } from "./teams";
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.8import type { PeopleClient } from "./people";
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca9import type { DeployKeysClient } from "./deploy-keys";
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)10import type { EmailConfirmed } from "./accounts";
Sidebar: the panels really slide11
12export type User = {
13 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 managers14 /** Lowercased: what the person is found, linked and mentioned by. */
Sidebar: the panels really slide15 username: string;
16 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers17 * The username as its owner wrote it (`Ana`), when that differs from
18 * `username`: what pages show (`shownUsername`). Set on the signed-in
19 * person and on people looked up by name.
20 */
21 display_username?: string;
22 /**
Sidebar: the panels really slide23 * `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 daily24 * tokens: `id` is then the workspace's and `username` its slug. `system`
25 * is g1t itself doing platform work, such as a security update
26 * (`username` `g1t`). Absent means `user`.
Sidebar: the panels really slide27 */
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily28 kind?: "user" | "workspace" | "agent" | "system";
Sidebar: the panels really slide29 /**
30 * 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)31 * resolved from credentials. An account that has not confirmed it can
32 * only confirm it: see `awaitsConfirmation`.
Sidebar: the panels really slide33 */
34 verified?: boolean;
35 /**
36 * The workspaces this user belongs to. Set on users resolved from
37 * credentials, so any service can authorize from it.
38 */
39 workspaces?: Membership[];
Workspace names and icons, and a component kit for every control40 /**
41 * The person's uploaded avatar: the SHA-256 of its bytes, served at
42 * `/avatars/<avatar>`. Only set on the signed-in person; absent means
43 * the generated letter avatar.
44 */
45 avatar?: string;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API46 /**
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent47 * 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 API48 * on behalf of syntaqx"), with which credential, and what it may do.
49 */
50 acting?: Acting;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look51 /**
52 * The repositories this user has a role on directly, whether or not they
53 * belong to its workspace. Set with `workspaces`; see `access.ts`.
54 */
55 grants?: RepoGrant[];
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step56 /**
57 * Set on a user resolved from an access token: its scopes (null for full
58 * access) and the workspaces or repositories it reaches. See scopes.ts.
59 */
60 token?: {
61 token_id: string;
62 scopes?: string[] | null;
63 legacy?: boolean;
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens64 /** The token's name, as its owner gave it. */
65 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 managers66 /**
67 * A token narrowed to one workspace (or none): its workspace and
68 * repositories. The key is kept from before tokens were one kind.
69 */
TS access mirrors the workspace token cap and fine-grained reach70 fine_grained?: {
71 workspace?: string | null;
72 repositories?: "all" | "selected" | "public";
73 repo_ids?: string[];
74 };
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers75 /** A workspace's own token with Repositories: admin. */
TS access mirrors the workspace token cap and fine-grained reach76 admin?: boolean;
77 /** Set on what a deploy key resolves to. */
78 deploy_key?: string;
79 /** The one repository a job's token or a deploy key reaches. */
80 repo?: string;
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts81 /** Set on a workflow job's token (`G1T_TOKEN`): the run and job it was made for. */
82 job?: { run_id: string; job_id: string; pull_requests?: boolean };
Merge main into Artifacts Phase 283 /**
84 * A person's token whose owner let it use the website as them, sent as
85 * `Authorization: Bearer` (apps/web, lib/website-token.ts). Not a scope.
86 */
87 website?: boolean;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step88 };
Merge main (membership, two-factor, GitHub repo roles) into tokens89 /**
90 * Workspaces the person belongs to but cannot use until they meet its
91 * policy, such as turning on two-factor authentication. Left out of
92 * `workspaces` and `grants` meanwhile.
93 */
94 held?: PolicyHold[];
Sidebar: the panels really slide95};
96
97/** What a member may do: an owner also manages the workspace's members. */
98export type Role = "owner" | "member";
99
Workspace names and icons, and a component kit for every control100export type Membership = {
101 /** The workspace's name in URLs: `g1t.sh/<slug>`. */
102 slug: string;
103 role: Role;
104 /** Its display name. Set on users resolved from credentials. */
105 name?: string;
106 /** Its uploaded icon, as `Workspace.avatar`. */
107 avatar?: string;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look108 /** The workspace's base permission: what members get on every repository. Absent means `write`. */
109 base_permission?: BasePermission;
Merge branch 'worktree-agent-ad7c6d88d93adc817'110 /** Who may create its teams. Absent means any member. */
111 team_creation?: TeamCreation;
Merge main (membership, two-factor, GitHub repo roles) into tokens112 /** The roles held besides owner or member. */
113 org_roles?: OrgRole[];
114 /** What the workspace lets its members do. Absent means the defaults. */
115 privileges?: MemberPrivileges;
Chat and workspace agents: channels, DMs and named agents you talk to116 /**
117 * Whether this member uses Code: repositories, issues, pull requests,
118 * checks, deploys. False for people who only use Chat, Docs and agents
119 * (support, sales, finance): they see no repository, whatever the base
120 * permission, and agents treat them as unable to change code. Absent
121 * means true.
122 */
123 code_access?: boolean;
Workspace names and icons, and a component kit for every control124};
Sidebar: the panels really slide125
Chat and workspace agents: channels, DMs and named agents you talk to126/** Whether a member uses Code. See `Membership.code_access`. */
127export function hasCodeAccess(membership: Pick<Membership, "code_access"> | null | undefined): boolean {
128 return membership?.code_access !== false;
129}
130
Agents and memory, checks and conflicts, profiles, slug renames, custom domains131/** How long an old workspace slug redirects, and stays reserved for it, after a rename. */
132export const SLUG_HOLD_DAYS = 90;
133
134/** How long a workspace must wait between renames. */
135export const RENAME_COOLDOWN_HOURS = 24;
136
Workspace names and icons, and a component kit for every control137/** The largest avatar that can be uploaded, in bytes. */
138export const MAX_AVATAR_BYTES = 1024 * 1024;
139
Sidebar: the panels really slide140/**
Merge branch 'worktree-agent-a2013627e5ea4ab13'141 * Where a workspace keeps its repositories' git data: anywhere g1t stores
142 * it (the default), or in the EU only. It applies to repositories made
143 * after it is set.
144 */
145export type DataResidency = "anywhere" | "eu";
146
147/**
Sidebar: the panels really slide148 * A workspace: the owner of repositories, and the first segment of their
149 * URLs. A person's own space and a team's are the same thing.
150 */
151export type Workspace = {
152 id: string;
153 slug: string;
154 name: string;
155 /** One line saying what the workspace is for. */
156 description: string | null;
157 /** RFC 3339. */
158 createdAt: string;
159 memberCount: number;
Workspace names and icons, and a component kit for every control160 /**
161 * The workspace's uploaded icon: the SHA-256 of its bytes, served at
162 * `/avatars/<avatar>`. Null means the generated letter avatar.
163 */
164 avatar: string | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look165 /** What every member gets on each repository; owners have Admin. */
166 basePermission?: BasePermission;
Merge branch 'worktree-agent-ad7c6d88d93adc817'167 /** Who may create its teams. Absent means any member. */
168 teamCreation?: TeamCreation;
Merge main (membership, two-factor, GitHub repo roles) into tokens169 /** Whether members and outside collaborators need two-factor authentication. */
170 twoFactorRequirementEnabled?: boolean;
171} & Partial<MemberPrivileges>;
Sidebar: the panels really slide172
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look173export type Member = {
174 username: string;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar175 /** The username as its owner wrote it (`Ana`), when that differs from `username`. */
176 display_username?: string;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look177 role: Role;
Merge main (membership, two-factor, GitHub repo roles) into tokens178 /** The roles they hold besides `role`. */
179 org_roles?: OrgRole[];
180 /** Whether two-factor authentication is on; owners only, null for anyone else. */
181 two_factor?: boolean | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look182 /** Their display name, when they set one. */
183 name?: string | null;
184 /** Their uploaded avatar's hash, served at `/avatars/<avatar>`; null for the generated letter avatar. */
185 avatar?: string | null;
186};
Sidebar: the panels really slide187
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace188/** An owner of a workspace, as staff see them. */
189export type AdminOwner = { username: string; email: string | null };
190
191/** A workspace as staff see it. Mirrors `AdminWorkspace` in `crates/contracts/src/identity.rs`. */
192export type AdminWorkspace = {
193 slug: string;
194 name: string;
195 /** RFC 3339. */
196 createdAt: string;
197 owners: AdminOwner[];
198 memberCount: number;
199};
200
201/** A member of a workspace, as staff see them. */
202export type AdminMember = { username: string; email: string | null; role: Role; /** RFC 3339. */ joined: string };
203
204export type AdminWorkspaceDetail = {
205 slug: string;
206 name: string;
207 description: string | null;
208 /** RFC 3339. */
209 createdAt: string;
210 /** Owners first, then by username. */
211 members: AdminMember[];
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member212 /** It can never be deleted, by anyone (identity's `PROTECTED_WORKSPACES`). */
213 protected: boolean;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace214};
215
216/** The most workspaces one `workspaces` call returns. */
217export const ADMIN_WORKSPACES_LIMIT = 500;
218
219/**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look220 * Whether anyone may make an account, or only someone with an invite.
221 * Identity's `REGISTRATION_MODE`; unset means `invite`.
222 */
223export type RegistrationMode = "invite" | "open";
224
225/** How many invites a person may have out at once, unless identity's `INVITES_PER_USER` says otherwise. */
226export const INVITES_PER_USER = 5;
227/** How long an invite works, unless identity's `INVITE_TTL_DAYS` says otherwise. */
228export const INVITE_TTL_DAYS = 30;
229
230/** 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)231/**
232 * `awaiting_confirmation`: used to make an account that has not confirmed its
233 * email address yet; what it gives is joined when the address is confirmed,
234 * unless it is revoked first.
235 */
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)236/**
237 * `awaiting_answer`: the account it made is confirmed, and the workspace it
238 * names waits for the person to accept or decline. `declined`: they said no.
239 */
240export type InviteStatus =
241 | "pending"
242 | "awaiting_confirmation"
243 | "awaiting_answer"
244 | "redeemed"
245 | "declined"
246 | "expired"
247 | "revoked";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look248
249/** One invite. Mirrors `Invite` in `crates/contracts/src/identity.rs`. */
250export type Invite = {
251 id: string;
252 /** `g1t-k7m2-…`: returned when it is made, and to its maker while pending. */
253 code: string | null;
254 /** The code's first group, such as `g1t-k7m2`. */
255 hint: string;
256 /** Only this address can use it. */
257 email: string | null;
258 /** `account` makes an account; `workspace` joins an existing one to `workspace`. */
259 kind: "account" | "workspace";
260 /** The workspace using it joins. */
261 workspace: string | null;
262 status: InviteStatus;
263 /** Whose allowance it used. */
264 chargedTo: "user" | "workspace" | "none";
265 /** Its maker's username; null when g1t staff made it. */
266 invitedBy: string | null;
267 /** The account that used it. */
268 redeemedBy: string | null;
269 /** RFC 3339. */
270 createdAt: string;
271 /** RFC 3339. */
272 expiresAt: string;
273 redeemedAt: string | null;
274 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)275 /** The account a workspace invitation is for, by username: someone invited by username, or the account the invite made. */
276 invitee?: string | null;
277 /** The role `workspace` is joined with; null when it names none. */
278 role?: Role | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look279 /** The staff member who minted it; only in staff views. */
280 staff?: string | null;
281};
282
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)283/**
284 * A workspace invitation waiting for its person's answer, as they see it.
285 * Mirrors `WorkspaceInvitation` in `crates/contracts/src/identity.rs`.
286 */
287export type WorkspaceInvitation = {
288 id: string;
289 workspace: ProfileWorkspace;
290 /** The role accepting joins with. */
291 role: Role;
292 /** Null when g1t staff sent it. */
293 invitedBy: { username: string; name: string | null; avatar: string | null } | null;
294 createdAt: string;
295 expiresAt: string;
296};
297
298/** Someone to invite, as `findPeople` finds them: never an email address. */
299export type PersonMatch = { username: string; name: string | null; avatar: string | null };
300
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look301/** How many invites someone may have out. `limit` and `remaining` are null for no limit. */
302export type Allowance = { limit: number | null; used: number; remaining: number | null };
303
304export type InvitesOverview = {
305 mode: RegistrationMode;
306 allowance: Allowance;
307 /** Workspaces the person owns that were granted invites to share. */
308 workspaces: { slug: string; allowance: Allowance }[];
309 invites: Invite[];
310};
311
312/** What a valid code is for, before it is used. */
313export type InvitePreview = {
314 kind: "account" | "workspace";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas315 /** Pending, unless `anyStatus` asked about a code that is spent. */
316 status: InviteStatus;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look317 /** Null when g1t staff sent it. */
318 invitedBy: { username: string; name: string | null; avatar: string | null } | null;
319 workspace: ProfileWorkspace | null;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas320 /** The repository it accepts an invitation to, such as `{ name: "flagon-io/g1t", role: "write" }`. */
321 repository: { name: string; role: string } | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look322 /** Partly hidden, such as `a•••@example.com`. */
323 email: string | null;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas324 /** The bound address in full, while the invite is pending: it fills in and locks the sign-up form. */
325 address: string | null;
326 /** Whether the bound address has a g1t account already: sign in to accept. */
327 hasAccount: boolean;
328 /** With a viewer: whether it is theirs (for one of their confirmed addresses, or used by them). */
329 forViewer: boolean | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look330 expiresAt: string;
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)331 /** A shared invite link's group, such as `Cloudflare judges`; null for a one-person invite. Not secret. */
332 sharedLabel: string | null;
333 /** The email domains a shared invite link is limited to; empty for any address. */
334 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 confirm335 /**
336 * Whether the page was opened from this pending invite's own email (its
337 * `proof` checked out): the account made with it starts with `address`
338 * confirmed. False without a proof, with a wrong one, or for an invite
339 * bound to no address.
340 */
341 emailProven: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look342};
343
344export type WaitlistStatus = "waiting" | "invited" | "dismissed";
345
346export type WaitlistEntry = {
347 id: string;
348 email: string;
349 about: string | null;
350 status: WaitlistStatus;
351 inviteId: string | null;
352 decidedBy: string | null;
353 decidedAt: string | null;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas354 /** What staff wrote when approving; it went in the invite email. */
355 note: string | null;
356 /** The account made with the invite, once it was used. */
357 joinedAs: string | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look358 /** When they first asked. */
359 createdAt: string;
360 /** When they last asked. */
361 updatedAt: string;
362};
363
364export type InviteGrant = { amount: number; note: string | null; grantedBy: string; createdAt: string };
365export type InviteTreeNode = { username: string; joinedAt: string; invited: InviteTreeNode[] };
366
367/** Where a person came from and whom they brought. For a workspace, `username` is its slug. */
368export type InviteTree = {
369 username: string;
370 /** Who invited them, then who invited that person, and so on. */
371 invitedBy: string[];
372 /** The staff member who minted their invite, when staff did. */
373 staff: string | null;
374 allowance: Allowance;
375 grants: InviteGrant[];
376 invites: Invite[];
377 /** Whom they invited, three levels down. */
378 invited: InviteTreeNode[];
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)379 /** The shared invite link the account was made with, if it was. */
380 shared: SharedInviteSource | null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look381};
382
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)383// --- Shared invite links, staff only ---------------------------------------------------
384//
385// One link for a group (a conference's judges, a post, a community): up to
386// `maxUses` new accounts, until it expires or staff revoke it, optionally only
387// for addresses at some domains. Each use makes a new account, which makes its
388// own workspace; it never joins an existing one and uses nobody's allowance.
389// The link is `https://g1t.sh/register?invite=<code>`. Mirrors the shared
390// invite types in `crates/contracts/src/identity.rs`.
391
392/** How long a shared invite link works when staff give no date. */
393export const SHARED_INVITE_TTL_DAYS = 14;
394/** The furthest ahead a shared invite link's last day may be set. */
395export const SHARED_INVITE_MAX_DAYS = 365;
396/** The most accounts one shared invite link makes. */
397export const MAX_SHARED_INVITE_USES = 1000;
398/** The most characters a shared invite link's label keeps. */
399export const MAX_SHARED_INVITE_LABEL = 80;
400/** The most email domains one shared invite link may be limited to. */
401export const MAX_SHARED_INVITE_DOMAINS = 10;
402
403/** Only a live link makes accounts; `used_up`: every use is taken. */
404export type SharedInviteStatus = "live" | "used_up" | "expired" | "revoked";
405
406/** The shared invite link an account was made with. */
407export type SharedInviteSource = { id: string; label: string };
408
409/** One shared invite link, as staff see it. */
410export type SharedInvite = {
411 /** `sinv_…`. */
412 id: string;
413 /** Whom it is for, such as `Cloudflare judges`. */
414 label: string;
415 /** The code, while it is live. */
416 code: string | null;
417 /** The code's first group, such as `g1t-k7m2`. */
418 hint: string;
419 maxUses: number;
420 /** Accounts made with it so far. */
421 uses: number;
422 /** Only addresses at these domains may use it; empty for any. */
423 domains: string[];
424 status: SharedInviteStatus;
425 /** The staff member who made it, by email. */
426 staff: string;
427 createdAt: string;
428 expiresAt: string;
429 revokedAt: string | null;
430 revokedBy: string | null;
431 /** The accounts made with it, oldest first; `username` is null once one is purged. */
432 accounts: { username: string | null; joinedAt: string }[];
433};
434
435/** What staff make a shared invite link from. */
436export type NewSharedInvite = {
437 label: string;
438 /** 1 to 1000. */
439 maxUses: number;
440 /** The last day it works, `YYYY-MM-DD` (UTC); null for 14 days from now. */
441 expiresOn: string | null;
442 /** Email domains it is limited to, such as `cloudflare.com`; empty for any address. */
443 domains: string[];
444};
445
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look446/** The most rows one staff listing of invites or the waitlist returns. */
447export const ADMIN_INVITES_LIMIT = 500;
448
449/**
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace450 * Staff-only identity, for sudo.g1t.sh. It takes no viewer and checks no
451 * membership: only sudo calls it, over its service binding, once Cloudflare
452 * Access and its staff list have let someone in. Never call it on behalf of
453 * a customer.
454 */
455export interface IdentityAdminApi {
456 /** Every workspace, newest first, at most 500; `query` matches slug, name, or an owner's username or email. */
457 workspaces(query?: string): Promise<AdminWorkspace[]>;
458 /** One workspace with all its members, or null. */
459 workspace(slug: string): Promise<AdminWorkspaceDetail | null>;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look460
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas461 /** 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 look462 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 quotas463 /** How many requests are waiting, for the navigation's badge. */
464 waitlistPending(): Promise<number>;
465 /**
466 * Approving mints an invite bound to the address and emails it, with
467 * `note` (up to 500 characters) if given; dismissing only marks it.
468 */
469 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 look470 /** Invites, newest first; `query` is a code's start, or part of an email, inviter or redeemer. */
471 invites(query?: string | null): Promise<Invite[]>;
472 revokeInvite(id: string, staff: string): Promise<Result<Invite>>;
473 /** An invite that uses nobody's allowance, optionally bound to (and emailed to) `email`. */
474 mintInvite(email: string | null, staff: string): Promise<Result<Invite>>;
475 /** More invites (or fewer, with a negative amount) for a person or a workspace. */
476 grantInvites(
477 target: "user" | "workspace",
478 name: string,
479 amount: number,
480 note: string,
481 staff: string,
482 ): Promise<Result<Allowance>>;
483 /** Where a person came from and whom they brought, or null. */
484 inviteTree(username: string): Promise<InviteTree | null>;
485 /** A workspace's granted invites and the invites made for it, or null. */
486 workspaceInvites(slug: string): Promise<InviteTree | null>;
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)487 /** Shared invite links, newest first, each with the accounts it made. */
488 sharedInvites(): Promise<SharedInvite[]>;
489 /** Makes a shared invite link; the result carries its code. Recorded in the audit log. */
490 createSharedInvite(link: NewSharedInvite, staff: string): Promise<Result<SharedInvite>>;
491 /** Stops a shared invite link making more accounts; those it made stay. Recorded in the audit log. */
492 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 member493
494 /** Workspaces owners deleted that are not purged yet, newest first. */
495 deletedWorkspaces(): Promise<DeletedWorkspace[]>;
496 /**
497 * Brings a deleted workspace back, with its members, tokens and what went
498 * with it, while it is still restorable. Publishes `workspace.restored`.
499 */
500 restoreWorkspace(workspaceId: string, staff: string): Promise<Result<boolean>>;
501 /**
502 * Purges a deleted workspace now rather than at `purgeAfter`. `confirm` is
503 * its slug, typed out. Refused for a protected workspace. Publishes
504 * `workspace.deleted`.
505 */
506 purgeWorkspace(workspaceId: string, staff: string, confirm: string): Promise<Result<boolean>>;
Merge branch 'worktree-agent-a8385d293d42c913a'507
508 /** Every workspace alias, by name. */
509 aliases(): Promise<WorkspaceAlias[]>;
510 /**
511 * Points `alias` at the workspace whose slug is `workspace`. Refused for
512 * one of the site's routes, anyone's username, a workspace's slug (deleted
513 * or held after a rename) and an existing alias. `note` says why.
514 */
515 setAlias(alias: string, workspace: string, note: string, staff: string): Promise<Result<WorkspaceAlias>>;
516 /** Removes an alias; `reason` goes in sudo's audit log. */
517 removeAlias(alias: string, reason: string, staff: string): Promise<Result<boolean>>;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace518}
519
Merge branch 'worktree-agent-a8385d293d42c913a'520/**
521 * A name g1t's staff point at a workspace, so its addresses lead there under
522 * the workspace's own name: `g1t`, the product, leads to `flagon-io`, Flagon,
523 * Inc. Staff-managed only; it follows the workspace through renames.
524 */
525export type WorkspaceAlias = {
526 alias: string;
527 workspaceId: string;
528 /** The workspace's slug and name now. */
529 workspace: string;
530 workspaceName: string;
531 /** Why it exists. */
532 note: string;
533 /** The staff member who set it, or `migration`. */
534 createdBy: string;
535 /** RFC 3339. */
536 createdAt: string;
537};
538
Sidebar: the panels really slide539/** Who is asking. Every read and write in every service takes one. */
540export type Viewer = User | null;
541
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)542/**
543 * Whether this is a person whose account has not confirmed its email
544 * address. Such an account can only confirm it, change it, or sign out.
545 */
546export function awaitsConfirmation(user: Pick<User, "kind" | "verified"> | null | undefined): boolean {
547 return !!user && (user.kind ?? "user") === "user" && !user.verified;
548}
549
Sidebar: the panels really slide550export type SshKey = {
551 id: string;
552 title: string;
553 fingerprint: string;
554 /** RFC 3339. */
555 createdAt: string;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca556 /** When it last signed in over SSH, RFC 3339, to within 5 minutes; null when it never has. */
557 lastUsedAt: string | null;
Sidebar: the panels really slide558};
559
560export type AccessToken = {
561 id: string;
562 name: string;
563 /** RFC 3339. */
564 createdAt: string;
565 /** RFC 3339, to within a few minutes. Null until it is first used. */
566 lastUsedAt: string | null;
567 /**
568 * For a workspace's token, the username of the member who made it. Null
569 * once that account is gone, and on personal tokens.
570 */
571 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 managers572 /** 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 step573 scopes: string[] | null;
574 /** Made before tokens had scopes: full access until someone narrows it. */
575 legacy: boolean;
576 /** RFC 3339. Null: it does not expire. */
577 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 managers578 /** Its scopes as permissions: each resource it may use, at the highest level. */
579 permissions?: Permissions;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules580 /** What it is for, as its owner wrote it. */
581 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 managers582 /**
583 * A personal token's reach: the workspace it is made for; null for every
584 * workspace you belong to (or, with `repositorySelection` public, none).
585 * Null on a workspace's own token, which reaches its workspace.
586 */
587 workspace?: string | null;
588 /** Which repositories of that workspace it reaches. */
589 repositorySelection?: RepositorySelection;
590 /** With `selected`: the repositories, as `owner/name`, that you can see. */
591 repositories?: string[];
592 /** Whether a token made for a workspace that approves tokens may be used there yet. */
593 status?: TokenStatus;
594 /** Why an owner denied or revoked it. */
595 reviewReason?: string | null;
596 /** A workspace's own token, acting as the workspace. */
597 workspaceOwned?: boolean;
598 /** A workspace's own token with Repositories: admin, an admin of its repositories. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules599 admin?: boolean;
Merge main into Artifacts Phase 2600 /** A personal token its owner let use the website as them. */
601 website?: boolean;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules602};
603
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers604/** Which repositories a token reaches in its workspace: all, the selected ones, or public ones only. */
605export type RepositorySelection = "all" | "selected" | "public";
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules606
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers607/** Whether a token made for a workspace may be used there yet. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules608export type TokenStatus = "active" | "pending" | "denied" | "revoked";
609
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers610/** A new access token: a person's, or (with `owner`) a workspace's. */
611export type TokenInput = {
612 /** A workspace's slug to make that workspace's token; null for your own. */
613 owner?: string | null;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules614 name: string;
615 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 managers616 /** 1 to 366 days; null for no expiry, where the workspaces it reaches allow that. */
617 ttlSeconds: number | null;
618 /**
619 * A personal token's reach: a workspace's slug, or null for every
620 * workspace you belong to (with `repositorySelection` public: none).
621 */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules622 workspace: string | null;
623 repositorySelection: RepositorySelection;
624 /** With `selected`: `owner/name` or names in the workspace. */
625 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 managers626 /** Each resource's level; left out is no access. */
627 permissions: Partial<Record<ScopeResource, ScopeLevel>>;
Merge main into Artifacts Phase 2628 /** A personal token: whether it may use the website as you. Off unless set. */
629 website?: boolean;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules630};
631
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers632/** A change to a token; what is left out stays. */
Merge main into Artifacts Phase 2633export 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 managers634
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules635/** A workspace's rules for personal access tokens. */
636export 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 managers637 /** A token made for every workspace of its owner reaches this one. */
638 allowTokensForAllWorkspaces: boolean;
639 /** A token may be made for this workspace alone. */
640 allowTokensForThisWorkspace: boolean;
641 /** A token made for this workspace waits for an owner's approval. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules642 requireApproval: boolean;
643 /** Null: no limit. */
644 maxLifetimeDays: number | null;
645 forbidNoExpiry: boolean;
646 updatedBy?: string | null;
647 updatedAt?: string | null;
648};
649
650/** A member's personal token that reaches a workspace, as its owners see it. */
651export type MemberToken = {
652 owner: string;
653 token: AccessToken;
654 /** Whether it reaches the workspace now. */
655 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 managers656 /** 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 rules657 blockedBy?: string | null;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step658};
659
660/** What a new or changed token may do. */
661export type TokenGrant = {
662 /** Null: full access. */
663 scopes: string[] | null;
Sidebar: the panels really slide664};
665
666export type DeviceStart = {
667 /** Secret held by the tool and exchanged for a token once approved. */
668 deviceCode: string;
669 /** Short code shown to the person, e.g. `WDJB-MJHT`. */
670 userCode: string;
671 /** Seconds until both codes stop working. */
672 expiresIn: number;
673 /** Seconds the tool should wait between polls. */
674 interval: number;
675};
676
677export type DeviceRequest = { userCode: string; clientName: string };
678
679export type DeviceClaim =
680 | { status: "pending" | "denied" | "expired" }
681 | { status: "approved"; token: string; user: User };
682
683/** What the site passes on once a person has approved an application. */
684export type OAuthApproval = {
685 clientId: string;
686 /** Shown wherever the application's access is listed. */
687 clientName: string;
688 redirectUri: string;
689 /** PKCE challenge, method S256. */
690 codeChallenge: string;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step691 /** What the person granted. Null: full access. */
692 scopes: string[] | null;
Sidebar: the panels really slide693};
694
695export type OAuthTokens = {
696 accessToken: string;
697 /** Works once; using it returns the next one. */
698 refreshToken: string;
699 /** Seconds until the access token stops working. */
700 expiresIn: number;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step701 /** The scopes granted, space-separated, or `*` for full access. */
702 scope?: string | null;
Sidebar: the panels really slide703};
704
705/** An application a person has signed in to. */
706export type OAuthGrant = {
707 id: string;
708 clientName: string;
709 /** RFC 3339. */
710 createdAt: string;
711 /** RFC 3339. */
712 lastUsedAt: string;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step713 /** What the person granted. Null: full access. */
714 scopes: string[] | null;
715 /** Signed in before applications had scopes: full access until narrowed. */
716 legacy: boolean;
Sidebar: the panels really slide717};
718
719/** Accounts, credentials and sessions. */
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member720/**
721 * What deleting a workspace takes with it, and what stands in the way:
722 * nothing does while `billing` is null and it is not `protected`.
723 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look724export type WorkspaceDeletion = {
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member725 /** Its live repositories, deleted with it. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look726 repositories: number;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member727 /** Its projects, hidden with it. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look728 projects: number;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member729 members: number;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look730 /** Why billing cannot close it yet, in words for its owner. */
731 billing: string | null;
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member732 /** It can never be deleted, by anyone. */
733 protected: boolean;
734};
735
736/** How long a deleted workspace is kept, for g1t's staff to restore, before it is purged. */
737export const WORKSPACE_RESTORE_DAYS = 30;
738
739/**
740 * Workspaces nobody can delete, whatever identity's `PROTECTED_WORKSPACES`
741 * says: Flagon's, which runs g1t. Services that act on `workspace.deleting`
742 * check it too, so one published for it by mistake changes nothing.
743 */
744export const ALWAYS_PROTECTED_WORKSPACES: readonly string[] = ["flagon-io"];
745
746/** Whether `slug` is one of `ALWAYS_PROTECTED_WORKSPACES`, in any case. */
747export function isProtectedWorkspace(slug: string): boolean {
748 return ALWAYS_PROTECTED_WORKSPACES.includes(slug.trim().toLowerCase());
749}
750
751/** A workspace an owner deleted, kept until `purgeAfter` for staff to restore. */
752export type DeletedWorkspace = {
753 workspaceId: string;
754 slug: string;
755 name: string;
756 /** RFC 3339. */
757 deletedAt: string;
Merge sudo: delete an account with the workspaces it alone owns, purge each758 /** 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 member759 deletedBy: string;
760 /** RFC 3339: when it is purged unless restored first. */
761 purgeAfter: string;
762 /** What went with it, counted when it was deleted. */
763 went: WorkspaceDeletion;
764 /** Whether staff can still restore it. */
765 restorable: boolean;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look766};
767
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.768export interface IdentityApi extends AccessClient, TeamsClient, PeopleClient, DeployKeysClient {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look769 /**
770 * Creates an account and signs it in. While registration is invite-only,
771 * `inviteCode` must be an unused, unexpired invite (and, when it names an
772 * email, that address); it is ignored while registration is open.
773 */
774 register(
775 username: string,
776 email: string,
777 password: string,
778 inviteCode?: string | null,
779 /** Who is asking, such as the visitor's IP address, for rate limits. */
780 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 confirm781 /**
782 * The `proof` from the invite email's link. When it is the invite's own
783 * and `email` is the address it was sent to, the account starts with that
784 * address confirmed; otherwise it is ignored.
785 */
786 emailProof?: string | null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look787 ): Promise<Result<{ user: User; sessionToken: string }>>;
Sidebar: the panels really slide788 /** Verifies a username and password for website sign-in. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look789 /**
790 * Verifies a username, or any confirmed address of the account, and its
791 * password. Wrong passwords are counted against the account and `client`
792 * (the visitor's IP address); past a limit nothing is checked for a while.
793 */
Merge main (membership, two-factor, GitHub repo roles) into tokens794 signIn(
795 username: string,
796 password: string,
797 client?: string | null,
798 ): Promise<Result<{ user: User; sessionToken: string; twoFactorChallenge?: string | null }>>;
799 /**
800 * The second step of signing in, for an account with two-factor
801 * authentication: the challenge `signIn` returned, and a code from the
802 * app or a recovery code.
803 */
804 twoFactorSignIn(challenge: string, code: string, client?: string | null): Promise<Result<{ user: User; sessionToken: string }>>;
Sidebar: the panels really slide805 signOut(sessionToken: string): Promise<void>;
806
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)807 /**
808 * Sends a new confirmation code and link to the primary of an account
809 * that has not confirmed it, at most once a minute.
810 */
Sidebar: the panels really slide811 resendVerification(user: User): Promise<Result<boolean>>;
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)812 /** Confirms the address the emailed link was sent to, signed in or not; ends the code sent with it. */
813 verifyEmail(token: string): Promise<Result<EmailConfirmed>>;
Sidebar: the panels really slide814 /** 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 look815 /**
816 * Any confirmed address of an account works; the link goes to it, and the
817 * primary and backup are told. A few an hour per address and per `client`.
818 */
819 requestPasswordReset(email: string, client?: string | null): Promise<boolean>;
Sidebar: the panels really slide820 /** Sets a new password from an emailed token and ends every session. */
821 resetPassword(token: string, password: string): Promise<Result<User>>;
822
823 /**
824 * Device sign-in (RFC 8628). A tool starts a request, a person approves
825 * its short code in a browser, and the tool claims an access token.
826 */
827 deviceStart(clientName: string): Promise<DeviceStart>;
828 /** What a user code is asking for, or null if it is not valid. */
829 deviceLookup(userCode: string): Promise<DeviceRequest | null>;
830 deviceResolve(userCode: string, user: User, approve: boolean): Promise<Result<boolean>>;
831 deviceClaim(deviceCode: string): Promise<DeviceClaim>;
832
833 /**
834 * OAuth 2.1 for applications that sign a person in through the browser.
835 * The caller has checked the client and its redirect address; this
836 * returns the one-time code the application exchanges for tokens.
837 */
838 oauthAuthorize(user: User, approval: OAuthApproval): Promise<{ code: string }>;
839 /** Redeems a code. It works once, for that client, with the PKCE verifier. */
840 oauthExchange(code: string, codeVerifier: string, clientId: string, redirectUri: string): Promise<Result<OAuthTokens>>;
841 /** Trades a refresh token for new tokens; the old ones stop working. */
842 oauthRefresh(refreshToken: string, clientId: string): Promise<Result<OAuthTokens>>;
843 /** Applications the user has signed in to, most recently used first. */
844 listOAuthGrants(user: User): Promise<OAuthGrant[]>;
845 /** Signs an application out. */
846 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 step847 /** Changes what an application may do, at once and when it refreshes. */
848 updateOAuthGrant(user: User, id: string, grant: TokenGrant): Promise<Result<OAuthGrant>>;
Sidebar: the panels really slide849
850 createWorkspace(user: User, slug: string, name: string): Promise<Result<Workspace>>;
851 /** Public details of a workspace, or null. */
852 getWorkspace(slug: string): Promise<Workspace | null>;
Merge branch 'worktree-agent-a2013627e5ea4ab13'853 /** Where a workspace keeps its repositories' git data; null when there is no such workspace. */
854 workspaceResidency(slug: string): Promise<DataResidency | null>;
855 /**
856 * Owners only. Applies to repositories made from then on. Offer `eu`
857 * only when the repos service's `storageOptions()` says it is available.
858 */
859 setWorkspaceResidency(actor: User, slug: string, residency: DataResidency): Promise<Result<DataResidency>>;
Sidebar: the panels really slide860 /** Members only. */
861 listMembers(slug: string, viewer: Viewer): Promise<Result<Member[]>>;
862 /** Owners only. */
863 addMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
Merge main (membership, two-factor, GitHub repo roles) into tokens864 /** Owners only; your own username is leaving. Never the last owner. */
Sidebar: the panels really slide865 removeMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
Merge main (membership, two-factor, GitHub repo roles) into tokens866 /** Owners only: owner or member, and the roles held besides it. Never leaves no owner. */
867 updateMember(actor: User, slug: string, username: string, change: { role?: Role; org_roles?: OrgRole[] }): Promise<Result<Member>>;
868 /** Owners only: `username` becomes an owner, and you a member. */
869 transferOwnership(actor: User, slug: string, username: string): Promise<Result<boolean>>;
870 /** You leave the workspace. Never the last owner. */
871 leaveWorkspace(user: User, slug: string): Promise<Result<boolean>>;
872 /** Owners only: change some member privileges; returns all of them. */
873 setMemberPrivileges(actor: User, slug: string, change: Partial<MemberPrivileges>): Promise<Result<MemberPrivileges>>;
874 /** Owners only, with two-factor on themselves: require it of everyone. */
875 setTwoFactorRequirement(actor: User, slug: string, required: boolean): Promise<Result<boolean>>;
Sidebar: the panels really slide876 /** Owners only. An empty name falls back to the slug. */
877 updateWorkspace(actor: User, slug: string, details: { name: string; description: string }): Promise<Result<Workspace>>;
Workspace names and icons, and a component kit for every control878 /**
Agents and memory, checks and conflicts, profiles, slug renames, custom domains879 * Owners only. Changes the slug, the first segment of the workspace's
880 * URLs; the display name is untouched. The old slug redirects to the new
881 * one, and stays reserved for this workspace, for `SLUG_HOLD_DAYS`.
882 * Publishes `workspace.renamed`.
883 */
884 renameWorkspace(actor: User, slug: string, newSlug: string): Promise<Result<Workspace>>;
885 /** Whether `renameWorkspace` would be allowed, changing nothing. */
886 checkWorkspaceRename(actor: User, slug: string, newSlug: string): Promise<Result<boolean>>;
887 /**
888 * The workspace's current slug when `slug` is one it was renamed from
Merge branch 'worktree-agent-a8385d293d42c913a'889 * within `SLUG_HOLD_DAYS`, or when `slug` is an alias staff set for it
890 * (`WorkspaceAlias`); null otherwise, including for a slug in use.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains891 */
892 resolveSlug(slug: string): Promise<string | null>;
893 /**
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look894 * 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 member895 * for a protected workspace, and while billing cannot settle it. Its
896 * repositories, projects and apps go with it; it is kept for
897 * `WORKSPACE_RESTORE_DAYS`, when g1t's staff can restore it, then purged.
898 * Its slug is never given to anyone else; the person whose username it is
899 * 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 look900 */
901 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 member902 /** 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 look903 checkWorkspaceDeletion(actor: User, slug: string): Promise<Result<WorkspaceDeletion>>;
904 /**
Workspace names and icons, and a component kit for every control905 * Owners only. `image` is the file in base64: PNG, JPEG, WebP or GIF, at
906 * most `MAX_AVATAR_BYTES`, checked by its bytes. Null removes the icon.
907 */
908 setWorkspaceAvatar(actor: User, slug: string, image: string | null): Promise<Result<Workspace>>;
909 /** A person's own avatar, as `setWorkspaceAvatar`: the new one, or null. */
910 setUserAvatar(user: User, image: string | null): Promise<Result<string | null>>;
Sidebar: the panels really slide911
912 /**
913 * A workspace's own access tokens. They belong to the workspace, act as
914 * it, and keep working when the member who made one leaves. Members only.
915 */
916 listWorkspaceTokens(slug: string, viewer: Viewer): Promise<Result<AccessToken[]>>;
917 /** Owners only. */
918 removeWorkspaceToken(actor: User, slug: string, id: string): Promise<Result<boolean>>;
919
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules920 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers921 * An access token: yours, or (with `input.owner`, owners only) a
922 * workspace's. People only, signed in. The plaintext token is returned
923 * once and never stored. A personal token made for a workspace that asks
924 * for approval starts pending unless you are an owner there.
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules925 */
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers926 createToken(actor: User, input: TokenInput): Promise<Result<{ token: string; info: AccessToken }>>;
927 /**
928 * Changes a token of yours, or (with `owner`, owners only) a
929 * workspace's; what is left out stays. Widening a token made for a
930 * workspace that approves tokens asks for approval again.
931 */
932 updateToken(actor: User, id: string, change: TokenChange, owner?: string | null): Promise<Result<AccessToken>>;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules933 /** A workspace's rules for personal access tokens. Members only. */
934 getTokenPolicy(slug: string, viewer: Viewer): Promise<Result<TokenPolicy>>;
935 /** Owners only, as people. `maxLifetimeDays` of 0 removes the limit. */
936 setTokenPolicy(
937 actor: User,
938 slug: string,
939 change: Partial<Omit<TokenPolicy, "updatedBy" | "updatedAt">>,
940 ): Promise<Result<TokenPolicy>>;
941 /** The members' tokens that can reach a workspace. Owners only. */
942 listMemberTokens(
943 actor: User,
944 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 managers945 filter?: { status?: TokenStatus },
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules946 ): 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 managers947 /** Approve or deny a token waiting for approval. Owners only. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules948 reviewTokenRequest(actor: User, slug: string, id: string, approve: boolean, reason?: string | null): Promise<Result<MemberToken>>;
949 /** Take a member's token out of the workspace. Owners only. */
950 revokeMemberToken(actor: User, slug: string, id: string, reason?: string | null): Promise<Result<boolean>>;
951
Sidebar: the panels really slide952 userForSession(sessionToken: string): Promise<Viewer>;
953
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look954 /** Whether registration is invite-only. */
955 registration(): Promise<RegistrationMode>;
956 /** A person's invites and what they have left. */
957 listInvites(user: User): Promise<InvitesOverview>;
958 /**
959 * A person makes an invite, optionally for one address (emailed to it),
960 * using one of theirs or, with `workspace`, one the workspace was granted.
961 * People only: never an agent or a workspace's token.
962 */
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)963 createInvite(
964 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)965 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)966 ): Promise<Result<Invite>>;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look967 /** Its maker, or an owner of its workspace, revokes a pending invite; the invite comes back. */
968 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 quotas969 /**
970 * What a code is for. Unknown, used, revoked and expired codes all get the
971 * 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 confirm972 * described, with its `status`. `viewer` sets `forViewer`; `emailProof`,
973 * 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 quotas974 */
975 checkInvite(
976 code: string,
977 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 confirm978 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 quotas979 ): Promise<Result<InvitePreview>>;
980 /**
981 * A signed-in person uses a workspace invite sent to their address, or one
982 * sent with a repository invitation; returns the workspace's slug, or
983 * `workspace/repo`.
984 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look985 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)986 /**
987 * Owners only. Invites someone into a workspace by address (always with an
988 * invite bound to it) or by `username`: a workspace invitation they accept
989 * or decline. Nobody joins without saying yes. `role` is what they join as.
990 */
991 inviteMember(
992 actor: User,
993 slug: string,
994 who: { email?: string | null; username?: string | null; role?: Role | null },
995 ): Promise<Result<Invite>>;
996 /** The workspace invitations waiting for the person's answer, newest first. */
997 listInvitations(user: User): Promise<WorkspaceInvitation[]>;
998 /** Joins the invitation's workspace with its role; returns the workspace's slug. */
999 acceptInvitation(user: User, id: string): Promise<Result<string>>;
1000 /** Declines it; whoever sent it is told in their inbox. */
1001 declineInvitation(user: User, id: string): Promise<Result<boolean>>;
1002 /** People to invite, by username prefix or name: a username, a name and an avatar each. */
1003 findPeople(query: string, limit?: number): Promise<PersonMatch[]>;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1004 /** Owners only: the workspace's invites, newest first. */
1005 workspaceInvites(slug: string, viewer: Viewer): Promise<Result<Invite[]>>;
1006 /** Owners only. */
1007 revokeWorkspaceInvite(actor: User, slug: string, id: string): Promise<Result<Invite>>;
1008 /** Someone without an invite asks for one. Always the same answer for a valid address. */
1009 requestAccess(email: string, about: string, client?: string | null): Promise<Result<boolean>>;
1010
Sidebar: the panels really slide1011 /** Verifies git credentials: the account password or an access token. */
1012 userForGitCredentials(username: string, secret: string): Promise<Viewer>;
1013 /** Resolves a `g1t_…` access token, as sent to the API and MCP server. */
1014 userForAccessToken(token: string): Promise<Viewer>;
1015 userForSshKey(fingerprint: string): Promise<Viewer>;
1016 userByUsername(username: string): Promise<Viewer>;
1017 /** The names behind account and workspace ids; unknown ids are left out. */
1018 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)1019 /**
1020 * Internal: the people behind these ids (at most 50) with their
1021 * workspaces, roles and repository grants, as a signed-in viewer has
1022 * them. For the agents service's audience checks only. Ids of no live
1023 * account are left out.
1024 */
1025 usersForAudience(ids: string[]): Promise<User[]>;
Sidebar: the panels really slide1026
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1027 /** A person's public profile, or null if there is no such account. Never an email address. */
1028 profile(username: string): Promise<Profile | null>;
1029 /** A person changes their own profile. Every field is replaced; an empty one is cleared. */
1030 updateProfile(actor: User, fields: ProfileFields): Promise<Result<Profile>>;
1031 /**
1032 * The workspaces a profile shows `viewer`: those the viewer belongs to
1033 * as well, and those of `publicIn` (where the person made a public
1034 * project) that the person really belongs to. Nothing else.
1035 */
1036 profileWorkspaces(username: string, viewer: Viewer, publicIn: string[]): Promise<ProfileWorkspace[]>;
1037
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.1038 /**
1039 * The apps `user` pinned to their dock in the workspace `workspace` (a
1040 * slug), in the order they set; null when they never saved any there or
1041 * are not one of its members. Kept with the account, so every device
1042 * shows the same dock.
1043 */
1044 dockPins(user: User, workspace: string): Promise<string[] | null>;
1045 /**
1046 * Replaces `user`'s dock pins in `workspace` with `apps`, in that order:
1047 * keys of lowercase letters, digits and hyphens, repeats dropped, at
1048 * most `MAX_DOCK_PINS`. Which keys are real apps is the caller's to check.
1049 */
1050 setDockPins(user: User, workspace: string, apps: string[]): Promise<Result<string[]>>;
1051
Sidebar: the panels really slide1052 listSshKeys(user: User): Promise<SshKey[]>;
1053 /** Takes one line in OpenSSH public key format. */
1054 addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>>;
1055 removeSshKey(user: User, id: string): Promise<void>;
1056
1057 listAccessTokens(user: User): Promise<AccessToken[]>;
1058 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers1059 * A credential minted for a person or workspace by another service. The
1060 * plaintext token is returned once and never stored. With `ttlSeconds`
1061 * the token expires and is left out of token lists; that form is used
1062 * for hosted agents. Tokens people make use `createToken`.
Sidebar: the panels really slide1063 */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1064 createAccessToken(
1065 user: User,
1066 name: string,
1067 ttlSeconds?: number,
1068 grant?: TokenGrant & { listed?: boolean },
1069 ): Promise<{ token: string; info: AccessToken }>;
Sidebar: the panels really slide1070 /**
1071 * 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-agent1072 * `g1t`, in `scope.repo` only, and only for `scope.operations`.
Sidebar: the panels really slide1073 */
1074 createAgentToken(
1075 onBehalfOf: User,
1076 scope: AgentScope,
1077 ttlSeconds: number,
1078 ): Promise<{ token: string; info: AccessToken }>;
1079 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 API1080 /**
1081 * A token for one sandbox run: it acts as the agent on behalf of
1082 * `onBehalfOf`, can do only what the run's kind needs in `repo`, and
1083 * expires after `ttlSeconds`. See `audit.ts`.
1084 */
1085 createRunCredential(input: CreateRunCredentialInput): Promise<{ token: string; info: AccessToken }>;
1086 /** Ties tokens, by the SHA-256 of their text in hex, to the agent run their sandbox recorded. */
1087 bindRunCredentials(tokenHashes: string[], runId: string): Promise<boolean>;
1088 /** Ends a sandbox's run credentials, by hash or by run. Never touches another token. */
1089 revokeRunCredentials(target: { tokenHashes?: string[]; runId?: string | null }): Promise<boolean>;
Sidebar: the panels really slide1090}
1091
1092
1093/** 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 API1094export type AgentScope = { repo: RepoPath; operations: string[]; run?: RunBinding };
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1095
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.1096/** The most apps a person pins in one workspace. Mirrors `MAX_DOCK_PINS` in `crates/contracts/src/identity.rs`. */
1097export const MAX_DOCK_PINS = 24;
1098
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1099/** 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)1100export 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 domains1101
1102/** What anyone may see about a person, at `g1t.sh/u/<username>`. */
1103export 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 managers1104 /** Lowercased: what the profile is found and linked by. */
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1105 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 managers1106 /** The username as its owner wrote it (`Ana`), when that differs: what the page shows. */
1107 displayUsername?: string | null;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1108 /** The name they go by, if they gave one. */
1109 name: string | null;
1110 bio: string | null;
1111 location: string | null;
1112 /** Always an `https://` address. */
1113 website: string | null;
1114 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)1115 /** The time zone they are in, an IANA name such as `America/Denver`. */
1116 timezone: string | null;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1117 /** The uploaded avatar's hash, served at `/avatars/<avatar>`. */
1118 avatar: string | null;
1119 /** When the account was made. RFC 3339. */
1120 createdAt: string;
1121};
1122
1123/** What a person may change on their profile. Empty clears a field. */
1124export type ProfileFields = {
1125 name: string;
1126 bio: string;
1127 location: string;
1128 /** `https://…`; a bare `example.com` is taken as `https://example.com`. */
1129 website: string;
1130 pronouns: string;
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)1131 /** An IANA time zone name, such as `America/Denver`; empty clears it. */
1132 timezone: string;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1133};
1134
1135/** A workspace on a person's profile. */
1136export 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.