Skip to content
954 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs1import type { ActionsApi } from "./actions";
Billing accounts, terms and enterprises; g1t is no longer free2import type { BillingAdminApi, BillingApi } from "./billing";
Deployments: a preview for every pull request, production on g1t.page3import type { DeploymentsApi } from "./deployments";
Projects: what a workspace builds and runs, first on every page4import type { ProjectsApi } from "./projects";
Webhooks: every event, to your own addresses, signed and retried5import type { EventsApi } from "./events";
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace6import type { IdentityAdminApi, IdentityApi } from "./identity";
Webhooks: every event, to your own addresses, signed and retried7import type { IntegrationsApi } from "./integrations";
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member8import type { PackagesApi } from "./packages";
Webhooks: every event, to your own addresses, signed and retried9import type { WebhooksApi } from "./webhooks";
10import type { ReposApi } from "./repos";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar11import type { PullDetail, WorkApi } from "./work";
12import type { Result } from "./result";
Fast pages, required checks on the branch, self-hosted runners, honest incidents13import type { RunnersApi } from "./runners";
Webhooks: every event, to your own addresses, signed and retried14
15/** A service binding, as far as these clients need it. */
16export type ServiceBinding = {
17 fetch(input: string, init?: RequestInit): Promise<Response>;
18};
19
20/**
21 * Calls a method on a service that speaks the JSON protocol used by the
22 * Rust services: `POST /rpc/<method>` with the arguments as the body.
23 */
24async function rpc<T>(
25 service: ServiceBinding,
26 method: string,
27 args: object,
28): Promise<T> {
29 // The hostname is ignored; a service binding always reaches its service.
30 const response = await service.fetch(`https://service/rpc/${method}`, {
31 method: "POST",
32 headers: { "content-type": "application/json" },
33 body: JSON.stringify(args),
34 });
35 if (!response.ok) {
36 throw new Error(`${method} failed with status ${response.status}`);
37 }
38 return (await response.json()) as T;
39}
40
41export function identityClient(service: ServiceBinding): IdentityApi {
42 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
43 return {
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 confirm44 register: (username, email, password, inviteCode, client, emailProof) =>
45 call("register", {
46 username,
47 email,
48 password,
49 invite_code: inviteCode ?? null,
50 email_proof: emailProof ?? null,
51 client: client ?? null,
52 }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look53 signIn: (username, password, client) => call("sign_in", { username, password, client: client ?? null }),
Merge main (membership, two-factor, GitHub repo roles) into tokens54 twoFactorSignIn: (challenge, code, client) => call("two_factor_sign_in", { challenge, code, client: client ?? null }),
Webhooks: every event, to your own addresses, signed and retried55 signOut: (sessionToken) => call("sign_out", { sessionToken }),
56 resendVerification: (user) => call("resend_verification", { user }),
57 verifyEmail: (token) => call("verify_email", { token }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look58 requestPasswordReset: (email, client) => call("request_password_reset", { email, client: client ?? null }),
Webhooks: every event, to your own addresses, signed and retried59 resetPassword: (token, password) =>
60 call("reset_password", { token, password }),
61 deviceStart: (clientName) => call("device_start", { clientName }),
62 deviceLookup: (userCode) => call("device_lookup", { userCode }),
63 deviceResolve: (userCode, user, approve) =>
64 call("device_resolve", { userCode, user, approve }),
65 deviceClaim: (deviceCode) => call("device_claim", { deviceCode }),
66 oauthAuthorize: (user, approval) => call("oauth_authorize", { user, ...approval }),
67 oauthExchange: (code, codeVerifier, clientId, redirectUri) =>
68 call("oauth_exchange", { code, codeVerifier, clientId, redirectUri }),
69 oauthRefresh: (refreshToken, clientId) =>
70 call("oauth_refresh", { refreshToken, clientId }),
71 listOAuthGrants: (user) => call("list_oauth_grants", { user }),
72 revokeOAuthGrant: (user, id) => call("revoke_oauth_grant", { user, id }),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step73 updateOAuthGrant: (user, id, grant) =>
74 call("update_oauth_grant", { user, id, scopes: grant.scopes }),
Webhooks: every event, to your own addresses, signed and retried75 createWorkspace: (user, slug, name) => call("create_workspace", { user, slug, name }),
76 getWorkspace: (slug) => call("get_workspace", { slug }),
Merge branch 'worktree-agent-a2013627e5ea4ab13'77 workspaceResidency: (slug) => call("workspace_residency", { slug }),
78 setWorkspaceResidency: (actor, slug, residency) => call("set_workspace_residency", { actor, slug, residency }),
Webhooks: every event, to your own addresses, signed and retried79 listMembers: (slug, viewer) => call("list_members", { slug, viewer }),
80 addMember: (actor, slug, username) => call("add_member", { actor, slug, username }),
81 removeMember: (actor, slug, username) =>
Merge main (membership, two-factor, GitHub repo roles) into tokens82 call("remove_member", { actor, slug, username, surface: "web" }),
83 updateMember: (actor, slug, username, change) =>
84 call("update_member", { actor, slug, username, role: change.role ?? null, org_roles: change.org_roles ?? null, surface: "web" }),
85 transferOwnership: (actor, slug, username) => call("transfer_ownership", { actor, slug, username, surface: "web" }),
86 leaveWorkspace: (user, slug) => call("leave_workspace", { user, slug, surface: "web" }),
87 setMemberPrivileges: (actor, slug, change) => call("set_member_privileges", { actor, slug, privileges: change, surface: "web" }),
88 setTwoFactorRequirement: (actor, slug, required) =>
89 call("set_two_factor_requirement", { actor, slug, required, surface: "web" }),
Webhooks: every event, to your own addresses, signed and retried90 updateWorkspace: (actor, slug, details) =>
91 call("update_workspace", { actor, slug, ...details }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains92 renameWorkspace: (actor, slug, newSlug) => call("rename_workspace", { actor, slug, newSlug }),
93 checkWorkspaceRename: (actor, slug, newSlug) =>
94 call("check_workspace_rename", { actor, slug, newSlug }),
95 resolveSlug: (slug) => call("resolve_slug", { slug }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look96 deleteWorkspace: (actor, slug, confirm) => call("delete_workspace", { actor, slug, confirm }),
97 checkWorkspaceDeletion: (actor, slug) => call("check_workspace_deletion", { actor, slug }),
Workspace names and icons, and a component kit for every control98 setWorkspaceAvatar: (actor, slug, image) => call("set_workspace_avatar", { actor, slug, image }),
99 setUserAvatar: (user, image) => call("set_user_avatar", { user, image }),
Webhooks: every event, to your own addresses, signed and retried100 listWorkspaceTokens: (slug, viewer) => call("list_workspace_tokens", { slug, viewer }),
101 removeWorkspaceToken: (actor, slug, id) =>
102 call("remove_workspace_token", { actor, slug, id }),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers103 createToken: (actor, input) =>
104 call("create_token", {
105 actor,
106 owner: input.owner ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules107 name: input.name,
108 description: input.description ?? null,
109 ttl_seconds: input.ttlSeconds,
110 workspace: input.workspace,
111 repository_selection: input.repositorySelection,
112 repositories: input.repositories,
113 permissions: input.permissions,
Merge main into Artifacts Phase 2114 website: input.website ?? false,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules115 }),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers116 updateToken: (actor, id, change, owner) =>
117 call("update_token", {
118 actor,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules119 id,
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers120 owner: owner ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules121 name: change.name ?? null,
122 description: change.description ?? null,
123 repository_selection: change.repositorySelection ?? null,
124 repositories: change.repositories ?? null,
125 permissions: change.permissions ?? null,
Merge main into Artifacts Phase 2126 website: change.website ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules127 }),
128 getTokenPolicy: (slug, viewer) => call("get_token_policy", { slug, viewer }),
129 setTokenPolicy: (actor, slug, change) =>
130 call("set_token_policy", {
131 actor,
132 slug,
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers133 allow_tokens_for_all_workspaces: change.allowTokensForAllWorkspaces ?? null,
134 allow_tokens_for_this_workspace: change.allowTokensForThisWorkspace ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules135 require_approval: change.requireApproval ?? null,
136 max_lifetime_days: change.maxLifetimeDays ?? null,
137 forbid_no_expiry: change.forbidNoExpiry ?? null,
138 surface: "web",
139 }),
140 listMemberTokens: (actor, slug, filter = {}) =>
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers141 call("list_member_tokens", { actor, slug, status: filter.status ?? null }),
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules142 reviewTokenRequest: (actor, slug, id, approve, reason) =>
143 call("review_token_request", { actor, slug, id, approve, reason: reason ?? null, surface: "web" }),
144 revokeMemberToken: (actor, slug, id, reason) =>
145 call("revoke_member_token", { actor, slug, id, reason: reason ?? null, surface: "web" }),
Webhooks: every event, to your own addresses, signed and retried146 userForSession: (sessionToken) => call("user_for_session", { sessionToken }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look147 registration: () => call("registration", {}),
148 listInvites: (user) => call("list_invites", { user }),
149 createInvite: (user, options = {}) =>
Merge two kinds of invite, kept apart: an invite to g1t (Settings, invite-only only, no workspace unless asked) and an invitation to a workspace (its People page)150 call("create_invite", { user, email: options.email ?? null, workspace: options.workspace ?? null, join: options.join ?? null, join_role: options.join ? (options.joinRole ?? null) : null }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look151 revokeInvite: (user, id) => call("revoke_invite", { user, id }),
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas152 checkInvite: (code, client, options = {}) =>
153 call("check_invite", {
154 code,
155 client: client ?? null,
156 viewer: options.viewer ?? null,
157 any_status: options.anyStatus ?? false,
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 confirm158 email_proof: options.emailProof ?? null,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas159 }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look160 acceptInvite: (user, code) => call("accept_invite", { user, code }),
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)161 inviteMember: (actor, slug, who) =>
162 call("invite_member", { actor, slug, email: who.email ?? "", username: who.username ?? null, role: who.role ?? null }),
163 listInvitations: (user) => call("list_invitations", { user }),
164 acceptInvitation: (user, id) => call("accept_invitation", { user, id, surface: "web" }),
165 declineInvitation: (user, id) => call("decline_invitation", { user, id, surface: "web" }),
166 findPeople: (query, limit) => call("find_people", { query, limit: limit ?? null }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look167 workspaceInvites: (slug, viewer) => call("workspace_invites", { slug, viewer }),
168 revokeWorkspaceInvite: (actor, slug, id) => call("revoke_workspace_invite", { actor, slug, id }),
169 requestAccess: (email, about, client) => call("request_access", { email, about, client: client ?? null }),
Webhooks: every event, to your own addresses, signed and retried170 userForGitCredentials: (username, secret) =>
171 call("user_for_git_credentials", { username, secret }),
172 userForAccessToken: (token) => call("user_for_access_token", { token }),
173 userForSshKey: (fingerprint) => call("user_for_ssh_key", { fingerprint }),
174 userByUsername: (username) => call("user_by_username", { username }),
175 usernames: (ids) => call("usernames", { ids }),
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)176 usersForAudience: (ids) => call("users_for_audience", { ids }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains177 profile: (username) => call("profile", { username }),
178 updateProfile: (actor, fields) => call("update_profile", { actor, ...fields }),
179 profileWorkspaces: (username, viewer, publicIn) =>
180 call("profile_workspaces", { username, viewer, public: publicIn }),
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.181 dockPins: (user, workspace) => call("dock_pins", { user, workspace }),
182 setDockPins: (user, workspace, apps) => call("set_dock_pins", { user, workspace, apps }),
Webhooks: every event, to your own addresses, signed and retried183 listSshKeys: (user) => call("list_ssh_keys", { user }),
184 addSshKey: (user, title, publicKey) =>
185 call("add_ssh_key", { user, title, publicKey }),
186 removeSshKey: (user, id) => call("remove_ssh_key", { user, id }),
187 listAccessTokens: (user) => call("list_access_tokens", { user }),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step188 createAccessToken: (user, name, ttlSeconds, grant) =>
189 call("create_access_token", {
190 user,
191 name,
192 ttlSeconds,
193 scopes: grant?.scopes ?? null,
194 listed: grant?.listed ?? false,
195 }),
Webhooks: every event, to your own addresses, signed and retried196 createAgentToken: (onBehalfOf, scope, ttlSeconds) =>
197 call("create_agent_token", { onBehalfOf, scope, ttlSeconds }),
198 removeAccessToken: (user, id) => call("remove_access_token", { user, id }),
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API199 createRunCredential: (input) => call("create_run_credential", input),
200 bindRunCredentials: (tokenHashes, runId) => call("bind_run_credentials", { tokenHashes, runId }),
201 revokeRunCredentials: (target) => call("revoke_run_credentials", target),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look202 // Who has access to a repository; see access.ts.
203 repoAccess: (owner, name, viewer) => call("repo_access", { viewer, path: { namespace: owner, name } }),
204 addCollaborator: (actor, owner, name, invitee, role) =>
205 call("add_collaborator", { actor, path: { namespace: owner, name }, invitee, role }),
206 setCollaboratorRole: (actor, owner, name, username, role) =>
207 call("set_collaborator_role", { actor, path: { namespace: owner, name }, username, role }),
208 removeCollaborator: (actor, owner, name, username) =>
209 call("remove_collaborator", { actor, path: { namespace: owner, name }, username }),
210 collaboratorPermission: (viewer, owner, name, username) =>
211 call("collaborator_permission", { viewer, path: { namespace: owner, name }, username }),
212 myRepoInvitations: (user) => call("my_repo_invitations", { user }),
213 respondRepoInvitation: (user, id, accept) => call("respond_repo_invitation", { user, id, accept }),
214 revokeRepoInvitation: (actor, owner, name, id) =>
215 call("revoke_repo_invitation", { actor, path: { namespace: owner, name }, id }),
216 setBasePermission: (actor, slug, base) => call("set_base_permission", { actor, slug, base_permission: base }),
217 outsideCollaborators: (viewer, slug) => call("outside_collaborators", { viewer, slug }),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca218 // A repository's deploy keys; see deploy-keys.ts.
219 listDeployKeys: (viewer, owner, name) => call("list_deploy_keys", { viewer, path: { namespace: owner, name } }),
220 addDeployKey: (actor, owner, name, key) =>
221 call("add_deploy_key", { actor, path: { namespace: owner, name }, title: key.title, key: key.key, read_only: key.readOnly }),
222 removeDeployKey: (actor, owner, name, id) => call("remove_deploy_key", { actor, path: { namespace: owner, name }, id }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar223 // Teams; see teams.ts.
224 listTeams: (viewer, workspace, query) => call("list_teams", { viewer, workspace, query: query ?? null }),
225 getTeam: (viewer, workspace, team) => call("get_team", { viewer, workspace, team }),
226 createTeam: (actor, workspace, team) => call("create_team", { actor, workspace, ...team }),
Merge branch 'worktree-agent-ad7c6d88d93adc817'227 setTeamCreation: (actor, slug, setting) => call("set_team_creation", { actor, slug, team_creation: setting }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar228 updateTeam: (actor, workspace, team, changes) => call("update_team", { actor, workspace, team, ...changes }),
229 deleteTeam: (actor, workspace, team) => call("delete_team", { actor, workspace, team }),
230 teamMembers: (viewer, workspace, team, includeChildTeams) =>
231 call("team_members", { viewer, workspace, team, include_child_teams: includeChildTeams ?? false }),
232 setTeamMember: (actor, workspace, team, username, role) =>
233 call("set_team_member", { actor, workspace, team, username, role }),
234 removeTeamMember: (actor, workspace, team, username) =>
235 call("remove_team_member", { actor, workspace, team, username }),
236 childTeams: (viewer, workspace, team) => call("child_teams", { viewer, workspace, team }),
237 teamRepos: (viewer, workspace, team) => call("team_repos", { viewer, workspace, team }),
238 setTeamRepo: (actor, workspace, team, owner, name, role) =>
239 call("set_team_repo", { actor, workspace, team, repo: { namespace: owner, name }, role }),
240 removeTeamRepo: (actor, workspace, team, owner, name) =>
241 call("remove_team_repo", { actor, workspace, team, repo: { namespace: owner, name } }),
242 userTeams: (viewer, workspace, username) => call("user_teams", { viewer, workspace, username }),
243 teamMemberships: (viewer, workspace) => call("team_memberships", { viewer, workspace }),
Webhooks: every event, to your own addresses, signed and retried244 };
245}
246
Agents and memory, checks and conflicts, profiles, slug renames, custom domains247/**
248 * The slug a renamed workspace has now, for a `workspace.renamed` handler:
249 * asked of identity by the workspace's id, so that renames delivered twice
250 * or out of order converge. Falls back to the event's `to`.
251 */
252export async function currentWorkspaceSlug(
253 identity: ServiceBinding,
254 renamed: { workspaceId: string; to: string },
255): Promise<string> {
256 const names = await identityClient(identity).usernames([renamed.workspaceId]);
257 return names[renamed.workspaceId] ?? renamed.to;
258}
259
260/** The slugs whose rows move to `current`: the two a rename names, less `current`. */
261export function staleSlugs(renamed: { from: string; to: string }, current: string): string[] {
262 return [...new Set([renamed.from, renamed.to])].filter((slug) => slug !== current);
263}
264
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look265/**
266 * Where a transferred repository is now, as `namespace/name`, for a
267 * `repo.transferred` handler: asked of repos by id, so transfers delivered
268 * twice or out of order converge. Falls back to the event's destination.
269 */
270export async function currentRepoPath(
271 repos: ServiceBinding,
272 transferred: { repoId: string; name: string; to: string },
273): Promise<string> {
274 const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: transferred.repoId });
275 return path ? `${path.namespace}/${path.name}` : `${transferred.to}/${transferred.name}`;
276}
277
278/** The paths whose rows move to `current`: the two a transfer names, less `current`. */
279export function stalePaths(transferred: { name: string; from: string; to: string }, current: string): string[] {
280 return [...new Set([transferred.from, transferred.to].map((ns) => `${ns}/${transferred.name}`))].filter(
281 (path) => path !== current,
282 );
283}
284
285/**
286 * A repository's path change, from `repo.transferred` or `repo.renamed`,
287 * read the same way: the two paths (`namespace/name`) the event names, old
288 * then new. Null for any other event.
289 */
290export type RepoMove = { repoId: string; paths: [string, string] };
291
292export function repoMove(event: { type: string; data: unknown }): RepoMove | null {
293 const data = event.data as Record<string, string>;
294 if (event.type === "repo.transferred") {
295 return { repoId: data.repoId!, paths: [`${data.from}/${data.name}`, `${data.to}/${data.name}`] };
296 }
297 if (event.type === "repo.renamed") {
298 return { repoId: data.repoId!, paths: [`${data.namespace}/${data.from}`, `${data.namespace}/${data.to}`] };
299 }
300 return null;
301}
302
303/**
304 * Where a moved repository is now, as `namespace/name`: asked of repos by
305 * id, so moves delivered twice or out of order converge. Falls back to the
306 * event's new path.
307 */
308export async function currentMovedPath(repos: ServiceBinding, move: RepoMove): Promise<string> {
309 const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: move.repoId });
310 return path ? `${path.namespace}/${path.name}` : move.paths[1];
311}
312
313/** The paths whose rows move to `current`: the two a move names, less `current`. */
314export function staleMovedPaths(move: RepoMove, current: string): string[] {
315 return [...new Set(move.paths)].filter((path) => path !== current);
316}
317
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace318/** Staff-only identity. Only sudo binds to it; see `IdentityAdminApi`. */
319export function identityAdminClient(service: ServiceBinding): IdentityAdminApi {
320 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
321 return {
322 workspaces: (query) => call("admin_workspaces", { query: query ?? null }),
323 workspace: (slug) => call("admin_workspace", { slug }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look324 waitlist: (query, status) => call("admin_waitlist", { query: query ?? null, status: status ?? null }),
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas325 waitlistPending: () => call("admin_waitlist_pending", {}),
326 decideWaitlist: (id, approve, staff, note) => call("admin_decide_waitlist", { id, approve, staff, note: note ?? null }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look327 invites: (query) => call("admin_invites", { query: query ?? null }),
328 revokeInvite: (id, staff) => call("admin_revoke_invite", { id, staff }),
329 mintInvite: (email, staff) => call("admin_mint_invite", { email, staff }),
330 grantInvites: (target, name, amount, note, staff) =>
331 call("admin_grant_invites", { target, name, amount, note, staff }),
332 inviteTree: (username) => call("admin_invite_tree", { username }),
333 workspaceInvites: (slug) => call("admin_workspace_invites", { slug }),
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)334 sharedInvites: () => call("admin_shared_invites", {}),
335 createSharedInvite: (link, staff) =>
336 call("admin_create_shared_invite", {
337 label: link.label,
338 max_uses: link.maxUses,
339 expires_on: link.expiresOn,
340 domains: link.domains,
341 staff,
342 }),
343 revokeSharedInvite: (id, staff) => call("admin_revoke_shared_invite", { id, staff }),
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member344 deletedWorkspaces: () => call("admin_deleted_workspaces", {}),
345 restoreWorkspace: (workspaceId, staff) => call("admin_restore_workspace", { workspaceId, staff }),
346 purgeWorkspace: (workspaceId, staff, confirm) => call("admin_purge_workspace", { workspaceId, staff, confirm }),
Merge branch 'worktree-agent-a8385d293d42c913a'347 aliases: () => call("admin_aliases", {}),
348 setAlias: (alias, workspace, note, staff) => call("admin_set_alias", { alias, workspace, note, staff }),
349 removeAlias: (alias, reason, staff) => call("admin_remove_alias", { alias, reason, staff }),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace350 };
351}
352
Webhooks: every event, to your own addresses, signed and retried353export function reposClient(service: ServiceBinding): ReposApi {
354 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
355 return {
356 get: (path, viewer) => call("get", { path, viewer }),
357 getById: (id, viewer) => call("get_by_id", { id, viewer }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents358 readable: (ids, viewer) => call("readable", { ids, viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains359 publicNamespaces: (ownerId) => call("public_namespaces", { ownerId }),
Webhooks: every event, to your own addresses, signed and retried360 list: (viewer, options = {}) => call("list", { viewer, ...options }),
361 create: (owner, input) => call("create", { owner, ...input }),
362 update: (actor, path, changes) => call("update", { actor, path, ...changes }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look363 transfer: (actor, path, to) => call("transfer", { actor, path, to }),
364 delete: (actor, path, confirm) => call("delete", { actor, path, confirm }),
365 deleted: (viewer, namespace) => call("deleted", { viewer, namespace }),
366 restore: (actor, path) => call("restore", { actor, path }),
367 purge: (actor, path, confirm) => call("purge", { actor, path, confirm }),
368 rename: (actor, path, name) => call("rename", { actor, path, name }),
369 archive: (actor, path, archived) => call("archive", { actor, path, archived }),
370 setVisibility: (actor, path, isPrivate, confirm) => call("set_visibility", { actor, path, isPrivate, confirm }),
371 setDefaultBranch: (actor, path, branch) => call("set_default_branch", { actor, path, branch }),
372 renameBranch: (actor, path, from, to) => call("rename_branch", { actor, path, from, to }),
373 resolveBranch: (repoId, branch) => call("resolve_branch", { repoId, branch }),
374 statusById: (id) => call("status_by_id", { id }),
Merge branch 'worktree-agent-a2013627e5ea4ab13'375 storageOptions: () => call("storage_options", {}),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look376 resolvePath: (path) => call("resolve_path", { path }),
Webhooks: every event, to your own addresses, signed and retried377 tree: (path, viewer, ref, treePath) =>
378 call("tree", { path, viewer, ref, treePath }),
379 blob: (path, viewer, ref, filePath) =>
380 call("blob", { path, viewer, ref, filePath }),
381 log: (path, viewer, ref, limit) => call("log", { path, viewer, ref, limit }),
382 blame: (path, viewer, ref, filePath) => call("blame", { path, viewer, ref, filePath }),
383 forkForPull: (sourceId, pullId, actor) =>
384 call("fork_for_pull", { sourceId, pullId, actor }),
385 gitAccess: (path, viewer, service) =>
386 call("git_access", { path, viewer, service }),
387 branches: (path, viewer) => call("branches", { path, viewer }),
Branches and Tags pages, each file's last commit, and the branch menu on files388 lastCommits: (path, viewer, ref, treePath) => call("last_commits", { path, viewer, ref, treePath }),
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 25389 branchDrift: (path, viewer, base, heads) => call("branch_drift", { path, viewer, base, heads }),
Branches and Tags pages, each file's last commit, and the branch menu on files390 tags: (path, viewer) => call("tags", { path, viewer }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97391 about: (path, viewer) => call("about", { path, viewer }),
392 languages: (path, viewer) => call("languages", { path, viewer }),
393 contributors: (path, viewer) => call("contributors", { path, viewer }),
394 license: (path, viewer) => call("license", { path, viewer }),
395 stars: (path, viewer) => call("stars", { path, viewer }),
396 star: (actor, path, starred) => call("star", { path, actor, starred }),
397 stargazers: (path, viewer, page) => call("stargazers", { path, viewer, page: page ?? null }),
398 starred: (username, viewer) => call("starred", { username, viewer }),
399 releases: (path, viewer) => call("releases", { path, viewer }),
400 release: (path, viewer, which) => call("release", { path, viewer, id: which.id ?? null, tag: which.tag ?? null, latest: which.latest ?? false }),
401 createRelease: (actor, path, release) => call("create_release", { path, actor, ...release }),
402 updateRelease: (actor, path, id, change) => call("update_release", { path, actor, id, ...change }),
403 deleteRelease: (actor, path, id) => call("delete_release", { path, actor, id }),
Download ZIP from the Code button; a slimmer lifecycle panel; agent steps say what was done, not sandbox paths404 listFiles: (repoId, ref, limit) => call("list_files", { repoId, ref, skipDirs: [], limit }),
405 rawBlobs: (repoId, hashes, maxBytes) => call("raw_blobs", { repoId, hashes, maxBytes }),
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address406 rawFile: (repoId, ref, path, maxBytes) => call("raw_file", { repoId, ref, path, maxBytes }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents407 commitFile: (repo, actor, file) => call("commit_file", { repo, actor, ...file }),
Webhooks: every event, to your own addresses, signed and retried408 land: (sourceId, actor, branch) => call("land", { sourceId, actor, branch }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar409 compare: (repoId, viewer, base, head, baseBranch) => call("compare", { repoId, viewer, base, head, baseBranch }),
Merge branch 'worktree-agent-ac5b181a013e54348'410 claimBackups: (limit, maxRunning) => call("claim_backups", { limit, maxRunning }),
411 // The sandbox's own calls are snake_case (they come through the API).
412 failBackup: (jobId, token, error) => call("backup_fail", { job_id: jobId, token, error }),
Webhooks: every event, to your own addresses, signed and retried413 };
414}
415
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar416/**
417 * `PullDetail`'s own fields that the work service writes in snake_case:
418 * `g1t_contracts::work::PullDetail` has no camelCase renaming, though what
419 * it holds (`Pull`, `CheckRun` and the rest) does. Each with its name here.
420 */
421const PULL_DETAIL_FIELDS = [
422 ["review_pending", "reviewPending"],
423 ["earlier_checks", "earlierChecks"],
424 ["required_checks", "requiredChecks"],
425 ["code_owners", "codeOwners"],
426] as const;
427
428/**
429 * A pull request's detail as the work service sent it, with its own fields
430 * in the camelCase this package uses: read at the edge, by `workClient`'s
431 * `getPull`. Either spelling is taken, so a service that already sends
432 * camelCase reads the same.
433 */
434export function pullDetailFromWire(raw: Record<string, unknown>): PullDetail {
435 const detail: Record<string, unknown> = { ...raw };
436 for (const [snake, camel] of PULL_DETAIL_FIELDS) {
437 if (snake in detail) {
438 if (!(camel in detail)) detail[camel] = detail[snake];
439 delete detail[snake];
440 }
441 }
442 if (typeof detail.reviewPending !== "boolean") detail.reviewPending = false;
443 return detail as PullDetail;
444}
445
Webhooks: every event, to your own addresses, signed and retried446export function workClient(service: ServiceBinding): WorkApi {
447 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
448 return {
449 openIssue: (actor, repo, input) => call("open_issue", { actor, repo, ...input }),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step450 delegateIssue: (actor, repo, input) => call("delegate_issue", { actor, repo, ...input }),
Webhooks: every event, to your own addresses, signed and retried451 listIssues: (repo, viewer, filter = {}) => call("list_issues", { repo, viewer, ...filter }),
452 getIssue: (repo, number, viewer) => call("get_issue", { repo, number, viewer }),
453 updateIssue: (actor, repo, number, input) =>
454 call("update_issue", { actor, repo, number, ...input }),
455 closeIssue: (actor, repo, number, reason) =>
456 call("close_issue", { actor, repo, number, reason }),
457 reopenIssue: (actor, repo, number) => call("reopen_issue", { actor, repo, number }),
458 listLabels: (repo, viewer) => call("list_labels", { repo, viewer }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar459 saveLabel: (actor, repo, label) => call("save_label", { actor, repo, ...label }),
460 deleteLabel: (actor, repo, name) => call("delete_label", { actor, repo, name }),
461 addDefaultLabels: (actor, repo) => call("add_default_labels", { actor, repo }),
462 setLabels: (actor, repo, number, labels, change = "set") =>
463 call("set_labels", { actor, repo, number, labels, change }),
464 listMilestones: (repo, viewer, state) => call("list_milestones", { repo, viewer, state }),
465 getMilestone: (repo, number, viewer) => call("get_milestone", { repo, number, viewer }),
466 saveMilestone: (actor, repo, milestone) => call("save_milestone", { actor, repo, ...milestone }),
467 deleteMilestone: (actor, repo, number) => call("delete_milestone", { actor, repo, number }),
Webhooks: every event, to your own addresses, signed and retried468 counts: (repo, viewer) => call("counts", { repo, viewer }),
469 addComment: (actor, repo, number, comment) =>
470 call("add_comment", { actor, repo, number, ...comment }),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts471 editComment: (actor, repo, commentId, body) => call("edit_comment", { actor, repo, commentId, body }),
472 deleteComment: (actor, repo, commentId) => call("delete_comment", { actor, repo, commentId }),
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar473 workspaceAgentComment: (repo, number, agent, actingFor, body) =>
474 call("workspace_agent_comment", { repo, number, agent, acting_for: actingFor, body }),
475 workspaceAgentReview: (repo, number, agent, actingFor, verdict, body) =>
476 call("workspace_agent_review", { repo, number, agent, acting_for: actingFor, verdict, body }),
Webhooks: every event, to your own addresses, signed and retried477 startChecks: (pullId) => call("start_checks", { pullId }),
478 reportChecks: (runId, token, report) =>
479 call("report_checks", { runId, token, ...report }),
480 startReview: (pullId) => call("start_review", { pullId }),
481 failReview: (runId, token, error) => call("report_review", { runId, token, error }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains482 startMergecheck: (pullId) => call("start_mergecheck", { pullId }),
483 failMergecheck: (pullId, token, error) => call("report_mergecheck", { pullId, token, error }),
Webhooks: every event, to your own addresses, signed and retried484 advance: (pullId) => call("advance", { pullId }),
485 stall: (pullId, reason) => call("stall", { pullId, reason }),
486 managedPulls: (repoId) => call("managed_pulls", { repoId }),
487 queue: (repo, viewer) => call("queue", { repo, viewer }),
488 queueBuild: (repoId) => call("queue_build", { repoId }),
489 failQueue: (entryId, token, error) => call("report_queue", { entryId, token, error }),
490 removeFromQueue: (actor, repo, number) => call("remove_from_queue", { actor, repo, number }),
491 messageAgent: (actor, repo, number, body) => call("message_agent", { actor, repo, number, body }),
492 catchUpJob: (pullId) => call("catch_up_job", { pullId }),
Agents asked while not at work are woken to answer493 wakeForMessages: (pullId) => call("wake_for_messages", { pullId }),
Webhooks: every event, to your own addresses, signed and retried494 getSettings: (repo, viewer) => call("get_settings", { repo, viewer }),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge495 // Rulesets travel in snake_case (rules.ts).
496 listRulesets: (owner, viewer, includeParents = false) =>
497 call("list_rulesets", { viewer, ...owner, include_parents: includeParents }),
498 getRuleset: (owner, id, viewer) => call("get_ruleset", { viewer, ...owner, id }),
499 saveRuleset: (actor, owner, ruleset, id) => call("save_ruleset", { actor, ...owner, id: id ?? null, ruleset }),
500 deleteRuleset: (actor, owner, id) => call("delete_ruleset", { actor, ...owner, id }),
501 effectiveRules: (repo, name, viewer, target = "branch") => call("effective_rules", { viewer, repo, name, target }),
502 ruleEvaluations: (owner, viewer, filter = {}) => call("rule_evaluations", { viewer, ...owner, ...filter }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents503 seenChecks: (repo, viewer) => call("seen_checks", { repo, viewer }),
Merge checks: statuses and check runs on every commit504 commitChecks: (repo, viewer, shas) => call("commit_checks", { repo, viewer, shas }),
505 getCheckRun: (repo, id, viewer) => call("get_check_run", { repo, id, viewer }),
506 checkRunAnnotations: (repo, id, viewer) => call("check_run_annotations", { repo, id, viewer }),
507 requestCheckAction: (actor, repo, id, identifier) => call("request_check_action", { actor, repo, id, identifier }),
508 rerequestCheckRun: (actor, repo, id) => call("rerequest_check_run", { actor, repo, id }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar509 codeownersErrors: (repo, viewer, ref) => call("codeowners_errors", { repo, viewer, ref: ref ?? null }),
Webhooks: every event, to your own addresses, signed and retried510 updateSettings: (actor, repo, settings) =>
511 call("update_settings", { actor, repo, settings }),
512 openPull: (actor, repo, input) => call("open_pull", { actor, repo, ...input }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar513 listPulls: (repo, viewer, state, filter = {}) => call("list_pulls", { repo, viewer, state, ...filter }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents514 pullsForRepos: (repoIds, viewer, limit) => call("pulls_for_repos", { repoIds, viewer, limit }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar515 // Its own fields arrive in snake_case: read into camelCase here.
516 getPull: (repo, number, viewer) =>
517 call<Result<Record<string, unknown>>>("get_pull", { repo, number, viewer }).then(
518 (found): Result<PullDetail> => (found.ok ? { ok: true, value: pullDetailFromWire(found.value) } : found),
519 ),
Webhooks: every event, to your own addresses, signed and retried520 updatePull: (actor, repo, number, changes) =>
521 call("update_pull", { actor, repo, number, ...changes }),
Catching up with main takes seconds when the two sides touched different files522 catchUpPull: (actor, repo, number) => call("catch_up_pull", { actor, repo, number }),
Webhooks: every event, to your own addresses, signed and retried523 readyPull: (actor, repo, number, summary) =>
524 call("ready_pull", { actor, repo, number, summary }),
525 closePull: (actor, repo, number) => call("close_pull", { actor, repo, number }),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts526 reopenPull: (actor, repo, number) => call("reopen_pull", { actor, repo, number }),
527 convertPullToDraft: (actor, repo, number) => call("convert_pull_to_draft", { actor, repo, number }),
Webhooks: every event, to your own addresses, signed and retried528 mergePull: (actor, repo, number, options = {}) =>
529 call("merge_pull", { actor, repo, number, ...options }),
530 listActivePulls: (viewer) => call("list_active_pulls", { viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains531 byAuthor: (username, viewer, filter = {}) => call("by_author", { username, viewer, ...filter }),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project532 contributions: (username, viewer) => call("contributions", { username, viewer }),
Webhooks: every event, to your own addresses, signed and retried533 startPlan: (actor, repo, brief) => call("start_plan", { actor, repo, brief }),
534 failPlan: (planId, token, error) => call("report_plan", { planId, token, error }),
535 getPlan: (repo, viewer, id) => call("get_plan", { repo, viewer, id }),
536 listPlans: (repo, viewer) => call("list_plans", { repo, viewer }),
537 applyPlan: (actor, repo, id, options = {}) =>
538 call("apply_plan", { actor, repo, id, ...options }),
539 queueIssue: (actor, repo, number, queued) =>
540 call("queue_issue", { actor, repo, number, queued }),
541 readyIssues: (repoId) => call("ready_issues", { repoId }),
542 listAssignedIssues: (viewer) => call("list_assigned_issues", { viewer }),
543 appendSession: (actor, repo, number, entries) =>
544 call("append_session", { actor, repo, number, entries }),
545 readSession: (repo, number, viewer, afterSeq = 0) =>
546 call("read_session", { repo, number, viewer, afterSeq }),
547 };
548}
549
550export function billingClient(service: ServiceBinding): BillingApi {
551 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
552 return {
553 status: () => call("status", {}),
554 account: (workspace, viewer) => call("account", { workspace, viewer }),
555 ledger: (workspace, viewer) => call("ledger", { workspace, viewer }),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging556 credits: (workspace, viewer) => call("credits", { workspace, viewer }),
The statement is a month at a time, a line per kind of charge557 statement: (workspace, viewer, month = null, group = "day") => call("statement", { workspace, viewer, month, group }),
558 statementEntries: (workspace, viewer, filter) =>
559 call("statement_entries", {
560 workspace,
561 viewer,
562 month: filter.month,
563 kind: filter.kind,
564 day: filter.day ?? null,
565 project: filter.project ?? null,
566 before: filter.before ?? null,
567 }),
Webhooks: every event, to your own addresses, signed and retried568 usage: (workspace, viewer, since) => call("usage", { workspace, viewer, since }),
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix569 tokenUsage: (workspace, viewer, options = {}) =>
570 call("token_usage", { workspace, viewer, person: options.person ?? null, days: options.days ?? null }),
571 recordTokens: (usage) => call("record_tokens", usage),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens572 gatewayModels: () => call("gateway_models", {}),
Merge branch 'main' into actions-toolkit-oidc-artifacts573 modelDefaults: () => call("model_defaults", {}),
574 recordDiscovery: (provider, models, by, error = null) => call("record_discovery", { provider, models, by, error }),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens575 gatewayAdmit: (workspace) => call("gateway_admit", { workspace }),
576 recordGateway: (record) => call("record_gateway", record),
577 gatewayRequests: (workspace, viewer, options = {}) =>
578 call("gateway_requests", { workspace, viewer, limit: options.limit ?? null, before: options.before ?? null }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look579 checkout: (actor, workspace, amountCents, returnUrl, method = "card") =>
580 call("checkout", { actor, workspace, amountCents, returnUrl, method }),
Webhooks: every event, to your own addresses, signed and retried581 confirm: (workspace, viewer, session) => call("confirm", { workspace, viewer, session }),
582 canStart: (workspace) => call("can_start", { workspace }),
A free allowance on g1t's models, so anyone can try its agents583 trial: (workspace, exempt) => call("trial", { workspace, exempt }),
Webhooks: every event, to your own addresses, signed and retried584 startRun: (run) => call("start_run", run),
Paid features: a workspace turns on Deployments with a monthly plan585 features: (workspace, viewer) => call("features", { workspace, viewer }),
586 subscribe: (actor, workspace, feature, returnUrl) =>
587 call("subscribe", { actor, workspace, feature, returnUrl }),
588 confirmSubscription: (workspace, viewer, session) =>
589 call("confirm_subscription", { workspace, viewer, session }),
590 cancelSubscription: (actor, workspace, feature, resume = false) =>
591 call("cancel_subscription", { actor, workspace, feature, resume }),
592 hasFeature: (workspace, feature) => call("has_feature", { workspace, feature }),
Merge Stripe Tax, the card fee on card payments, and one free workspace per person593 freeWorkspaces: (workspaces) => call("free_workspaces", { workspaces }),
Paid features: a workspace turns on Deployments with a monthly plan594 chargeFeature: (charge) => call("charge_feature", charge),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace595 billingPortal: (actor, workspace, returnUrl) => call("billing_portal", { actor, workspace, return_url: returnUrl }),
Every sandbox is metered by the second596 recordSandbox: (usage) => call("record_sandbox", usage),
Usage limits: unpaid usage can only go so far597 limit: (workspace, viewer) => call("limit", { workspace, viewer }),
598 checkLimit: (workspace) => call("check_limit", { workspace }),
Prices keep themselves current with what g1t pays599 prices: () => call("prices", {}),
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas600 notePending: (workspace, source, costMicros, detail = null) => call("note_pending", { workspace, source, costMicros, detail }),
601 usageMeters: (workspace, viewer) => call("usage_meters", { workspace, viewer }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look602 setSpendLimit: (actor, workspace, spendLimitMicros, useFullLimit = false, raiseOnce = false) =>
603 call("set_spend_limit", { actor, workspace, spendLimitMicros, use_full_limit: useFullLimit, raise_once: raiseOnce }),
Two limits, real invoices, trust that grows by itself, sales signals604 invoices: (workspace, viewer) => call("invoices", { workspace, viewer }),
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put605 entitlements: (workspace) => call("entitlements", { workspace }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look606 reserve: (reservation) => call("reserve", reservation),
607 settle: (reservationId, actualMicros) => call("settle", { reservationId, actualMicros }),
608 cardCheck: (actor, workspace, returnUrl) => call("card_check", { actor, workspace, returnUrl }),
609 confirmCardCheck: (workspace, viewer, session) => call("confirm_card_check", { workspace, viewer, session }),
610 requestLimit: (actor, workspace, request) => call("request_limit", { actor, workspace, ...request }),
611 limitRequests: (workspace, viewer) => call("limit_requests", { workspace, viewer }),
612 confirmSpike: (actor, workspace, keepGoing) => call("confirm_spike", { actor, workspace, keepGoing }),
613 setCaps: (actor, workspace, caps) => call("set_caps", { actor, workspace, ...caps }),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit614 usageReport: (workspace, viewer, range) =>
615 call("usage_report", { workspace, viewer, from: range.from, until: range.until, products: range.products ?? [], projects: range.projects ?? [] }),
616 aiCredit: (workspace, viewer) => call("ai_credit", { workspace, viewer }),
617 buyAiCredit: (actor, workspace, amountCents, returnUrl) => call("buy_ai_credit", { actor, workspace, amountCents, returnUrl }),
618 confirmAiCredit: (workspace, viewer, session) => call("confirm_ai_credit", { workspace, viewer, session }),
619 setAiReload: (actor, workspace, reload) => call("set_ai_reload", { actor, workspace, ...reload }),
620 setBudget: (actor, workspace, budget) => call("set_budget", { actor, workspace, ...budget }),
621 billingDetails: (workspace, viewer) => call("billing_details", { workspace, viewer }),
622 setBillingDetails: (actor, workspace, details) => call("set_billing_details", { actor, workspace, ...details }),
Webhooks: every event, to your own addresses, signed and retried623 };
624}
625
Billing accounts, terms and enterprises; g1t is no longer free626export function billingAdminClient(service: ServiceBinding): BillingAdminApi {
627 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
628 return {
629 accounts: (query) => call("admin_accounts", { query: query ?? null }),
630 account: (id) => call("admin_account", { id }),
631 setTerms: (id, terms, by) => call("admin_set_terms", { id, terms, by }),
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put632 setAllowances: (id, allowances, note, by) => call("admin_set_allowances", { id, allowances, note, by }),
Billing accounts, terms and enterprises; g1t is no longer free633 createEnterprise: (name, workspaces, by) => call("admin_create_enterprise", { name, workspaces, by }),
634 attach: (workspace, account, by) => call("admin_attach", { workspace, account, by }),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging635 credit: (workspace, amountMicros, note, by, options = { kind: "goodwill" }) =>
636 call("admin_credit", {
637 workspace,
638 amount_micros: amountMicros,
639 note,
640 by,
641 kind: options.kind,
642 expires_at: options.expiresAt ?? null,
643 refund_for: options.refundFor ?? null,
644 refund_day: options.refundDay ?? null,
645 }),
646 credits: (filter = {}) =>
647 call("admin_credits", {
648 workspace: filter.workspace ?? null,
649 kind: filter.kind ?? null,
650 month: filter.month ?? null,
651 by: filter.by ?? null,
652 }),
653 revokeCredit: (id, note, by) => call("admin_revoke_credit", { id, note, by }),
sudo: reset a test workspace's billing so it starts again as a new customer; refused on a live Stripe key, for comped workspaces and for an enterprise's654 resetBilling: (workspace, confirm, note, by) => call("admin_reset_billing", { workspace, confirm, note, by }),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace655 billingLink: (workspace, by) => call("admin_billing_link", { workspace, by }),
Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard656 stripe: (fix = false, by) => call("admin_stripe", { fix, by: by ?? null }),
Stripe webhooks, enterprise invoices, and sudo for both657 enterpriseBilling: (id, email, by) => call("admin_enterprise_billing", { id, email, by }),
Merge Stripe Tax, the card fee on card payments, and one free workspace per person658 enterpriseAddress: (id, address, taxIdType, taxId, by) => call("admin_enterprise_address", { id, address, taxIdType, taxId, by }),
Stripe webhooks, enterprise invoices, and sudo for both659 invoiceEnterprise: (id, by) => call("admin_invoice_enterprise", { id, by }),
660 accountsFor: (workspaces) => call("admin_accounts", { query: null, workspaces }),
Two limits, real invoices, trust that grows by itself, sales signals661 signals: () => call("admin_signals", {}),
662 overview: () => call("admin_overview", {}),
663 sales: (workspace) => call("admin_sales", { workspace }),
664 setSales: (workspace, record, by) =>
665 call("admin_set_sales", {
666 workspace,
667 stage: record.stage,
668 owner: record.owner ?? null,
669 next_step: record.nextStep ?? null,
670 next_at: record.nextAt ?? null,
671 by,
672 }),
673 addNote: (workspace, text, by) => call("admin_add_note", { workspace, text, by }),
674 workspaceInvoices: (workspace) => call("admin_workspace_invoices", { workspace }),
Billing lists every invoice and every staff change; signals carry follow-ups675 allInvoices: (filter = {}) => call("admin_invoices", { status: filter.status ?? null, month: filter.month ?? null }),
676 audit: (filter = {}) =>
677 call("admin_audit", { by: filter.by ?? null, action: filter.action ?? null, before: filter.before ?? null }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look678 limitRequests: (status = "open") => call("admin_limit_requests", { status }),
679 decideLimitRequest: (id, decision, amountMicros, note, by) =>
680 call("admin_decide_limit_request", { id, decision, amount_micros: amountMicros, note, by }),
681 overages: () => call("admin_overages", {}),
682 goodwill: (workspace, amountMicros, reason, by, day = null) =>
683 call("admin_goodwill", { workspace, amount_micros: amountMicros, reason, by, day }),
684 velocity: () => call("admin_velocity", {}),
685 recordPayment: (workspace, amountMicros, reference, note, by) =>
686 call("admin_record_payment", { workspace, amount_micros: amountMicros, reference, note, by }),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily687 costs: (days) => call("admin_costs", { days: days ?? null }),
688 costAlerts: () => call("admin_cost_alerts", {}),
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays689 spendCaps: () => call("admin_spend_caps", {}),
690 liftBreaker: (note, by) => call("admin_lift_breaker", { note, by }),
Merge platform pause and the hourly usage watcher: staff can pause compute, schedules, indexing or renders for everyone, the watcher emails on a breach and is never blind quietly, and the models proxy holds each run to its cap (billing 0051, integrations 0006)691 platformGuard: () => call("admin_platform_guard", {}),
692 setPause: (level, paused, note, by) => call("admin_set_pause", { level, paused, note, by }),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily693 decideProposal: (id, decision, note, by) => call("admin_decide_proposal", { id, decision, note, by }),
694 setCostSettings: (settings, by) => call("admin_set_cost_settings", { settings, by }),
695 setCostMapping: (mapping, by) =>
696 call("admin_set_cost_mapping", {
697 product: mapping.product,
698 meter: mapping.meter,
699 bucket: mapping.bucket ?? "",
700 price_meter: mapping.priceMeter ?? null,
701 own_meter: mapping.ownMeter ?? null,
702 scale_to_own: mapping.scaleToOwn ?? false,
703 drift_percent: mapping.driftPercent ?? null,
704 note: mapping.note ?? "",
705 remove: mapping.remove ?? false,
706 by,
707 }),
708 runCosts: (by) => call("admin_run_costs", { by }),
Merge branch 'main' into actions-toolkit-oidc-artifacts709 models: () => call("admin_models", {}),
710 decideModel: (model, decision, details, reason, by) =>
711 call("admin_decide_model", {
712 model,
713 decision,
714 name: details.name ?? null,
715 tier_hint: details.tierHint ?? null,
716 prices: details.prices
717 ? {
718 input_micros: details.prices.inputMicros,
719 output_micros: details.prices.outputMicros,
720 cache_read_micros: details.prices.cacheReadMicros,
721 cache_write_micros: details.prices.cacheWriteMicros,
722 cache_write_1h_micros: details.prices.cacheWrite1hMicros,
723 threshold: details.prices.threshold,
724 over_input_micros: details.prices.overInputMicros,
725 over_output_micros: details.prices.overOutputMicros,
726 over_cache_read_micros: details.prices.overCacheReadMicros,
727 over_cache_write_micros: details.prices.overCacheWriteMicros,
728 over_cache_write_1h_micros: details.prices.overCacheWrite1hMicros,
729 }
730 : null,
731 reason,
732 by,
733 }),
734 setModelDefault: (purpose, value, reason, by) =>
735 call("admin_set_model_default", {
736 purpose,
737 model: value.model ?? null,
738 tier: value.tier ?? null,
739 effort: value.effort ?? null,
740 reason,
741 by,
742 }),
Billing accounts, terms and enterprises; g1t is no longer free743 };
744}
745
Webhooks: every event, to your own addresses, signed and retried746export function eventsClient(service: ServiceBinding): EventsApi {
747 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
748 return {
749 publish: (events) => call("publish", { events }),
750 list: (query) => call("list", query),
751 };
752}
753
754export function integrationsClient(service: ServiceBinding): IntegrationsApi {
755 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
756 return {
757 list: (workspace, viewer) => call("list", { workspace, viewer }),
758 connect: (actor, workspace, input) => call("connect", { actor, workspace, ...input }),
759 update: (actor, workspace, id, input) => call("update", { actor, workspace, id, ...input }),
760 disconnect: (actor, workspace, id) => call("disconnect", { actor, workspace, id }),
761 test: (actor, workspace, id) => call("test", { actor, workspace, id }),
762 deliveries: (workspace, viewer, id) => call("deliveries", { workspace, viewer, id }),
763 resolve: (workspace, viewer, reference) => call("resolve", { workspace, viewer, reference }),
764 references: (workspace, text, limit) => call("references", { workspace, text, limit }),
765 import: (actor, repo, reference, assign) => call("import", { actor, repo, reference, assign }),
766 links: (repo, number) => call("links", { repo, number }),
767 modelProvider: (workspace) => call("model_provider", { workspace }),
768 openModelSession: (run) => call("open_model_session", run),
769 modelUpstream: (token) => call("model_upstream", { token }),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens770 gatewayUpstream: (workspace) => call("gateway_upstream", { workspace }),
AI Gateway: OpenAI's format, open models, and your own providers771 gatewayProviders: (workspace) => call("gateway_providers", { workspace }),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily772 closeModelSessions: (tokenHashes) => call("close_model_sessions", { token_hashes: tokenHashes }),
Merge platform pause and the hourly usage watcher: staff can pause compute, schedules, indexing or renders for everyone, the watcher emails on a breach and is never blind quietly, and the models proxy holds each run to its cap (billing 0051, integrations 0006)773 capModelSessions: (tokenHashes, capMicros) => call("cap_model_sessions", { token_hashes: tokenHashes, cap_micros: capMicros }),
Webhooks: every event, to your own addresses, signed and retried774 routes: (workspace, viewer) => call("routes", { workspace, viewer }),
775 setRoutes: (actor, workspace, routes) => call("set_routes", { actor, workspace, routes }),
776 };
777}
778
779export function webhooksClient(service: ServiceBinding): WebhooksApi {
780 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
781 return {
782 list: (viewer, owner) => call("list", { viewer, ...owner }),
783 create: (actor, owner, input) => call("create", { actor, ...owner, ...input }),
784 update: (actor, owner, id, input) => call("update", { actor, ...owner, id, ...input }),
785 delete: (actor, owner, id) => call("delete", { actor, ...owner, id }),
786 ping: (actor, owner, id) => call("ping", { actor, ...owner, id }),
787 deliveries: (viewer, owner, id) => call("deliveries", { viewer, ...owner, id }),
788 redeliver: (actor, owner, deliveryId) => call("redeliver", { actor, ...owner, deliveryId }),
789 };
790}
Automations: rules in .g1t/automations that act when something happens791
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs792export function actionsClient(service: ServiceBinding): ActionsApi {
793 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
794 return {
795 workflows: (repo, viewer) => call("workflows", { repo, viewer }),
796 runs: (repo, viewer, filter = {}) => call("runs", { repo, viewer, ...filter }),
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)797 run: (repo, viewer, id, attempt) => call("run", { repo, viewer, id, attempt: attempt ?? null }),
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs798 logs: (repo, viewer, job, after = 0) => call("logs", { repo, viewer, job, after }),
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)799 summaries: (repo, viewer, id, attempt) => call("summaries", { repo, viewer, id, attempt: attempt ?? null }),
800 jobLogText: (repo, viewer, job) => call("job_log_text", { repo, viewer, job }),
801 runLogs: (repo, viewer, id, attempt) => call("run_logs", { repo, viewer, id, attempt: attempt ?? null }),
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs802 dispatch: (actor, repo, workflow, ref, inputs) => call("dispatch", { actor, repo, workflow, ref, inputs }),
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)803 cancel: (actor, repo, id, force = false) => call("cancel", { actor, repo, id, force }),
804 rerun: (actor, repo, id, failedOnly = false, options = {}) =>
805 call("rerun", { actor, repo, id, failed_only: failedOnly, job: options.job ?? null, debug: options.debug ?? false }),
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs806 setWorkflowEnabled: (actor, repo, workflow, enabled) =>
807 call("set_workflow_enabled", { actor, repo, workflow, enabled }),
808 settings: (actor, owner, kind) => call("settings", { actor, ...owner, kind }),
Secrets and variables: one list, rows per environment, for workflows and deployments809 setSetting: (actor, owner, kind, name, value, options = {}) =>
810 call("set_setting", { actor, ...owner, kind, name, value, ...options }),
811 deleteSetting: (actor, owner, kind, name, id) => call("delete_setting", { actor, ...owner, kind, name, id }),
Merge branch 'worktree-agent-a3abfcce648e87dca'812 approveRun: (actor, repo, id) => call("approve_run", { actor, repo, id }),
813 pendingDeployments: (repo, viewer, id) => call("pending_deployments", { repo, viewer, id }),
814 reviewDeployments: (actor, repo, id, state, environments = [], comment) =>
815 call("review_deployments", { actor, repo, id, state, environments, comment: comment ?? null }),
816 actionsSettings: (repo, viewer) => call("actions_settings", { repo, viewer }),
817 setActionsSettings: (actor, repo, change) => call("set_actions_settings", { actor, repo, ...change }),
818 workspaceActionsSettings: (workspace, viewer) => call("workspace_actions_settings", { workspace, viewer }),
819 setWorkspaceActionsSettings: (actor, workspace, change) =>
820 call("set_workspace_actions_settings", { actor, workspace, ...change }),
821 environments: (repo, viewer) => call("environments", { repo, viewer }),
822 setEnvironment: (actor, repo, name, change) => call("set_environment", { actor, repo, name, ...change }),
823 deleteEnvironment: (actor, repo, name) => call("delete_environment", { actor, repo, name }),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2824 artifacts: (repo, viewer, filter = {}) => call("artifacts", { repo, viewer, ...filter }),
825 artifactDownload: (repo, viewer, by) => call("artifact_download", { repo, viewer, ...by }),
826 deleteArtifact: (actor, repo, id) => call("delete_artifact", { actor, repo, id }),
827 artifactRetention: (repo, viewer, days) => call("artifact_retention", { repo, viewer, days }),
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs828 };
829}
830
Deployments: a preview for every pull request, production on g1t.page831
Fast pages, required checks on the branch, self-hosted runners, honest incidents832/** Self-hosted runners, kept by the actions service. */
833export function runnersClient(service: ServiceBinding): RunnersApi {
834 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
835 return {
836 stuck: (viewer) => call("stuck_jobs", { viewer }),
837 list: (actor, owner) => call("runners", { actor, ...owner }),
838 createToken: (actor, owner, group) => call("create_registration_token", { actor, ...owner, group }),
839 remove: (actor, owner, id) => call("remove_runner", { actor, ...owner, id }),
840 groups: (actor, workspace) => call("runner_groups", { actor, workspace }),
841 setGroup: (actor, workspace, group) => call("set_runner_group", { actor, workspace, ...group }),
842 deleteGroup: (actor, workspace, id) => call("delete_runner_group", { actor, workspace, id }),
843 settings: (actor, owner) => call("runner_settings", { actor, ...owner }),
844 setSettings: (actor, owner, change) => call("set_runner_settings", { actor, ...owner, ...change }),
845 };
846}
847
Deployments: a preview for every pull request, production on g1t.page848export function deploymentsClient(service: ServiceBinding): DeploymentsApi {
849 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
850 return {
Projects: what a workspace builds and runs, first on every page851 settings: (project, viewer) => call("settings", { project, viewer }),
852 updateSettings: (actor, project, changes) => call("update_settings", { actor, project, changes }),
853 list: (project, viewer) => call("list", { project, viewer }),
854 get: (project, id, viewer) => call("get", { project, id, viewer }),
855 redeploy: (actor, project, branch) => call("redeploy", { actor, project, branch }),
856 takeDown: (actor, project, branch) => call("take_down", { actor, project, branch }),
857 overview: (workspace, viewer) => call("overview", { workspace, viewer }),
Deployments: a preview for every pull request, production on g1t.page858 usage: (workspace, viewer) => call("usage", { workspace, viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains859 domains: (project, viewer) => call("domains", { project, viewer }),
860 addDomain: (actor, project, hostname, options = {}) =>
861 call("add_domain", { actor, project, hostname, twin: !!options.twin }),
862 removeDomain: (actor, project, id) => call("remove_domain", { actor, project, id }),
863 refreshDomain: (actor, project, id) => call("refresh_domain", { actor, project, id }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97864 repoDeployments: (repo, viewer, filter = {}) => call("list_deployments", { repo, viewer, ...filter }),
865 repoDeployment: (repo, id, viewer) => call("get_deployment", { repo, id, viewer }),
866 environments: (repo, viewer) => call("list_environments", { repo, viewer }),
867 createDeployment: (actor, repo, input) => call("create_deployment", { repo, actor, ...input }),
868 createDeploymentStatus: (actor, repo, id, input) => call("create_deployment_status", { repo, actor, id, ...input }),
Deployments: a preview for every pull request, production on g1t.page869 };
870}
Projects: what a workspace builds and runs, first on every page871
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member872/** Packages: the registries beside the code (services/packages). */
873export function packagesClient(service: ServiceBinding): PackagesApi {
874 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
875 return {
876 list: (workspace, viewer, filter = {}) =>
877 call("list_packages", {
878 workspace,
879 viewer,
880 ecosystem: filter.ecosystem ?? null,
881 repo_id: filter.repoId ?? null,
882 query: filter.query ?? null,
883 }),
884 get: (workspace, ecosystem, name, viewer) => call("get_package", { workspace, ecosystem, name, viewer }),
885 deleteVersion: (actor, workspace, ecosystem, name, version, surface) =>
886 call("delete_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }),
887 deletePackage: (actor, workspace, ecosystem, name, surface) =>
888 call("delete_package", { actor, workspace, ecosystem, name, surface: surface ?? null }),
889 set: (actor, workspace, ecosystem, name, change, surface) =>
890 call("set_package", {
891 actor,
892 workspace,
893 ecosystem,
894 name,
895 visibility: change.visibility ?? null,
896 link: change.link ?? null,
897 unlink: change.unlink ?? false,
Merge packages: roles, Actions access, source label, soft delete, API898 inherit_access: change.inheritAccess ?? null,
899 surface: surface ?? null,
900 }),
901 settings: (workspace, ecosystem, name, viewer) => call("package_settings", { workspace, ecosystem, name, viewer }),
902 deleted: (workspace, viewer) => call("deleted_packages", { workspace, viewer }),
903 restorePackage: (actor, workspace, ecosystem, name, surface) =>
904 call("restore_package", { actor, workspace, ecosystem, name, surface: surface ?? null }),
905 restoreVersion: (actor, workspace, ecosystem, name, version, surface) =>
906 call("restore_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }),
907 setAccess: (actor, workspace, ecosystem, name, who, role, surface) =>
908 call("set_package_access", {
909 actor,
910 workspace,
911 ecosystem,
912 name,
913 user: "user" in who ? who.user : null,
914 team: "team" in who ? who.team : null,
915 role,
916 surface: surface ?? null,
917 }),
918 removeAccess: (actor, workspace, ecosystem, name, who, surface) =>
919 call("remove_package_access", {
920 actor,
921 workspace,
922 ecosystem,
923 name,
924 user: "user" in who ? who.user : null,
925 team: "team" in who ? who.team : null,
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member926 surface: surface ?? null,
927 }),
Merge packages: roles, Actions access, source label, soft delete, API928 setActionsAccess: (actor, workspace, ecosystem, name, repo, role, surface) =>
929 call("set_actions_access", { actor, workspace, ecosystem, name, repo, role, surface: surface ?? null }),
930 removeActionsAccess: (actor, workspace, ecosystem, name, repo, surface) =>
931 call("remove_actions_access", { actor, workspace, ecosystem, name, repo, surface: surface ?? null }),
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member932 storage: (workspace) => call("storage", { workspace }),
933 storageAll: () => call("storage_all", {}),
Composer from the workspace's own repositories, and go get from g1t.sh934 syncComposer: (repoId) => call("sync_composer", { repo_id: repoId }),
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member935 };
936}
937
Projects: what a workspace builds and runs, first on every page938export function projectsClient(service: ServiceBinding): ProjectsApi {
939 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
940 return {
941 list: (workspace, viewer) => call("list", { workspace, viewer }),
942 get: (workspace, slug, viewer) => call("get", { workspace, slug, viewer }),
943 byRepo: (repoId) => call("by_repo", { repoId }),
944 create: (actor, workspace, input) => call("create", { actor, workspace, input }),
945 update: (actor, workspace, slug, changes) => call("update", { actor, workspace, slug, changes }),
A project is an app or a library: libraries show their package and how to ship a release, not production946 deploymentsChanged: (projectId, enabled) => call("deployments_changed", { projectId, enabled }),
Projects: pinned and recent projects, per person per workspace947 shortcuts: (workspace, viewer) => call("shortcuts", { workspace, viewer }),
948 pin: (actor, workspace, slug, position) => call("pin", { actor, workspace, slug, position: position ?? null }),
949 unpin: (actor, workspace, slug) => call("unpin", { actor, workspace, slug }),
950 reorderPins: (actor, workspace, slugs) => call("reorder_pins", { actor, workspace, slugs }),
951 visited: (actor, projectId) => call("visited", { actor, projectId }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97952 publicLinks: (repos) => call("public_links", { repos }),
Projects: what a workspace builds and runs, first on every page953 };
954}

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