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. | 1 | import 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 running | 2 | import 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 managers | 3 | import type { Permissions, ScopeLevel, ScopeResource } from "./scopes"; |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 4 | import 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 running | 5 | import type { Acting, CreateRunCredentialInput, RunBinding } from "./audit"; |
| 6 | import type { RepoPath } from "./repos"; | |
| 7 | import type { Result } from "./result"; | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 8 | import 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. | 9 | import type { PeopleClient } from "./people"; |
| Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca | 10 | import type { DeployKeysClient } from "./deploy-keys"; |
| Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036) | 11 | import 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 running | 12 | |
| 13 | export 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 managers | 15 | /** 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 running | 16 | 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 managers | 18 | * 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 running | 24 | * `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 running | 34 | */ |
| 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 running | 51 | */ |
| 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 tokens | 66 | /** 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 managers | 68 | /** |
| 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 reach | 72 | 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 managers | 77 | /** A workspace's own token with Repositories: admin. */ |
| TS access mirrors the workspace token cap and fine-grained reach | 78 | 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 timeouts | 83 | /** 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 2 | 85 | /** |
| 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 running | 90 | }; |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 91 | /** |
| 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 running | 97 | }; |
| 98 | ||
| 99 | /** What a member may do: an owner also manages the workspace's members. */ | |
| 100 | export type Role = "owner" | "member"; | |
| 101 | ||
| 102 | export 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 tokens | 114 | /** 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 to | 118 | /** |
| 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 running | 126 | }; |
| 127 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 128 | /** Whether a member uses Code. See `Membership.code_access`. */ |
| 129 | export 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 running | 133 | /** How long an old workspace slug redirects, and stays reserved for it, after a rename. */ |
| 134 | export const SLUG_HOLD_DAYS = 90; | |
| 135 | ||
| 136 | /** How long a workspace must wait between renames. */ | |
| 137 | export const RENAME_COOLDOWN_HOURS = 24; | |
| 138 | ||
| 139 | /** The largest avatar that can be uploaded, in bytes. */ | |
| 140 | export 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 | */ | |
| 147 | export 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 | */ | |
| 153 | export 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 tokens | 171 | /** 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 running | 174 | |
| 175 | export type Member = { | |
| 176 | username: string; | |
| Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar | 177 | /** 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 running | 179 | role: Role; |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 180 | /** 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 running | 184 | /** 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. */ | |
| 191 | export type AdminOwner = { username: string; email: string | null }; | |
| 192 | ||
| 193 | /** A workspace as staff see it. Mirrors `AdminWorkspace` in `crates/contracts/src/identity.rs`. */ | |
| 194 | export 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. */ | |
| 204 | export type AdminMember = { username: string; email: string | null; role: Role; /** RFC 3339. */ joined: string }; | |
| 205 | ||
| 206 | export 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 running | 218 | }; |
| 219 | ||
| 220 | /** The most workspaces one `workspaces` call returns. */ | |
| 221 | export const ADMIN_WORKSPACES_LIMIT = 500; | |
| 222 | ||
| 223 | /** How long an invite works, unless identity's `INVITE_TTL_DAYS` says otherwise. */ | |
| 224 | export 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 | */ | |
| 230 | export 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`). */ |
| 232 | export 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 running | 233 | |
| 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 | */ | |
| 244 | export 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 running | 252 | |
| 253 | /** One invite. Mirrors `Invite` in `crates/contracts/src/identity.rs`. */ | |
| 254 | export 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 running | 273 | /** 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 running | 287 | /** 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 | */ | |
| 295 | export 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. */ |
| 307 | export type Allowance = { limit: number; used: number; remaining: number }; | |
| 308 | ||
| 309 | /** A person's invites to g1t, newest first, and what they have left. */ | |
| 310 | export 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. */ | |
| 313 | export 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. */ |
| 322 | export 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 running | 323 | |
| 324 | /** What a valid code is for, before it is used. */ | |
| 325 | export 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 confirm | 343 | /** |
| 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 running | 350 | }; |
| 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) | 356 | export 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. */ |
| 359 | export 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. */ |
| 362 | export 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 running | 365 | export 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 | */ | |
| 373 | export 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 running | 381 | 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 running | 384 | |
| 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 running | 398 | /** 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 running | 422 | } |
| 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 | */ | |
| 429 | export 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 running | 443 | /** Who is asking. Every read and write in every service takes one. */ |
| 444 | export 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 | */ | |
| 450 | export 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 running | 454 | export 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-a16631325aecf58ca | 460 | /** 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 running | 462 | }; |
| 463 | ||
| 464 | export 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 managers | 476 | /** 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 running | 477 | 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 managers | 482 | /** 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 rules | 484 | /** 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 managers | 486 | /** |
| 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 rules | 503 | admin?: boolean; |
| Merge main into Artifacts Phase 2 | 504 | /** 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 rules | 506 | }; |
| 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 managers | 508 | /** Which repositories a token reaches in its workspace: all, the selected ones, or public ones only. */ |
| 509 | export type RepositorySelection = "all" | "selected" | "public"; | |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 510 | |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 511 | /** Whether a token made for a workspace may be used there yet. */ |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 512 | export 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 managers | 514 | /** A new access token: a person's, or (with `owner`) a workspace's. */ |
| 515 | export 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 rules | 518 | 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 managers | 520 | /** 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 rules | 526 | 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 managers | 530 | /** Each resource's level; left out is no access. */ |
| 531 | permissions: Partial<Record<ScopeResource, ScopeLevel>>; | |
| Merge main into Artifacts Phase 2 | 532 | /** 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 rules | 534 | }; |
| 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 managers | 536 | /** A change to a token; what is left out stays. */ |
| Merge main into Artifacts Phase 2 | 537 | export 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 managers | 538 | |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 539 | /** A workspace's rules for personal access tokens. */ |
| 540 | export 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 managers | 541 | /** 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 rules | 546 | 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. */ | |
| 555 | export 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 managers | 560 | /** 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 rules | 561 | 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 running | 562 | }; |
| 563 | ||
| 564 | /** What a new or changed token may do. */ | |
| 565 | export type TokenGrant = { | |
| 566 | /** Null: full access. */ | |
| 567 | scopes: string[] | null; | |
| 568 | }; | |
| 569 | ||
| 570 | export 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 | ||
| 581 | export type DeviceRequest = { userCode: string; clientName: string }; | |
| 582 | ||
| 583 | export 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. */ | |
| 588 | export 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 | ||
| 599 | export 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. */ | |
| 610 | export 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 | */ | |
| 628 | export 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. */ | |
| 641 | export 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 | */ | |
| 648 | export const ALWAYS_PROTECTED_WORKSPACES: readonly string[] = ["flagon-io"]; | |
| 649 | ||
| 650 | /** Whether `slug` is one of `ALWAYS_PROTECTED_WORKSPACES`, in any case. */ | |
| 651 | export 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. */ | |
| 656 | export 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 each | 662 | /** 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 running | 663 | 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. | 672 | export 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 running | 673 | /** |
| 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 running | 678 | */ |
| 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 confirm | 686 | /** |
| 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 running | 692 | ): 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 tokens | 699 | 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 running | 710 | 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 running | 716 | 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 running | 719 | /** 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 tokens | 769 | /** 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 running | 770 | removeMember(actor: User, slug: string, username: string): Promise<Result<boolean>>; |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 771 | /** 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 running | 781 | /** 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 running | 796 | */ |
| 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 rules | 825 | /** |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 826 | * 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 rules | 830 | */ |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 831 | 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 rules | 845 | /** 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 managers | 857 | filter?: { status?: TokenStatus }, |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 858 | ): 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 managers | 859 | /** Approve or deny a token waiting for approval. Owners only. */ |
| Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules | 860 | 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 running | 864 | 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 running | 878 | /** |
| 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 confirm | 881 | * 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 running | 883 | */ |
| 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 confirm | 887 | 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 running | 888 | ): 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 running | 913 | /** 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 running | 919 | |
| 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 running | 935 | |
| 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 running | 973 | 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 managers | 980 | * 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 running | 984 | */ |
| 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 running | 1011 | export 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`. */ |
| 1014 | export 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 running | 1016 | /** 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) | 1017 | export 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 running | 1018 | |
| 1019 | /** What anyone may see about a person, at `g1t.sh/u/<username>`. */ | |
| 1020 | export 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 managers | 1021 | /** 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 running | 1022 | 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 managers | 1023 | /** 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 running | 1025 | /** 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 running | 1034 | /** 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. */ | |
| 1041 | export 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 running | 1050 | }; |
| 1051 | ||
| 1052 | /** A workspace on a person's profile. */ | |
| 1053 | export 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.