Skip to content
1,053 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.

Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.1import type { SoundSettings, SoundSettingsChange } from "./sounds";
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running2import 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 managers3import type { Permissions, ScopeLevel, ScopeResource } from "./scopes";
Merge main (membership, two-factor, GitHub repo roles) into tokens4import type { MemberPrivileges, OrgRole, PolicyHold } from "./members";
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running5import type { Acting, CreateRunCredentialInput, RunBinding } from "./audit";
6import type { RepoPath } from "./repos";
7import type { Result } from "./result";
Merge branch 'worktree-agent-ad7c6d88d93adc817'8import 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.9import type { PeopleClient } from "./people";
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca10import type { DeployKeysClient } from "./deploy-keys";
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)11import type { EmailConfirmed } from "./accounts";
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running12
13export type User = {
14 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 managers15 /** Lowercased: what the person is found, linked and mentioned by. */
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running16 username: string;
17 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers18 * The username as its owner wrote it (`Ana`), when that differs from
19 * `username`: what pages show (`shownUsername`). Set on the signed-in
20 * person and on people looked up by name.
21 */
22 display_username?: string;
23 /**
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running24 * `workspace` when a workspace is acting through one of its own access
25 * tokens: `id` is then the workspace's and `username` its slug. `system`
26 * is g1t itself doing platform work, such as a security update
27 * (`username` `g1t`). Absent means `user`.
28 */
29 kind?: "user" | "workspace" | "agent" | "system";
30 /**
31 * 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)32 * resolved from credentials. An account that has not confirmed it can
33 * only confirm it: see `awaitsConfirmation`.
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running34 */
35 verified?: boolean;
36 /**
37 * The workspaces this user belongs to. Set on users resolved from
38 * credentials, so any service can authorize from it.
39 */
40 workspaces?: Membership[];
41 /**
42 * The person's uploaded avatar: the SHA-256 of its bytes, served at
43 * `/avatars/<avatar>`. Only set on the signed-in person; absent means
44 * the generated letter avatar.
45 */
46 avatar?: string;
47 /**
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.48 * Set on a run credential resolved from its token: who it acts for,
49 * with which credential, and what it may do. Older rows written by
50 * g1t's retired hosted agents carry it too ("g1t on behalf of syntaqx").
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running51 */
52 acting?: Acting;
53 /**
54 * The repositories this user has a role on directly, whether or not they
55 * belong to its workspace. Set with `workspaces`; see `access.ts`.
56 */
57 grants?: RepoGrant[];
58 /**
59 * Set on a user resolved from an access token: its scopes (null for full
60 * access) and the workspaces or repositories it reaches. See scopes.ts.
61 */
62 token?: {
63 token_id: string;
64 scopes?: string[] | null;
65 legacy?: boolean;
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens66 /** The token's name, as its owner gave it. */
67 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 managers68 /**
69 * A token narrowed to one workspace (or none): its workspace and
70 * repositories. The key is kept from before tokens were one kind.
71 */
TS access mirrors the workspace token cap and fine-grained reach72 fine_grained?: {
73 workspace?: string | null;
74 repositories?: "all" | "selected" | "public";
75 repo_ids?: string[];
76 };
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers77 /** A workspace's own token with Repositories: admin. */
TS access mirrors the workspace token cap and fine-grained reach78 admin?: boolean;
79 /** Set on what a deploy key resolves to. */
80 deploy_key?: string;
81 /** The one repository a job's token or a deploy key reaches. */
82 repo?: string;
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts83 /** Set on a workflow job's token (`G1T_TOKEN`): the run and job it was made for. */
84 job?: { run_id: string; job_id: string; pull_requests?: boolean };
Merge main into Artifacts Phase 285 /**
86 * A person's token whose owner let it use the website as them, sent as
87 * `Authorization: Bearer` (apps/web, lib/website-token.ts). Not a scope.
88 */
89 website?: boolean;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running90 };
Merge main (membership, two-factor, GitHub repo roles) into tokens91 /**
92 * Workspaces the person belongs to but cannot use until they meet its
93 * policy, such as turning on two-factor authentication. Left out of
94 * `workspaces` and `grants` meanwhile.
95 */
96 held?: PolicyHold[];
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running97};
98
99/** What a member may do: an owner also manages the workspace's members. */
100export type Role = "owner" | "member";
101
102export type Membership = {
103 /** The workspace's name in URLs: `g1t.sh/<slug>`. */
104 slug: string;
105 role: Role;
106 /** Its display name. Set on users resolved from credentials. */
107 name?: string;
108 /** Its uploaded icon, as `Workspace.avatar`. */
109 avatar?: string;
110 /** The workspace's base permission: what members get on every repository. Absent means `write`. */
111 base_permission?: BasePermission;
Merge branch 'worktree-agent-ad7c6d88d93adc817'112 /** Who may create its teams. Absent means any member. */
113 team_creation?: TeamCreation;
Merge main (membership, two-factor, GitHub repo roles) into tokens114 /** The roles held besides owner or member. */
115 org_roles?: OrgRole[];
116 /** What the workspace lets its members do. Absent means the defaults. */
117 privileges?: MemberPrivileges;
Chat and workspace agents: channels, DMs and named agents you talk to118 /**
119 * Whether this member uses Code: repositories, issues, pull requests,
120 * checks, deploys. False for people who only use Chat, Docs and agents
121 * (support, sales, finance): they see no repository, whatever the base
122 * permission, and agents treat them as unable to change code. Absent
123 * means true.
124 */
125 code_access?: boolean;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running126};
127
Chat and workspace agents: channels, DMs and named agents you talk to128/** Whether a member uses Code. See `Membership.code_access`. */
129export function hasCodeAccess(membership: Pick<Membership, "code_access"> | null | undefined): boolean {
130 return membership?.code_access !== false;
131}
132
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running133/** How long an old workspace slug redirects, and stays reserved for it, after a rename. */
134export const SLUG_HOLD_DAYS = 90;
135
136/** How long a workspace must wait between renames. */
137export const RENAME_COOLDOWN_HOURS = 24;
138
139/** The largest avatar that can be uploaded, in bytes. */
140export const MAX_AVATAR_BYTES = 1024 * 1024;
141
142/**
143 * Where a workspace keeps its repositories' git data: anywhere g1t stores
144 * it (the default), or in the EU only. It applies to repositories made
145 * after it is set.
146 */
147export type DataResidency = "anywhere" | "eu";
148
149/**
150 * A workspace: the owner of repositories, and the first segment of their
151 * URLs. A person's own space and a team's are the same thing.
152 */
153export type Workspace = {
154 id: string;
155 slug: string;
156 name: string;
157 /** One line saying what the workspace is for. */
158 description: string | null;
159 /** RFC 3339. */
160 createdAt: string;
161 memberCount: number;
162 /**
163 * The workspace's uploaded icon: the SHA-256 of its bytes, served at
164 * `/avatars/<avatar>`. Null means the generated letter avatar.
165 */
166 avatar: string | null;
167 /** What every member gets on each repository; owners have Admin. */
168 basePermission?: BasePermission;
Merge branch 'worktree-agent-ad7c6d88d93adc817'169 /** Who may create its teams. Absent means any member. */
170 teamCreation?: TeamCreation;
Merge main (membership, two-factor, GitHub repo roles) into tokens171 /** Whether members and outside collaborators need two-factor authentication. */
172 twoFactorRequirementEnabled?: boolean;
173} & Partial<MemberPrivileges>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running174
175export type Member = {
176 username: string;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar177 /** The username as its owner wrote it (`Ana`), when that differs from `username`. */
178 display_username?: string;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running179 role: Role;
Merge main (membership, two-factor, GitHub repo roles) into tokens180 /** The roles they hold besides `role`. */
181 org_roles?: OrgRole[];
182 /** Whether two-factor authentication is on; owners only, null for anyone else. */
183 two_factor?: boolean | null;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running184 /** Their display name, when they set one. */
185 name?: string | null;
186 /** Their uploaded avatar's hash, served at `/avatars/<avatar>`; null for the generated letter avatar. */
187 avatar?: string | null;
188};
189
190/** An owner of a workspace, as staff see them. */
191export type AdminOwner = { username: string; email: string | null };
192
193/** A workspace as staff see it. Mirrors `AdminWorkspace` in `crates/contracts/src/identity.rs`. */
194export type AdminWorkspace = {
195 slug: string;
196 name: string;
197 /** RFC 3339. */
198 createdAt: string;
199 owners: AdminOwner[];
200 memberCount: number;
201};
202
203/** A member of a workspace, as staff see them. */
204export type AdminMember = { username: string; email: string | null; role: Role; /** RFC 3339. */ joined: string };
205
206export type AdminWorkspaceDetail = {
207 slug: string;
208 name: string;
209 description: string | null;
210 /** RFC 3339. */
211 createdAt: string;
212 /** Owners first, then by username. */
213 members: AdminMember[];
214 /** It can never be deleted, by anyone (identity's `PROTECTED_WORKSPACES`). */
215 protected: boolean;
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.216 /** Set while staff have it suspended. */
217 suspended?: Suspension | null;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running218};
219
220/** The most workspaces one `workspaces` call returns. */
221export const ADMIN_WORKSPACES_LIMIT = 500;
222
223/** How long an invite works, unless identity's `INVITE_TTL_DAYS` says otherwise. */
224export const INVITE_TTL_DAYS = 30;
Anyone on g1t can invite a friend again: Invite a friend, in the account menu with how many are left beside it, opens Settings → Invites to g1t with a dialog for their email address and a note. The friend gets an email from them whose link opens sign-up with the address filled in and, because it came from that email, confirmed; their account starts with a workspace of its own, and the page lists every invite sent with where it stands (Pending, confirming their email, Joined as @them), with Copy link, Send again and Revoke on each. It is word of mouth, never a gate: sign-ups stay open to everyone, nothing asks for an invite, and running out says friends can still sign up without one.225/**
226 * How many friends one person may invite to g1t (Invite a friend), unless
227 * identity's `INVITES_PER_USER` says otherwise; staff may grant more. Never
228 * needed to sign up.
229 */
230export const INVITES_PER_USER = 20;
Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now.231/** The most characters an invitation's note keeps (`invite_member`'s `message`). */
232export const MAX_INVITE_MESSAGE = 500;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running233
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.234/** Only a pending invite can be used or revoked. */
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)235/**
236 * `awaiting_confirmation`: used to make an account that has not confirmed its
237 * email address yet; what it gives is joined when the address is confirmed,
238 * unless it is revoked first.
239 */
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)240/**
241 * `awaiting_answer`: the account it made is confirmed, and the workspace it
242 * names waits for the person to accept or decline. `declined`: they said no.
243 */
244export type InviteStatus =
245 | "pending"
246 | "awaiting_confirmation"
247 | "awaiting_answer"
248 | "redeemed"
249 | "declined"
250 | "expired"
251 | "revoked";
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running252
253/** One invite. Mirrors `Invite` in `crates/contracts/src/identity.rs`. */
254export type Invite = {
255 id: string;
256 /** `g1t-k7m2-…`: returned when it is made, and to its maker while pending. */
257 code: string | null;
258 /** The code's first group, such as `g1t-k7m2`. */
259 hint: string;
260 /** Only this address can use it. */
261 email: string | null;
262 /** `account` makes an account; `workspace` joins an existing one to `workspace`. */
263 kind: "account" | "workspace";
264 /** The workspace using it joins. */
265 workspace: string | null;
266 status: InviteStatus;
Anyone on g1t can invite a friend again: Invite a friend, in the account menu with how many are left beside it, opens Settings → Invites to g1t with a dialog for their email address and a note. The friend gets an email from them whose link opens sign-up with the address filled in and, because it came from that email, confirmed; their account starts with a workspace of its own, and the page lists every invite sent with where it stands (Pending, confirming their email, Joined as @them), with Copy link, Send again and Revoke on each. It is word of mouth, never a gate: sign-ups stay open to everyone, nothing asks for an invite, and running out says friends can still sign up without one.267 /**
268 * Whose invites it used: `user` for an invite to g1t from Invite a friend,
269 * counted against its maker's; `none` for an invitation into a workspace or
270 * onto a repository; `workspace` only on invites from before sign-ups opened.
271 */
272 chargedTo: "user" | "workspace" | "none";
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running273 /** Its maker's username; null when g1t staff made it. */
274 invitedBy: string | null;
275 /** The account that used it. */
276 redeemedBy: string | null;
277 /** RFC 3339. */
278 createdAt: string;
279 /** RFC 3339. */
280 expiresAt: string;
281 redeemedAt: string | null;
282 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)283 /** The account a workspace invitation is for, by username: someone invited by username, or the account the invite made. */
284 invitee?: string | null;
285 /** The role `workspace` is joined with; null when it names none. */
286 role?: Role | null;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running287 /** The staff member who minted it; only in staff views. */
288 staff?: string | null;
289};
290
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)291/**
292 * A workspace invitation waiting for its person's answer, as they see it.
293 * Mirrors `WorkspaceInvitation` in `crates/contracts/src/identity.rs`.
294 */
295export type WorkspaceInvitation = {
296 id: string;
297 workspace: ProfileWorkspace;
298 /** The role accepting joins with. */
299 role: Role;
300 /** Null when g1t staff sent it. */
301 invitedBy: { username: string; name: string | null; avatar: string | null } | null;
302 createdAt: string;
303 expiresAt: string;
304};
305
Anyone on g1t can invite a friend again: Invite a friend, in the account menu with how many are left beside it, opens Settings → Invites to g1t with a dialog for their email address and a note. The friend gets an email from them whose link opens sign-up with the address filled in and, because it came from that email, confirmed; their account starts with a workspace of its own, and the page lists every invite sent with where it stands (Pending, confirming their email, Joined as @them), with Copy link, Send again and Revoke on each. It is word of mouth, never a gate: sign-ups stay open to everyone, nothing asks for an invite, and running out says friends can still sign up without one.306/** How many friends a person may invite to g1t, and how many they have. */
307export type Allowance = { limit: number; used: number; remaining: number };
308
309/** A person's invites to g1t, newest first, and what they have left. */
310export type InvitesOverview = { allowance: Allowance; invites: Invite[] };
311
312/** What Invite a friend sends: the address, a note, and optionally a paid workspace the person owns. */
313export type NewInvite = {
314 email: string;
315 message?: string | null;
316 /** Also invite them to this workspace, by slug: one the person owns, on a paid plan. */
317 join?: string | null;
318 joinRole?: "owner" | "member" | null;
319};
320
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)321/** Someone to invite, as `findPeople` finds them: never an email address. */
322export type PersonMatch = { username: string; name: string | null; avatar: string | null };
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running323
324/** What a valid code is for, before it is used. */
325export type InvitePreview = {
326 kind: "account" | "workspace";
327 /** Pending, unless `anyStatus` asked about a code that is spent. */
328 status: InviteStatus;
329 /** Null when g1t staff sent it. */
330 invitedBy: { username: string; name: string | null; avatar: string | null } | null;
331 workspace: ProfileWorkspace | null;
332 /** The repository it accepts an invitation to, such as `{ name: "flagon-io/g1t", role: "write" }`. */
333 repository: { name: string; role: string } | null;
334 /** Partly hidden, such as `a•••@example.com`. */
335 email: string | null;
336 /** The bound address in full, while the invite is pending: it fills in and locks the sign-up form. */
337 address: string | null;
338 /** Whether the bound address has a g1t account already: sign in to accept. */
339 hasAccount: boolean;
340 /** With a viewer: whether it is theirs (for one of their confirmed addresses, or used by them). */
341 forViewer: boolean | null;
342 expiresAt: 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 confirm343 /**
344 * Whether the page was opened from this pending invite's own email (its
345 * `proof` checked out): the account made with it starts with `address`
346 * confirmed. False without a proof, with a wrong one, or for an invite
347 * bound to no address.
348 */
349 emailProven: boolean;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running350};
351
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.352/**
353 * The shared invite link an account was made with. Before registration
354 * opened, staff handed groups shared links; none are made or used any more.
355 */
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)356export type SharedInviteSource = { id: string; label: string };
357
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.358/** Why and since when staff suspended an account or a workspace. */
359export type Suspension = { reason: string; at: string; by: string };
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)360
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.361/** An email domain no new account may sign up with; its subdomains too. */
362export type BlockedDomain = { domain: string; reason: string; blockedBy: string; createdAt: string };
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)363
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.364/** The most rows one staff listing of invites returns. */
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running365export const ADMIN_INVITES_LIMIT = 500;
366
367/**
368 * Staff-only identity, for sudo.g1t.sh. It takes no viewer and checks no
369 * membership: only sudo calls it, over its service binding, once Cloudflare
370 * Access and its staff list have let someone in. Never call it on behalf of
371 * a customer.
372 */
373export interface IdentityAdminApi {
374 /** Every workspace, newest first, at most 500; `query` matches slug, name, or an owner's username or email. */
375 workspaces(query?: string): Promise<AdminWorkspace[]>;
376 /** One workspace with all its members, or null. */
377 workspace(slug: string): Promise<AdminWorkspaceDetail | null>;
378
379 /** Invites, newest first; `query` is a code's start, or part of an email, inviter or redeemer. */
Anyone on g1t can invite a friend again: Invite a friend, in the account menu with how many are left beside it, opens Settings → Invites to g1t with a dialog for their email address and a note. The friend gets an email from them whose link opens sign-up with the address filled in and, because it came from that email, confirmed; their account starts with a workspace of its own, and the page lists every invite sent with where it stands (Pending, confirming their email, Joined as @them), with Copy link, Send again and Revoke on each. It is word of mouth, never a gate: sign-ups stay open to everyone, nothing asks for an invite, and running out says friends can still sign up without one.380 invites(query?: string | null, options?: { toG1t?: boolean }): Promise<Invite[]>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running381 revokeInvite(id: string, staff: string): Promise<Result<Invite>>;
Anyone on g1t can invite a friend again: Invite a friend, in the account menu with how many are left beside it, opens Settings → Invites to g1t with a dialog for their email address and a note. The friend gets an email from them whose link opens sign-up with the address filled in and, because it came from that email, confirmed; their account starts with a workspace of its own, and the page lists every invite sent with where it stands (Pending, confirming their email, Joined as @them), with Copy link, Send again and Revoke on each. It is word of mouth, never a gate: sign-ups stay open to everyone, nothing asks for an invite, and running out says friends can still sign up without one.382 /** More invites to g1t for a person (or fewer, with a negative amount), with why. Their allowance afterwards. */
383 grantInvites(username: string, amount: number, note: string, staff: string): Promise<Result<Allowance>>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running384
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.385 /**
386 * Suspends an account (by username): it cannot sign in, and its sessions
387 * and tokens stop at once. `suspend: false` lifts it. A reason is required
388 * to suspend.
389 */
390 suspendUser(username: string, suspend: boolean, reason: string, staff: string): Promise<Result<Suspension | null>>;
391 /** Suspends a workspace (by slug): its own tokens stop. Sudo also holds its compute in billing. */
392 suspendWorkspace(slug: string, suspend: boolean, reason: string, staff: string): Promise<Result<Suspension | null>>;
393 /** Email domains no new account may sign up with. */
394 blockedDomains(): Promise<BlockedDomain[]>;
395 /** Blocks (or with `block: false` unblocks) an email domain; returns the list now. */
396 blockDomain(domain: string, block: boolean, reason: string, staff: string): Promise<Result<BlockedDomain[]>>;
397
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running398 /** Workspaces owners deleted that are not purged yet, newest first. */
399 deletedWorkspaces(): Promise<DeletedWorkspace[]>;
400 /**
401 * Brings a deleted workspace back, with its members, tokens and what went
402 * with it, while it is still restorable. Publishes `workspace.restored`.
403 */
404 restoreWorkspace(workspaceId: string, staff: string): Promise<Result<boolean>>;
405 /**
406 * Purges a deleted workspace now rather than at `purgeAfter`. `confirm` is
407 * its slug, typed out. Refused for a protected workspace. Publishes
408 * `workspace.deleted`.
409 */
410 purgeWorkspace(workspaceId: string, staff: string, confirm: string): Promise<Result<boolean>>;
Merge branch 'worktree-agent-a8385d293d42c913a'411
412 /** Every workspace alias, by name. */
413 aliases(): Promise<WorkspaceAlias[]>;
414 /**
415 * Points `alias` at the workspace whose slug is `workspace`. Refused for
416 * one of the site's routes, anyone's username, a workspace's slug (deleted
417 * or held after a rename) and an existing alias. `note` says why.
418 */
419 setAlias(alias: string, workspace: string, note: string, staff: string): Promise<Result<WorkspaceAlias>>;
420 /** Removes an alias; `reason` goes in sudo's audit log. */
421 removeAlias(alias: string, reason: string, staff: string): Promise<Result<boolean>>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running422}
423
Merge branch 'worktree-agent-a8385d293d42c913a'424/**
425 * A name g1t's staff point at a workspace, so its addresses lead there under
426 * the workspace's own name: `g1t`, the product, leads to `flagon-io`, Flagon,
427 * Inc. Staff-managed only; it follows the workspace through renames.
428 */
429export type WorkspaceAlias = {
430 alias: string;
431 workspaceId: string;
432 /** The workspace's slug and name now. */
433 workspace: string;
434 workspaceName: string;
435 /** Why it exists. */
436 note: string;
437 /** The staff member who set it, or `migration`. */
438 createdBy: string;
439 /** RFC 3339. */
440 createdAt: string;
441};
442
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running443/** Who is asking. Every read and write in every service takes one. */
444export type Viewer = User | null;
445
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)446/**
447 * Whether this is a person whose account has not confirmed its email
448 * address. Such an account can only confirm it, change it, or sign out.
449 */
450export function awaitsConfirmation(user: Pick<User, "kind" | "verified"> | null | undefined): boolean {
451 return !!user && (user.kind ?? "user") === "user" && !user.verified;
452}
453
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running454export type SshKey = {
455 id: string;
456 title: string;
457 fingerprint: string;
458 /** RFC 3339. */
459 createdAt: string;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca460 /** When it last signed in over SSH, RFC 3339, to within 5 minutes; null when it never has. */
461 lastUsedAt: string | null;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running462};
463
464export type AccessToken = {
465 id: string;
466 name: string;
467 /** RFC 3339. */
468 createdAt: string;
469 /** RFC 3339, to within a few minutes. Null until it is first used. */
470 lastUsedAt: string | null;
471 /**
472 * For a workspace's token, the username of the member who made it. Null
473 * once that account is gone, and on personal tokens.
474 */
475 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 managers476 /** Its scopes, as `resource:level`, the highest of each resource. Null: full access. */
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running477 scopes: string[] | null;
478 /** Made before tokens had scopes: full access until someone narrows it. */
479 legacy: boolean;
480 /** RFC 3339. Null: it does not expire. */
481 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 managers482 /** Its scopes as permissions: each resource it may use, at the highest level. */
483 permissions?: Permissions;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules484 /** What it is for, as its owner wrote it. */
485 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 managers486 /**
487 * A personal token's reach: the workspace it is made for; null for every
488 * workspace you belong to (or, with `repositorySelection` public, none).
489 * Null on a workspace's own token, which reaches its workspace.
490 */
491 workspace?: string | null;
492 /** Which repositories of that workspace it reaches. */
493 repositorySelection?: RepositorySelection;
494 /** With `selected`: the repositories, as `owner/name`, that you can see. */
495 repositories?: string[];
496 /** Whether a token made for a workspace that approves tokens may be used there yet. */
497 status?: TokenStatus;
498 /** Why an owner denied or revoked it. */
499 reviewReason?: string | null;
500 /** A workspace's own token, acting as the workspace. */
501 workspaceOwned?: boolean;
502 /** A workspace's own token with Repositories: admin, an admin of its repositories. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules503 admin?: boolean;
Merge main into Artifacts Phase 2504 /** A personal token its owner let use the website as them. */
505 website?: boolean;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules506};
507
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers508/** Which repositories a token reaches in its workspace: all, the selected ones, or public ones only. */
509export type RepositorySelection = "all" | "selected" | "public";
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules510
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers511/** Whether a token made for a workspace may be used there yet. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules512export type TokenStatus = "active" | "pending" | "denied" | "revoked";
513
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers514/** A new access token: a person's, or (with `owner`) a workspace's. */
515export type TokenInput = {
516 /** A workspace's slug to make that workspace's token; null for your own. */
517 owner?: string | null;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules518 name: string;
519 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 managers520 /** 1 to 366 days; null for no expiry, where the workspaces it reaches allow that. */
521 ttlSeconds: number | null;
522 /**
523 * A personal token's reach: a workspace's slug, or null for every
524 * workspace you belong to (with `repositorySelection` public: none).
525 */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules526 workspace: string | null;
527 repositorySelection: RepositorySelection;
528 /** With `selected`: `owner/name` or names in the workspace. */
529 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 managers530 /** Each resource's level; left out is no access. */
531 permissions: Partial<Record<ScopeResource, ScopeLevel>>;
Merge main into Artifacts Phase 2532 /** A personal token: whether it may use the website as you. Off unless set. */
533 website?: boolean;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules534};
535
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers536/** A change to a token; what is left out stays. */
Merge main into Artifacts Phase 2537export 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 managers538
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules539/** A workspace's rules for personal access tokens. */
540export 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 managers541 /** A token made for every workspace of its owner reaches this one. */
542 allowTokensForAllWorkspaces: boolean;
543 /** A token may be made for this workspace alone. */
544 allowTokensForThisWorkspace: boolean;
545 /** A token made for this workspace waits for an owner's approval. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules546 requireApproval: boolean;
547 /** Null: no limit. */
548 maxLifetimeDays: number | null;
549 forbidNoExpiry: boolean;
550 updatedBy?: string | null;
551 updatedAt?: string | null;
552};
553
554/** A member's personal token that reaches a workspace, as its owners see it. */
555export type MemberToken = {
556 owner: string;
557 token: AccessToken;
558 /** Whether it reaches the workspace now. */
559 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 managers560 /** 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 rules561 blockedBy?: string | null;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running562};
563
564/** What a new or changed token may do. */
565export type TokenGrant = {
566 /** Null: full access. */
567 scopes: string[] | null;
568};
569
570export type DeviceStart = {
571 /** Secret held by the tool and exchanged for a token once approved. */
572 deviceCode: string;
573 /** Short code shown to the person, e.g. `WDJB-MJHT`. */
574 userCode: string;
575 /** Seconds until both codes stop working. */
576 expiresIn: number;
577 /** Seconds the tool should wait between polls. */
578 interval: number;
579};
580
581export type DeviceRequest = { userCode: string; clientName: string };
582
583export type DeviceClaim =
584 | { status: "pending" | "denied" | "expired" }
585 | { status: "approved"; token: string; user: User };
586
587/** What the site passes on once a person has approved an application. */
588export type OAuthApproval = {
589 clientId: string;
590 /** Shown wherever the application's access is listed. */
591 clientName: string;
592 redirectUri: string;
593 /** PKCE challenge, method S256. */
594 codeChallenge: string;
595 /** What the person granted. Null: full access. */
596 scopes: string[] | null;
597};
598
599export type OAuthTokens = {
600 accessToken: string;
601 /** Works once; using it returns the next one. */
602 refreshToken: string;
603 /** Seconds until the access token stops working. */
604 expiresIn: number;
605 /** The scopes granted, space-separated, or `*` for full access. */
606 scope?: string | null;
607};
608
609/** An application a person has signed in to. */
610export type OAuthGrant = {
611 id: string;
612 clientName: string;
613 /** RFC 3339. */
614 createdAt: string;
615 /** RFC 3339. */
616 lastUsedAt: string;
617 /** What the person granted. Null: full access. */
618 scopes: string[] | null;
619 /** Signed in before applications had scopes: full access until narrowed. */
620 legacy: boolean;
621};
622
623/** Accounts, credentials and sessions. */
624/**
625 * What deleting a workspace takes with it, and what stands in the way:
626 * nothing does while `billing` is null and it is not `protected`.
627 */
628export type WorkspaceDeletion = {
629 /** Its live repositories, deleted with it. */
630 repositories: number;
631 /** Its projects, hidden with it. */
632 projects: number;
633 members: number;
634 /** Why billing cannot close it yet, in words for its owner. */
635 billing: string | null;
636 /** It can never be deleted, by anyone. */
637 protected: boolean;
638};
639
640/** How long a deleted workspace is kept, for g1t's staff to restore, before it is purged. */
641export const WORKSPACE_RESTORE_DAYS = 30;
642
643/**
644 * Workspaces nobody can delete, whatever identity's `PROTECTED_WORKSPACES`
645 * says: Flagon's, which runs g1t. Services that act on `workspace.deleting`
646 * check it too, so one published for it by mistake changes nothing.
647 */
648export const ALWAYS_PROTECTED_WORKSPACES: readonly string[] = ["flagon-io"];
649
650/** Whether `slug` is one of `ALWAYS_PROTECTED_WORKSPACES`, in any case. */
651export function isProtectedWorkspace(slug: string): boolean {
652 return ALWAYS_PROTECTED_WORKSPACES.includes(slug.trim().toLowerCase());
653}
654
655/** A workspace an owner deleted, kept until `purgeAfter` for staff to restore. */
656export type DeletedWorkspace = {
657 workspaceId: string;
658 slug: string;
659 name: string;
660 /** RFC 3339. */
661 deletedAt: string;
Merge sudo: delete an account with the workspaces it alone owns, purge each662 /** The username of the owner who deleted it, or the staff member who deleted it with the account that alone owned it. */
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running663 deletedBy: string;
664 /** RFC 3339: when it is purged unless restored first. */
665 purgeAfter: string;
666 /** What went with it, counted when it was deleted. */
667 went: WorkspaceDeletion;
668 /** Whether staff can still restore it. */
669 restorable: boolean;
670};
671
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.672export interface IdentityApi extends AccessClient, TeamsClient, PeopleClient, DeployKeysClient {
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running673 /**
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.674 * Creates an account and signs it in. Anyone may register; at most
675 * five accounts an hour per `client`. `inviteCode` is optional: one from an
676 * invitation sent to `email` applies that invitation once the address is
677 * confirmed, and any other code is ignored.
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running678 */
679 register(
680 username: string,
681 email: string,
682 password: string,
683 inviteCode?: string | null,
684 /** Who is asking, such as the visitor's IP address, for rate limits. */
685 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 confirm686 /**
687 * The `proof` from the invite email's link. When it is the invite's own
688 * and `email` is the address it was sent to, the account starts with that
689 * address confirmed; otherwise it is ignored.
690 */
691 emailProof?: string | null,
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running692 ): Promise<Result<{ user: User; sessionToken: string }>>;
693 /** Verifies a username and password for website sign-in. */
694 /**
695 * Verifies a username, or any confirmed address of the account, and its
696 * password. Wrong passwords are counted against the account and `client`
697 * (the visitor's IP address); past a limit nothing is checked for a while.
698 */
Merge main (membership, two-factor, GitHub repo roles) into tokens699 signIn(
700 username: string,
701 password: string,
702 client?: string | null,
703 ): Promise<Result<{ user: User; sessionToken: string; twoFactorChallenge?: string | null }>>;
704 /**
705 * The second step of signing in, for an account with two-factor
706 * authentication: the challenge `signIn` returned, and a code from the
707 * app or a recovery code.
708 */
709 twoFactorSignIn(challenge: string, code: string, client?: string | null): Promise<Result<{ user: User; sessionToken: string }>>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running710 signOut(sessionToken: string): Promise<void>;
711
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)712 /**
713 * Sends a new confirmation code and link to the primary of an account
714 * that has not confirmed it, at most once a minute.
715 */
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running716 resendVerification(user: User): Promise<Result<boolean>>;
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)717 /** Confirms the address the emailed link was sent to, signed in or not; ends the code sent with it. */
718 verifyEmail(token: string): Promise<Result<EmailConfirmed>>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running719 /** Emails a reset link if the address has an account. Always resolves. */
720 /**
721 * Any confirmed address of an account works; the link goes to it, and the
722 * primary and backup are told. A few an hour per address and per `client`.
723 */
724 requestPasswordReset(email: string, client?: string | null): Promise<boolean>;
725 /** Sets a new password from an emailed token and ends every session. */
726 resetPassword(token: string, password: string): Promise<Result<User>>;
727
728 /**
729 * Device sign-in (RFC 8628). A tool starts a request, a person approves
730 * its short code in a browser, and the tool claims an access token.
731 */
732 deviceStart(clientName: string): Promise<DeviceStart>;
733 /** What a user code is asking for, or null if it is not valid. */
734 deviceLookup(userCode: string): Promise<DeviceRequest | null>;
735 deviceResolve(userCode: string, user: User, approve: boolean): Promise<Result<boolean>>;
736 deviceClaim(deviceCode: string): Promise<DeviceClaim>;
737
738 /**
739 * OAuth 2.1 for applications that sign a person in through the browser.
740 * The caller has checked the client and its redirect address; this
741 * returns the one-time code the application exchanges for tokens.
742 */
743 oauthAuthorize(user: User, approval: OAuthApproval): Promise<{ code: string }>;
744 /** Redeems a code. It works once, for that client, with the PKCE verifier. */
745 oauthExchange(code: string, codeVerifier: string, clientId: string, redirectUri: string): Promise<Result<OAuthTokens>>;
746 /** Trades a refresh token for new tokens; the old ones stop working. */
747 oauthRefresh(refreshToken: string, clientId: string): Promise<Result<OAuthTokens>>;
748 /** Applications the user has signed in to, most recently used first. */
749 listOAuthGrants(user: User): Promise<OAuthGrant[]>;
750 /** Signs an application out. */
751 revokeOAuthGrant(user: User, id: string): Promise<void>;
752 /** Changes what an application may do, at once and when it refreshes. */
753 updateOAuthGrant(user: User, id: string, grant: TokenGrant): Promise<Result<OAuthGrant>>;
754
755 createWorkspace(user: User, slug: string, name: string): Promise<Result<Workspace>>;
756 /** Public details of a workspace, or null. */
757 getWorkspace(slug: string): Promise<Workspace | null>;
758 /** Where a workspace keeps its repositories' git data; null when there is no such workspace. */
759 workspaceResidency(slug: string): Promise<DataResidency | null>;
760 /**
761 * Owners only. Applies to repositories made from then on. Offer `eu`
762 * only when the repos service's `storageOptions()` says it is available.
763 */
764 setWorkspaceResidency(actor: User, slug: string, residency: DataResidency): Promise<Result<DataResidency>>;
765 /** Members only. */
766 listMembers(slug: string, viewer: Viewer): Promise<Result<Member[]>>;
767 /** Owners only. */
768 addMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
Merge main (membership, two-factor, GitHub repo roles) into tokens769 /** Owners only; your own username is leaving. Never the last owner. */
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running770 removeMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
Merge main (membership, two-factor, GitHub repo roles) into tokens771 /** Owners only: owner or member, and the roles held besides it. Never leaves no owner. */
772 updateMember(actor: User, slug: string, username: string, change: { role?: Role; org_roles?: OrgRole[] }): Promise<Result<Member>>;
773 /** Owners only: `username` becomes an owner, and you a member. */
774 transferOwnership(actor: User, slug: string, username: string): Promise<Result<boolean>>;
775 /** You leave the workspace. Never the last owner. */
776 leaveWorkspace(user: User, slug: string): Promise<Result<boolean>>;
777 /** Owners only: change some member privileges; returns all of them. */
778 setMemberPrivileges(actor: User, slug: string, change: Partial<MemberPrivileges>): Promise<Result<MemberPrivileges>>;
779 /** Owners only, with two-factor on themselves: require it of everyone. */
780 setTwoFactorRequirement(actor: User, slug: string, required: boolean): Promise<Result<boolean>>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running781 /** Owners only. An empty name falls back to the slug. */
782 updateWorkspace(actor: User, slug: string, details: { name: string; description: string }): Promise<Result<Workspace>>;
783 /**
784 * Owners only. Changes the slug, the first segment of the workspace's
785 * URLs; the display name is untouched. The old slug redirects to the new
786 * one, and stays reserved for this workspace, for `SLUG_HOLD_DAYS`.
787 * Publishes `workspace.renamed`.
788 */
789 renameWorkspace(actor: User, slug: string, newSlug: string): Promise<Result<Workspace>>;
790 /** Whether `renameWorkspace` would be allowed, changing nothing. */
791 checkWorkspaceRename(actor: User, slug: string, newSlug: string): Promise<Result<boolean>>;
792 /**
793 * The workspace's current slug when `slug` is one it was renamed from
Merge branch 'worktree-agent-a8385d293d42c913a'794 * within `SLUG_HOLD_DAYS`, or when `slug` is an alias staff set for it
795 * (`WorkspaceAlias`); null otherwise, including for a slug in use.
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running796 */
797 resolveSlug(slug: string): Promise<string | null>;
798 /**
799 * Owners only, a person only. `confirm` is the slug, typed out. Refused
800 * for a protected workspace, and while billing cannot settle it. Its
801 * repositories, projects and apps go with it; it is kept for
802 * `WORKSPACE_RESTORE_DAYS`, when g1t's staff can restore it, then purged.
803 * Its slug is never given to anyone else; the person whose username it is
804 * may make it again once it is purged. Publishes `workspace.deleting`.
805 */
806 deleteWorkspace(actor: User, slug: string, confirm: string): Promise<Result<boolean>>;
807 /** What `deleteWorkspace` would take with it, and what stands in its way, changing nothing. */
808 checkWorkspaceDeletion(actor: User, slug: string): Promise<Result<WorkspaceDeletion>>;
809 /**
810 * Owners only. `image` is the file in base64: PNG, JPEG, WebP or GIF, at
811 * most `MAX_AVATAR_BYTES`, checked by its bytes. Null removes the icon.
812 */
813 setWorkspaceAvatar(actor: User, slug: string, image: string | null): Promise<Result<Workspace>>;
814 /** A person's own avatar, as `setWorkspaceAvatar`: the new one, or null. */
815 setUserAvatar(user: User, image: string | null): Promise<Result<string | null>>;
816
817 /**
818 * A workspace's own access tokens. They belong to the workspace, act as
819 * it, and keep working when the member who made one leaves. Members only.
820 */
821 listWorkspaceTokens(slug: string, viewer: Viewer): Promise<Result<AccessToken[]>>;
822 /** Owners only. */
823 removeWorkspaceToken(actor: User, slug: string, id: string): Promise<Result<boolean>>;
824
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules825 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers826 * An access token: yours, or (with `input.owner`, owners only) a
827 * workspace's. People only, signed in. The plaintext token is returned
828 * once and never stored. A personal token made for a workspace that asks
829 * for approval starts pending unless you are an owner there.
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules830 */
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers831 createToken(actor: User, input: TokenInput): Promise<Result<{ token: string; info: AccessToken }>>;
832 /**
833 * Changes a token of yours, or (with `owner`, owners only) a
834 * workspace's; what is left out stays. Widening a token made for a
835 * workspace that approves tokens asks for approval again.
836 */
837 updateToken(actor: User, id: string, change: TokenChange, owner?: string | null): Promise<Result<AccessToken>>;
An access token can be regenerated: a new secret, its name, permissions, repositories and reach kept, and the old secret refused at once. Its page (yours under Settings, a workspace's under its settings for owners) has Regenerate with a new expiry, within the workspace's rules on lifetime, and shows the secret once; identity's regenerate_token logs it to the security log or the workspace's audit log, and refuses a revoked token. The token form's quick permissions have All, every resource at its highest level. A project's Observability settings no longer list the token g1t gives its apps on g1t.page: it is g1t's, and the page says apps there need none.838 /**
839 * A new secret for a token, everything else kept, the old secret refused
840 * from then on: rotating it. `ttlSeconds` is its new lifetime from now
841 * (null: it does not expire), within its workspace's rules. The secret
842 * is shown once.
843 */
844 regenerateToken(actor: User, id: string, ttlSeconds: number | null, owner?: string | null): Promise<Result<{ token: string; info: AccessToken }>>;
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules845 /** A workspace's rules for personal access tokens. Members only. */
846 getTokenPolicy(slug: string, viewer: Viewer): Promise<Result<TokenPolicy>>;
847 /** Owners only, as people. `maxLifetimeDays` of 0 removes the limit. */
848 setTokenPolicy(
849 actor: User,
850 slug: string,
851 change: Partial<Omit<TokenPolicy, "updatedBy" | "updatedAt">>,
852 ): Promise<Result<TokenPolicy>>;
853 /** The members' tokens that can reach a workspace. Owners only. */
854 listMemberTokens(
855 actor: User,
856 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 managers857 filter?: { status?: TokenStatus },
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules858 ): 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 managers859 /** Approve or deny a token waiting for approval. Owners only. */
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules860 reviewTokenRequest(actor: User, slug: string, id: string, approve: boolean, reason?: string | null): Promise<Result<MemberToken>>;
861 /** Take a member's token out of the workspace. Owners only. */
862 revokeMemberToken(actor: User, slug: string, id: string, reason?: string | null): Promise<Result<boolean>>;
863
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running864 userForSession(sessionToken: string): Promise<Viewer>;
865
Anyone on g1t can invite a friend again: Invite a friend, in the account menu with how many are left beside it, opens Settings → Invites to g1t with a dialog for their email address and a note. The friend gets an email from them whose link opens sign-up with the address filled in and, because it came from that email, confirmed; their account starts with a workspace of its own, and the page lists every invite sent with where it stands (Pending, confirming their email, Joined as @them), with Copy link, Send again and Revoke on each. It is word of mouth, never a gate: sign-ups stay open to everyone, nothing asks for an invite, and running out says friends can still sign up without one.866 /** A person's invites to g1t (Invite a friend) and how many are left. */
867 listInvites(user: User): Promise<InvitesOverview>;
868 /**
869 * A person invites a friend to g1t by email: an email from them with a link
870 * that opens sign-up with the address filled in and confirmed. Uses one of
871 * their invites. People only, once their address is confirmed.
872 */
873 createInvite(user: User, invite: NewInvite): Promise<Result<Invite>>;
874 /** Its maker revokes a pending invite; one never used comes back. */
875 revokeInvite(user: User, id: string): Promise<Result<Invite>>;
876 /** Its maker sends a pending invite to g1t again: the same email and link. */
877 resendInvite(user: User, id: string): Promise<Result<Invite>>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running878 /**
879 * What a code is for. Unknown, used, revoked and expired codes all get the
880 * 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 confirm881 * described, with its `status`. `viewer` sets `forViewer`; `emailProof`,
882 * the `proof` from the invite email's link, sets `emailProven`.
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running883 */
884 checkInvite(
885 code: string,
886 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 confirm887 options?: { viewer?: User | null; anyStatus?: boolean; emailProof?: string | null },
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running888 ): Promise<Result<InvitePreview>>;
889 /**
890 * A signed-in person uses a workspace invite sent to their address, or one
891 * sent with a repository invitation; returns the workspace's slug, or
892 * `workspace/repo`.
893 */
894 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)895 /**
896 * Owners only. Invites someone into a workspace by address (always with an
897 * invite bound to it) or by `username`: a workspace invitation they accept
898 * or decline. Nobody joins without saying yes. `role` is what they join as.
899 */
900 inviteMember(
901 actor: User,
902 slug: string,
Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now.903 who: { email?: string | null; username?: string | null; role?: Role | null; message?: 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)904 ): Promise<Result<Invite>>;
905 /** The workspace invitations waiting for the person's answer, newest first. */
906 listInvitations(user: User): Promise<WorkspaceInvitation[]>;
907 /** Joins the invitation's workspace with its role; returns the workspace's slug. */
908 acceptInvitation(user: User, id: string): Promise<Result<string>>;
909 /** Declines it; whoever sent it is told in their inbox. */
910 declineInvitation(user: User, id: string): Promise<Result<boolean>>;
911 /** People to invite, by username prefix or name: a username, a name and an avatar each. */
912 findPeople(query: string, limit?: number): Promise<PersonMatch[]>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running913 /** Owners only: the workspace's invites, newest first. */
914 workspaceInvites(slug: string, viewer: Viewer): Promise<Result<Invite[]>>;
915 /** Owners only. */
916 revokeWorkspaceInvite(actor: User, slug: string, id: string): Promise<Result<Invite>>;
Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now.917 /** Owners only: sends a pending invitation again, by email and in the invited account's inbox. Unchanged otherwise. */
918 resendWorkspaceInvite(actor: User, slug: string, id: string): Promise<Result<Invite>>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running919
920 /** Verifies git credentials: the account password or an access token. */
921 userForGitCredentials(username: string, secret: string): Promise<Viewer>;
922 /** Resolves a `g1t_…` access token, as sent to the API and MCP server. */
923 userForAccessToken(token: string): Promise<Viewer>;
924 userForSshKey(fingerprint: string): Promise<Viewer>;
925 userByUsername(username: string): Promise<Viewer>;
926 /** The names behind account and workspace ids; unknown ids are left out. */
927 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)928 /**
929 * Internal: the people behind these ids (at most 50) with their
930 * workspaces, roles and repository grants, as a signed-in viewer has
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.931 * them. For artifacts' audience checks only. Ids of no live account are
932 * left out.
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)933 */
934 usersForAudience(ids: string[]): Promise<User[]>;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running935
936 /** A person's public profile, or null if there is no such account. Never an email address. */
937 profile(username: string): Promise<Profile | null>;
938 /** A person changes their own profile. Every field is replaced; an empty one is cleared. */
939 updateProfile(actor: User, fields: ProfileFields): Promise<Result<Profile>>;
940 /**
941 * The workspaces a profile shows `viewer`: those the viewer belongs to
942 * as well, and those of `publicIn` (where the person made a public
943 * project) that the person really belongs to. Nothing else.
944 */
945 profileWorkspaces(username: string, viewer: Viewer, publicIn: string[]): Promise<ProfileWorkspace[]>;
946
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.947 /**
948 * The apps `user` pinned to their dock in the workspace `workspace` (a
949 * slug), in the order they set; null when they never saved any there or
950 * are not one of its members. Kept with the account, so every device
951 * shows the same dock.
952 */
953 dockPins(user: User, workspace: string): Promise<string[] | null>;
954 /**
955 * Replaces `user`'s dock pins in `workspace` with `apps`, in that order:
956 * keys of lowercase letters, digits and hyphens, repeats dropped, at
957 * most `MAX_DOCK_PINS`. Which keys are real apps is the caller's to check.
958 */
959 setDockPins(user: User, workspace: string, apps: string[]): Promise<Result<string[]>>;
960
Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.961 /**
962 * How chat sounds and desktop notifications reach `user`, kept with the
963 * account so every device and the desktop app behave the same
964 * (`SoundSettings`). The defaults when they never changed them.
965 */
966 soundSettings(user: User): Promise<SoundSettings>;
967 /**
968 * Changes some of them: only the fields sent change, and `sound_cues`
969 * merges one cue at a time. Returns the settings as kept.
970 */
971 setSoundSettings(user: User, change: SoundSettingsChange): Promise<Result<SoundSettings>>;
972
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running973 listSshKeys(user: User): Promise<SshKey[]>;
974 /** Takes one line in OpenSSH public key format. */
975 addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>>;
976 removeSshKey(user: User, id: string): Promise<void>;
977
978 listAccessTokens(user: User): Promise<AccessToken[]>;
979 /**
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers980 * A credential minted for a person or workspace by another service. The
981 * plaintext token is returned once and never stored. With `ttlSeconds`
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.982 * the token expires and is left out of token lists. Tokens people make
983 * use `createToken`.
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running984 */
985 createAccessToken(
986 user: User,
987 name: string,
988 ttlSeconds?: number,
989 grant?: TokenGrant & { listed?: boolean },
990 ): Promise<{ token: string; info: AccessToken }>;
991 removeAccessToken(user: User, id: string): Promise<void>;
992 /**
993 * A token for one sandbox run: it acts as the agent on behalf of
994 * `onBehalfOf`, can do only what the run's kind needs in `repo`, and
995 * expires after `ttlSeconds`. See `audit.ts`.
996 */
997 createRunCredential(input: CreateRunCredentialInput): Promise<{ token: string; info: AccessToken }>;
998 /** Ties tokens, by the SHA-256 of their text in hex, to the agent run their sandbox recorded. */
999 bindRunCredentials(tokenHashes: string[], runId: string): Promise<boolean>;
1000 /** Ends a sandbox's run credentials, by hash or by run. Never touches another token. */
1001 revokeRunCredentials(target: { tokenHashes?: string[]; runId?: string | null }): Promise<boolean>;
1002}
1003
1004
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.1005/**
1006 * What a scoped token may do: these operations, in this repository. Run
1007 * credentials carry one today; tokens g1t's retired hosted agents were
1008 * issued carry one too and still parse, so their rows and the `@g1t`
1009 * history they wrote read as before.
1010 */
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1011export type AgentScope = { repo: RepoPath; operations: string[]; run?: RunBinding };
1012
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.1013/** The most apps a person pins in one workspace. Mirrors `MAX_DOCK_PINS` in `crates/contracts/src/identity.rs`. */
1014export const MAX_DOCK_PINS = 24;
1015
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1016/** 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)1017export const PROFILE_LIMITS = { name: 80, bio: 160, location: 80, website: 200, pronouns: 40, timezone: 64 } as const;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1018
1019/** What anyone may see about a person, at `g1t.sh/u/<username>`. */
1020export 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 managers1021 /** Lowercased: what the profile is found and linked by. */
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1022 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 managers1023 /** The username as its owner wrote it (`Ana`), when that differs: what the page shows. */
1024 displayUsername?: string | null;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1025 /** The name they go by, if they gave one. */
1026 name: string | null;
1027 bio: string | null;
1028 location: string | null;
1029 /** Always an `https://` address. */
1030 website: string | null;
1031 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)1032 /** The time zone they are in, an IANA name such as `America/Denver`. */
1033 timezone: string | null;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1034 /** The uploaded avatar's hash, served at `/avatars/<avatar>`. */
1035 avatar: string | null;
1036 /** When the account was made. RFC 3339. */
1037 createdAt: string;
1038};
1039
1040/** What a person may change on their profile. Empty clears a field. */
1041export type ProfileFields = {
1042 name: string;
1043 bio: string;
1044 location: string;
1045 /** `https://…`; a bare `example.com` is taken as `https://example.com`. */
1046 website: string;
1047 pronouns: string;
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)1048 /** An IANA time zone name, such as `America/Denver`; empty clears it. */
1049 timezone: string;
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1050};
1051
1052/** A workspace on a person's profile. */
1053export 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.