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

Deployments work end to end: fixes from the first live run1import type { ActionsApi } from "./actions";
Billing accounts, terms and enterprises; g1t is no longer free2import type { BillingAdminApi, BillingApi } from "./billing";
Deployments work end to end: fixes from the first live run3import type { DeploymentsApi } from "./deployments";
Projects: what a workspace builds and runs, first on every page4import type { ProjectsApi } from "./projects";
Deployments work end to end: fixes from the first live run5import type { EventsApi } from "./events";
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace6import type { IdentityAdminApi, IdentityApi } from "./identity";
Deployments work end to end: fixes from the first live run7import 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";
Deployments work end to end: fixes from the first live run9import 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";
Deployments work end to end: fixes from the first live run15
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 }),
Deployments work end to end: fixes from the first live run56 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 }),
Deployments work end to end: fixes from the first live run60 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 }),
Deployments work end to end: fixes from the first live run76 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 }),
Deployments work end to end: fixes from the first live run80 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" }),
Deployments work end to end: fixes from the first live run91 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 }),
Deployments work end to end: fixes from the first live run101 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" }),
Deployments work end to end: fixes from the first live run147 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) =>
163 call("invite_member", { actor, slug, email: who.email ?? "", username: who.username ?? null, role: who.role ?? null }),
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 }),
170 requestAccess: (email, about, client) => call("request_access", { email, about, client: client ?? null }),
Deployments work end to end: fixes from the first live run171 userForGitCredentials: (username, secret) =>
172 call("user_for_git_credentials", { username, secret }),
173 userForAccessToken: (token) => call("user_for_access_token", { token }),
174 userForSshKey: (fingerprint) => call("user_for_ssh_key", { fingerprint }),
175 userByUsername: (username) => call("user_by_username", { username }),
176 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)177 usersForAudience: (ids) => call("users_for_audience", { ids }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains178 profile: (username) => call("profile", { username }),
179 updateProfile: (actor, fields) => call("update_profile", { actor, ...fields }),
180 profileWorkspaces: (username, viewer, publicIn) =>
181 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.182 dockPins: (user, workspace) => call("dock_pins", { user, workspace }),
183 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.184 soundSettings: (user) => call("sound_settings", { user }),
185 setSoundSettings: (user, change) => call("set_sound_settings", { user, change }),
Deployments work end to end: fixes from the first live run186 listSshKeys: (user) => call("list_ssh_keys", { user }),
187 addSshKey: (user, title, publicKey) =>
188 call("add_ssh_key", { user, title, publicKey }),
189 removeSshKey: (user, id) => call("remove_ssh_key", { user, id }),
190 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 step191 createAccessToken: (user, name, ttlSeconds, grant) =>
192 call("create_access_token", {
193 user,
194 name,
195 ttlSeconds,
196 scopes: grant?.scopes ?? null,
197 listed: grant?.listed ?? false,
198 }),
Deployments work end to end: fixes from the first live run199 createAgentToken: (onBehalfOf, scope, ttlSeconds) =>
200 call("create_agent_token", { onBehalfOf, scope, ttlSeconds }),
201 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 API202 createRunCredential: (input) => call("create_run_credential", input),
203 bindRunCredentials: (tokenHashes, runId) => call("bind_run_credentials", { tokenHashes, runId }),
204 revokeRunCredentials: (target) => call("revoke_run_credentials", target),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look205 // Who has access to a repository; see access.ts.
206 repoAccess: (owner, name, viewer) => call("repo_access", { viewer, path: { namespace: owner, name } }),
207 addCollaborator: (actor, owner, name, invitee, role) =>
208 call("add_collaborator", { actor, path: { namespace: owner, name }, invitee, role }),
209 setCollaboratorRole: (actor, owner, name, username, role) =>
210 call("set_collaborator_role", { actor, path: { namespace: owner, name }, username, role }),
211 removeCollaborator: (actor, owner, name, username) =>
212 call("remove_collaborator", { actor, path: { namespace: owner, name }, username }),
213 collaboratorPermission: (viewer, owner, name, username) =>
214 call("collaborator_permission", { viewer, path: { namespace: owner, name }, username }),
215 myRepoInvitations: (user) => call("my_repo_invitations", { user }),
216 respondRepoInvitation: (user, id, accept) => call("respond_repo_invitation", { user, id, accept }),
217 revokeRepoInvitation: (actor, owner, name, id) =>
218 call("revoke_repo_invitation", { actor, path: { namespace: owner, name }, id }),
219 setBasePermission: (actor, slug, base) => call("set_base_permission", { actor, slug, base_permission: base }),
220 outsideCollaborators: (viewer, slug) => call("outside_collaborators", { viewer, slug }),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca221 // A repository's deploy keys; see deploy-keys.ts.
222 listDeployKeys: (viewer, owner, name) => call("list_deploy_keys", { viewer, path: { namespace: owner, name } }),
223 addDeployKey: (actor, owner, name, key) =>
224 call("add_deploy_key", { actor, path: { namespace: owner, name }, title: key.title, key: key.key, read_only: key.readOnly }),
225 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 bar226 // Teams; see teams.ts.
227 listTeams: (viewer, workspace, query) => call("list_teams", { viewer, workspace, query: query ?? null }),
228 getTeam: (viewer, workspace, team) => call("get_team", { viewer, workspace, team }),
229 createTeam: (actor, workspace, team) => call("create_team", { actor, workspace, ...team }),
Merge branch 'worktree-agent-ad7c6d88d93adc817'230 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 bar231 updateTeam: (actor, workspace, team, changes) => call("update_team", { actor, workspace, team, ...changes }),
232 deleteTeam: (actor, workspace, team) => call("delete_team", { actor, workspace, team }),
233 teamMembers: (viewer, workspace, team, includeChildTeams) =>
234 call("team_members", { viewer, workspace, team, include_child_teams: includeChildTeams ?? false }),
235 setTeamMember: (actor, workspace, team, username, role) =>
236 call("set_team_member", { actor, workspace, team, username, role }),
237 removeTeamMember: (actor, workspace, team, username) =>
238 call("remove_team_member", { actor, workspace, team, username }),
239 childTeams: (viewer, workspace, team) => call("child_teams", { viewer, workspace, team }),
240 teamRepos: (viewer, workspace, team) => call("team_repos", { viewer, workspace, team }),
241 setTeamRepo: (actor, workspace, team, owner, name, role) =>
242 call("set_team_repo", { actor, workspace, team, repo: { namespace: owner, name }, role }),
243 removeTeamRepo: (actor, workspace, team, owner, name) =>
244 call("remove_team_repo", { actor, workspace, team, repo: { namespace: owner, name } }),
245 userTeams: (viewer, workspace, username) => call("user_teams", { viewer, workspace, username }),
246 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.247 teamAgents: (viewer, workspace, team) => call("team_agents", { viewer, workspace, team }),
248 setTeamAgent: (actor, workspace, team, agentId) => call("set_team_agent", { actor, workspace, team, agent_id: agentId }),
249 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.250 agentTeams: (workspace, agentId) => call("agent_teams", { workspace, agent_id: agentId }),
251 teamAgentIndex: (workspace) => call("team_agent_index", { workspace }),
252 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.253 // People; see people.ts.
254 peopleDirectory: (viewer, workspace) => call("people_directory", { viewer, workspace }),
255 setMemberProfile: (actor, workspace, username, changes) => call("set_member_profile", { actor, workspace, username, ...changes }),
Deployments work end to end: fixes from the first live run256 };
257}
258
Agents and memory, checks and conflicts, profiles, slug renames, custom domains259/**
260 * The slug a renamed workspace has now, for a `workspace.renamed` handler:
261 * asked of identity by the workspace's id, so that renames delivered twice
262 * or out of order converge. Falls back to the event's `to`.
263 */
264export async function currentWorkspaceSlug(
265 identity: ServiceBinding,
266 renamed: { workspaceId: string; to: string },
267): Promise<string> {
268 const names = await identityClient(identity).usernames([renamed.workspaceId]);
269 return names[renamed.workspaceId] ?? renamed.to;
270}
271
272/** The slugs whose rows move to `current`: the two a rename names, less `current`. */
273export function staleSlugs(renamed: { from: string; to: string }, current: string): string[] {
274 return [...new Set([renamed.from, renamed.to])].filter((slug) => slug !== current);
275}
276
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look277/**
278 * Where a transferred repository is now, as `namespace/name`, for a
279 * `repo.transferred` handler: asked of repos by id, so transfers delivered
280 * twice or out of order converge. Falls back to the event's destination.
281 */
282export async function currentRepoPath(
283 repos: ServiceBinding,
284 transferred: { repoId: string; name: string; to: string },
285): Promise<string> {
286 const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: transferred.repoId });
287 return path ? `${path.namespace}/${path.name}` : `${transferred.to}/${transferred.name}`;
288}
289
290/** The paths whose rows move to `current`: the two a transfer names, less `current`. */
291export function stalePaths(transferred: { name: string; from: string; to: string }, current: string): string[] {
292 return [...new Set([transferred.from, transferred.to].map((ns) => `${ns}/${transferred.name}`))].filter(
293 (path) => path !== current,
294 );
295}
296
297/**
298 * A repository's path change, from `repo.transferred` or `repo.renamed`,
299 * read the same way: the two paths (`namespace/name`) the event names, old
300 * then new. Null for any other event.
301 */
302export type RepoMove = { repoId: string; paths: [string, string] };
303
304export function repoMove(event: { type: string; data: unknown }): RepoMove | null {
305 const data = event.data as Record<string, string>;
306 if (event.type === "repo.transferred") {
307 return { repoId: data.repoId!, paths: [`${data.from}/${data.name}`, `${data.to}/${data.name}`] };
308 }
309 if (event.type === "repo.renamed") {
310 return { repoId: data.repoId!, paths: [`${data.namespace}/${data.from}`, `${data.namespace}/${data.to}`] };
311 }
312 return null;
313}
314
315/**
316 * Where a moved repository is now, as `namespace/name`: asked of repos by
317 * id, so moves delivered twice or out of order converge. Falls back to the
318 * event's new path.
319 */
320export async function currentMovedPath(repos: ServiceBinding, move: RepoMove): Promise<string> {
321 const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: move.repoId });
322 return path ? `${path.namespace}/${path.name}` : move.paths[1];
323}
324
325/** The paths whose rows move to `current`: the two a move names, less `current`. */
326export function staleMovedPaths(move: RepoMove, current: string): string[] {
327 return [...new Set(move.paths)].filter((path) => path !== current);
328}
329
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace330/** Staff-only identity. Only sudo binds to it; see `IdentityAdminApi`. */
331export function identityAdminClient(service: ServiceBinding): IdentityAdminApi {
332 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
333 return {
334 workspaces: (query) => call("admin_workspaces", { query: query ?? null }),
335 workspace: (slug) => call("admin_workspace", { slug }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look336 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 quotas337 waitlistPending: () => call("admin_waitlist_pending", {}),
338 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 look339 invites: (query) => call("admin_invites", { query: query ?? null }),
340 revokeInvite: (id, staff) => call("admin_revoke_invite", { id, staff }),
341 mintInvite: (email, staff) => call("admin_mint_invite", { email, staff }),
342 grantInvites: (target, name, amount, note, staff) =>
343 call("admin_grant_invites", { target, name, amount, note, staff }),
344 inviteTree: (username) => call("admin_invite_tree", { username }),
345 workspaceInvites: (slug) => call("admin_workspace_invites", { slug }),
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)346 sharedInvites: () => call("admin_shared_invites", {}),
347 createSharedInvite: (link, staff) =>
348 call("admin_create_shared_invite", {
349 label: link.label,
350 max_uses: link.maxUses,
351 expires_on: link.expiresOn,
352 domains: link.domains,
353 staff,
354 }),
355 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 member356 deletedWorkspaces: () => call("admin_deleted_workspaces", {}),
357 restoreWorkspace: (workspaceId, staff) => call("admin_restore_workspace", { workspaceId, staff }),
358 purgeWorkspace: (workspaceId, staff, confirm) => call("admin_purge_workspace", { workspaceId, staff, confirm }),
Merge branch 'worktree-agent-a8385d293d42c913a'359 aliases: () => call("admin_aliases", {}),
360 setAlias: (alias, workspace, note, staff) => call("admin_set_alias", { alias, workspace, note, staff }),
361 removeAlias: (alias, reason, staff) => call("admin_remove_alias", { alias, reason, staff }),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace362 };
363}
364
Deployments work end to end: fixes from the first live run365export function reposClient(service: ServiceBinding): ReposApi {
366 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
367 return {
368 get: (path, viewer) => call("get", { path, viewer }),
369 getById: (id, viewer) => call("get_by_id", { id, viewer }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents370 readable: (ids, viewer) => call("readable", { ids, viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains371 publicNamespaces: (ownerId) => call("public_namespaces", { ownerId }),
Deployments work end to end: fixes from the first live run372 list: (viewer, options = {}) => call("list", { viewer, ...options }),
373 create: (owner, input) => call("create", { owner, ...input }),
374 update: (actor, path, changes) => call("update", { actor, path, ...changes }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look375 transfer: (actor, path, to) => call("transfer", { actor, path, to }),
376 delete: (actor, path, confirm) => call("delete", { actor, path, confirm }),
377 deleted: (viewer, namespace) => call("deleted", { viewer, namespace }),
378 restore: (actor, path) => call("restore", { actor, path }),
379 purge: (actor, path, confirm) => call("purge", { actor, path, confirm }),
380 rename: (actor, path, name) => call("rename", { actor, path, name }),
381 archive: (actor, path, archived) => call("archive", { actor, path, archived }),
382 setVisibility: (actor, path, isPrivate, confirm) => call("set_visibility", { actor, path, isPrivate, confirm }),
383 setDefaultBranch: (actor, path, branch) => call("set_default_branch", { actor, path, branch }),
384 renameBranch: (actor, path, from, to) => call("rename_branch", { actor, path, from, to }),
385 resolveBranch: (repoId, branch) => call("resolve_branch", { repoId, branch }),
386 statusById: (id) => call("status_by_id", { id }),
Merge branch 'worktree-agent-a2013627e5ea4ab13'387 storageOptions: () => call("storage_options", {}),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look388 resolvePath: (path) => call("resolve_path", { path }),
Deployments work end to end: fixes from the first live run389 tree: (path, viewer, ref, treePath) =>
390 call("tree", { path, viewer, ref, treePath }),
391 blob: (path, viewer, ref, filePath) =>
392 call("blob", { path, viewer, ref, filePath }),
393 log: (path, viewer, ref, limit) => call("log", { path, viewer, ref, limit }),
394 blame: (path, viewer, ref, filePath) => call("blame", { path, viewer, ref, filePath }),
395 forkForPull: (sourceId, pullId, actor) =>
396 call("fork_for_pull", { sourceId, pullId, actor }),
397 gitAccess: (path, viewer, service) =>
398 call("git_access", { path, viewer, service }),
399 branches: (path, viewer) => call("branches", { path, viewer }),
Branches and Tags pages, each file's last commit, and the branch menu on files400 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 25401 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 files402 tags: (path, viewer) => call("tags", { path, viewer }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97403 about: (path, viewer) => call("about", { path, viewer }),
404 languages: (path, viewer) => call("languages", { path, viewer }),
405 contributors: (path, viewer) => call("contributors", { path, viewer }),
406 license: (path, viewer) => call("license", { path, viewer }),
407 stars: (path, viewer) => call("stars", { path, viewer }),
408 star: (actor, path, starred) => call("star", { path, actor, starred }),
409 stargazers: (path, viewer, page) => call("stargazers", { path, viewer, page: page ?? null }),
410 starred: (username, viewer) => call("starred", { username, viewer }),
411 releases: (path, viewer) => call("releases", { path, viewer }),
412 release: (path, viewer, which) => call("release", { path, viewer, id: which.id ?? null, tag: which.tag ?? null, latest: which.latest ?? false }),
413 createRelease: (actor, path, release) => call("create_release", { path, actor, ...release }),
414 updateRelease: (actor, path, id, change) => call("update_release", { path, actor, id, ...change }),
415 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 paths416 listFiles: (repoId, ref, limit) => call("list_files", { repoId, ref, skipDirs: [], limit }),
417 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 address418 rawFile: (repoId, ref, path, maxBytes) => call("raw_file", { repoId, ref, path, maxBytes }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents419 commitFile: (repo, actor, file) => call("commit_file", { repo, actor, ...file }),
Deployments work end to end: fixes from the first live run420 land: (sourceId, actor, branch) => call("land", { sourceId, actor, branch }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar421 compare: (repoId, viewer, base, head, baseBranch) => call("compare", { repoId, viewer, base, head, baseBranch }),
Merge branch 'worktree-agent-ac5b181a013e54348'422 claimBackups: (limit, maxRunning) => call("claim_backups", { limit, maxRunning }),
423 // The sandbox's own calls are snake_case (they come through the API).
424 failBackup: (jobId, token, error) => call("backup_fail", { job_id: jobId, token, error }),
Deployments work end to end: fixes from the first live run425 };
426}
427
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar428/**
429 * `PullDetail`'s own fields that the work service writes in snake_case:
430 * `g1t_contracts::work::PullDetail` has no camelCase renaming, though what
431 * it holds (`Pull`, `CheckRun` and the rest) does. Each with its name here.
432 */
433const PULL_DETAIL_FIELDS = [
434 ["review_pending", "reviewPending"],
435 ["earlier_checks", "earlierChecks"],
436 ["required_checks", "requiredChecks"],
437 ["code_owners", "codeOwners"],
438] as const;
439
440/**
441 * A pull request's detail as the work service sent it, with its own fields
442 * in the camelCase this package uses: read at the edge, by `workClient`'s
443 * `getPull`. Either spelling is taken, so a service that already sends
444 * camelCase reads the same.
445 */
446export function pullDetailFromWire(raw: Record<string, unknown>): PullDetail {
447 const detail: Record<string, unknown> = { ...raw };
448 for (const [snake, camel] of PULL_DETAIL_FIELDS) {
449 if (snake in detail) {
450 if (!(camel in detail)) detail[camel] = detail[snake];
451 delete detail[snake];
452 }
453 }
454 if (typeof detail.reviewPending !== "boolean") detail.reviewPending = false;
455 return detail as PullDetail;
456}
457
Deployments work end to end: fixes from the first live run458export function workClient(service: ServiceBinding): WorkApi {
459 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
460 return {
461 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 step462 delegateIssue: (actor, repo, input) => call("delegate_issue", { actor, repo, ...input }),
Deployments work end to end: fixes from the first live run463 listIssues: (repo, viewer, filter = {}) => call("list_issues", { repo, viewer, ...filter }),
464 getIssue: (repo, number, viewer) => call("get_issue", { repo, number, viewer }),
465 updateIssue: (actor, repo, number, input) =>
466 call("update_issue", { actor, repo, number, ...input }),
467 closeIssue: (actor, repo, number, reason) =>
468 call("close_issue", { actor, repo, number, reason }),
469 reopenIssue: (actor, repo, number) => call("reopen_issue", { actor, repo, number }),
470 listLabels: (repo, viewer) => call("list_labels", { repo, viewer }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar471 saveLabel: (actor, repo, label) => call("save_label", { actor, repo, ...label }),
472 deleteLabel: (actor, repo, name) => call("delete_label", { actor, repo, name }),
473 addDefaultLabels: (actor, repo) => call("add_default_labels", { actor, repo }),
474 setLabels: (actor, repo, number, labels, change = "set") =>
475 call("set_labels", { actor, repo, number, labels, change }),
476 listMilestones: (repo, viewer, state) => call("list_milestones", { repo, viewer, state }),
477 getMilestone: (repo, number, viewer) => call("get_milestone", { repo, number, viewer }),
478 saveMilestone: (actor, repo, milestone) => call("save_milestone", { actor, repo, ...milestone }),
479 deleteMilestone: (actor, repo, number) => call("delete_milestone", { actor, repo, number }),
Deployments work end to end: fixes from the first live run480 counts: (repo, viewer) => call("counts", { repo, viewer }),
481 addComment: (actor, repo, number, comment) =>
482 call("add_comment", { actor, repo, number, ...comment }),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts483 editComment: (actor, repo, commentId, body) => call("edit_comment", { actor, repo, commentId, body }),
484 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 calendar485 workspaceAgentComment: (repo, number, agent, actingFor, body) =>
486 call("workspace_agent_comment", { repo, number, agent, acting_for: actingFor, body }),
487 workspaceAgentReview: (repo, number, agent, actingFor, verdict, body) =>
488 call("workspace_agent_review", { repo, number, agent, acting_for: actingFor, verdict, body }),
Deployments work end to end: fixes from the first live run489 startChecks: (pullId) => call("start_checks", { pullId }),
490 reportChecks: (runId, token, report) =>
491 call("report_checks", { runId, token, ...report }),
492 startReview: (pullId) => call("start_review", { pullId }),
493 failReview: (runId, token, error) => call("report_review", { runId, token, error }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains494 startMergecheck: (pullId) => call("start_mergecheck", { pullId }),
495 failMergecheck: (pullId, token, error) => call("report_mergecheck", { pullId, token, error }),
Deployments work end to end: fixes from the first live run496 advance: (pullId) => call("advance", { pullId }),
497 stall: (pullId, reason) => call("stall", { pullId, reason }),
498 managedPulls: (repoId) => call("managed_pulls", { repoId }),
499 queue: (repo, viewer) => call("queue", { repo, viewer }),
500 queueBuild: (repoId) => call("queue_build", { repoId }),
501 failQueue: (entryId, token, error) => call("report_queue", { entryId, token, error }),
502 removeFromQueue: (actor, repo, number) => call("remove_from_queue", { actor, repo, number }),
503 messageAgent: (actor, repo, number, body) => call("message_agent", { actor, repo, number, body }),
504 catchUpJob: (pullId) => call("catch_up_job", { pullId }),
505 wakeForMessages: (pullId) => call("wake_for_messages", { pullId }),
506 getSettings: (repo, viewer) => call("get_settings", { repo, viewer }),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge507 // Rulesets travel in snake_case (rules.ts).
508 listRulesets: (owner, viewer, includeParents = false) =>
509 call("list_rulesets", { viewer, ...owner, include_parents: includeParents }),
510 getRuleset: (owner, id, viewer) => call("get_ruleset", { viewer, ...owner, id }),
511 saveRuleset: (actor, owner, ruleset, id) => call("save_ruleset", { actor, ...owner, id: id ?? null, ruleset }),
512 deleteRuleset: (actor, owner, id) => call("delete_ruleset", { actor, ...owner, id }),
513 effectiveRules: (repo, name, viewer, target = "branch") => call("effective_rules", { viewer, repo, name, target }),
514 ruleEvaluations: (owner, viewer, filter = {}) => call("rule_evaluations", { viewer, ...owner, ...filter }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents515 seenChecks: (repo, viewer) => call("seen_checks", { repo, viewer }),
Merge checks: statuses and check runs on every commit516 commitChecks: (repo, viewer, shas) => call("commit_checks", { repo, viewer, shas }),
517 getCheckRun: (repo, id, viewer) => call("get_check_run", { repo, id, viewer }),
518 checkRunAnnotations: (repo, id, viewer) => call("check_run_annotations", { repo, id, viewer }),
519 requestCheckAction: (actor, repo, id, identifier) => call("request_check_action", { actor, repo, id, identifier }),
520 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 bar521 codeownersErrors: (repo, viewer, ref) => call("codeowners_errors", { repo, viewer, ref: ref ?? null }),
Deployments work end to end: fixes from the first live run522 updateSettings: (actor, repo, settings) =>
523 call("update_settings", { actor, repo, settings }),
524 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 bar525 listPulls: (repo, viewer, state, filter = {}) => call("list_pulls", { repo, viewer, state, ...filter }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents526 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 bar527 // Its own fields arrive in snake_case: read into camelCase here.
528 getPull: (repo, number, viewer) =>
529 call<Result<Record<string, unknown>>>("get_pull", { repo, number, viewer }).then(
530 (found): Result<PullDetail> => (found.ok ? { ok: true, value: pullDetailFromWire(found.value) } : found),
531 ),
Deployments work end to end: fixes from the first live run532 updatePull: (actor, repo, number, changes) =>
533 call("update_pull", { actor, repo, number, ...changes }),
Catching up with main takes seconds when the two sides touched different files534 catchUpPull: (actor, repo, number) => call("catch_up_pull", { actor, repo, number }),
Deployments work end to end: fixes from the first live run535 readyPull: (actor, repo, number, summary) =>
536 call("ready_pull", { actor, repo, number, summary }),
537 closePull: (actor, repo, number) => call("close_pull", { actor, repo, number }),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts538 reopenPull: (actor, repo, number) => call("reopen_pull", { actor, repo, number }),
539 convertPullToDraft: (actor, repo, number) => call("convert_pull_to_draft", { actor, repo, number }),
Deployments work end to end: fixes from the first live run540 mergePull: (actor, repo, number, options = {}) =>
541 call("merge_pull", { actor, repo, number, ...options }),
542 listActivePulls: (viewer) => call("list_active_pulls", { viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains543 byAuthor: (username, viewer, filter = {}) => call("by_author", { username, viewer, ...filter }),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project544 contributions: (username, viewer) => call("contributions", { username, viewer }),
Deployments work end to end: fixes from the first live run545 startPlan: (actor, repo, brief) => call("start_plan", { actor, repo, brief }),
546 failPlan: (planId, token, error) => call("report_plan", { planId, token, error }),
547 getPlan: (repo, viewer, id) => call("get_plan", { repo, viewer, id }),
548 listPlans: (repo, viewer) => call("list_plans", { repo, viewer }),
549 applyPlan: (actor, repo, id, options = {}) =>
550 call("apply_plan", { actor, repo, id, ...options }),
551 queueIssue: (actor, repo, number, queued) =>
552 call("queue_issue", { actor, repo, number, queued }),
553 readyIssues: (repoId) => call("ready_issues", { repoId }),
554 listAssignedIssues: (viewer) => call("list_assigned_issues", { viewer }),
555 appendSession: (actor, repo, number, entries) =>
556 call("append_session", { actor, repo, number, entries }),
557 readSession: (repo, number, viewer, afterSeq = 0) =>
558 call("read_session", { repo, number, viewer, afterSeq }),
559 };
560}
561
562export function billingClient(service: ServiceBinding): BillingApi {
563 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
564 return {
565 status: () => call("status", {}),
566 account: (workspace, viewer) => call("account", { workspace, viewer }),
567 ledger: (workspace, viewer) => call("ledger", { workspace, viewer }),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging568 credits: (workspace, viewer) => call("credits", { workspace, viewer }),
The statement is a month at a time, a line per kind of charge569 statement: (workspace, viewer, month = null, group = "day") => call("statement", { workspace, viewer, month, group }),
570 statementEntries: (workspace, viewer, filter) =>
571 call("statement_entries", {
572 workspace,
573 viewer,
574 month: filter.month,
575 kind: filter.kind,
576 day: filter.day ?? null,
577 project: filter.project ?? null,
578 before: filter.before ?? null,
579 }),
Deployments work end to end: fixes from the first live run580 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 mix581 tokenUsage: (workspace, viewer, options = {}) =>
582 call("token_usage", { workspace, viewer, person: options.person ?? null, days: options.days ?? null }),
583 recordTokens: (usage) => call("record_tokens", usage),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens584 gatewayModels: () => call("gateway_models", {}),
Merge branch 'main' into actions-toolkit-oidc-artifacts585 modelDefaults: () => call("model_defaults", {}),
586 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 tokens587 gatewayAdmit: (workspace) => call("gateway_admit", { workspace }),
588 recordGateway: (record) => call("record_gateway", record),
589 gatewayRequests: (workspace, viewer, options = {}) =>
590 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 look591 checkout: (actor, workspace, amountCents, returnUrl, method = "card") =>
592 call("checkout", { actor, workspace, amountCents, returnUrl, method }),
Deployments work end to end: fixes from the first live run593 confirm: (workspace, viewer, session) => call("confirm", { workspace, viewer, session }),
594 canStart: (workspace) => call("can_start", { workspace }),
595 trial: (workspace, exempt) => call("trial", { workspace, exempt }),
596 startRun: (run) => call("start_run", run),
597 features: (workspace, viewer) => call("features", { workspace, viewer }),
598 subscribe: (actor, workspace, feature, returnUrl) =>
599 call("subscribe", { actor, workspace, feature, returnUrl }),
600 confirmSubscription: (workspace, viewer, session) =>
601 call("confirm_subscription", { workspace, viewer, session }),
602 cancelSubscription: (actor, workspace, feature, resume = false) =>
603 call("cancel_subscription", { actor, workspace, feature, resume }),
604 hasFeature: (workspace, feature) => call("has_feature", { workspace, feature }),
Merge Stripe Tax, the card fee on card payments, and one free workspace per person605 freeWorkspaces: (workspaces) => call("free_workspaces", { workspaces }),
Deployments work end to end: fixes from the first live run606 chargeFeature: (charge) => call("charge_feature", charge),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace607 billingPortal: (actor, workspace, returnUrl) => call("billing_portal", { actor, workspace, return_url: returnUrl }),
Every sandbox is metered by the second608 recordSandbox: (usage) => call("record_sandbox", usage),
Usage limits: unpaid usage can only go so far609 limit: (workspace, viewer) => call("limit", { workspace, viewer }),
610 checkLimit: (workspace) => call("check_limit", { workspace }),
Prices keep themselves current with what g1t pays611 prices: () => call("prices", {}),
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas612 notePending: (workspace, source, costMicros, detail = null) => call("note_pending", { workspace, source, costMicros, detail }),
613 usageMeters: (workspace, viewer) => call("usage_meters", { workspace, viewer }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look614 setSpendLimit: (actor, workspace, spendLimitMicros, useFullLimit = false, raiseOnce = false) =>
615 call("set_spend_limit", { actor, workspace, spendLimitMicros, use_full_limit: useFullLimit, raise_once: raiseOnce }),
Two limits, real invoices, trust that grows by itself, sales signals616 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 put617 entitlements: (workspace) => call("entitlements", { workspace }),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look618 reserve: (reservation) => call("reserve", reservation),
619 settle: (reservationId, actualMicros) => call("settle", { reservationId, actualMicros }),
620 cardCheck: (actor, workspace, returnUrl) => call("card_check", { actor, workspace, returnUrl }),
621 confirmCardCheck: (workspace, viewer, session) => call("confirm_card_check", { workspace, viewer, session }),
622 requestLimit: (actor, workspace, request) => call("request_limit", { actor, workspace, ...request }),
623 limitRequests: (workspace, viewer) => call("limit_requests", { workspace, viewer }),
624 confirmSpike: (actor, workspace, keepGoing) => call("confirm_spike", { actor, workspace, keepGoing }),
625 setCaps: (actor, workspace, caps) => call("set_caps", { actor, workspace, ...caps }),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit626 usageReport: (workspace, viewer, range) =>
627 call("usage_report", { workspace, viewer, from: range.from, until: range.until, products: range.products ?? [], projects: range.projects ?? [] }),
628 aiCredit: (workspace, viewer) => call("ai_credit", { workspace, viewer }),
629 buyAiCredit: (actor, workspace, amountCents, returnUrl) => call("buy_ai_credit", { actor, workspace, amountCents, returnUrl }),
630 confirmAiCredit: (workspace, viewer, session) => call("confirm_ai_credit", { workspace, viewer, session }),
631 setAiReload: (actor, workspace, reload) => call("set_ai_reload", { actor, workspace, ...reload }),
632 setBudget: (actor, workspace, budget) => call("set_budget", { actor, workspace, ...budget }),
633 billingDetails: (workspace, viewer) => call("billing_details", { workspace, viewer }),
634 setBillingDetails: (actor, workspace, details) => call("set_billing_details", { actor, workspace, ...details }),
Deployments work end to end: fixes from the first live run635 };
636}
637
Billing accounts, terms and enterprises; g1t is no longer free638export function billingAdminClient(service: ServiceBinding): BillingAdminApi {
639 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
640 return {
641 accounts: (query) => call("admin_accounts", { query: query ?? null }),
642 account: (id) => call("admin_account", { id }),
643 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 put644 setAllowances: (id, allowances, note, by) => call("admin_set_allowances", { id, allowances, note, by }),
Billing accounts, terms and enterprises; g1t is no longer free645 createEnterprise: (name, workspaces, by) => call("admin_create_enterprise", { name, workspaces, by }),
646 attach: (workspace, account, by) => call("admin_attach", { workspace, account, by }),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging647 credit: (workspace, amountMicros, note, by, options = { kind: "goodwill" }) =>
648 call("admin_credit", {
649 workspace,
650 amount_micros: amountMicros,
651 note,
652 by,
653 kind: options.kind,
654 expires_at: options.expiresAt ?? null,
655 refund_for: options.refundFor ?? null,
656 refund_day: options.refundDay ?? null,
657 }),
658 credits: (filter = {}) =>
659 call("admin_credits", {
660 workspace: filter.workspace ?? null,
661 kind: filter.kind ?? null,
662 month: filter.month ?? null,
663 by: filter.by ?? null,
664 }),
665 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's666 resetBilling: (workspace, confirm, note, by) => call("admin_reset_billing", { workspace, confirm, note, by }),
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace667 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 dashboard668 stripe: (fix = false, by) => call("admin_stripe", { fix, by: by ?? null }),
Stripe webhooks, enterprise invoices, and sudo for both669 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 person670 enterpriseAddress: (id, address, taxIdType, taxId, by) => call("admin_enterprise_address", { id, address, taxIdType, taxId, by }),
Stripe webhooks, enterprise invoices, and sudo for both671 invoiceEnterprise: (id, by) => call("admin_invoice_enterprise", { id, by }),
672 accountsFor: (workspaces) => call("admin_accounts", { query: null, workspaces }),
Two limits, real invoices, trust that grows by itself, sales signals673 signals: () => call("admin_signals", {}),
674 overview: () => call("admin_overview", {}),
675 sales: (workspace) => call("admin_sales", { workspace }),
676 setSales: (workspace, record, by) =>
677 call("admin_set_sales", {
678 workspace,
679 stage: record.stage,
680 owner: record.owner ?? null,
681 next_step: record.nextStep ?? null,
682 next_at: record.nextAt ?? null,
683 by,
684 }),
685 addNote: (workspace, text, by) => call("admin_add_note", { workspace, text, by }),
686 workspaceInvoices: (workspace) => call("admin_workspace_invoices", { workspace }),
Billing lists every invoice and every staff change; signals carry follow-ups687 allInvoices: (filter = {}) => call("admin_invoices", { status: filter.status ?? null, month: filter.month ?? null }),
688 audit: (filter = {}) =>
689 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 look690 limitRequests: (status = "open") => call("admin_limit_requests", { status }),
691 decideLimitRequest: (id, decision, amountMicros, note, by) =>
692 call("admin_decide_limit_request", { id, decision, amount_micros: amountMicros, note, by }),
693 overages: () => call("admin_overages", {}),
694 goodwill: (workspace, amountMicros, reason, by, day = null) =>
695 call("admin_goodwill", { workspace, amount_micros: amountMicros, reason, by, day }),
696 velocity: () => call("admin_velocity", {}),
697 recordPayment: (workspace, amountMicros, reference, note, by) =>
698 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 daily699 costs: (days) => call("admin_costs", { days: days ?? null }),
700 costAlerts: () => call("admin_cost_alerts", {}),
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays701 spendCaps: () => call("admin_spend_caps", {}),
702 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)703 platformGuard: () => call("admin_platform_guard", {}),
704 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 daily705 decideProposal: (id, decision, note, by) => call("admin_decide_proposal", { id, decision, note, by }),
706 setCostSettings: (settings, by) => call("admin_set_cost_settings", { settings, by }),
707 setCostMapping: (mapping, by) =>
708 call("admin_set_cost_mapping", {
709 product: mapping.product,
710 meter: mapping.meter,
711 bucket: mapping.bucket ?? "",
712 price_meter: mapping.priceMeter ?? null,
713 own_meter: mapping.ownMeter ?? null,
714 scale_to_own: mapping.scaleToOwn ?? false,
715 drift_percent: mapping.driftPercent ?? null,
716 note: mapping.note ?? "",
717 remove: mapping.remove ?? false,
718 by,
719 }),
720 runCosts: (by) => call("admin_run_costs", { by }),
Merge branch 'main' into actions-toolkit-oidc-artifacts721 models: () => call("admin_models", {}),
722 decideModel: (model, decision, details, reason, by) =>
723 call("admin_decide_model", {
724 model,
725 decision,
726 name: details.name ?? null,
727 tier_hint: details.tierHint ?? null,
728 prices: details.prices
729 ? {
730 input_micros: details.prices.inputMicros,
731 output_micros: details.prices.outputMicros,
732 cache_read_micros: details.prices.cacheReadMicros,
733 cache_write_micros: details.prices.cacheWriteMicros,
734 cache_write_1h_micros: details.prices.cacheWrite1hMicros,
735 threshold: details.prices.threshold,
736 over_input_micros: details.prices.overInputMicros,
737 over_output_micros: details.prices.overOutputMicros,
738 over_cache_read_micros: details.prices.overCacheReadMicros,
739 over_cache_write_micros: details.prices.overCacheWriteMicros,
740 over_cache_write_1h_micros: details.prices.overCacheWrite1hMicros,
741 }
742 : null,
743 reason,
744 by,
745 }),
746 setModelDefault: (purpose, value, reason, by) =>
747 call("admin_set_model_default", {
748 purpose,
749 model: value.model ?? null,
750 tier: value.tier ?? null,
751 effort: value.effort ?? null,
752 reason,
753 by,
754 }),
Billing accounts, terms and enterprises; g1t is no longer free755 };
756}
757
Deployments work end to end: fixes from the first live run758export function eventsClient(service: ServiceBinding): EventsApi {
759 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
760 return {
761 publish: (events) => call("publish", { events }),
762 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.763 activityDigest: (args) => call("activity_digest", args),
Deployments work end to end: fixes from the first live run764 };
765}
766
767export function integrationsClient(service: ServiceBinding): IntegrationsApi {
768 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
769 return {
770 list: (workspace, viewer) => call("list", { workspace, viewer }),
771 connect: (actor, workspace, input) => call("connect", { actor, workspace, ...input }),
772 update: (actor, workspace, id, input) => call("update", { actor, workspace, id, ...input }),
773 disconnect: (actor, workspace, id) => call("disconnect", { actor, workspace, id }),
774 test: (actor, workspace, id) => call("test", { actor, workspace, id }),
775 deliveries: (workspace, viewer, id) => call("deliveries", { workspace, viewer, id }),
776 resolve: (workspace, viewer, reference) => call("resolve", { workspace, viewer, reference }),
777 references: (workspace, text, limit) => call("references", { workspace, text, limit }),
778 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.779 comment: (actor, workspace, reference, text, link) => call("comment", { actor, workspace, reference, text, link }),
780 close: (actor, workspace, reference, text, link) => call("close", { actor, workspace, reference, text, link }),
Deployments work end to end: fixes from the first live run781 links: (repo, number) => call("links", { repo, number }),
782 modelProvider: (workspace) => call("model_provider", { workspace }),
783 openModelSession: (run) => call("open_model_session", run),
784 modelUpstream: (token) => call("model_upstream", { token }),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens785 gatewayUpstream: (workspace) => call("gateway_upstream", { workspace }),
AI Gateway: OpenAI's format, open models, and your own providers786 gatewayProviders: (workspace) => call("gateway_providers", { workspace }),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily787 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)788 capModelSessions: (tokenHashes, capMicros) => call("cap_model_sessions", { token_hashes: tokenHashes, cap_micros: capMicros }),
Deployments work end to end: fixes from the first live run789 routes: (workspace, viewer) => call("routes", { workspace, viewer }),
790 setRoutes: (actor, workspace, routes) => call("set_routes", { actor, workspace, routes }),
791 };
792}
793
794export function webhooksClient(service: ServiceBinding): WebhooksApi {
795 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
796 return {
797 list: (viewer, owner) => call("list", { viewer, ...owner }),
798 create: (actor, owner, input) => call("create", { actor, ...owner, ...input }),
799 update: (actor, owner, id, input) => call("update", { actor, ...owner, id, ...input }),
800 delete: (actor, owner, id) => call("delete", { actor, ...owner, id }),
801 ping: (actor, owner, id) => call("ping", { actor, ...owner, id }),
802 deliveries: (viewer, owner, id) => call("deliveries", { viewer, ...owner, id }),
803 redeliver: (actor, owner, deliveryId) => call("redeliver", { actor, ...owner, deliveryId }),
804 };
805}
806
807export function actionsClient(service: ServiceBinding): ActionsApi {
808 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
809 return {
810 workflows: (repo, viewer) => call("workflows", { repo, viewer }),
811 runs: (repo, viewer, filter = {}) => call("runs", { repo, viewer, ...filter }),
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)812 run: (repo, viewer, id, attempt) => call("run", { repo, viewer, id, attempt: attempt ?? null }),
Deployments work end to end: fixes from the first live run813 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)814 summaries: (repo, viewer, id, attempt) => call("summaries", { repo, viewer, id, attempt: attempt ?? null }),
815 jobLogText: (repo, viewer, job) => call("job_log_text", { repo, viewer, job }),
816 runLogs: (repo, viewer, id, attempt) => call("run_logs", { repo, viewer, id, attempt: attempt ?? null }),
Deployments work end to end: fixes from the first live run817 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)818 cancel: (actor, repo, id, force = false) => call("cancel", { actor, repo, id, force }),
819 rerun: (actor, repo, id, failedOnly = false, options = {}) =>
820 call("rerun", { actor, repo, id, failed_only: failedOnly, job: options.job ?? null, debug: options.debug ?? false }),
Deployments work end to end: fixes from the first live run821 setWorkflowEnabled: (actor, repo, workflow, enabled) =>
822 call("set_workflow_enabled", { actor, repo, workflow, enabled }),
823 settings: (actor, owner, kind) => call("settings", { actor, ...owner, kind }),
824 setSetting: (actor, owner, kind, name, value, options = {}) =>
825 call("set_setting", { actor, ...owner, kind, name, value, ...options }),
826 deleteSetting: (actor, owner, kind, name, id) => call("delete_setting", { actor, ...owner, kind, name, id }),
Merge branch 'worktree-agent-a3abfcce648e87dca'827 approveRun: (actor, repo, id) => call("approve_run", { actor, repo, id }),
828 pendingDeployments: (repo, viewer, id) => call("pending_deployments", { repo, viewer, id }),
829 reviewDeployments: (actor, repo, id, state, environments = [], comment) =>
830 call("review_deployments", { actor, repo, id, state, environments, comment: comment ?? null }),
831 actionsSettings: (repo, viewer) => call("actions_settings", { repo, viewer }),
832 setActionsSettings: (actor, repo, change) => call("set_actions_settings", { actor, repo, ...change }),
833 workspaceActionsSettings: (workspace, viewer) => call("workspace_actions_settings", { workspace, viewer }),
834 setWorkspaceActionsSettings: (actor, workspace, change) =>
835 call("set_workspace_actions_settings", { actor, workspace, ...change }),
836 environments: (repo, viewer) => call("environments", { repo, viewer }),
837 setEnvironment: (actor, repo, name, change) => call("set_environment", { actor, repo, name, ...change }),
838 deleteEnvironment: (actor, repo, name) => call("delete_environment", { actor, repo, name }),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2839 artifacts: (repo, viewer, filter = {}) => call("artifacts", { repo, viewer, ...filter }),
840 artifactDownload: (repo, viewer, by) => call("artifact_download", { repo, viewer, ...by }),
841 deleteArtifact: (actor, repo, id) => call("delete_artifact", { actor, repo, id }),
842 artifactRetention: (repo, viewer, days) => call("artifact_retention", { repo, viewer, days }),
Deployments work end to end: fixes from the first live run843 };
844}
845
846
Fast pages, required checks on the branch, self-hosted runners, honest incidents847/** Self-hosted runners, kept by the actions service. */
848export function runnersClient(service: ServiceBinding): RunnersApi {
849 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
850 return {
851 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.852 activity: (actor, workspace) => call("runner_activity", { actor, workspace }),
Fast pages, required checks on the branch, self-hosted runners, honest incidents853 list: (actor, owner) => call("runners", { actor, ...owner }),
854 createToken: (actor, owner, group) => call("create_registration_token", { actor, ...owner, group }),
855 remove: (actor, owner, id) => call("remove_runner", { actor, ...owner, id }),
856 groups: (actor, workspace) => call("runner_groups", { actor, workspace }),
857 setGroup: (actor, workspace, group) => call("set_runner_group", { actor, workspace, ...group }),
858 deleteGroup: (actor, workspace, id) => call("delete_runner_group", { actor, workspace, id }),
859 settings: (actor, owner) => call("runner_settings", { actor, ...owner }),
860 setSettings: (actor, owner, change) => call("set_runner_settings", { actor, ...owner, ...change }),
861 };
862}
863
Deployments work end to end: fixes from the first live run864export function deploymentsClient(service: ServiceBinding): DeploymentsApi {
865 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
866 return {
Projects: what a workspace builds and runs, first on every page867 settings: (project, viewer) => call("settings", { project, viewer }),
868 updateSettings: (actor, project, changes) => call("update_settings", { actor, project, changes }),
869 list: (project, viewer) => call("list", { project, viewer }),
870 get: (project, id, viewer) => call("get", { project, id, viewer }),
871 redeploy: (actor, project, branch) => call("redeploy", { actor, project, branch }),
872 takeDown: (actor, project, branch) => call("take_down", { actor, project, branch }),
873 overview: (workspace, viewer) => call("overview", { workspace, viewer }),
Deployments work end to end: fixes from the first live run874 usage: (workspace, viewer) => call("usage", { workspace, viewer }),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains875 domains: (project, viewer) => call("domains", { project, viewer }),
876 addDomain: (actor, project, hostname, options = {}) =>
877 call("add_domain", { actor, project, hostname, twin: !!options.twin }),
878 removeDomain: (actor, project, id) => call("remove_domain", { actor, project, id }),
879 refreshDomain: (actor, project, id) => call("refresh_domain", { actor, project, id }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97880 repoDeployments: (repo, viewer, filter = {}) => call("list_deployments", { repo, viewer, ...filter }),
881 repoDeployment: (repo, id, viewer) => call("get_deployment", { repo, id, viewer }),
882 environments: (repo, viewer) => call("list_environments", { repo, viewer }),
883 createDeployment: (actor, repo, input) => call("create_deployment", { repo, actor, ...input }),
884 createDeploymentStatus: (actor, repo, id, input) => call("create_deployment_status", { repo, actor, id, ...input }),
Deployments work end to end: fixes from the first live run885 };
886}
Projects: what a workspace builds and runs, first on every page887
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member888/** Packages: the registries beside the code (services/packages). */
889export function packagesClient(service: ServiceBinding): PackagesApi {
890 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
891 return {
892 list: (workspace, viewer, filter = {}) =>
893 call("list_packages", {
894 workspace,
895 viewer,
896 ecosystem: filter.ecosystem ?? null,
897 repo_id: filter.repoId ?? null,
898 query: filter.query ?? null,
899 }),
900 get: (workspace, ecosystem, name, viewer) => call("get_package", { workspace, ecosystem, name, viewer }),
901 deleteVersion: (actor, workspace, ecosystem, name, version, surface) =>
902 call("delete_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }),
903 deletePackage: (actor, workspace, ecosystem, name, surface) =>
904 call("delete_package", { actor, workspace, ecosystem, name, surface: surface ?? null }),
905 set: (actor, workspace, ecosystem, name, change, surface) =>
906 call("set_package", {
907 actor,
908 workspace,
909 ecosystem,
910 name,
911 visibility: change.visibility ?? null,
912 link: change.link ?? null,
913 unlink: change.unlink ?? false,
Merge packages: roles, Actions access, source label, soft delete, API914 inherit_access: change.inheritAccess ?? null,
915 surface: surface ?? null,
916 }),
917 settings: (workspace, ecosystem, name, viewer) => call("package_settings", { workspace, ecosystem, name, viewer }),
918 deleted: (workspace, viewer) => call("deleted_packages", { workspace, viewer }),
919 restorePackage: (actor, workspace, ecosystem, name, surface) =>
920 call("restore_package", { actor, workspace, ecosystem, name, surface: surface ?? null }),
921 restoreVersion: (actor, workspace, ecosystem, name, version, surface) =>
922 call("restore_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }),
923 setAccess: (actor, workspace, ecosystem, name, who, role, surface) =>
924 call("set_package_access", {
925 actor,
926 workspace,
927 ecosystem,
928 name,
929 user: "user" in who ? who.user : null,
930 team: "team" in who ? who.team : null,
931 role,
932 surface: surface ?? null,
933 }),
934 removeAccess: (actor, workspace, ecosystem, name, who, surface) =>
935 call("remove_package_access", {
936 actor,
937 workspace,
938 ecosystem,
939 name,
940 user: "user" in who ? who.user : null,
941 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 member942 surface: surface ?? null,
943 }),
Merge packages: roles, Actions access, source label, soft delete, API944 setActionsAccess: (actor, workspace, ecosystem, name, repo, role, surface) =>
945 call("set_actions_access", { actor, workspace, ecosystem, name, repo, role, surface: surface ?? null }),
946 removeActionsAccess: (actor, workspace, ecosystem, name, repo, surface) =>
947 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 member948 storage: (workspace) => call("storage", { workspace }),
949 storageAll: () => call("storage_all", {}),
Composer from the workspace's own repositories, and go get from g1t.sh950 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 member951 };
952}
953
Projects: what a workspace builds and runs, first on every page954export function projectsClient(service: ServiceBinding): ProjectsApi {
955 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
956 return {
957 list: (workspace, viewer) => call("list", { workspace, viewer }),
958 get: (workspace, slug, viewer) => call("get", { workspace, slug, viewer }),
959 byRepo: (repoId) => call("by_repo", { repoId }),
960 create: (actor, workspace, input) => call("create", { actor, workspace, input }),
961 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 production962 deploymentsChanged: (projectId, enabled) => call("deployments_changed", { projectId, enabled }),
Projects: pinned and recent projects, per person per workspace963 shortcuts: (workspace, viewer) => call("shortcuts", { workspace, viewer }),
964 pin: (actor, workspace, slug, position) => call("pin", { actor, workspace, slug, position: position ?? null }),
965 unpin: (actor, workspace, slug) => call("unpin", { actor, workspace, slug }),
966 reorderPins: (actor, workspace, slugs) => call("reorder_pins", { actor, workspace, slugs }),
967 visited: (actor, projectId) => call("visited", { actor, projectId }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97968 publicLinks: (repos) => call("public_links", { repos }),
Projects: what a workspace builds and runs, first on every page969 };
970}
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.971
972/**
973 * An agent's own computer, on the runner (services/runner `AgentComputer`):
974 * what the agents service calls for a session's `run_command` and file
975 * tools, and for the Computer tab. Arguments and answers are snake_case.
976 */
977export function runnerComputerClient(service: ServiceBinding): RunnerComputerApi {
978 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
979 return {
980 computerStatus: (agentId) => call("computer_status", { agent_id: agentId }),
981 computerWake: (args) => call("computer_wake", args),
982 computerExec: (args) => call("computer_exec", args),
983 computerReadFile: (args) => call("computer_read_file", args),
984 computerWriteFile: (args) => call("computer_write_file", args),
985 computerSleep: (agentId) => call("computer_sleep", { agent_id: agentId }),
986 computerReset: (agentId) => call("computer_reset", { agent_id: agentId }),
987 computerForget: (agentId) => call("computer_forget", { agent_id: agentId }),
988 computerCommands: (agentId, sessionId) => call("computer_commands", { agent_id: agentId, session_id: sessionId ?? null }),
989 };
990}

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