Skip to content
991 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.

Pull requests say where they are live; the project menu shows what is coming1import type { ActionsApi } from "./actions";
Billing accounts, terms and enterprises; g1t is no longer free2import type { BillingAdminApi, BillingApi } from "./billing";
Pull requests say where they are live; the project menu shows what is coming3import type { DeploymentsApi } from "./deployments";
4import type { ProjectsApi } from "./projects";
5import type { EventsApi } from "./events";
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace6import type { IdentityAdminApi, IdentityApi } from "./identity";
Pull requests say where they are live; the project menu shows what is coming7import 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";
Pull requests say where they are live; the project menu shows what is coming9import 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";
Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next.13import type { RunnerComputerApi } from "./runner";
Fast pages, required checks on the branch, self-hosted runners, honest incidents14import type { RunnersApi } from "./runners";
Pull requests say where they are live; the project menu shows what is coming15
16/** A service binding, as far as these clients need it. */
17export type ServiceBinding = {
18 fetch(input: string, init?: RequestInit): Promise<Response>;
19};
20
21/**
22 * Calls a method on a service that speaks the JSON protocol used by the
23 * Rust services: `POST /rpc/<method>` with the arguments as the body.
24 */
25async function rpc<T>(
26 service: ServiceBinding,
27 method: string,
28 args: object,
29): Promise<T> {
30 // The hostname is ignored; a service binding always reaches its service.
31 const response = await service.fetch(`https://service/rpc/${method}`, {
32 method: "POST",
33 headers: { "content-type": "application/json" },
34 body: JSON.stringify(args),
35 });
36 if (!response.ok) {
37 throw new Error(`${method} failed with status ${response.status}`);
38 }
39 return (await response.json()) as T;
40}
41
42export function identityClient(service: ServiceBinding): IdentityApi {
43 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
44 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 confirm45 register: (username, email, password, inviteCode, client, emailProof) =>
46 call("register", {
47 username,
48 email,
49 password,
50 invite_code: inviteCode ?? null,
51 email_proof: emailProof ?? null,
52 client: client ?? null,
53 }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look54 signIn: (username, password, client) => call("sign_in", { username, password, client: client ?? null }),
Merge main (membership, two-factor, GitHub repo roles) into tokens55 twoFactorSignIn: (challenge, code, client) => call("two_factor_sign_in", { challenge, code, client: client ?? null }),
Pull requests say where they are live; the project menu shows what is coming56 signOut: (sessionToken) => call("sign_out", { sessionToken }),
57 resendVerification: (user) => call("resend_verification", { user }),
58 verifyEmail: (token) => call("verify_email", { token }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look59 requestPasswordReset: (email, client) => call("request_password_reset", { email, client: client ?? null }),
Pull requests say where they are live; the project menu shows what is coming60 resetPassword: (token, password) =>
61 call("reset_password", { token, password }),
62 deviceStart: (clientName) => call("device_start", { clientName }),
63 deviceLookup: (userCode) => call("device_lookup", { userCode }),
64 deviceResolve: (userCode, user, approve) =>
65 call("device_resolve", { userCode, user, approve }),
66 deviceClaim: (deviceCode) => call("device_claim", { deviceCode }),
67 oauthAuthorize: (user, approval) => call("oauth_authorize", { user, ...approval }),
68 oauthExchange: (code, codeVerifier, clientId, redirectUri) =>
69 call("oauth_exchange", { code, codeVerifier, clientId, redirectUri }),
70 oauthRefresh: (refreshToken, clientId) =>
71 call("oauth_refresh", { refreshToken, clientId }),
72 listOAuthGrants: (user) => call("list_oauth_grants", { user }),
73 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 step74 updateOAuthGrant: (user, id, grant) =>
75 call("update_oauth_grant", { user, id, scopes: grant.scopes }),
Pull requests say where they are live; the project menu shows what is coming76 createWorkspace: (user, slug, name) => call("create_workspace", { user, slug, name }),
77 getWorkspace: (slug) => call("get_workspace", { slug }),
Merge branch 'worktree-agent-a2013627e5ea4ab13'78 workspaceResidency: (slug) => call("workspace_residency", { slug }),
79 setWorkspaceResidency: (actor, slug, residency) => call("set_workspace_residency", { actor, slug, residency }),
Pull requests say where they are live; the project menu shows what is coming80 listMembers: (slug, viewer) => call("list_members", { slug, viewer }),
81 addMember: (actor, slug, username) => call("add_member", { actor, slug, username }),
82 removeMember: (actor, slug, username) =>
Merge main (membership, two-factor, GitHub repo roles) into tokens83 call("remove_member", { actor, slug, username, surface: "web" }),
84 updateMember: (actor, slug, username, change) =>
85 call("update_member", { actor, slug, username, role: change.role ?? null, org_roles: change.org_roles ?? null, surface: "web" }),
86 transferOwnership: (actor, slug, username) => call("transfer_ownership", { actor, slug, username, surface: "web" }),
87 leaveWorkspace: (user, slug) => call("leave_workspace", { user, slug, surface: "web" }),
88 setMemberPrivileges: (actor, slug, change) => call("set_member_privileges", { actor, slug, privileges: change, surface: "web" }),
89 setTwoFactorRequirement: (actor, slug, required) =>
90 call("set_two_factor_requirement", { actor, slug, required, surface: "web" }),
Pull requests say where they are live; the project menu shows what is coming91 updateWorkspace: (actor, slug, details) =>
92 call("update_workspace", { actor, slug, ...details }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains93 renameWorkspace: (actor, slug, newSlug) => call("rename_workspace", { actor, slug, newSlug }),
94 checkWorkspaceRename: (actor, slug, newSlug) =>
95 call("check_workspace_rename", { actor, slug, newSlug }),
96 resolveSlug: (slug) => call("resolve_slug", { slug }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look97 deleteWorkspace: (actor, slug, confirm) => call("delete_workspace", { actor, slug, confirm }),
98 checkWorkspaceDeletion: (actor, slug) => call("check_workspace_deletion", { actor, slug }),
Workspace names and icons, and a component kit for every control99 setWorkspaceAvatar: (actor, slug, image) => call("set_workspace_avatar", { actor, slug, image }),
100 setUserAvatar: (user, image) => call("set_user_avatar", { user, image }),
Pull requests say where they are live; the project menu shows what is coming101 listWorkspaceTokens: (slug, viewer) => call("list_workspace_tokens", { slug, viewer }),
102 removeWorkspaceToken: (actor, slug, id) =>
103 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 managers104 createToken: (actor, input) =>
105 call("create_token", {
106 actor,
107 owner: input.owner ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules108 name: input.name,
109 description: input.description ?? null,
110 ttl_seconds: input.ttlSeconds,
111 workspace: input.workspace,
112 repository_selection: input.repositorySelection,
113 repositories: input.repositories,
114 permissions: input.permissions,
Merge main into Artifacts Phase 2115 website: input.website ?? false,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules116 }),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers117 updateToken: (actor, id, change, owner) =>
118 call("update_token", {
119 actor,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules120 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 managers121 owner: owner ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules122 name: change.name ?? null,
123 description: change.description ?? null,
124 repository_selection: change.repositorySelection ?? null,
125 repositories: change.repositories ?? null,
126 permissions: change.permissions ?? null,
Merge main into Artifacts Phase 2127 website: change.website ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules128 }),
129 getTokenPolicy: (slug, viewer) => call("get_token_policy", { slug, viewer }),
130 setTokenPolicy: (actor, slug, change) =>
131 call("set_token_policy", {
132 actor,
133 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 managers134 allow_tokens_for_all_workspaces: change.allowTokensForAllWorkspaces ?? null,
135 allow_tokens_for_this_workspace: change.allowTokensForThisWorkspace ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules136 require_approval: change.requireApproval ?? null,
137 max_lifetime_days: change.maxLifetimeDays ?? null,
138 forbid_no_expiry: change.forbidNoExpiry ?? null,
139 surface: "web",
140 }),
141 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 managers142 call("list_member_tokens", { actor, slug, status: filter.status ?? null }),
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules143 reviewTokenRequest: (actor, slug, id, approve, reason) =>
144 call("review_token_request", { actor, slug, id, approve, reason: reason ?? null, surface: "web" }),
145 revokeMemberToken: (actor, slug, id, reason) =>
146 call("revoke_member_token", { actor, slug, id, reason: reason ?? null, surface: "web" }),
Pull requests say where they are live; the project menu shows what is coming147 userForSession: (sessionToken) => call("user_for_session", { sessionToken }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look148 registration: () => call("registration", {}),
149 listInvites: (user) => call("list_invites", { user }),
150 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)151 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 look152 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 quotas153 checkInvite: (code, client, options = {}) =>
154 call("check_invite", {
155 code,
156 client: client ?? null,
157 viewer: options.viewer ?? null,
158 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 confirm159 email_proof: options.emailProof ?? null,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas160 }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look161 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)162 inviteMember: (actor, slug, who) =>
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.163 call("invite_member", { actor, slug, email: who.email ?? "", username: who.username ?? null, role: who.role ?? null, message: who.message ?? 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)164 listInvitations: (user) => call("list_invitations", { user }),
165 acceptInvitation: (user, id) => call("accept_invitation", { user, id, surface: "web" }),
166 declineInvitation: (user, id) => call("decline_invitation", { user, id, surface: "web" }),
167 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 look168 workspaceInvites: (slug, viewer) => call("workspace_invites", { slug, viewer }),
169 revokeWorkspaceInvite: (actor, slug, id) => call("revoke_workspace_invite", { actor, slug, id }),
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.170 resendWorkspaceInvite: (actor, slug, id) => call("resend_workspace_invite", { actor, slug, id }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look171 requestAccess: (email, about, client) => call("request_access", { email, about, client: client ?? null }),
Pull requests say where they are live; the project menu shows what is coming172 userForGitCredentials: (username, secret) =>
173 call("user_for_git_credentials", { username, secret }),
174 userForAccessToken: (token) => call("user_for_access_token", { token }),
175 userForSshKey: (fingerprint) => call("user_for_ssh_key", { fingerprint }),
176 userByUsername: (username) => call("user_by_username", { username }),
177 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)178 usersForAudience: (ids) => call("users_for_audience", { ids }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains179 profile: (username) => call("profile", { username }),
180 updateProfile: (actor, fields) => call("update_profile", { actor, ...fields }),
181 profileWorkspaces: (username, viewer, publicIn) =>
182 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.183 dockPins: (user, workspace) => call("dock_pins", { user, workspace }),
184 setDockPins: (user, workspace, apps) => call("set_dock_pins", { user, workspace, apps }),
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.185 soundSettings: (user) => call("sound_settings", { user }),
186 setSoundSettings: (user, change) => call("set_sound_settings", { user, change }),
Pull requests say where they are live; the project menu shows what is coming187 listSshKeys: (user) => call("list_ssh_keys", { user }),
188 addSshKey: (user, title, publicKey) =>
189 call("add_ssh_key", { user, title, publicKey }),
190 removeSshKey: (user, id) => call("remove_ssh_key", { user, id }),
191 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 step192 createAccessToken: (user, name, ttlSeconds, grant) =>
193 call("create_access_token", {
194 user,
195 name,
196 ttlSeconds,
197 scopes: grant?.scopes ?? null,
198 listed: grant?.listed ?? false,
199 }),
Pull requests say where they are live; the project menu shows what is coming200 createAgentToken: (onBehalfOf, scope, ttlSeconds) =>
201 call("create_agent_token", { onBehalfOf, scope, ttlSeconds }),
202 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 API203 createRunCredential: (input) => call("create_run_credential", input),
204 bindRunCredentials: (tokenHashes, runId) => call("bind_run_credentials", { tokenHashes, runId }),
205 revokeRunCredentials: (target) => call("revoke_run_credentials", target),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look206 // Who has access to a repository; see access.ts.
207 repoAccess: (owner, name, viewer) => call("repo_access", { viewer, path: { namespace: owner, name } }),
208 addCollaborator: (actor, owner, name, invitee, role) =>
209 call("add_collaborator", { actor, path: { namespace: owner, name }, invitee, role }),
210 setCollaboratorRole: (actor, owner, name, username, role) =>
211 call("set_collaborator_role", { actor, path: { namespace: owner, name }, username, role }),
212 removeCollaborator: (actor, owner, name, username) =>
213 call("remove_collaborator", { actor, path: { namespace: owner, name }, username }),
214 collaboratorPermission: (viewer, owner, name, username) =>
215 call("collaborator_permission", { viewer, path: { namespace: owner, name }, username }),
216 myRepoInvitations: (user) => call("my_repo_invitations", { user }),
217 respondRepoInvitation: (user, id, accept) => call("respond_repo_invitation", { user, id, accept }),
218 revokeRepoInvitation: (actor, owner, name, id) =>
219 call("revoke_repo_invitation", { actor, path: { namespace: owner, name }, id }),
220 setBasePermission: (actor, slug, base) => call("set_base_permission", { actor, slug, base_permission: base }),
221 outsideCollaborators: (viewer, slug) => call("outside_collaborators", { viewer, slug }),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca222 // A repository's deploy keys; see deploy-keys.ts.
223 listDeployKeys: (viewer, owner, name) => call("list_deploy_keys", { viewer, path: { namespace: owner, name } }),
224 addDeployKey: (actor, owner, name, key) =>
225 call("add_deploy_key", { actor, path: { namespace: owner, name }, title: key.title, key: key.key, read_only: key.readOnly }),
226 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 bar227 // Teams; see teams.ts.
228 listTeams: (viewer, workspace, query) => call("list_teams", { viewer, workspace, query: query ?? null }),
229 getTeam: (viewer, workspace, team) => call("get_team", { viewer, workspace, team }),
230 createTeam: (actor, workspace, team) => call("create_team", { actor, workspace, ...team }),
Merge branch 'worktree-agent-ad7c6d88d93adc817'231 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 bar232 updateTeam: (actor, workspace, team, changes) => call("update_team", { actor, workspace, team, ...changes }),
233 deleteTeam: (actor, workspace, team) => call("delete_team", { actor, workspace, team }),
234 teamMembers: (viewer, workspace, team, includeChildTeams) =>
235 call("team_members", { viewer, workspace, team, include_child_teams: includeChildTeams ?? false }),
236 setTeamMember: (actor, workspace, team, username, role) =>
237 call("set_team_member", { actor, workspace, team, username, role }),
238 removeTeamMember: (actor, workspace, team, username) =>
239 call("remove_team_member", { actor, workspace, team, username }),
240 childTeams: (viewer, workspace, team) => call("child_teams", { viewer, workspace, team }),
241 teamRepos: (viewer, workspace, team) => call("team_repos", { viewer, workspace, team }),
242 setTeamRepo: (actor, workspace, team, owner, name, role) =>
243 call("set_team_repo", { actor, workspace, team, repo: { namespace: owner, name }, role }),
244 removeTeamRepo: (actor, workspace, team, owner, name) =>
245 call("remove_team_repo", { actor, workspace, team, repo: { namespace: owner, name } }),
246 userTeams: (viewer, workspace, username) => call("user_teams", { viewer, workspace, username }),
247 teamMemberships: (viewer, workspace) => call("team_memberships", { viewer, workspace }),
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.248 teamAgents: (viewer, workspace, team) => call("team_agents", { viewer, workspace, team }),
249 setTeamAgent: (actor, workspace, team, agentId) => call("set_team_agent", { actor, workspace, team, agent_id: agentId }),
250 removeTeamAgent: (actor, workspace, team, agentId) => call("remove_team_agent", { actor, workspace, team, agent_id: agentId }),
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.251 agentTeams: (workspace, agentId) => call("agent_teams", { workspace, agent_id: agentId }),
252 teamAgentIndex: (workspace) => call("team_agent_index", { workspace }),
253 adoptAgentTeams: (workspaceId, agents) => call("adopt_agent_teams", { workspace_id: workspaceId, agents }),
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.254 // People; see people.ts.
255 peopleDirectory: (viewer, workspace) => call("people_directory", { viewer, workspace }),
256 setMemberProfile: (actor, workspace, username, changes) => call("set_member_profile", { actor, workspace, username, ...changes }),
Pull requests say where they are live; the project menu shows what is coming257 };
258}
259
Agents and memory, checks and conflicts, profiles, slug renames, custom domains260/**
261 * The slug a renamed workspace has now, for a `workspace.renamed` handler:
262 * asked of identity by the workspace's id, so that renames delivered twice
263 * or out of order converge. Falls back to the event's `to`.
264 */
265export async function currentWorkspaceSlug(
266 identity: ServiceBinding,
267 renamed: { workspaceId: string; to: string },
268): Promise<string> {
269 const names = await identityClient(identity).usernames([renamed.workspaceId]);
270 return names[renamed.workspaceId] ?? renamed.to;
271}
272
273/** The slugs whose rows move to `current`: the two a rename names, less `current`. */
274export function staleSlugs(renamed: { from: string; to: string }, current: string): string[] {
275 return [...new Set([renamed.from, renamed.to])].filter((slug) => slug !== current);
276}
277
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look278/**
279 * Where a transferred repository is now, as `namespace/name`, for a
280 * `repo.transferred` handler: asked of repos by id, so transfers delivered
281 * twice or out of order converge. Falls back to the event's destination.
282 */
283export async function currentRepoPath(
284 repos: ServiceBinding,
285 transferred: { repoId: string; name: string; to: string },
286): Promise<string> {
287 const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: transferred.repoId });
288 return path ? `${path.namespace}/${path.name}` : `${transferred.to}/${transferred.name}`;
289}
290
291/** The paths whose rows move to `current`: the two a transfer names, less `current`. */
292export function stalePaths(transferred: { name: string; from: string; to: string }, current: string): string[] {
293 return [...new Set([transferred.from, transferred.to].map((ns) => `${ns}/${transferred.name}`))].filter(
294 (path) => path !== current,
295 );
296}
297
298/**
299 * A repository's path change, from `repo.transferred` or `repo.renamed`,
300 * read the same way: the two paths (`namespace/name`) the event names, old
301 * then new. Null for any other event.
302 */
303export type RepoMove = { repoId: string; paths: [string, string] };
304
305export function repoMove(event: { type: string; data: unknown }): RepoMove | null {
306 const data = event.data as Record<string, string>;
307 if (event.type === "repo.transferred") {
308 return { repoId: data.repoId!, paths: [`${data.from}/${data.name}`, `${data.to}/${data.name}`] };
309 }
310 if (event.type === "repo.renamed") {
311 return { repoId: data.repoId!, paths: [`${data.namespace}/${data.from}`, `${data.namespace}/${data.to}`] };
312 }
313 return null;
314}
315
316/**
317 * Where a moved repository is now, as `namespace/name`: asked of repos by
318 * id, so moves delivered twice or out of order converge. Falls back to the
319 * event's new path.
320 */
321export async function currentMovedPath(repos: ServiceBinding, move: RepoMove): Promise<string> {
322 const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: move.repoId });
323 return path ? `${path.namespace}/${path.name}` : move.paths[1];
324}
325
326/** The paths whose rows move to `current`: the two a move names, less `current`. */
327export function staleMovedPaths(move: RepoMove, current: string): string[] {
328 return [...new Set(move.paths)].filter((path) => path !== current);
329}
330
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace331/** Staff-only identity. Only sudo binds to it; see `IdentityAdminApi`. */
332export function identityAdminClient(service: ServiceBinding): IdentityAdminApi {
333 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
334 return {
335 workspaces: (query) => call("admin_workspaces", { query: query ?? null }),
336 workspace: (slug) => call("admin_workspace", { slug }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look337 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 quotas338 waitlistPending: () => call("admin_waitlist_pending", {}),
339 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 look340 invites: (query) => call("admin_invites", { query: query ?? null }),
341 revokeInvite: (id, staff) => call("admin_revoke_invite", { id, staff }),
342 mintInvite: (email, staff) => call("admin_mint_invite", { email, staff }),
343 grantInvites: (target, name, amount, note, staff) =>
344 call("admin_grant_invites", { target, name, amount, note, staff }),
345 inviteTree: (username) => call("admin_invite_tree", { username }),
346 workspaceInvites: (slug) => call("admin_workspace_invites", { slug }),
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)347 sharedInvites: () => call("admin_shared_invites", {}),
348 createSharedInvite: (link, staff) =>
349 call("admin_create_shared_invite", {
350 label: link.label,
351 max_uses: link.maxUses,
352 expires_on: link.expiresOn,
353 domains: link.domains,
354 staff,
355 }),
356 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 member357 deletedWorkspaces: () => call("admin_deleted_workspaces", {}),
358 restoreWorkspace: (workspaceId, staff) => call("admin_restore_workspace", { workspaceId, staff }),
359 purgeWorkspace: (workspaceId, staff, confirm) => call("admin_purge_workspace", { workspaceId, staff, confirm }),
Merge branch 'worktree-agent-a8385d293d42c913a'360 aliases: () => call("admin_aliases", {}),
361 setAlias: (alias, workspace, note, staff) => call("admin_set_alias", { alias, workspace, note, staff }),
362 removeAlias: (alias, reason, staff) => call("admin_remove_alias", { alias, reason, staff }),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace363 };
364}
365
Pull requests say where they are live; the project menu shows what is coming366export function reposClient(service: ServiceBinding): ReposApi {
367 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
368 return {
369 get: (path, viewer) => call("get", { path, viewer }),
370 getById: (id, viewer) => call("get_by_id", { id, viewer }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents371 readable: (ids, viewer) => call("readable", { ids, viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains372 publicNamespaces: (ownerId) => call("public_namespaces", { ownerId }),
Pull requests say where they are live; the project menu shows what is coming373 list: (viewer, options = {}) => call("list", { viewer, ...options }),
374 create: (owner, input) => call("create", { owner, ...input }),
375 update: (actor, path, changes) => call("update", { actor, path, ...changes }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look376 transfer: (actor, path, to) => call("transfer", { actor, path, to }),
377 delete: (actor, path, confirm) => call("delete", { actor, path, confirm }),
378 deleted: (viewer, namespace) => call("deleted", { viewer, namespace }),
379 restore: (actor, path) => call("restore", { actor, path }),
380 purge: (actor, path, confirm) => call("purge", { actor, path, confirm }),
381 rename: (actor, path, name) => call("rename", { actor, path, name }),
382 archive: (actor, path, archived) => call("archive", { actor, path, archived }),
383 setVisibility: (actor, path, isPrivate, confirm) => call("set_visibility", { actor, path, isPrivate, confirm }),
384 setDefaultBranch: (actor, path, branch) => call("set_default_branch", { actor, path, branch }),
385 renameBranch: (actor, path, from, to) => call("rename_branch", { actor, path, from, to }),
386 resolveBranch: (repoId, branch) => call("resolve_branch", { repoId, branch }),
387 statusById: (id) => call("status_by_id", { id }),
Merge branch 'worktree-agent-a2013627e5ea4ab13'388 storageOptions: () => call("storage_options", {}),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look389 resolvePath: (path) => call("resolve_path", { path }),
Pull requests say where they are live; the project menu shows what is coming390 tree: (path, viewer, ref, treePath) =>
391 call("tree", { path, viewer, ref, treePath }),
392 blob: (path, viewer, ref, filePath) =>
393 call("blob", { path, viewer, ref, filePath }),
394 log: (path, viewer, ref, limit) => call("log", { path, viewer, ref, limit }),
395 blame: (path, viewer, ref, filePath) => call("blame", { path, viewer, ref, filePath }),
396 forkForPull: (sourceId, pullId, actor) =>
397 call("fork_for_pull", { sourceId, pullId, actor }),
398 gitAccess: (path, viewer, service) =>
399 call("git_access", { path, viewer, service }),
400 branches: (path, viewer) => call("branches", { path, viewer }),
Branches and Tags pages, each file's last commit, and the branch menu on files401 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 25402 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 files403 tags: (path, viewer) => call("tags", { path, viewer }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97404 about: (path, viewer) => call("about", { path, viewer }),
405 languages: (path, viewer) => call("languages", { path, viewer }),
406 contributors: (path, viewer) => call("contributors", { path, viewer }),
407 license: (path, viewer) => call("license", { path, viewer }),
408 stars: (path, viewer) => call("stars", { path, viewer }),
409 star: (actor, path, starred) => call("star", { path, actor, starred }),
410 stargazers: (path, viewer, page) => call("stargazers", { path, viewer, page: page ?? null }),
411 starred: (username, viewer) => call("starred", { username, viewer }),
412 releases: (path, viewer) => call("releases", { path, viewer }),
413 release: (path, viewer, which) => call("release", { path, viewer, id: which.id ?? null, tag: which.tag ?? null, latest: which.latest ?? false }),
414 createRelease: (actor, path, release) => call("create_release", { path, actor, ...release }),
415 updateRelease: (actor, path, id, change) => call("update_release", { path, actor, id, ...change }),
416 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 paths417 listFiles: (repoId, ref, limit) => call("list_files", { repoId, ref, skipDirs: [], limit }),
418 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 address419 rawFile: (repoId, ref, path, maxBytes) => call("raw_file", { repoId, ref, path, maxBytes }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents420 commitFile: (repo, actor, file) => call("commit_file", { repo, actor, ...file }),
Pull requests say where they are live; the project menu shows what is coming421 land: (sourceId, actor, branch) => call("land", { sourceId, actor, branch }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar422 compare: (repoId, viewer, base, head, baseBranch) => call("compare", { repoId, viewer, base, head, baseBranch }),
Merge branch 'worktree-agent-ac5b181a013e54348'423 claimBackups: (limit, maxRunning) => call("claim_backups", { limit, maxRunning }),
424 // The sandbox's own calls are snake_case (they come through the API).
425 failBackup: (jobId, token, error) => call("backup_fail", { job_id: jobId, token, error }),
Pull requests say where they are live; the project menu shows what is coming426 };
427}
428
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar429/**
430 * `PullDetail`'s own fields that the work service writes in snake_case:
431 * `g1t_contracts::work::PullDetail` has no camelCase renaming, though what
432 * it holds (`Pull`, `CheckRun` and the rest) does. Each with its name here.
433 */
434const PULL_DETAIL_FIELDS = [
435 ["review_pending", "reviewPending"],
436 ["earlier_checks", "earlierChecks"],
437 ["required_checks", "requiredChecks"],
438 ["code_owners", "codeOwners"],
439] as const;
440
441/**
442 * A pull request's detail as the work service sent it, with its own fields
443 * in the camelCase this package uses: read at the edge, by `workClient`'s
444 * `getPull`. Either spelling is taken, so a service that already sends
445 * camelCase reads the same.
446 */
447export function pullDetailFromWire(raw: Record<string, unknown>): PullDetail {
448 const detail: Record<string, unknown> = { ...raw };
449 for (const [snake, camel] of PULL_DETAIL_FIELDS) {
450 if (snake in detail) {
451 if (!(camel in detail)) detail[camel] = detail[snake];
452 delete detail[snake];
453 }
454 }
455 if (typeof detail.reviewPending !== "boolean") detail.reviewPending = false;
456 return detail as PullDetail;
457}
458
Pull requests say where they are live; the project menu shows what is coming459export function workClient(service: ServiceBinding): WorkApi {
460 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
461 return {
462 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 step463 delegateIssue: (actor, repo, input) => call("delegate_issue", { actor, repo, ...input }),
Pull requests say where they are live; the project menu shows what is coming464 listIssues: (repo, viewer, filter = {}) => call("list_issues", { repo, viewer, ...filter }),
465 getIssue: (repo, number, viewer) => call("get_issue", { repo, number, viewer }),
466 updateIssue: (actor, repo, number, input) =>
467 call("update_issue", { actor, repo, number, ...input }),
468 closeIssue: (actor, repo, number, reason) =>
469 call("close_issue", { actor, repo, number, reason }),
470 reopenIssue: (actor, repo, number) => call("reopen_issue", { actor, repo, number }),
471 listLabels: (repo, viewer) => call("list_labels", { repo, viewer }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar472 saveLabel: (actor, repo, label) => call("save_label", { actor, repo, ...label }),
473 deleteLabel: (actor, repo, name) => call("delete_label", { actor, repo, name }),
474 addDefaultLabels: (actor, repo) => call("add_default_labels", { actor, repo }),
475 setLabels: (actor, repo, number, labels, change = "set") =>
476 call("set_labels", { actor, repo, number, labels, change }),
477 listMilestones: (repo, viewer, state) => call("list_milestones", { repo, viewer, state }),
478 getMilestone: (repo, number, viewer) => call("get_milestone", { repo, number, viewer }),
479 saveMilestone: (actor, repo, milestone) => call("save_milestone", { actor, repo, ...milestone }),
480 deleteMilestone: (actor, repo, number) => call("delete_milestone", { actor, repo, number }),
Pull requests say where they are live; the project menu shows what is coming481 counts: (repo, viewer) => call("counts", { repo, viewer }),
482 addComment: (actor, repo, number, comment) =>
483 call("add_comment", { actor, repo, number, ...comment }),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts484 editComment: (actor, repo, commentId, body) => call("edit_comment", { actor, repo, commentId, body }),
485 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 calendar486 workspaceAgentComment: (repo, number, agent, actingFor, body) =>
487 call("workspace_agent_comment", { repo, number, agent, acting_for: actingFor, body }),
488 workspaceAgentReview: (repo, number, agent, actingFor, verdict, body) =>
489 call("workspace_agent_review", { repo, number, agent, acting_for: actingFor, verdict, body }),
Pull requests say where they are live; the project menu shows what is coming490 startChecks: (pullId) => call("start_checks", { pullId }),
491 reportChecks: (runId, token, report) =>
492 call("report_checks", { runId, token, ...report }),
493 startReview: (pullId) => call("start_review", { pullId }),
494 failReview: (runId, token, error) => call("report_review", { runId, token, error }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains495 startMergecheck: (pullId) => call("start_mergecheck", { pullId }),
496 failMergecheck: (pullId, token, error) => call("report_mergecheck", { pullId, token, error }),
Pull requests say where they are live; the project menu shows what is coming497 advance: (pullId) => call("advance", { pullId }),
498 stall: (pullId, reason) => call("stall", { pullId, reason }),
499 managedPulls: (repoId) => call("managed_pulls", { repoId }),
500 queue: (repo, viewer) => call("queue", { repo, viewer }),
501 queueBuild: (repoId) => call("queue_build", { repoId }),
502 failQueue: (entryId, token, error) => call("report_queue", { entryId, token, error }),
503 removeFromQueue: (actor, repo, number) => call("remove_from_queue", { actor, repo, number }),
504 messageAgent: (actor, repo, number, body) => call("message_agent", { actor, repo, number, body }),
505 catchUpJob: (pullId) => call("catch_up_job", { pullId }),
506 wakeForMessages: (pullId) => call("wake_for_messages", { pullId }),
507 getSettings: (repo, viewer) => call("get_settings", { repo, viewer }),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge508 // Rulesets travel in snake_case (rules.ts).
509 listRulesets: (owner, viewer, includeParents = false) =>
510 call("list_rulesets", { viewer, ...owner, include_parents: includeParents }),
511 getRuleset: (owner, id, viewer) => call("get_ruleset", { viewer, ...owner, id }),
512 saveRuleset: (actor, owner, ruleset, id) => call("save_ruleset", { actor, ...owner, id: id ?? null, ruleset }),
513 deleteRuleset: (actor, owner, id) => call("delete_ruleset", { actor, ...owner, id }),
514 effectiveRules: (repo, name, viewer, target = "branch") => call("effective_rules", { viewer, repo, name, target }),
515 ruleEvaluations: (owner, viewer, filter = {}) => call("rule_evaluations", { viewer, ...owner, ...filter }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents516 seenChecks: (repo, viewer) => call("seen_checks", { repo, viewer }),
Merge checks: statuses and check runs on every commit517 commitChecks: (repo, viewer, shas) => call("commit_checks", { repo, viewer, shas }),
518 getCheckRun: (repo, id, viewer) => call("get_check_run", { repo, id, viewer }),
519 checkRunAnnotations: (repo, id, viewer) => call("check_run_annotations", { repo, id, viewer }),
520 requestCheckAction: (actor, repo, id, identifier) => call("request_check_action", { actor, repo, id, identifier }),
521 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 bar522 codeownersErrors: (repo, viewer, ref) => call("codeowners_errors", { repo, viewer, ref: ref ?? null }),
Pull requests say where they are live; the project menu shows what is coming523 updateSettings: (actor, repo, settings) =>
524 call("update_settings", { actor, repo, settings }),
525 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 bar526 listPulls: (repo, viewer, state, filter = {}) => call("list_pulls", { repo, viewer, state, ...filter }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents527 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 bar528 // Its own fields arrive in snake_case: read into camelCase here.
529 getPull: (repo, number, viewer) =>
530 call<Result<Record<string, unknown>>>("get_pull", { repo, number, viewer }).then(
531 (found): Result<PullDetail> => (found.ok ? { ok: true, value: pullDetailFromWire(found.value) } : found),
532 ),
Pull requests say where they are live; the project menu shows what is coming533 updatePull: (actor, repo, number, changes) =>
534 call("update_pull", { actor, repo, number, ...changes }),
Catching up with main takes seconds when the two sides touched different files535 catchUpPull: (actor, repo, number) => call("catch_up_pull", { actor, repo, number }),
Pull requests say where they are live; the project menu shows what is coming536 readyPull: (actor, repo, number, summary) =>
537 call("ready_pull", { actor, repo, number, summary }),
538 closePull: (actor, repo, number) => call("close_pull", { actor, repo, number }),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts539 reopenPull: (actor, repo, number) => call("reopen_pull", { actor, repo, number }),
540 convertPullToDraft: (actor, repo, number) => call("convert_pull_to_draft", { actor, repo, number }),
Pull requests say where they are live; the project menu shows what is coming541 mergePull: (actor, repo, number, options = {}) =>
542 call("merge_pull", { actor, repo, number, ...options }),
543 listActivePulls: (viewer) => call("list_active_pulls", { viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains544 byAuthor: (username, viewer, filter = {}) => call("by_author", { username, viewer, ...filter }),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project545 contributions: (username, viewer) => call("contributions", { username, viewer }),
Pull requests say where they are live; the project menu shows what is coming546 startPlan: (actor, repo, brief) => call("start_plan", { actor, repo, brief }),
547 failPlan: (planId, token, error) => call("report_plan", { planId, token, error }),
548 getPlan: (repo, viewer, id) => call("get_plan", { repo, viewer, id }),
549 listPlans: (repo, viewer) => call("list_plans", { repo, viewer }),
550 applyPlan: (actor, repo, id, options = {}) =>
551 call("apply_plan", { actor, repo, id, ...options }),
552 queueIssue: (actor, repo, number, queued) =>
553 call("queue_issue", { actor, repo, number, queued }),
554 readyIssues: (repoId) => call("ready_issues", { repoId }),
555 listAssignedIssues: (viewer) => call("list_assigned_issues", { viewer }),
556 appendSession: (actor, repo, number, entries) =>
557 call("append_session", { actor, repo, number, entries }),
558 readSession: (repo, number, viewer, afterSeq = 0) =>
559 call("read_session", { repo, number, viewer, afterSeq }),
560 };
561}
562
563export function billingClient(service: ServiceBinding): BillingApi {
564 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
565 return {
566 status: () => call("status", {}),
567 account: (workspace, viewer) => call("account", { workspace, viewer }),
568 ledger: (workspace, viewer) => call("ledger", { workspace, viewer }),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging569 credits: (workspace, viewer) => call("credits", { workspace, viewer }),
The statement is a month at a time, a line per kind of charge570 statement: (workspace, viewer, month = null, group = "day") => call("statement", { workspace, viewer, month, group }),
571 statementEntries: (workspace, viewer, filter) =>
572 call("statement_entries", {
573 workspace,
574 viewer,
575 month: filter.month,
576 kind: filter.kind,
577 day: filter.day ?? null,
578 project: filter.project ?? null,
579 before: filter.before ?? null,
580 }),
Pull requests say where they are live; the project menu shows what is coming581 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 mix582 tokenUsage: (workspace, viewer, options = {}) =>
583 call("token_usage", { workspace, viewer, person: options.person ?? null, days: options.days ?? null }),
584 recordTokens: (usage) => call("record_tokens", usage),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens585 gatewayModels: () => call("gateway_models", {}),
Merge branch 'main' into actions-toolkit-oidc-artifacts586 modelDefaults: () => call("model_defaults", {}),
587 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 tokens588 gatewayAdmit: (workspace) => call("gateway_admit", { workspace }),
589 recordGateway: (record) => call("record_gateway", record),
590 gatewayRequests: (workspace, viewer, options = {}) =>
591 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 look592 checkout: (actor, workspace, amountCents, returnUrl, method = "card") =>
593 call("checkout", { actor, workspace, amountCents, returnUrl, method }),
Pull requests say where they are live; the project menu shows what is coming594 confirm: (workspace, viewer, session) => call("confirm", { workspace, viewer, session }),
595 canStart: (workspace) => call("can_start", { workspace }),
596 trial: (workspace, exempt) => call("trial", { workspace, exempt }),
597 startRun: (run) => call("start_run", run),
598 features: (workspace, viewer) => call("features", { workspace, viewer }),
599 subscribe: (actor, workspace, feature, returnUrl) =>
600 call("subscribe", { actor, workspace, feature, returnUrl }),
601 confirmSubscription: (workspace, viewer, session) =>
602 call("confirm_subscription", { workspace, viewer, session }),
603 cancelSubscription: (actor, workspace, feature, resume = false) =>
604 call("cancel_subscription", { actor, workspace, feature, resume }),
605 hasFeature: (workspace, feature) => call("has_feature", { workspace, feature }),
Merge Stripe Tax, the card fee on card payments, and one free workspace per person606 freeWorkspaces: (workspaces) => call("free_workspaces", { workspaces }),
Pull requests say where they are live; the project menu shows what is coming607 chargeFeature: (charge) => call("charge_feature", charge),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace608 billingPortal: (actor, workspace, returnUrl) => call("billing_portal", { actor, workspace, return_url: returnUrl }),
Every sandbox is metered by the second609 recordSandbox: (usage) => call("record_sandbox", usage),
Usage limits: unpaid usage can only go so far610 limit: (workspace, viewer) => call("limit", { workspace, viewer }),
611 checkLimit: (workspace) => call("check_limit", { workspace }),
Prices keep themselves current with what g1t pays612 prices: () => call("prices", {}),
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas613 notePending: (workspace, source, costMicros, detail = null) => call("note_pending", { workspace, source, costMicros, detail }),
614 usageMeters: (workspace, viewer) => call("usage_meters", { workspace, viewer }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look615 setSpendLimit: (actor, workspace, spendLimitMicros, useFullLimit = false, raiseOnce = false) =>
616 call("set_spend_limit", { actor, workspace, spendLimitMicros, use_full_limit: useFullLimit, raise_once: raiseOnce }),
Two limits, real invoices, trust that grows by itself, sales signals617 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 put618 entitlements: (workspace) => call("entitlements", { workspace }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look619 reserve: (reservation) => call("reserve", reservation),
620 settle: (reservationId, actualMicros) => call("settle", { reservationId, actualMicros }),
621 cardCheck: (actor, workspace, returnUrl) => call("card_check", { actor, workspace, returnUrl }),
622 confirmCardCheck: (workspace, viewer, session) => call("confirm_card_check", { workspace, viewer, session }),
623 requestLimit: (actor, workspace, request) => call("request_limit", { actor, workspace, ...request }),
624 limitRequests: (workspace, viewer) => call("limit_requests", { workspace, viewer }),
625 confirmSpike: (actor, workspace, keepGoing) => call("confirm_spike", { actor, workspace, keepGoing }),
626 setCaps: (actor, workspace, caps) => call("set_caps", { actor, workspace, ...caps }),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit627 usageReport: (workspace, viewer, range) =>
628 call("usage_report", { workspace, viewer, from: range.from, until: range.until, products: range.products ?? [], projects: range.projects ?? [] }),
629 aiCredit: (workspace, viewer) => call("ai_credit", { workspace, viewer }),
630 buyAiCredit: (actor, workspace, amountCents, returnUrl) => call("buy_ai_credit", { actor, workspace, amountCents, returnUrl }),
631 confirmAiCredit: (workspace, viewer, session) => call("confirm_ai_credit", { workspace, viewer, session }),
632 setAiReload: (actor, workspace, reload) => call("set_ai_reload", { actor, workspace, ...reload }),
633 setBudget: (actor, workspace, budget) => call("set_budget", { actor, workspace, ...budget }),
634 billingDetails: (workspace, viewer) => call("billing_details", { workspace, viewer }),
635 setBillingDetails: (actor, workspace, details) => call("set_billing_details", { actor, workspace, ...details }),
Pull requests say where they are live; the project menu shows what is coming636 };
637}
638
Billing accounts, terms and enterprises; g1t is no longer free639export function billingAdminClient(service: ServiceBinding): BillingAdminApi {
640 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
641 return {
642 accounts: (query) => call("admin_accounts", { query: query ?? null }),
643 account: (id) => call("admin_account", { id }),
644 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 put645 setAllowances: (id, allowances, note, by) => call("admin_set_allowances", { id, allowances, note, by }),
Billing accounts, terms and enterprises; g1t is no longer free646 createEnterprise: (name, workspaces, by) => call("admin_create_enterprise", { name, workspaces, by }),
647 attach: (workspace, account, by) => call("admin_attach", { workspace, account, by }),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging648 credit: (workspace, amountMicros, note, by, options = { kind: "goodwill" }) =>
649 call("admin_credit", {
650 workspace,
651 amount_micros: amountMicros,
652 note,
653 by,
654 kind: options.kind,
655 expires_at: options.expiresAt ?? null,
656 refund_for: options.refundFor ?? null,
657 refund_day: options.refundDay ?? null,
658 }),
659 credits: (filter = {}) =>
660 call("admin_credits", {
661 workspace: filter.workspace ?? null,
662 kind: filter.kind ?? null,
663 month: filter.month ?? null,
664 by: filter.by ?? null,
665 }),
666 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's667 resetBilling: (workspace, confirm, note, by) => call("admin_reset_billing", { workspace, confirm, note, by }),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace668 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 dashboard669 stripe: (fix = false, by) => call("admin_stripe", { fix, by: by ?? null }),
Stripe webhooks, enterprise invoices, and sudo for both670 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 person671 enterpriseAddress: (id, address, taxIdType, taxId, by) => call("admin_enterprise_address", { id, address, taxIdType, taxId, by }),
Stripe webhooks, enterprise invoices, and sudo for both672 invoiceEnterprise: (id, by) => call("admin_invoice_enterprise", { id, by }),
673 accountsFor: (workspaces) => call("admin_accounts", { query: null, workspaces }),
Two limits, real invoices, trust that grows by itself, sales signals674 signals: () => call("admin_signals", {}),
675 overview: () => call("admin_overview", {}),
676 sales: (workspace) => call("admin_sales", { workspace }),
677 setSales: (workspace, record, by) =>
678 call("admin_set_sales", {
679 workspace,
680 stage: record.stage,
681 owner: record.owner ?? null,
682 next_step: record.nextStep ?? null,
683 next_at: record.nextAt ?? null,
684 by,
685 }),
686 addNote: (workspace, text, by) => call("admin_add_note", { workspace, text, by }),
687 workspaceInvoices: (workspace) => call("admin_workspace_invoices", { workspace }),
Billing lists every invoice and every staff change; signals carry follow-ups688 allInvoices: (filter = {}) => call("admin_invoices", { status: filter.status ?? null, month: filter.month ?? null }),
689 audit: (filter = {}) =>
690 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 look691 limitRequests: (status = "open") => call("admin_limit_requests", { status }),
692 decideLimitRequest: (id, decision, amountMicros, note, by) =>
693 call("admin_decide_limit_request", { id, decision, amount_micros: amountMicros, note, by }),
694 overages: () => call("admin_overages", {}),
695 goodwill: (workspace, amountMicros, reason, by, day = null) =>
696 call("admin_goodwill", { workspace, amount_micros: amountMicros, reason, by, day }),
697 velocity: () => call("admin_velocity", {}),
698 recordPayment: (workspace, amountMicros, reference, note, by) =>
699 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 daily700 costs: (days) => call("admin_costs", { days: days ?? null }),
701 costAlerts: () => call("admin_cost_alerts", {}),
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays702 spendCaps: () => call("admin_spend_caps", {}),
703 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)704 platformGuard: () => call("admin_platform_guard", {}),
705 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 daily706 decideProposal: (id, decision, note, by) => call("admin_decide_proposal", { id, decision, note, by }),
707 setCostSettings: (settings, by) => call("admin_set_cost_settings", { settings, by }),
708 setCostMapping: (mapping, by) =>
709 call("admin_set_cost_mapping", {
710 product: mapping.product,
711 meter: mapping.meter,
712 bucket: mapping.bucket ?? "",
713 price_meter: mapping.priceMeter ?? null,
714 own_meter: mapping.ownMeter ?? null,
715 scale_to_own: mapping.scaleToOwn ?? false,
716 drift_percent: mapping.driftPercent ?? null,
717 note: mapping.note ?? "",
718 remove: mapping.remove ?? false,
719 by,
720 }),
721 runCosts: (by) => call("admin_run_costs", { by }),
Merge branch 'main' into actions-toolkit-oidc-artifacts722 models: () => call("admin_models", {}),
723 decideModel: (model, decision, details, reason, by) =>
724 call("admin_decide_model", {
725 model,
726 decision,
727 name: details.name ?? null,
728 tier_hint: details.tierHint ?? null,
729 prices: details.prices
730 ? {
731 input_micros: details.prices.inputMicros,
732 output_micros: details.prices.outputMicros,
733 cache_read_micros: details.prices.cacheReadMicros,
734 cache_write_micros: details.prices.cacheWriteMicros,
735 cache_write_1h_micros: details.prices.cacheWrite1hMicros,
736 threshold: details.prices.threshold,
737 over_input_micros: details.prices.overInputMicros,
738 over_output_micros: details.prices.overOutputMicros,
739 over_cache_read_micros: details.prices.overCacheReadMicros,
740 over_cache_write_micros: details.prices.overCacheWriteMicros,
741 over_cache_write_1h_micros: details.prices.overCacheWrite1hMicros,
742 }
743 : null,
744 reason,
745 by,
746 }),
747 setModelDefault: (purpose, value, reason, by) =>
748 call("admin_set_model_default", {
749 purpose,
750 model: value.model ?? null,
751 tier: value.tier ?? null,
752 effort: value.effort ?? null,
753 reason,
754 by,
755 }),
Billing accounts, terms and enterprises; g1t is no longer free756 };
757}
758
Pull requests say where they are live; the project menu shows what is coming759export function eventsClient(service: ServiceBinding): EventsApi {
760 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
761 return {
762 publish: (events) => call("publish", { events }),
763 list: (query) => call("list", query),
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.764 activityDigest: (args) => call("activity_digest", args),
Pull requests say where they are live; the project menu shows what is coming765 };
766}
767
768export function integrationsClient(service: ServiceBinding): IntegrationsApi {
769 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
770 return {
771 list: (workspace, viewer) => call("list", { workspace, viewer }),
772 connect: (actor, workspace, input) => call("connect", { actor, workspace, ...input }),
773 update: (actor, workspace, id, input) => call("update", { actor, workspace, id, ...input }),
774 disconnect: (actor, workspace, id) => call("disconnect", { actor, workspace, id }),
775 test: (actor, workspace, id) => call("test", { actor, workspace, id }),
776 deliveries: (workspace, viewer, id) => call("deliveries", { workspace, viewer, id }),
777 resolve: (workspace, viewer, reference) => call("resolve", { workspace, viewer, reference }),
778 references: (workspace, text, limit) => call("references", { workspace, text, limit }),
779 import: (actor, repo, reference, assign) => call("import", { actor, repo, reference, assign }),
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.780 comment: (actor, workspace, reference, text, link) => call("comment", { actor, workspace, reference, text, link }),
781 close: (actor, workspace, reference, text, link) => call("close", { actor, workspace, reference, text, link }),
Pull requests say where they are live; the project menu shows what is coming782 links: (repo, number) => call("links", { repo, number }),
783 modelProvider: (workspace) => call("model_provider", { workspace }),
784 openModelSession: (run) => call("open_model_session", run),
785 modelUpstream: (token) => call("model_upstream", { token }),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens786 gatewayUpstream: (workspace) => call("gateway_upstream", { workspace }),
AI Gateway: OpenAI's format, open models, and your own providers787 gatewayProviders: (workspace) => call("gateway_providers", { workspace }),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily788 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)789 capModelSessions: (tokenHashes, capMicros) => call("cap_model_sessions", { token_hashes: tokenHashes, cap_micros: capMicros }),
Pull requests say where they are live; the project menu shows what is coming790 routes: (workspace, viewer) => call("routes", { workspace, viewer }),
791 setRoutes: (actor, workspace, routes) => call("set_routes", { actor, workspace, routes }),
792 };
793}
794
795export function webhooksClient(service: ServiceBinding): WebhooksApi {
796 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
797 return {
798 list: (viewer, owner) => call("list", { viewer, ...owner }),
799 create: (actor, owner, input) => call("create", { actor, ...owner, ...input }),
800 update: (actor, owner, id, input) => call("update", { actor, ...owner, id, ...input }),
801 delete: (actor, owner, id) => call("delete", { actor, ...owner, id }),
802 ping: (actor, owner, id) => call("ping", { actor, ...owner, id }),
803 deliveries: (viewer, owner, id) => call("deliveries", { viewer, ...owner, id }),
804 redeliver: (actor, owner, deliveryId) => call("redeliver", { actor, ...owner, deliveryId }),
805 };
806}
807
808export function actionsClient(service: ServiceBinding): ActionsApi {
809 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
810 return {
811 workflows: (repo, viewer) => call("workflows", { repo, viewer }),
812 runs: (repo, viewer, filter = {}) => call("runs", { repo, viewer, ...filter }),
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)813 run: (repo, viewer, id, attempt) => call("run", { repo, viewer, id, attempt: attempt ?? null }),
Pull requests say where they are live; the project menu shows what is coming814 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)815 summaries: (repo, viewer, id, attempt) => call("summaries", { repo, viewer, id, attempt: attempt ?? null }),
816 jobLogText: (repo, viewer, job) => call("job_log_text", { repo, viewer, job }),
817 runLogs: (repo, viewer, id, attempt) => call("run_logs", { repo, viewer, id, attempt: attempt ?? null }),
Pull requests say where they are live; the project menu shows what is coming818 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)819 cancel: (actor, repo, id, force = false) => call("cancel", { actor, repo, id, force }),
820 rerun: (actor, repo, id, failedOnly = false, options = {}) =>
821 call("rerun", { actor, repo, id, failed_only: failedOnly, job: options.job ?? null, debug: options.debug ?? false }),
Pull requests say where they are live; the project menu shows what is coming822 setWorkflowEnabled: (actor, repo, workflow, enabled) =>
823 call("set_workflow_enabled", { actor, repo, workflow, enabled }),
824 settings: (actor, owner, kind) => call("settings", { actor, ...owner, kind }),
825 setSetting: (actor, owner, kind, name, value, options = {}) =>
826 call("set_setting", { actor, ...owner, kind, name, value, ...options }),
827 deleteSetting: (actor, owner, kind, name, id) => call("delete_setting", { actor, ...owner, kind, name, id }),
Merge branch 'worktree-agent-a3abfcce648e87dca'828 approveRun: (actor, repo, id) => call("approve_run", { actor, repo, id }),
829 pendingDeployments: (repo, viewer, id) => call("pending_deployments", { repo, viewer, id }),
830 reviewDeployments: (actor, repo, id, state, environments = [], comment) =>
831 call("review_deployments", { actor, repo, id, state, environments, comment: comment ?? null }),
832 actionsSettings: (repo, viewer) => call("actions_settings", { repo, viewer }),
833 setActionsSettings: (actor, repo, change) => call("set_actions_settings", { actor, repo, ...change }),
834 workspaceActionsSettings: (workspace, viewer) => call("workspace_actions_settings", { workspace, viewer }),
835 setWorkspaceActionsSettings: (actor, workspace, change) =>
836 call("set_workspace_actions_settings", { actor, workspace, ...change }),
837 environments: (repo, viewer) => call("environments", { repo, viewer }),
838 setEnvironment: (actor, repo, name, change) => call("set_environment", { actor, repo, name, ...change }),
839 deleteEnvironment: (actor, repo, name) => call("delete_environment", { actor, repo, name }),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2840 artifacts: (repo, viewer, filter = {}) => call("artifacts", { repo, viewer, ...filter }),
841 artifactDownload: (repo, viewer, by) => call("artifact_download", { repo, viewer, ...by }),
842 deleteArtifact: (actor, repo, id) => call("delete_artifact", { actor, repo, id }),
843 artifactRetention: (repo, viewer, days) => call("artifact_retention", { repo, viewer, days }),
Pull requests say where they are live; the project menu shows what is coming844 };
845}
846
847
Fast pages, required checks on the branch, self-hosted runners, honest incidents848/** Self-hosted runners, kept by the actions service. */
849export function runnersClient(service: ServiceBinding): RunnersApi {
850 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
851 return {
852 stuck: (viewer) => call("stuck_jobs", { viewer }),
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.853 activity: (actor, workspace) => call("runner_activity", { actor, workspace }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents854 list: (actor, owner) => call("runners", { actor, ...owner }),
855 createToken: (actor, owner, group) => call("create_registration_token", { actor, ...owner, group }),
856 remove: (actor, owner, id) => call("remove_runner", { actor, ...owner, id }),
857 groups: (actor, workspace) => call("runner_groups", { actor, workspace }),
858 setGroup: (actor, workspace, group) => call("set_runner_group", { actor, workspace, ...group }),
859 deleteGroup: (actor, workspace, id) => call("delete_runner_group", { actor, workspace, id }),
860 settings: (actor, owner) => call("runner_settings", { actor, ...owner }),
861 setSettings: (actor, owner, change) => call("set_runner_settings", { actor, ...owner, ...change }),
862 };
863}
864
Pull requests say where they are live; the project menu shows what is coming865export function deploymentsClient(service: ServiceBinding): DeploymentsApi {
866 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
867 return {
868 settings: (project, viewer) => call("settings", { project, viewer }),
869 updateSettings: (actor, project, changes) => call("update_settings", { actor, project, changes }),
870 list: (project, viewer) => call("list", { project, viewer }),
871 get: (project, id, viewer) => call("get", { project, id, viewer }),
872 redeploy: (actor, project, branch) => call("redeploy", { actor, project, branch }),
873 takeDown: (actor, project, branch) => call("take_down", { actor, project, branch }),
874 overview: (workspace, viewer) => call("overview", { workspace, viewer }),
875 usage: (workspace, viewer) => call("usage", { workspace, viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains876 domains: (project, viewer) => call("domains", { project, viewer }),
877 addDomain: (actor, project, hostname, options = {}) =>
878 call("add_domain", { actor, project, hostname, twin: !!options.twin }),
879 removeDomain: (actor, project, id) => call("remove_domain", { actor, project, id }),
880 refreshDomain: (actor, project, id) => call("refresh_domain", { actor, project, id }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97881 repoDeployments: (repo, viewer, filter = {}) => call("list_deployments", { repo, viewer, ...filter }),
882 repoDeployment: (repo, id, viewer) => call("get_deployment", { repo, id, viewer }),
883 environments: (repo, viewer) => call("list_environments", { repo, viewer }),
884 createDeployment: (actor, repo, input) => call("create_deployment", { repo, actor, ...input }),
885 createDeploymentStatus: (actor, repo, id, input) => call("create_deployment_status", { repo, actor, id, ...input }),
Pull requests say where they are live; the project menu shows what is coming886 };
887}
888
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member889/** Packages: the registries beside the code (services/packages). */
890export function packagesClient(service: ServiceBinding): PackagesApi {
891 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
892 return {
893 list: (workspace, viewer, filter = {}) =>
894 call("list_packages", {
895 workspace,
896 viewer,
897 ecosystem: filter.ecosystem ?? null,
898 repo_id: filter.repoId ?? null,
899 query: filter.query ?? null,
900 }),
901 get: (workspace, ecosystem, name, viewer) => call("get_package", { workspace, ecosystem, name, viewer }),
902 deleteVersion: (actor, workspace, ecosystem, name, version, surface) =>
903 call("delete_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }),
904 deletePackage: (actor, workspace, ecosystem, name, surface) =>
905 call("delete_package", { actor, workspace, ecosystem, name, surface: surface ?? null }),
906 set: (actor, workspace, ecosystem, name, change, surface) =>
907 call("set_package", {
908 actor,
909 workspace,
910 ecosystem,
911 name,
912 visibility: change.visibility ?? null,
913 link: change.link ?? null,
914 unlink: change.unlink ?? false,
Merge packages: roles, Actions access, source label, soft delete, API915 inherit_access: change.inheritAccess ?? null,
916 surface: surface ?? null,
917 }),
918 settings: (workspace, ecosystem, name, viewer) => call("package_settings", { workspace, ecosystem, name, viewer }),
919 deleted: (workspace, viewer) => call("deleted_packages", { workspace, viewer }),
920 restorePackage: (actor, workspace, ecosystem, name, surface) =>
921 call("restore_package", { actor, workspace, ecosystem, name, surface: surface ?? null }),
922 restoreVersion: (actor, workspace, ecosystem, name, version, surface) =>
923 call("restore_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }),
924 setAccess: (actor, workspace, ecosystem, name, who, role, surface) =>
925 call("set_package_access", {
926 actor,
927 workspace,
928 ecosystem,
929 name,
930 user: "user" in who ? who.user : null,
931 team: "team" in who ? who.team : null,
932 role,
933 surface: surface ?? null,
934 }),
935 removeAccess: (actor, workspace, ecosystem, name, who, surface) =>
936 call("remove_package_access", {
937 actor,
938 workspace,
939 ecosystem,
940 name,
941 user: "user" in who ? who.user : null,
942 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 member943 surface: surface ?? null,
944 }),
Merge packages: roles, Actions access, source label, soft delete, API945 setActionsAccess: (actor, workspace, ecosystem, name, repo, role, surface) =>
946 call("set_actions_access", { actor, workspace, ecosystem, name, repo, role, surface: surface ?? null }),
947 removeActionsAccess: (actor, workspace, ecosystem, name, repo, surface) =>
948 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 member949 storage: (workspace) => call("storage", { workspace }),
950 storageAll: () => call("storage_all", {}),
Composer from the workspace's own repositories, and go get from g1t.sh951 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 member952 };
953}
954
Pull requests say where they are live; the project menu shows what is coming955export function projectsClient(service: ServiceBinding): ProjectsApi {
956 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
957 return {
958 list: (workspace, viewer) => call("list", { workspace, viewer }),
959 get: (workspace, slug, viewer) => call("get", { workspace, slug, viewer }),
960 byRepo: (repoId) => call("by_repo", { repoId }),
961 create: (actor, workspace, input) => call("create", { actor, workspace, input }),
962 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 production963 deploymentsChanged: (projectId, enabled) => call("deployments_changed", { projectId, enabled }),
Projects: pinned and recent projects, per person per workspace964 shortcuts: (workspace, viewer) => call("shortcuts", { workspace, viewer }),
965 pin: (actor, workspace, slug, position) => call("pin", { actor, workspace, slug, position: position ?? null }),
966 unpin: (actor, workspace, slug) => call("unpin", { actor, workspace, slug }),
967 reorderPins: (actor, workspace, slugs) => call("reorder_pins", { actor, workspace, slugs }),
968 visited: (actor, projectId) => call("visited", { actor, projectId }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97969 publicLinks: (repos) => call("public_links", { repos }),
Pull requests say where they are live; the project menu shows what is coming970 };
971}
Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next.972
973/**
974 * An agent's own computer, on the runner (services/runner `AgentComputer`):
975 * what the agents service calls for a session's `run_command` and file
976 * tools, and for the Computer tab. Arguments and answers are snake_case.
977 */
978export function runnerComputerClient(service: ServiceBinding): RunnerComputerApi {
979 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
980 return {
981 computerStatus: (agentId) => call("computer_status", { agent_id: agentId }),
982 computerWake: (args) => call("computer_wake", args),
983 computerExec: (args) => call("computer_exec", args),
984 computerReadFile: (args) => call("computer_read_file", args),
985 computerWriteFile: (args) => call("computer_write_file", args),
986 computerSleep: (agentId) => call("computer_sleep", { agent_id: agentId }),
987 computerReset: (agentId) => call("computer_reset", { agent_id: agentId }),
988 computerForget: (agentId) => call("computer_forget", { agent_id: agentId }),
989 computerCommands: (agentId, sessionId) => call("computer_commands", { agent_id: agentId, session_id: sessionId ?? null }),
990 };
991}

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