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

README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise1import type { ActionsApi } from "./actions";
2import type { BillingAdminApi, BillingApi } from "./billing";
3import type { DeploymentsApi } from "./deployments";
4import type { ProjectsApi } from "./projects";
5import type { EventsApi } from "./events";
6import type { IdentityAdminApi, IdentityApi } from "./identity";
7import type { IntegrationsApi } from "./integrations";
8import type { PackagesApi } from "./packages";
9import 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";
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.13import type { RunnersAdminApi, RunnersApi } from "./runners";
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise14
15/** A service binding, as far as these clients need it. */
16export type ServiceBinding = {
17 fetch(input: string, init?: RequestInit): Promise<Response>;
18};
19
20/**
21 * Calls a method on a service that speaks the JSON protocol used by the
22 * Rust services: `POST /rpc/<method>` with the arguments as the body.
23 */
24async function rpc<T>(
25 service: ServiceBinding,
26 method: string,
27 args: object,
28): Promise<T> {
29 // The hostname is ignored; a service binding always reaches its service.
30 const response = await service.fetch(`https://service/rpc/${method}`, {
31 method: "POST",
32 headers: { "content-type": "application/json" },
33 body: JSON.stringify(args),
34 });
35 if (!response.ok) {
36 throw new Error(`${method} failed with status ${response.status}`);
37 }
38 return (await response.json()) as T;
39}
40
41export function identityClient(service: ServiceBinding): IdentityApi {
42 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
43 return {
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm44 register: (username, email, password, inviteCode, client, emailProof) =>
45 call("register", {
46 username,
47 email,
48 password,
49 invite_code: inviteCode ?? null,
50 email_proof: emailProof ?? null,
51 client: client ?? null,
52 }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise53 signIn: (username, password, client) => call("sign_in", { username, password, client: client ?? null }),
Merge main (membership, two-factor, GitHub repo roles) into tokens54 twoFactorSignIn: (challenge, code, client) => call("two_factor_sign_in", { challenge, code, client: client ?? null }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise55 signOut: (sessionToken) => call("sign_out", { sessionToken }),
56 resendVerification: (user) => call("resend_verification", { user }),
57 verifyEmail: (token) => call("verify_email", { token }),
58 requestPasswordReset: (email, client) => call("request_password_reset", { email, client: client ?? null }),
59 resetPassword: (token, password) =>
60 call("reset_password", { token, password }),
61 deviceStart: (clientName) => call("device_start", { clientName }),
62 deviceLookup: (userCode) => call("device_lookup", { userCode }),
63 deviceResolve: (userCode, user, approve) =>
64 call("device_resolve", { userCode, user, approve }),
65 deviceClaim: (deviceCode) => call("device_claim", { deviceCode }),
66 oauthAuthorize: (user, approval) => call("oauth_authorize", { user, ...approval }),
67 oauthExchange: (code, codeVerifier, clientId, redirectUri) =>
68 call("oauth_exchange", { code, codeVerifier, clientId, redirectUri }),
69 oauthRefresh: (refreshToken, clientId) =>
70 call("oauth_refresh", { refreshToken, clientId }),
71 listOAuthGrants: (user) => call("list_oauth_grants", { user }),
72 revokeOAuthGrant: (user, id) => call("revoke_oauth_grant", { user, id }),
73 updateOAuthGrant: (user, id, grant) =>
74 call("update_oauth_grant", { user, id, scopes: grant.scopes }),
75 createWorkspace: (user, slug, name) => call("create_workspace", { user, slug, name }),
76 getWorkspace: (slug) => call("get_workspace", { slug }),
Merge branch 'worktree-agent-a2013627e5ea4ab13'77 workspaceResidency: (slug) => call("workspace_residency", { slug }),
78 setWorkspaceResidency: (actor, slug, residency) => call("set_workspace_residency", { actor, slug, residency }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise79 listMembers: (slug, viewer) => call("list_members", { slug, viewer }),
80 addMember: (actor, slug, username) => call("add_member", { actor, slug, username }),
81 removeMember: (actor, slug, username) =>
Merge main (membership, two-factor, GitHub repo roles) into tokens82 call("remove_member", { actor, slug, username, surface: "web" }),
83 updateMember: (actor, slug, username, change) =>
84 call("update_member", { actor, slug, username, role: change.role ?? null, org_roles: change.org_roles ?? null, surface: "web" }),
85 transferOwnership: (actor, slug, username) => call("transfer_ownership", { actor, slug, username, surface: "web" }),
86 leaveWorkspace: (user, slug) => call("leave_workspace", { user, slug, surface: "web" }),
87 setMemberPrivileges: (actor, slug, change) => call("set_member_privileges", { actor, slug, privileges: change, surface: "web" }),
88 setTwoFactorRequirement: (actor, slug, required) =>
89 call("set_two_factor_requirement", { actor, slug, required, surface: "web" }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise90 updateWorkspace: (actor, slug, details) =>
91 call("update_workspace", { actor, slug, ...details }),
92 renameWorkspace: (actor, slug, newSlug) => call("rename_workspace", { actor, slug, newSlug }),
93 checkWorkspaceRename: (actor, slug, newSlug) =>
94 call("check_workspace_rename", { actor, slug, newSlug }),
95 resolveSlug: (slug) => call("resolve_slug", { slug }),
96 deleteWorkspace: (actor, slug, confirm) => call("delete_workspace", { actor, slug, confirm }),
97 checkWorkspaceDeletion: (actor, slug) => call("check_workspace_deletion", { actor, slug }),
98 setWorkspaceAvatar: (actor, slug, image) => call("set_workspace_avatar", { actor, slug, image }),
99 setUserAvatar: (user, image) => call("set_user_avatar", { user, image }),
100 listWorkspaceTokens: (slug, viewer) => call("list_workspace_tokens", { slug, viewer }),
101 removeWorkspaceToken: (actor, slug, id) =>
102 call("remove_workspace_token", { actor, slug, id }),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers103 createToken: (actor, input) =>
104 call("create_token", {
105 actor,
106 owner: input.owner ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules107 name: input.name,
108 description: input.description ?? null,
109 ttl_seconds: input.ttlSeconds,
110 workspace: input.workspace,
111 repository_selection: input.repositorySelection,
112 repositories: input.repositories,
113 permissions: input.permissions,
Merge main into Artifacts Phase 2114 website: input.website ?? false,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules115 }),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers116 updateToken: (actor, id, change, owner) =>
117 call("update_token", {
118 actor,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules119 id,
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers120 owner: owner ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules121 name: change.name ?? null,
122 description: change.description ?? null,
123 repository_selection: change.repositorySelection ?? null,
124 repositories: change.repositories ?? null,
125 permissions: change.permissions ?? null,
Merge main into Artifacts Phase 2126 website: change.website ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules127 }),
An access token can be regenerated: a new secret, its name, permissions, repositories and reach kept, and the old secret refused at once. Its page (yours under Settings, a workspace's under its settings for owners) has Regenerate with a new expiry, within the workspace's rules on lifetime, and shows the secret once; identity's regenerate_token logs it to the security log or the workspace's audit log, and refuses a revoked token. The token form's quick permissions have All, every resource at its highest level. A project's Observability settings no longer list the token g1t gives its apps on g1t.page: it is g1t's, and the page says apps there need none.128 regenerateToken: (actor, id, ttlSeconds, owner) =>
129 call("regenerate_token", { actor, id, owner: owner ?? null, ttl_seconds: ttlSeconds ?? null }),
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules130 getTokenPolicy: (slug, viewer) => call("get_token_policy", { slug, viewer }),
131 setTokenPolicy: (actor, slug, change) =>
132 call("set_token_policy", {
133 actor,
134 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 managers135 allow_tokens_for_all_workspaces: change.allowTokensForAllWorkspaces ?? null,
136 allow_tokens_for_this_workspace: change.allowTokensForThisWorkspace ?? null,
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules137 require_approval: change.requireApproval ?? null,
138 max_lifetime_days: change.maxLifetimeDays ?? null,
139 forbid_no_expiry: change.forbidNoExpiry ?? null,
140 surface: "web",
141 }),
142 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 managers143 call("list_member_tokens", { actor, slug, status: filter.status ?? null }),
Settings: fine-grained tokens, workspace token Admin, workspace personal access token rules144 reviewTokenRequest: (actor, slug, id, approve, reason) =>
145 call("review_token_request", { actor, slug, id, approve, reason: reason ?? null, surface: "web" }),
146 revokeMemberToken: (actor, slug, id, reason) =>
147 call("revoke_member_token", { actor, slug, id, reason: reason ?? null, surface: "web" }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise148 userForSession: (sessionToken) => call("user_for_session", { sessionToken }),
149 checkInvite: (code, client, options = {}) =>
150 call("check_invite", {
151 code,
152 client: client ?? null,
153 viewer: options.viewer ?? null,
154 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 confirm155 email_proof: options.emailProof ?? null,
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise156 }),
157 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)158 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.159 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)160 listInvitations: (user) => call("list_invitations", { user }),
161 acceptInvitation: (user, id) => call("accept_invitation", { user, id, surface: "web" }),
162 declineInvitation: (user, id) => call("decline_invitation", { user, id, surface: "web" }),
163 findPeople: (query, limit) => call("find_people", { query, limit: limit ?? null }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise164 workspaceInvites: (slug, viewer) => call("workspace_invites", { slug, viewer }),
165 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.166 resendWorkspaceInvite: (actor, slug, id) => call("resend_workspace_invite", { actor, slug, id }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise167 userForGitCredentials: (username, secret) =>
168 call("user_for_git_credentials", { username, secret }),
169 userForAccessToken: (token) => call("user_for_access_token", { token }),
170 userForSshKey: (fingerprint) => call("user_for_ssh_key", { fingerprint }),
171 userByUsername: (username) => call("user_by_username", { username }),
172 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)173 usersForAudience: (ids) => call("users_for_audience", { ids }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise174 profile: (username) => call("profile", { username }),
175 updateProfile: (actor, fields) => call("update_profile", { actor, ...fields }),
176 profileWorkspaces: (username, viewer, publicIn) =>
177 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.178 dockPins: (user, workspace) => call("dock_pins", { user, workspace }),
179 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.180 soundSettings: (user) => call("sound_settings", { user }),
181 setSoundSettings: (user, change) => call("set_sound_settings", { user, change }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise182 listSshKeys: (user) => call("list_ssh_keys", { user }),
183 addSshKey: (user, title, publicKey) =>
184 call("add_ssh_key", { user, title, publicKey }),
185 removeSshKey: (user, id) => call("remove_ssh_key", { user, id }),
186 listAccessTokens: (user) => call("list_access_tokens", { user }),
187 createAccessToken: (user, name, ttlSeconds, grant) =>
188 call("create_access_token", {
189 user,
190 name,
191 ttlSeconds,
192 scopes: grant?.scopes ?? null,
193 listed: grant?.listed ?? false,
194 }),
195 removeAccessToken: (user, id) => call("remove_access_token", { user, id }),
196 createRunCredential: (input) => call("create_run_credential", input),
197 bindRunCredentials: (tokenHashes, runId) => call("bind_run_credentials", { tokenHashes, runId }),
198 revokeRunCredentials: (target) => call("revoke_run_credentials", target),
199 // Who has access to a repository; see access.ts.
200 repoAccess: (owner, name, viewer) => call("repo_access", { viewer, path: { namespace: owner, name } }),
201 addCollaborator: (actor, owner, name, invitee, role) =>
202 call("add_collaborator", { actor, path: { namespace: owner, name }, invitee, role }),
203 setCollaboratorRole: (actor, owner, name, username, role) =>
204 call("set_collaborator_role", { actor, path: { namespace: owner, name }, username, role }),
205 removeCollaborator: (actor, owner, name, username) =>
206 call("remove_collaborator", { actor, path: { namespace: owner, name }, username }),
207 collaboratorPermission: (viewer, owner, name, username) =>
208 call("collaborator_permission", { viewer, path: { namespace: owner, name }, username }),
209 myRepoInvitations: (user) => call("my_repo_invitations", { user }),
210 respondRepoInvitation: (user, id, accept) => call("respond_repo_invitation", { user, id, accept }),
211 revokeRepoInvitation: (actor, owner, name, id) =>
212 call("revoke_repo_invitation", { actor, path: { namespace: owner, name }, id }),
213 setBasePermission: (actor, slug, base) => call("set_base_permission", { actor, slug, base_permission: base }),
214 outsideCollaborators: (viewer, slug) => call("outside_collaborators", { viewer, slug }),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca215 // A repository's deploy keys; see deploy-keys.ts.
216 listDeployKeys: (viewer, owner, name) => call("list_deploy_keys", { viewer, path: { namespace: owner, name } }),
217 addDeployKey: (actor, owner, name, key) =>
218 call("add_deploy_key", { actor, path: { namespace: owner, name }, title: key.title, key: key.key, read_only: key.readOnly }),
219 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 bar220 // Teams; see teams.ts.
221 listTeams: (viewer, workspace, query) => call("list_teams", { viewer, workspace, query: query ?? null }),
222 getTeam: (viewer, workspace, team) => call("get_team", { viewer, workspace, team }),
223 createTeam: (actor, workspace, team) => call("create_team", { actor, workspace, ...team }),
Merge branch 'worktree-agent-ad7c6d88d93adc817'224 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 bar225 updateTeam: (actor, workspace, team, changes) => call("update_team", { actor, workspace, team, ...changes }),
226 deleteTeam: (actor, workspace, team) => call("delete_team", { actor, workspace, team }),
227 teamMembers: (viewer, workspace, team, includeChildTeams) =>
228 call("team_members", { viewer, workspace, team, include_child_teams: includeChildTeams ?? false }),
229 setTeamMember: (actor, workspace, team, username, role) =>
230 call("set_team_member", { actor, workspace, team, username, role }),
231 removeTeamMember: (actor, workspace, team, username) =>
232 call("remove_team_member", { actor, workspace, team, username }),
233 childTeams: (viewer, workspace, team) => call("child_teams", { viewer, workspace, team }),
234 teamRepos: (viewer, workspace, team) => call("team_repos", { viewer, workspace, team }),
235 setTeamRepo: (actor, workspace, team, owner, name, role) =>
236 call("set_team_repo", { actor, workspace, team, repo: { namespace: owner, name }, role }),
237 removeTeamRepo: (actor, workspace, team, owner, name) =>
238 call("remove_team_repo", { actor, workspace, team, repo: { namespace: owner, name } }),
239 userTeams: (viewer, workspace, username) => call("user_teams", { viewer, workspace, username }),
240 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.241 // People; see people.ts.
242 peopleDirectory: (viewer, workspace) => call("people_directory", { viewer, workspace }),
243 setMemberProfile: (actor, workspace, username, changes) => call("set_member_profile", { actor, workspace, username, ...changes }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise244 };
245}
246
247/**
248 * The slug a renamed workspace has now, for a `workspace.renamed` handler:
249 * asked of identity by the workspace's id, so that renames delivered twice
250 * or out of order converge. Falls back to the event's `to`.
251 */
252export async function currentWorkspaceSlug(
253 identity: ServiceBinding,
254 renamed: { workspaceId: string; to: string },
255): Promise<string> {
256 const names = await identityClient(identity).usernames([renamed.workspaceId]);
257 return names[renamed.workspaceId] ?? renamed.to;
258}
259
260/** The slugs whose rows move to `current`: the two a rename names, less `current`. */
261export function staleSlugs(renamed: { from: string; to: string }, current: string): string[] {
262 return [...new Set([renamed.from, renamed.to])].filter((slug) => slug !== current);
263}
264
265/**
266 * Where a transferred repository is now, as `namespace/name`, for a
267 * `repo.transferred` handler: asked of repos by id, so transfers delivered
268 * twice or out of order converge. Falls back to the event's destination.
269 */
270export async function currentRepoPath(
271 repos: ServiceBinding,
272 transferred: { repoId: string; name: string; to: string },
273): Promise<string> {
274 const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: transferred.repoId });
275 return path ? `${path.namespace}/${path.name}` : `${transferred.to}/${transferred.name}`;
276}
277
278/** The paths whose rows move to `current`: the two a transfer names, less `current`. */
279export function stalePaths(transferred: { name: string; from: string; to: string }, current: string): string[] {
280 return [...new Set([transferred.from, transferred.to].map((ns) => `${ns}/${transferred.name}`))].filter(
281 (path) => path !== current,
282 );
283}
284
285/**
286 * A repository's path change, from `repo.transferred` or `repo.renamed`,
287 * read the same way: the two paths (`namespace/name`) the event names, old
288 * then new. Null for any other event.
289 */
290export type RepoMove = { repoId: string; paths: [string, string] };
291
292export function repoMove(event: { type: string; data: unknown }): RepoMove | null {
293 const data = event.data as Record<string, string>;
294 if (event.type === "repo.transferred") {
295 return { repoId: data.repoId!, paths: [`${data.from}/${data.name}`, `${data.to}/${data.name}`] };
296 }
297 if (event.type === "repo.renamed") {
298 return { repoId: data.repoId!, paths: [`${data.namespace}/${data.from}`, `${data.namespace}/${data.to}`] };
299 }
300 return null;
301}
302
303/**
304 * Where a moved repository is now, as `namespace/name`: asked of repos by
305 * id, so moves delivered twice or out of order converge. Falls back to the
306 * event's new path.
307 */
308export async function currentMovedPath(repos: ServiceBinding, move: RepoMove): Promise<string> {
309 const path = await rpc<{ namespace: string; name: string } | null>(repos, "path_by_id", { id: move.repoId });
310 return path ? `${path.namespace}/${path.name}` : move.paths[1];
311}
312
313/** The paths whose rows move to `current`: the two a move names, less `current`. */
314export function staleMovedPaths(move: RepoMove, current: string): string[] {
315 return [...new Set(move.paths)].filter((path) => path !== current);
316}
317
318/** Staff-only identity. Only sudo binds to it; see `IdentityAdminApi`. */
319export function identityAdminClient(service: ServiceBinding): IdentityAdminApi {
320 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
321 return {
322 workspaces: (query) => call("admin_workspaces", { query: query ?? null }),
323 workspace: (slug) => call("admin_workspace", { slug }),
324 invites: (query) => call("admin_invites", { query: query ?? null }),
325 revokeInvite: (id, staff) => call("admin_revoke_invite", { id, staff }),
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.326 suspendUser: (name, suspend, reason, staff) => call("admin_suspend_user", { name, suspend, reason, staff }),
327 suspendWorkspace: (name, suspend, reason, staff) => call("admin_suspend_workspace", { name, suspend, reason, staff }),
328 blockedDomains: () => call("admin_blocked_domains", {}),
329 blockDomain: (domain, block, reason, staff) => call("admin_block_domain", { domain, block, reason, staff }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise330 deletedWorkspaces: () => call("admin_deleted_workspaces", {}),
331 restoreWorkspace: (workspaceId, staff) => call("admin_restore_workspace", { workspaceId, staff }),
332 purgeWorkspace: (workspaceId, staff, confirm) => call("admin_purge_workspace", { workspaceId, staff, confirm }),
Merge branch 'worktree-agent-a8385d293d42c913a'333 aliases: () => call("admin_aliases", {}),
334 setAlias: (alias, workspace, note, staff) => call("admin_set_alias", { alias, workspace, note, staff }),
335 removeAlias: (alias, reason, staff) => call("admin_remove_alias", { alias, reason, staff }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise336 };
337}
338
339export function reposClient(service: ServiceBinding): ReposApi {
340 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
341 return {
342 get: (path, viewer) => call("get", { path, viewer }),
343 getById: (id, viewer) => call("get_by_id", { id, viewer }),
344 readable: (ids, viewer) => call("readable", { ids, viewer }),
345 publicNamespaces: (ownerId) => call("public_namespaces", { ownerId }),
346 list: (viewer, options = {}) => call("list", { viewer, ...options }),
347 create: (owner, input) => call("create", { owner, ...input }),
348 update: (actor, path, changes) => call("update", { actor, path, ...changes }),
349 transfer: (actor, path, to) => call("transfer", { actor, path, to }),
350 delete: (actor, path, confirm) => call("delete", { actor, path, confirm }),
351 deleted: (viewer, namespace) => call("deleted", { viewer, namespace }),
352 restore: (actor, path) => call("restore", { actor, path }),
353 purge: (actor, path, confirm) => call("purge", { actor, path, confirm }),
354 rename: (actor, path, name) => call("rename", { actor, path, name }),
355 archive: (actor, path, archived) => call("archive", { actor, path, archived }),
356 setVisibility: (actor, path, isPrivate, confirm) => call("set_visibility", { actor, path, isPrivate, confirm }),
357 setDefaultBranch: (actor, path, branch) => call("set_default_branch", { actor, path, branch }),
358 renameBranch: (actor, path, from, to) => call("rename_branch", { actor, path, from, to }),
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.359 createBranch: (actor, path, name, from = "") => call("create_branch", { actor, path, name, from }),
360 removeBranch: (actor, path, branch, head) => call("remove_branch", { actor, path, branch, head }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise361 resolveBranch: (repoId, branch) => call("resolve_branch", { repoId, branch }),
362 statusById: (id) => call("status_by_id", { id }),
Merge branch 'worktree-agent-a2013627e5ea4ab13'363 storageOptions: () => call("storage_options", {}),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise364 resolvePath: (path) => call("resolve_path", { path }),
365 tree: (path, viewer, ref, treePath) =>
366 call("tree", { path, viewer, ref, treePath }),
367 blob: (path, viewer, ref, filePath) =>
368 call("blob", { path, viewer, ref, filePath }),
369 log: (path, viewer, ref, limit) => call("log", { path, viewer, ref, limit }),
370 blame: (path, viewer, ref, filePath) => call("blame", { path, viewer, ref, filePath }),
371 forkForPull: (sourceId, pullId, actor) =>
372 call("fork_for_pull", { sourceId, pullId, actor }),
373 gitAccess: (path, viewer, service) =>
374 call("git_access", { path, viewer, service }),
375 branches: (path, viewer) => call("branches", { path, viewer }),
376 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 25377 branchDrift: (path, viewer, base, heads) => call("branch_drift", { path, viewer, base, heads }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise378 tags: (path, viewer) => call("tags", { path, viewer }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97379 about: (path, viewer) => call("about", { path, viewer }),
380 languages: (path, viewer) => call("languages", { path, viewer }),
381 contributors: (path, viewer) => call("contributors", { path, viewer }),
382 license: (path, viewer) => call("license", { path, viewer }),
383 stars: (path, viewer) => call("stars", { path, viewer }),
384 star: (actor, path, starred) => call("star", { path, actor, starred }),
385 stargazers: (path, viewer, page) => call("stargazers", { path, viewer, page: page ?? null }),
386 starred: (username, viewer) => call("starred", { username, viewer }),
387 releases: (path, viewer) => call("releases", { path, viewer }),
388 release: (path, viewer, which) => call("release", { path, viewer, id: which.id ?? null, tag: which.tag ?? null, latest: which.latest ?? false }),
389 createRelease: (actor, path, release) => call("create_release", { path, actor, ...release }),
390 updateRelease: (actor, path, id, change) => call("update_release", { path, actor, id, ...change }),
391 deleteRelease: (actor, path, id) => call("delete_release", { path, actor, id }),
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.392 listFiles: (repoId, ref, limit, options) => call("list_files", { repoId, ref, skipDirs: [], limit, links: options?.links ?? false }),
393 openArchiveToken: (path, token) => call("open_archive_token", { path, token }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise394 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 address395 rawFile: (repoId, ref, path, maxBytes) => call("raw_file", { repoId, ref, path, maxBytes }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise396 commitFile: (repo, actor, file) => call("commit_file", { repo, actor, ...file }),
397 land: (sourceId, actor, branch) => call("land", { sourceId, actor, branch }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar398 compare: (repoId, viewer, base, head, baseBranch) => call("compare", { repoId, viewer, base, head, baseBranch }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise399 claimBackups: (limit, maxRunning) => call("claim_backups", { limit, maxRunning }),
400 // The sandbox's own calls are snake_case (they come through the API).
401 failBackup: (jobId, token, error) => call("backup_fail", { job_id: jobId, token, error }),
402 };
403}
404
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar405/**
406 * `PullDetail`'s own fields that the work service writes in snake_case:
407 * `g1t_contracts::work::PullDetail` has no camelCase renaming, though what
408 * it holds (`Pull`, `CheckRun` and the rest) does. Each with its name here.
409 */
410const PULL_DETAIL_FIELDS = [
411 ["earlier_checks", "earlierChecks"],
412 ["required_checks", "requiredChecks"],
413 ["code_owners", "codeOwners"],
414] as const;
415
416/**
417 * A pull request's detail as the work service sent it, with its own fields
418 * in the camelCase this package uses: read at the edge, by `workClient`'s
419 * `getPull`. Either spelling is taken, so a service that already sends
420 * camelCase reads the same.
421 */
422export function pullDetailFromWire(raw: Record<string, unknown>): PullDetail {
423 const detail: Record<string, unknown> = { ...raw };
424 for (const [snake, camel] of PULL_DETAIL_FIELDS) {
425 if (snake in detail) {
426 if (!(camel in detail)) detail[camel] = detail[snake];
427 delete detail[snake];
428 }
429 }
430 return detail as PullDetail;
431}
432
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise433export function workClient(service: ServiceBinding): WorkApi {
434 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
435 return {
436 openIssue: (actor, repo, input) => call("open_issue", { actor, repo, ...input }),
437 listIssues: (repo, viewer, filter = {}) => call("list_issues", { repo, viewer, ...filter }),
438 getIssue: (repo, number, viewer) => call("get_issue", { repo, number, viewer }),
439 updateIssue: (actor, repo, number, input) =>
440 call("update_issue", { actor, repo, number, ...input }),
441 closeIssue: (actor, repo, number, reason) =>
442 call("close_issue", { actor, repo, number, reason }),
443 reopenIssue: (actor, repo, number) => call("reopen_issue", { actor, repo, number }),
444 listLabels: (repo, viewer) => call("list_labels", { repo, viewer }),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar445 saveLabel: (actor, repo, label) => call("save_label", { actor, repo, ...label }),
446 deleteLabel: (actor, repo, name) => call("delete_label", { actor, repo, name }),
447 addDefaultLabels: (actor, repo) => call("add_default_labels", { actor, repo }),
448 setLabels: (actor, repo, number, labels, change = "set") =>
449 call("set_labels", { actor, repo, number, labels, change }),
450 listMilestones: (repo, viewer, state) => call("list_milestones", { repo, viewer, state }),
451 getMilestone: (repo, number, viewer) => call("get_milestone", { repo, number, viewer }),
452 saveMilestone: (actor, repo, milestone) => call("save_milestone", { actor, repo, ...milestone }),
453 deleteMilestone: (actor, repo, number) => call("delete_milestone", { actor, repo, number }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise454 counts: (repo, viewer) => call("counts", { repo, viewer }),
455 addComment: (actor, repo, number, comment) =>
456 call("add_comment", { actor, repo, number, ...comment }),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts457 editComment: (actor, repo, commentId, body) => call("edit_comment", { actor, repo, commentId, body }),
458 deleteComment: (actor, repo, commentId) => call("delete_comment", { actor, repo, commentId }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise459 startChecks: (pullId) => call("start_checks", { pullId }),
460 reportChecks: (runId, token, report) =>
461 call("report_checks", { runId, token, ...report }),
462 startMergecheck: (pullId) => call("start_mergecheck", { pullId }),
463 failMergecheck: (pullId, token, error) => call("report_mergecheck", { pullId, token, error }),
464 advance: (pullId) => call("advance", { pullId }),
465 stall: (pullId, reason) => call("stall", { pullId, reason }),
466 managedPulls: (repoId) => call("managed_pulls", { repoId }),
467 queue: (repo, viewer) => call("queue", { repo, viewer }),
468 queueBuild: (repoId) => call("queue_build", { repoId }),
469 failQueue: (entryId, token, error) => call("report_queue", { entryId, token, error }),
470 removeFromQueue: (actor, repo, number) => call("remove_from_queue", { actor, repo, number }),
471 getSettings: (repo, viewer) => call("get_settings", { repo, viewer }),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge472 // Rulesets travel in snake_case (rules.ts).
473 listRulesets: (owner, viewer, includeParents = false) =>
474 call("list_rulesets", { viewer, ...owner, include_parents: includeParents }),
475 getRuleset: (owner, id, viewer) => call("get_ruleset", { viewer, ...owner, id }),
476 saveRuleset: (actor, owner, ruleset, id) => call("save_ruleset", { actor, ...owner, id: id ?? null, ruleset }),
477 deleteRuleset: (actor, owner, id) => call("delete_ruleset", { actor, ...owner, id }),
478 effectiveRules: (repo, name, viewer, target = "branch") => call("effective_rules", { viewer, repo, name, target }),
479 ruleEvaluations: (owner, viewer, filter = {}) => call("rule_evaluations", { viewer, ...owner, ...filter }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise480 seenChecks: (repo, viewer) => call("seen_checks", { repo, viewer }),
Merge checks: statuses and check runs on every commit481 commitChecks: (repo, viewer, shas) => call("commit_checks", { repo, viewer, shas }),
482 getCheckRun: (repo, id, viewer) => call("get_check_run", { repo, id, viewer }),
483 checkRunAnnotations: (repo, id, viewer) => call("check_run_annotations", { repo, id, viewer }),
484 requestCheckAction: (actor, repo, id, identifier) => call("request_check_action", { actor, repo, id, identifier }),
485 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 bar486 codeownersErrors: (repo, viewer, ref) => call("codeowners_errors", { repo, viewer, ref: ref ?? null }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise487 updateSettings: (actor, repo, settings) =>
488 call("update_settings", { actor, repo, settings }),
489 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 bar490 listPulls: (repo, viewer, state, filter = {}) => call("list_pulls", { repo, viewer, state, ...filter }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise491 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 bar492 // Its own fields arrive in snake_case: read into camelCase here.
493 getPull: (repo, number, viewer) =>
494 call<Result<Record<string, unknown>>>("get_pull", { repo, number, viewer }).then(
495 (found): Result<PullDetail> => (found.ok ? { ok: true, value: pullDetailFromWire(found.value) } : found),
496 ),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise497 updatePull: (actor, repo, number, changes) =>
498 call("update_pull", { actor, repo, number, ...changes }),
499 catchUpPull: (actor, repo, number) => call("catch_up_pull", { actor, repo, number }),
500 readyPull: (actor, repo, number, summary) =>
501 call("ready_pull", { actor, repo, number, summary }),
502 closePull: (actor, repo, number) => call("close_pull", { actor, repo, number }),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts503 reopenPull: (actor, repo, number) => call("reopen_pull", { actor, repo, number }),
504 convertPullToDraft: (actor, repo, number) => call("convert_pull_to_draft", { actor, repo, number }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise505 mergePull: (actor, repo, number, options = {}) =>
506 call("merge_pull", { actor, repo, number, ...options }),
507 listActivePulls: (viewer) => call("list_active_pulls", { viewer }),
508 byAuthor: (username, viewer, filter = {}) => call("by_author", { username, viewer, ...filter }),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project509 contributions: (username, viewer) => call("contributions", { username, viewer }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise510 listAssignedIssues: (viewer) => call("list_assigned_issues", { viewer }),
511 appendSession: (actor, repo, number, entries) =>
512 call("append_session", { actor, repo, number, entries }),
513 readSession: (repo, number, viewer, afterSeq = 0) =>
514 call("read_session", { repo, number, viewer, afterSeq }),
Merge branch 'worktree-agent-aa68cf6ecc03dffaf'515 // Coordination: harness sessions, claims, an issue's whole context and
516 // decision records. Their arguments are snake_case, as the Rust service
517 // reads them; what comes back is camelCase.
518 getWork: (repo, number, viewer) => call("get_work", { repo, number, viewer }),
519 listClaims: (repo, viewer, filter = {}) =>
520 call("list_claims", { repo, viewer, state: filter.state ?? null, issue: filter.issue ?? null }),
521 listWorkSessions: (repo, viewer, filter = {}) =>
522 call("list_work_sessions", {
523 repo,
524 viewer,
525 state: filter.state ?? null,
526 issue: filter.issue ?? null,
527 pull: filter.pull ?? null,
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.528 path: filter.path ?? null,
529 subagents: filter.subagents ?? false,
Merge branch 'worktree-agent-aa68cf6ecc03dffaf'530 }),
531 getWorkSession: (repo, id, viewer, afterSeq = 0) =>
532 call("get_work_session", { repo, id, viewer, after_seq: afterSeq }),
533 listDecisions: (repo, number, viewer) => call("list_decisions", { repo, number, viewer }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise534 };
535}
536
537export function billingClient(service: ServiceBinding): BillingApi {
538 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
539 return {
540 status: () => call("status", {}),
541 account: (workspace, viewer) => call("account", { workspace, viewer }),
542 ledger: (workspace, viewer) => call("ledger", { workspace, viewer }),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging543 credits: (workspace, viewer) => call("credits", { workspace, viewer }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise544 statement: (workspace, viewer, month = null, group = "day") => call("statement", { workspace, viewer, month, group }),
545 statementEntries: (workspace, viewer, filter) =>
546 call("statement_entries", {
547 workspace,
548 viewer,
549 month: filter.month,
550 kind: filter.kind,
551 day: filter.day ?? null,
552 project: filter.project ?? null,
553 before: filter.before ?? null,
554 }),
555 usage: (workspace, viewer, since) => call("usage", { workspace, viewer, since }),
556 checkout: (actor, workspace, amountCents, returnUrl, method = "card") =>
557 call("checkout", { actor, workspace, amountCents, returnUrl, method }),
558 confirm: (workspace, viewer, session) => call("confirm", { workspace, viewer, session }),
559 trial: (workspace, exempt) => call("trial", { workspace, exempt }),
560 features: (workspace, viewer) => call("features", { workspace, viewer }),
561 subscribe: (actor, workspace, feature, returnUrl) =>
562 call("subscribe", { actor, workspace, feature, returnUrl }),
563 confirmSubscription: (workspace, viewer, session) =>
564 call("confirm_subscription", { workspace, viewer, session }),
565 cancelSubscription: (actor, workspace, feature, resume = false) =>
566 call("cancel_subscription", { actor, workspace, feature, resume }),
567 hasFeature: (workspace, feature) => call("has_feature", { workspace, feature }),
Merge Stripe Tax, the card fee on card payments, and one free workspace per person568 freeWorkspaces: (workspaces) => call("free_workspaces", { workspaces }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise569 chargeFeature: (charge) => call("charge_feature", charge),
570 billingPortal: (actor, workspace, returnUrl) => call("billing_portal", { actor, workspace, return_url: returnUrl }),
571 recordSandbox: (usage) => call("record_sandbox", usage),
572 limit: (workspace, viewer) => call("limit", { workspace, viewer }),
573 checkLimit: (workspace) => call("check_limit", { workspace }),
574 prices: () => call("prices", {}),
575 notePending: (workspace, source, costMicros, detail = null) => call("note_pending", { workspace, source, costMicros, detail }),
576 usageMeters: (workspace, viewer) => call("usage_meters", { workspace, viewer }),
577 setSpendLimit: (actor, workspace, spendLimitMicros, useFullLimit = false, raiseOnce = false) =>
578 call("set_spend_limit", { actor, workspace, spendLimitMicros, use_full_limit: useFullLimit, raise_once: raiseOnce }),
579 invoices: (workspace, viewer) => call("invoices", { workspace, viewer }),
580 entitlements: (workspace) => call("entitlements", { workspace }),
581 reserve: (reservation) => call("reserve", reservation),
582 settle: (reservationId, actualMicros) => call("settle", { reservationId, actualMicros }),
583 cardCheck: (actor, workspace, returnUrl) => call("card_check", { actor, workspace, returnUrl }),
584 confirmCardCheck: (workspace, viewer, session) => call("confirm_card_check", { workspace, viewer, session }),
585 requestLimit: (actor, workspace, request) => call("request_limit", { actor, workspace, ...request }),
586 limitRequests: (workspace, viewer) => call("limit_requests", { workspace, viewer }),
587 confirmSpike: (actor, workspace, keepGoing) => call("confirm_spike", { actor, workspace, keepGoing }),
588 setCaps: (actor, workspace, caps) => call("set_caps", { actor, workspace, ...caps }),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit589 usageReport: (workspace, viewer, range) =>
590 call("usage_report", { workspace, viewer, from: range.from, until: range.until, products: range.products ?? [], projects: range.projects ?? [] }),
591 setBudget: (actor, workspace, budget) => call("set_budget", { actor, workspace, ...budget }),
592 billingDetails: (workspace, viewer) => call("billing_details", { workspace, viewer }),
593 setBillingDetails: (actor, workspace, details) => call("set_billing_details", { actor, workspace, ...details }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise594 };
595}
596
597export function billingAdminClient(service: ServiceBinding): BillingAdminApi {
598 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
599 return {
600 accounts: (query) => call("admin_accounts", { query: query ?? null }),
601 account: (id) => call("admin_account", { id }),
602 setTerms: (id, terms, by) => call("admin_set_terms", { id, terms, by }),
603 setAllowances: (id, allowances, note, by) => call("admin_set_allowances", { id, allowances, note, by }),
604 createEnterprise: (name, workspaces, by) => call("admin_create_enterprise", { name, workspaces, by }),
605 attach: (workspace, account, by) => call("admin_attach", { workspace, account, by }),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging606 credit: (workspace, amountMicros, note, by, options = { kind: "goodwill" }) =>
607 call("admin_credit", {
608 workspace,
609 amount_micros: amountMicros,
610 note,
611 by,
612 kind: options.kind,
613 expires_at: options.expiresAt ?? null,
614 refund_for: options.refundFor ?? null,
615 refund_day: options.refundDay ?? null,
616 }),
617 credits: (filter = {}) =>
618 call("admin_credits", {
619 workspace: filter.workspace ?? null,
620 kind: filter.kind ?? null,
621 month: filter.month ?? null,
622 by: filter.by ?? null,
623 }),
624 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's625 resetBilling: (workspace, confirm, note, by) => call("admin_reset_billing", { workspace, confirm, note, by }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise626 billingLink: (workspace, by) => call("admin_billing_link", { workspace, by }),
627 stripe: (fix = false, by) => call("admin_stripe", { fix, by: by ?? null }),
628 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 person629 enterpriseAddress: (id, address, taxIdType, taxId, by) => call("admin_enterprise_address", { id, address, taxIdType, taxId, by }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise630 invoiceEnterprise: (id, by) => call("admin_invoice_enterprise", { id, by }),
631 accountsFor: (workspaces) => call("admin_accounts", { query: null, workspaces }),
632 signals: () => call("admin_signals", {}),
633 overview: () => call("admin_overview", {}),
634 sales: (workspace) => call("admin_sales", { workspace }),
635 setSales: (workspace, record, by) =>
636 call("admin_set_sales", {
637 workspace,
638 stage: record.stage,
639 owner: record.owner ?? null,
640 next_step: record.nextStep ?? null,
641 next_at: record.nextAt ?? null,
642 by,
643 }),
644 addNote: (workspace, text, by) => call("admin_add_note", { workspace, text, by }),
645 workspaceInvoices: (workspace) => call("admin_workspace_invoices", { workspace }),
646 allInvoices: (filter = {}) => call("admin_invoices", { status: filter.status ?? null, month: filter.month ?? null }),
647 audit: (filter = {}) =>
648 call("admin_audit", { by: filter.by ?? null, action: filter.action ?? null, before: filter.before ?? null }),
649 limitRequests: (status = "open") => call("admin_limit_requests", { status }),
650 decideLimitRequest: (id, decision, amountMicros, note, by) =>
651 call("admin_decide_limit_request", { id, decision, amount_micros: amountMicros, note, by }),
652 overages: () => call("admin_overages", {}),
653 goodwill: (workspace, amountMicros, reason, by, day = null) =>
654 call("admin_goodwill", { workspace, amount_micros: amountMicros, reason, by, day }),
655 velocity: () => call("admin_velocity", {}),
656 recordPayment: (workspace, amountMicros, reference, note, by) =>
657 call("admin_record_payment", { workspace, amount_micros: amountMicros, reference, note, by }),
658 costs: (days) => call("admin_costs", { days: days ?? null }),
659 costAlerts: () => call("admin_cost_alerts", {}),
660 spendCaps: () => call("admin_spend_caps", {}),
661 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)662 platformGuard: () => call("admin_platform_guard", {}),
663 setPause: (level, paused, note, by) => call("admin_set_pause", { level, paused, note, by }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise664 decideProposal: (id, decision, note, by) => call("admin_decide_proposal", { id, decision, note, by }),
665 setCostSettings: (settings, by) => call("admin_set_cost_settings", { settings, by }),
666 setCostMapping: (mapping, by) =>
667 call("admin_set_cost_mapping", {
668 product: mapping.product,
669 meter: mapping.meter,
670 bucket: mapping.bucket ?? "",
671 price_meter: mapping.priceMeter ?? null,
672 own_meter: mapping.ownMeter ?? null,
673 scale_to_own: mapping.scaleToOwn ?? false,
674 drift_percent: mapping.driftPercent ?? null,
675 note: mapping.note ?? "",
676 remove: mapping.remove ?? false,
677 by,
678 }),
679 runCosts: (by) => call("admin_run_costs", { by }),
680 };
681}
682
683export function eventsClient(service: ServiceBinding): EventsApi {
684 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
685 return {
686 publish: (events) => call("publish", { events }),
687 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.688 activityDigest: (args) => call("activity_digest", args),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise689 };
690}
691
692export function integrationsClient(service: ServiceBinding): IntegrationsApi {
693 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
694 return {
695 list: (workspace, viewer) => call("list", { workspace, viewer }),
696 connect: (actor, workspace, input) => call("connect", { actor, workspace, ...input }),
697 update: (actor, workspace, id, input) => call("update", { actor, workspace, id, ...input }),
698 disconnect: (actor, workspace, id) => call("disconnect", { actor, workspace, id }),
699 test: (actor, workspace, id) => call("test", { actor, workspace, id }),
700 deliveries: (workspace, viewer, id) => call("deliveries", { workspace, viewer, id }),
701 resolve: (workspace, viewer, reference) => call("resolve", { workspace, viewer, reference }),
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.702 import: (actor, repo, reference) => call("import", { actor, repo, reference }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise703 links: (repo, number) => call("links", { repo, number }),
Merge branch 'ask-g1t'704 askSettings: (workspace, viewer) => call("ask_settings", { workspace, viewer }),
705 setAskSettings: (actor, workspace, connectionId, model) =>
706 call("set_ask_settings", { actor, workspace, connection_id: connectionId, model }),
707 // Streamed: the response itself, not its parsed body.
708 ask: (actor, workspace, messages, page, signal) =>
709 service.fetch("https://service/rpc/ask", {
710 method: "POST",
711 headers: { "content-type": "application/json" },
712 body: JSON.stringify({ actor, workspace, messages, page }),
713 signal,
714 }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise715 };
716}
717
718export function webhooksClient(service: ServiceBinding): WebhooksApi {
719 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
720 return {
721 list: (viewer, owner) => call("list", { viewer, ...owner }),
722 create: (actor, owner, input) => call("create", { actor, ...owner, ...input }),
723 update: (actor, owner, id, input) => call("update", { actor, ...owner, id, ...input }),
724 delete: (actor, owner, id) => call("delete", { actor, ...owner, id }),
725 ping: (actor, owner, id) => call("ping", { actor, ...owner, id }),
726 deliveries: (viewer, owner, id) => call("deliveries", { viewer, ...owner, id }),
727 redeliver: (actor, owner, deliveryId) => call("redeliver", { actor, ...owner, deliveryId }),
728 };
729}
730
731export function actionsClient(service: ServiceBinding): ActionsApi {
732 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
733 return {
734 workflows: (repo, viewer) => call("workflows", { repo, viewer }),
735 runs: (repo, viewer, filter = {}) => call("runs", { repo, viewer, ...filter }),
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)736 run: (repo, viewer, id, attempt) => call("run", { repo, viewer, id, attempt: attempt ?? null }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise737 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)738 summaries: (repo, viewer, id, attempt) => call("summaries", { repo, viewer, id, attempt: attempt ?? null }),
739 jobLogText: (repo, viewer, job) => call("job_log_text", { repo, viewer, job }),
740 runLogs: (repo, viewer, id, attempt) => call("run_logs", { repo, viewer, id, attempt: attempt ?? null }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise741 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)742 cancel: (actor, repo, id, force = false) => call("cancel", { actor, repo, id, force }),
743 rerun: (actor, repo, id, failedOnly = false, options = {}) =>
744 call("rerun", { actor, repo, id, failed_only: failedOnly, job: options.job ?? null, debug: options.debug ?? false }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise745 setWorkflowEnabled: (actor, repo, workflow, enabled) =>
746 call("set_workflow_enabled", { actor, repo, workflow, enabled }),
747 settings: (actor, owner, kind) => call("settings", { actor, ...owner, kind }),
748 setSetting: (actor, owner, kind, name, value, options = {}) =>
749 call("set_setting", { actor, ...owner, kind, name, value, ...options }),
750 deleteSetting: (actor, owner, kind, name, id) => call("delete_setting", { actor, ...owner, kind, name, id }),
Merge branch 'worktree-agent-a3abfcce648e87dca'751 approveRun: (actor, repo, id) => call("approve_run", { actor, repo, id }),
752 pendingDeployments: (repo, viewer, id) => call("pending_deployments", { repo, viewer, id }),
753 reviewDeployments: (actor, repo, id, state, environments = [], comment) =>
754 call("review_deployments", { actor, repo, id, state, environments, comment: comment ?? null }),
755 actionsSettings: (repo, viewer) => call("actions_settings", { repo, viewer }),
756 setActionsSettings: (actor, repo, change) => call("set_actions_settings", { actor, repo, ...change }),
757 workspaceActionsSettings: (workspace, viewer) => call("workspace_actions_settings", { workspace, viewer }),
758 setWorkspaceActionsSettings: (actor, workspace, change) =>
759 call("set_workspace_actions_settings", { actor, workspace, ...change }),
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.760 workspaceManagedWorkflows: (workspace, viewer) => call("workspace_managed_workflows", { workspace, viewer }),
761 setWorkspaceManagedWorkflow: (actor, workspace, name, change, create = false) =>
762 call("set_workspace_managed_workflow", { actor, workspace, name, create, ...change }),
763 removeWorkspaceManagedWorkflow: (actor, workspace, name) =>
764 call("remove_workspace_managed_workflow", { actor, workspace, name }),
Merge branch 'worktree-agent-a3abfcce648e87dca'765 environments: (repo, viewer) => call("environments", { repo, viewer }),
766 setEnvironment: (actor, repo, name, change) => call("set_environment", { actor, repo, name, ...change }),
767 deleteEnvironment: (actor, repo, name) => call("delete_environment", { actor, repo, name }),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2768 artifacts: (repo, viewer, filter = {}) => call("artifacts", { repo, viewer, ...filter }),
769 artifactDownload: (repo, viewer, by) => call("artifact_download", { repo, viewer, ...by }),
770 deleteArtifact: (actor, repo, id) => call("delete_artifact", { actor, repo, id }),
771 artifactRetention: (repo, viewer, days) => call("artifact_retention", { repo, viewer, days }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise772 };
773}
774
775
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.776/** Staff-only self-hosted runners, on the actions service: only sudo calls it. */
777export function runnersAdminClient(service: ServiceBinding): RunnersAdminApi {
778 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
779 return {
780 list: (workspace) => call("admin_runners", { workspace }),
781 remove: (workspace, id, staff) => call("admin_remove_runners", { workspace, id, staff }),
782 };
783}
784
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise785/** Self-hosted runners, kept by the actions service. */
786export function runnersClient(service: ServiceBinding): RunnersApi {
787 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
788 return {
789 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.790 activity: (actor, workspace) => call("runner_activity", { actor, workspace }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise791 list: (actor, owner) => call("runners", { actor, ...owner }),
792 createToken: (actor, owner, group) => call("create_registration_token", { actor, ...owner, group }),
793 remove: (actor, owner, id) => call("remove_runner", { actor, ...owner, id }),
794 groups: (actor, workspace) => call("runner_groups", { actor, workspace }),
795 setGroup: (actor, workspace, group) => call("set_runner_group", { actor, workspace, ...group }),
796 deleteGroup: (actor, workspace, id) => call("delete_runner_group", { actor, workspace, id }),
797 settings: (actor, owner) => call("runner_settings", { actor, ...owner }),
798 setSettings: (actor, owner, change) => call("set_runner_settings", { actor, ...owner, ...change }),
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.799 reportDesktop: (actor, report) => call("report_desktop", { actor, ...report }),
800 desktops: (actor, workspace) => call("desktop_runners", { actor, workspace }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise801 };
802}
803
804export function deploymentsClient(service: ServiceBinding): DeploymentsApi {
805 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
806 return {
807 settings: (project, viewer) => call("settings", { project, viewer }),
808 updateSettings: (actor, project, changes) => call("update_settings", { actor, project, changes }),
809 list: (project, viewer) => call("list", { project, viewer }),
810 get: (project, id, viewer) => call("get", { project, id, viewer }),
811 redeploy: (actor, project, branch) => call("redeploy", { actor, project, branch }),
812 takeDown: (actor, project, branch) => call("take_down", { actor, project, branch }),
813 overview: (workspace, viewer) => call("overview", { workspace, viewer }),
814 usage: (workspace, viewer) => call("usage", { workspace, viewer }),
815 domains: (project, viewer) => call("domains", { project, viewer }),
816 addDomain: (actor, project, hostname, options = {}) =>
817 call("add_domain", { actor, project, hostname, twin: !!options.twin }),
818 removeDomain: (actor, project, id) => call("remove_domain", { actor, project, id }),
819 refreshDomain: (actor, project, id) => call("refresh_domain", { actor, project, id }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97820 repoDeployments: (repo, viewer, filter = {}) => call("list_deployments", { repo, viewer, ...filter }),
821 repoDeployment: (repo, id, viewer) => call("get_deployment", { repo, id, viewer }),
822 environments: (repo, viewer) => call("list_environments", { repo, viewer }),
823 createDeployment: (actor, repo, input) => call("create_deployment", { repo, actor, ...input }),
824 createDeploymentStatus: (actor, repo, id, input) => call("create_deployment_status", { repo, actor, id, ...input }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise825 };
826}
827
828/** Packages: the registries beside the code (services/packages). */
829export function packagesClient(service: ServiceBinding): PackagesApi {
830 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
831 return {
832 list: (workspace, viewer, filter = {}) =>
833 call("list_packages", {
834 workspace,
835 viewer,
836 ecosystem: filter.ecosystem ?? null,
837 repo_id: filter.repoId ?? null,
838 query: filter.query ?? null,
839 }),
840 get: (workspace, ecosystem, name, viewer) => call("get_package", { workspace, ecosystem, name, viewer }),
841 deleteVersion: (actor, workspace, ecosystem, name, version, surface) =>
842 call("delete_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }),
843 deletePackage: (actor, workspace, ecosystem, name, surface) =>
844 call("delete_package", { actor, workspace, ecosystem, name, surface: surface ?? null }),
845 set: (actor, workspace, ecosystem, name, change, surface) =>
846 call("set_package", {
847 actor,
848 workspace,
849 ecosystem,
850 name,
851 visibility: change.visibility ?? null,
852 link: change.link ?? null,
853 unlink: change.unlink ?? false,
Merge packages: roles, Actions access, source label, soft delete, API854 inherit_access: change.inheritAccess ?? null,
855 surface: surface ?? null,
856 }),
857 settings: (workspace, ecosystem, name, viewer) => call("package_settings", { workspace, ecosystem, name, viewer }),
858 deleted: (workspace, viewer) => call("deleted_packages", { workspace, viewer }),
859 restorePackage: (actor, workspace, ecosystem, name, surface) =>
860 call("restore_package", { actor, workspace, ecosystem, name, surface: surface ?? null }),
861 restoreVersion: (actor, workspace, ecosystem, name, version, surface) =>
862 call("restore_version", { actor, workspace, ecosystem, name, version, surface: surface ?? null }),
863 setAccess: (actor, workspace, ecosystem, name, who, role, surface) =>
864 call("set_package_access", {
865 actor,
866 workspace,
867 ecosystem,
868 name,
869 user: "user" in who ? who.user : null,
870 team: "team" in who ? who.team : null,
871 role,
872 surface: surface ?? null,
873 }),
874 removeAccess: (actor, workspace, ecosystem, name, who, surface) =>
875 call("remove_package_access", {
876 actor,
877 workspace,
878 ecosystem,
879 name,
880 user: "user" in who ? who.user : null,
881 team: "team" in who ? who.team : null,
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise882 surface: surface ?? null,
883 }),
Merge packages: roles, Actions access, source label, soft delete, API884 setActionsAccess: (actor, workspace, ecosystem, name, repo, role, surface) =>
885 call("set_actions_access", { actor, workspace, ecosystem, name, repo, role, surface: surface ?? null }),
886 removeActionsAccess: (actor, workspace, ecosystem, name, repo, surface) =>
887 call("remove_actions_access", { actor, workspace, ecosystem, name, repo, surface: surface ?? null }),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise888 storage: (workspace) => call("storage", { workspace }),
889 storageAll: () => call("storage_all", {}),
890 syncComposer: (repoId) => call("sync_composer", { repo_id: repoId }),
891 };
892}
893
894export function projectsClient(service: ServiceBinding): ProjectsApi {
895 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
896 return {
897 list: (workspace, viewer) => call("list", { workspace, viewer }),
898 get: (workspace, slug, viewer) => call("get", { workspace, slug, viewer }),
899 byRepo: (repoId) => call("by_repo", { repoId }),
900 create: (actor, workspace, input) => call("create", { actor, workspace, input }),
901 update: (actor, workspace, slug, changes) => call("update", { actor, workspace, slug, changes }),
Merge branch 'worktree-agent-a5b0d9e2733e9e382'902 setProductionImage: (actor, workspace, slug, change) => call("set_production_image", { actor, workspace, slug, change }),
903 externalProductions: () => call("external_productions", {}),
README: run the core forge locally with Docker Compose; forks described as copies, not copy-on-write, until Cloudflare says otherwise904 deploymentsChanged: (projectId, enabled) => call("deployments_changed", { projectId, enabled }),
Projects: pinned and recent projects, per person per workspace905 shortcuts: (workspace, viewer) => call("shortcuts", { workspace, viewer }),
906 pin: (actor, workspace, slug, position) => call("pin", { actor, workspace, slug, position: position ?? null }),
907 unpin: (actor, workspace, slug) => call("unpin", { actor, workspace, slug }),
908 reorderPins: (actor, workspace, slugs) => call("reorder_pins", { actor, workspace, slugs }),
909 visited: (actor, projectId) => call("visited", { actor, projectId }),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97910 publicLinks: (repos) => call("public_links", { repos }),
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.911 };
912}

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