Skip to content
1,479 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.

The balance shows what was spent this month and in total1//! REST: each route maps an HTTP request onto one operation.
2
3use serde_json::{Map, Value};
4
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb975use crate::about::AboutOp;
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.6use crate::run_artifacts::ArtifactsOp;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r97use crate::customize::CustomizeOp;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca8use crate::deploy_keys::DeployKeysOp;
Merge branch 'mirroring' into artifacts-mode9use crate::mirrors::MirrorsOp;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9710use crate::deployments::DeploymentsOp;
Merge packages: roles, Actions access, source label, soft delete, API11use crate::packages::PackagesOp;
Merge main into Artifacts Phase 212use crate::folios::FoliosOp;
Merge branch 'worktree-agent-a3abfcce648e87dca'13use crate::protection::ProtectionOp;
API and MCP for a workspace's personal access token rules, members' tokens and approvals14use crate::token_policy::TokenOp;
The balance shows what was spent this month and in total15use crate::operations::Op;
Merge checks: statuses and check runs on every commit16use crate::checks::ChecksOp;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge17use crate::rules::RulesOp;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar18use crate::security::SecurityOp;
The balance shows what was spent this month and in total19
20pub struct Route {
21 pub method: &'static str,
22 /// Segments starting with `:` are parameters.
23 pub path: &'static str,
24 pub op: Op,
25 /// Query parameters the route reads, as `(name in the URL, input name)`.
26 pub query: &'static [(&'static str, &'static str)],
27}
28
29const fn route(
30 method: &'static str,
31 path: &'static str,
32 op: Op,
33 query: &'static [(&'static str, &'static str)],
34) -> Route {
35 Route {
36 method,
37 path,
38 op,
39 query,
40 }
41}
42
43pub const ROUTES: &[Route] = &[
44 route("GET", "/user", Op::Whoami, &[]),
45 route("POST", "/workspaces", Op::CreateWorkspace, &[]),
Merge branch 'worktree-agent-ad7c6d88d93adc817'46 route("GET", "/workspaces/:workspace", Op::GetWorkspace, &[]),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look47 route("DELETE", "/workspaces/:workspace", Op::DeleteWorkspace, &[]),
48 route("GET", "/user/emails", Op::ListEmails, &[]),
49 route("POST", "/user/emails", Op::AddEmail, &[]),
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)50 route("POST", "/user/emails/confirm", Op::ConfirmEmail, &[]),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look51 route("DELETE", "/user/emails/:email", Op::RemoveEmail, &[]),
52 route("PATCH", "/user/email-settings", Op::UpdateEmailSettings, &[]),
53 route("GET", "/user/invites", Op::ListInvites, &[]),
54 route("POST", "/user/invites", Op::CreateInvite, &[]),
55 route("DELETE", "/user/invites/:id", Op::RevokeInvite, &[]),
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)56 // Invitations to workspaces waiting for your answer.
57 route("GET", "/user/invitations", Op::ListInvitations, &[]),
58 route("POST", "/user/invitations/:id/accept", Op::AcceptInvitation, &[]),
59 route("POST", "/user/invitations/:id/decline", Op::DeclineInvitation, &[]),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look60 route("GET", "/workspaces/:workspace/invitations", Op::ListWorkspaceInvites, &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals61 // A workspace's rules for personal access tokens, and its members' tokens.
62 route("GET", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::GetTokenPolicy), &[]),
63 route("PATCH", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::SetTokenPolicy), &[]),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers64 route("GET", "/workspaces/:workspace/personal-access-tokens", Op::Tokens(TokenOp::ListMemberTokens), &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals65 route("POST", "/workspaces/:workspace/personal-access-tokens/:id", Op::Tokens(TokenOp::RevokeMemberToken), &[]),
66 route("GET", "/workspaces/:workspace/personal-access-token-requests", Op::Tokens(TokenOp::ListTokenRequests), &[]),
67 route("POST", "/workspaces/:workspace/personal-access-token-requests/:id", Op::Tokens(TokenOp::ReviewTokenRequest), &[]),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look68 route("POST", "/workspaces/:workspace/invitations", Op::InviteMember, &[]),
69 route("DELETE", "/workspaces/:workspace/invitations/:id", Op::RevokeWorkspaceInvite, &[]),
70 // Who has access. GitHub's addresses, but for adding someone, which
71 // takes an email address as well as a username.
72 route("GET", "/repos/:owner/:name/collaborators", Op::ListCollaborators, &[]),
73 route("POST", "/repos/:owner/:name/collaborators", Op::AddCollaborator, &[]),
74 route("PATCH", "/repos/:owner/:name/collaborators/:username", Op::UpdateCollaborator, &[]),
75 route("DELETE", "/repos/:owner/:name/collaborators/:username", Op::RemoveCollaborator, &[]),
76 route(
77 "GET",
78 "/repos/:owner/:name/collaborators/:username/permission",
79 Op::GetCollaboratorPermission,
80 &[],
81 ),
82 route("GET", "/repos/:owner/:name/invitations", Op::ListRepoInvitations, &[]),
83 route("DELETE", "/repos/:owner/:name/invitations/:id", Op::RevokeRepoInvitation, &[]),
84 route("GET", "/user/repository_invitations", Op::ListMyRepoInvitations, &[]),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca85 // Deploy keys, at GitHub's addresses.
86 route("GET", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::ListDeployKeys), &[]),
87 route("POST", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::CreateDeployKey), &[]),
88 route("GET", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::GetDeployKey), &[]),
89 route("DELETE", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::DeleteDeployKey), &[]),
Merge branch 'mirroring' into artifacts-mode90 // Mirroring: a repository's remotes, takeovers and hand-backs.
91 route("GET", "/repos/:owner/:name/mirror", Op::Mirrors(MirrorsOp::GetMirror), &[]),
92 route("GET", "/repos/:owner/:name/mirror/hand-back", Op::Mirrors(MirrorsOp::GetHandBackPlan), &[]),
93 route("POST", "/repos/:owner/:name/mirror/take-over", Op::Mirrors(MirrorsOp::TakeOver), &[]),
94 route("POST", "/repos/:owner/:name/mirror/ci", Op::Mirrors(MirrorsOp::SetCiFailover), &[]),
95 route("POST", "/repos/:owner/:name/mirror/hand-back", Op::Mirrors(MirrorsOp::HandBack), &[]),
96 route("POST", "/repos/:owner/:name/mirror/move-to-g1t", Op::Mirrors(MirrorsOp::MoveToG1t), &[]),
97 route("POST", "/repos/:owner/:name/mirror/sync", Op::Mirrors(MirrorsOp::SyncMirror), &[]),
98 route("POST", "/repos/:owner/:name/mirror/remotes", Op::Mirrors(MirrorsOp::AddRemote), &[]),
99 route("PATCH", "/repos/:owner/:name/mirror/remotes/:id", Op::Mirrors(MirrorsOp::UpdateRemote), &[]),
100 route("DELETE", "/repos/:owner/:name/mirror/remotes/:id", Op::Mirrors(MirrorsOp::RemoveRemote), &[]),
API: notifications over REST and MCP, with notifications scopes101 // Your notifications: threads, marking them, and what you subscribe
102 // to and watch. GitHub's addresses, with g1t's saved and snoozed.
103 route("GET", "/notifications", Op::ListNotifications, &[("all", "all"), ("participating", "participating"), ("view", "view"), ("reason", "reason"), ("severity", "severity"), ("since", "since"), ("before", "before"), ("cursor", "cursor"), ("per_page", "per_page")]),
104 route("PUT", "/notifications", Op::MarkNotificationsRead, &[]),
105 route("GET", "/notifications/threads/:id", Op::GetNotificationThread, &[]),
106 route("PATCH", "/notifications/threads/:id", Op::MarkThreadRead, &[]),
107 route("DELETE", "/notifications/threads/:id", Op::MarkThreadDone, &[]),
108 route("PUT", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
109 route("DELETE", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
110 route("PUT", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
111 route("DELETE", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
112 route("GET", "/notifications/threads/:id/subscription", Op::GetThreadSubscription, &[]),
113 route("PUT", "/notifications/threads/:id/subscription", Op::SetThreadSubscription, &[]),
114 route("DELETE", "/notifications/threads/:id/subscription", Op::DeleteThreadSubscription, &[]),
115 route("GET", "/repos/:owner/:name/notifications", Op::ListNotifications, &[("all", "all"), ("participating", "participating"), ("view", "view"), ("reason", "reason"), ("severity", "severity"), ("since", "since"), ("before", "before"), ("cursor", "cursor"), ("per_page", "per_page")]),
116 route("PUT", "/repos/:owner/:name/notifications", Op::MarkNotificationsRead, &[]),
117 route("GET", "/repos/:owner/:name/subscription", Op::GetRepoSubscription, &[]),
118 route("PUT", "/repos/:owner/:name/subscription", Op::SetRepoSubscription, &[]),
119 route("DELETE", "/repos/:owner/:name/subscription", Op::DeleteRepoSubscription, &[]),
120 route("GET", "/repos/:owner/:name/issues/:number/subscription", Op::GetThreadSubscription, &[]),
121 route("PUT", "/repos/:owner/:name/issues/:number/subscription", Op::SetThreadSubscription, &[]),
122 route("DELETE", "/repos/:owner/:name/issues/:number/subscription", Op::DeleteThreadSubscription, &[]),
123 route("GET", "/user/subscriptions", Op::ListWatchedRepos, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97124 // Stars: yours, and who starred a repository.
125 route("GET", "/user/starred", Op::About(AboutOp::ListStarred), &[]),
126 route("GET", "/user/starred/:owner/:name", Op::About(AboutOp::CheckStarred), &[]),
127 route("PUT", "/user/starred/:owner/:name", Op::About(AboutOp::Star), &[]),
128 route("DELETE", "/user/starred/:owner/:name", Op::About(AboutOp::Unstar), &[]),
129 route("GET", "/repos/:owner/:name/stargazers", Op::About(AboutOp::ListStargazers), &[("page", "page")]),
130 // What the default branch says about a repository, kept by commit.
131 route("GET", "/repos/:owner/:name/languages", Op::About(AboutOp::GetLanguages), &[]),
132 route("GET", "/repos/:owner/:name/contributors", Op::About(AboutOp::ListContributors), &[]),
133 route("GET", "/repos/:owner/:name/license", Op::About(AboutOp::GetLicense), &[]),
134 // Releases: `latest` and `tags/…` before an id.
135 route("GET", "/repos/:owner/:name/releases", Op::About(AboutOp::ListReleases), &[]),
136 route("POST", "/repos/:owner/:name/releases", Op::About(AboutOp::CreateRelease), &[]),
137 route("GET", "/repos/:owner/:name/releases/latest", Op::About(AboutOp::GetLatestRelease), &[]),
138 route("GET", "/repos/:owner/:name/releases/tags/:tag", Op::About(AboutOp::GetReleaseByTag), &[]),
139 route("GET", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::GetRelease), &[]),
140 route("PATCH", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::UpdateRelease), &[]),
141 route("DELETE", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::DeleteRelease), &[]),
API: pinned projects over REST and MCP142 // Your pinned projects in a workspace, in your order.
143 route("GET", "/user/pinned_projects/:workspace", Op::ListPinnedProjects, &[]),
144 route("PUT", "/user/pinned_projects/:workspace", Op::ReorderPinnedProjects, &[]),
145 route("PUT", "/user/pinned_projects/:workspace/:project", Op::PinProject, &[]),
146 route("DELETE", "/user/pinned_projects/:workspace/:project", Op::UnpinProject, &[]),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look147 route("PATCH", "/user/repository_invitations/:id", Op::AcceptRepoInvitation, &[]),
148 route("DELETE", "/user/repository_invitations/:id", Op::DeclineRepoInvitation, &[]),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily149 route("PATCH", "/workspaces/:workspace", Op::UpdateWorkspace, &[]),
Merge main (membership, two-factor, GitHub repo roles) into tokens150 // Members and owners: GitHub's organization members, by username.
151 route("GET", "/workspaces/:workspace/members", Op::ListMembers, &[]),
152 route("PATCH", "/workspaces/:workspace/members/:username", Op::UpdateMember, &[]),
153 route("DELETE", "/workspaces/:workspace/members/:username", Op::RemoveMember, &[]),
154 route("POST", "/workspaces/:workspace/transfer_ownership", Op::TransferOwnership, &[]),
155 route("DELETE", "/user/memberships/:workspace", Op::LeaveWorkspace, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97156 // A workspace's projects: what each is, where it runs, its links.
157 route("GET", "/workspaces/:workspace/projects", Op::ListProjects, &[]),
158 route("GET", "/workspaces/:workspace/projects/:project", Op::GetProject, &[]),
159 route("PATCH", "/workspaces/:workspace/projects/:project", Op::UpdateProject, &[]),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily160 route("PUT", "/workspaces/:workspace/base_permission", Op::SetBasePermission, &[]),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look161 route(
162 "GET",
163 "/workspaces/:workspace/outside_collaborators",
164 Op::ListOutsideCollaborators,
165 &[],
166 ),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar167 // Teams: a workspace's groups of members, with roles on repositories.
168 route("GET", "/workspaces/:workspace/teams", Op::ListTeams, &[("q", "query")]),
169 route("POST", "/workspaces/:workspace/teams", Op::CreateTeam, &[]),
170 route("GET", "/workspaces/:workspace/teams/:team", Op::GetTeam, &[]),
171 route("PATCH", "/workspaces/:workspace/teams/:team", Op::UpdateTeam, &[]),
172 route("DELETE", "/workspaces/:workspace/teams/:team", Op::DeleteTeam, &[]),
173 route(
174 "GET",
175 "/workspaces/:workspace/teams/:team/members",
176 Op::ListTeamMembers,
177 &[("include_child_teams", "include_child_teams")],
178 ),
179 route("PUT", "/workspaces/:workspace/teams/:team/members/:username", Op::SetTeamMember, &[]),
180 route("DELETE", "/workspaces/:workspace/teams/:team/members/:username", Op::RemoveTeamMember, &[]),
181 route("GET", "/workspaces/:workspace/teams/:team/teams", Op::ListChildTeams, &[]),
182 route("GET", "/workspaces/:workspace/teams/:team/repos", Op::ListTeamRepos, &[]),
183 route("PUT", "/workspaces/:workspace/teams/:team/repos/:repo", Op::SetTeamRepo, &[]),
184 route("DELETE", "/workspaces/:workspace/teams/:team/repos/:repo", Op::RemoveTeamRepo, &[]),
185 route(
186 "PUT",
187 "/workspaces/:workspace/teams/:team/review_assignment",
188 Op::SetTeamReviewAssignment,
189 &[],
190 ),
191 route("GET", "/workspaces/:workspace/members/:username/teams", Op::ListUserTeams, &[]),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit192 // A workspace's billing: usage, budget, AI credit and invoices.
193 route(
194 "GET",
195 "/workspaces/:workspace/usage",
196 Op::GetUsage,
197 &[("from", "from"), ("until", "until"), ("products", "products"), ("projects", "projects"), ("group_by", "group_by")],
198 ),
199 route("GET", "/workspaces/:workspace/budget", Op::GetBudget, &[]),
200 route("PUT", "/workspaces/:workspace/budget", Op::SetBudget, &[]),
201 route("GET", "/workspaces/:workspace/ai_credit", Op::GetAiCredit, &[]),
202 route("POST", "/workspaces/:workspace/ai_credit/checkout", Op::BuyAiCredit, &[]),
203 route("GET", "/workspaces/:workspace/invoices", Op::ListInvoices, &[]),
204 route("GET", "/workspaces/:workspace/billing_details", Op::GetBillingDetails, &[]),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens205 // The AI Gateway's log of a workspace's requests.
206 route("GET", "/workspaces/:workspace/gateway/requests", Op::ListGatewayRequests, &[("limit", "limit"), ("before", "before")]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar207 // Code owners: the CODEOWNERS file, checked.
208 route("GET", "/repos/:owner/:name/codeowners/errors", Op::GetCodeownersErrors, &[("ref", "ref")]),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily209 // Security alerts: secrets and vulnerable dependencies.
210 route(
211 "GET",
212 "/repos/:owner/:name/security/alerts",
213 Op::ListSecurityAlerts,
214 &[("state", "state"), ("kind", "kind")],
215 ),
216 route("POST", "/repos/:owner/:name/security/alerts/:id/dismiss", Op::DismissSecurityAlert, &[]),
217 route("POST", "/repos/:owner/:name/security/alerts/:id/reopen", Op::ReopenSecurityAlert, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar218 // The security suite: secret scanning, code scanning, vulnerability
219 // alerts and the supply chain, at the common addresses.
220 route("GET", "/repos/:owner/:name/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
221 route("GET", "/workspaces/:workspace/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
222 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::GetSecretAlert), &[]),
223 route("PATCH", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::UpdateSecretAlert), &[]),
224 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id/locations", Op::Security(SecurityOp::ListSecretLocations), &[]),
225 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/bypass", Op::Security(SecurityOp::BypassPushProtection), &[]),
226 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/validity", Op::Security(SecurityOp::CheckSecretValidity), &[]),
227 route("GET", "/workspaces/:workspace/secret-scanning/bypass-requests", Op::Security(SecurityOp::ListBypassRequests), &[("state", "state"), ("repo", "repo")]),
228 route("PATCH", "/workspaces/:workspace/secret-scanning/bypass-requests/:id", Op::Security(SecurityOp::ReviewBypassRequest), &[]),
229 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
230 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
231 route("GET", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
232 route("GET", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
233 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
234 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
235 route("PATCH", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
236 route("PATCH", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
237 route("DELETE", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
238 route("DELETE", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
239 route("GET", "/repos/:owner/:name/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
240 route("GET", "/workspaces/:workspace/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
241 route("GET", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::GetCodeAlert), &[]),
242 route("PATCH", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::UpdateCodeAlert), &[]),
243 route("GET", "/repos/:owner/:name/code-scanning/analyses", Op::Security(SecurityOp::ListAnalyses), &[]),
244 route("POST", "/repos/:owner/:name/code-scanning/sarifs", Op::Security(SecurityOp::UploadSarif), &[]),
245 route("GET", "/repos/:owner/:name/code-scanning/sarifs/:id", Op::Security(SecurityOp::GetSarifUpload), &[]),
246 route("GET", "/repos/:owner/:name/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
247 route("GET", "/workspaces/:workspace/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
248 route("GET", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::GetVulnerabilityAlert), &[]),
249 route("PATCH", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::UpdateVulnerabilityAlert), &[]),
250 route("POST", "/repos/:owner/:name/security/alerts/:id/fix", Op::Security(SecurityOp::FixAlert), &[]),
251 route("GET", "/repos/:owner/:name/dependency-graph", Op::Security(SecurityOp::GetDependencyGraph), &[]),
252 route("GET", "/repos/:owner/:name/dependency-graph/sbom", Op::Security(SecurityOp::GetSbom), &[]),
253 route("GET", "/repos/:owner/:name/dependency-graph/compare/:basehead", Op::Security(SecurityOp::CompareDependencies), &[]),
254 route("GET", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::GetSettings), &[]),
255 route("PATCH", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::UpdateSettings), &[]),
256 route("GET", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::GetWorkspaceSettings), &[]),
257 route("PATCH", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::UpdateWorkspaceSettings), &[]),
258 route("GET", "/workspaces/:workspace/security/overview", Op::Security(SecurityOp::GetOverview), &[("days", "days")]),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9259 // Reporting: a repository's advisories, and the vulnerability reports sent to its maintainers.
260 route("GET", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::ListAdvisories), &[("state", "state")]),
261 route("POST", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::CreateAdvisory), &[]),
262 route("GET", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::GetAdvisory), &[]),
263 route("PATCH", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::UpdateAdvisory), &[]),
264 route("POST", "/repos/:owner/:name/security/advisories/:id/state", Op::Security(SecurityOp::SetAdvisoryState), &[]),
265 route("GET", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ListReports), &[("state", "state")]),
266 route("POST", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ReportVulnerability), &[]),
267 route("PATCH", "/repos/:owner/:name/security/reports/:id", Op::Security(SecurityOp::ReviewReport), &[]),
The balance shows what was spent this month and in total268 route("GET", "/repos", Op::ListRepos, &[("q", "query")]),
Search across all of g1t, Explore, and a command palette269 route(
270 "GET",
271 "/search",
272 Op::Search,
273 &[("q", "query"), ("type", "type"), ("page", "page"), ("per_page", "per_page")],
274 ),
The balance shows what was spent this month and in total275 route("POST", "/repos", Op::CreateRepo, &[]),
276 route("GET", "/repos/:owner/:name", Op::GetRepo, &[]),
277 route("PATCH", "/repos/:owner/:name", Op::UpdateRepo, &[]),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look278 route("POST", "/repos/:owner/:name/transfer", Op::TransferRepo, &[]),
279 route("DELETE", "/repos/:owner/:name", Op::DeleteRepo, &[]),
280 route(
281 "GET",
282 "/workspaces/:workspace/repos/deleted",
283 Op::ListDeletedRepos,
284 &[],
285 ),
286 route("POST", "/repos/:owner/:name/restore", Op::RestoreRepo, &[]),
287 route("POST", "/repos/:owner/:name/purge", Op::PurgeRepo, &[]),
288 route("POST", "/repos/:owner/:name/rename", Op::RenameRepo, &[]),
289 route("POST", "/repos/:owner/:name/archive", Op::ArchiveRepo, &[]),
290 route("POST", "/repos/:owner/:name/unarchive", Op::UnarchiveRepo, &[]),
291 route(
292 "POST",
293 "/repos/:owner/:name/visibility",
294 Op::SetRepoVisibility,
295 &[],
296 ),
297 route(
298 "POST",
299 "/repos/:owner/:name/branches/:branch/rename",
300 Op::RenameBranch,
301 &[],
302 ),
The balance shows what was spent this month and in total303 route(
304 "GET",
305 "/repos/:owner/:name/settings",
306 Op::GetRepoSettings,
307 &[],
308 ),
309 route(
310 "PATCH",
311 "/repos/:owner/:name/settings",
312 Op::UpdateRepoSettings,
313 &[],
314 ),
Fast pages, required checks on the branch, self-hosted runners, honest incidents315 route("GET", "/repos/:owner/:name/check-names", Op::ListCheckNames, &[]),
Merge checks: statuses and check runs on every commit316 // Checks: GitHub's addresses for statuses, check runs and check suites.
317 route("POST", "/repos/:owner/:name/statuses/:sha", Op::Checks(ChecksOp::CreateCommitStatus), &[]),
318 route("GET", "/repos/:owner/:name/commits/:ref/statuses", Op::Checks(ChecksOp::ListCommitStatuses), &[]),
319 route("GET", "/repos/:owner/:name/commits/:ref/status", Op::Checks(ChecksOp::GetCombinedStatus), &[]),
320 route(
321 "GET",
322 "/repos/:owner/:name/commits/:ref/check-runs",
323 Op::Checks(ChecksOp::ListCheckRunsForRef),
324 &[("check_name", "check_name"), ("status", "status"), ("app", "app"), ("filter", "filter")],
325 ),
326 route(
327 "GET",
328 "/repos/:owner/:name/commits/:ref/check-suites",
329 Op::Checks(ChecksOp::ListCheckSuitesForRef),
330 &[("app", "app"), ("check_name", "check_name")],
331 ),
332 route("POST", "/repos/:owner/:name/check-runs", Op::Checks(ChecksOp::CreateCheckRun), &[]),
333 route("GET", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::GetCheckRun), &[]),
334 route("PATCH", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::UpdateCheckRun), &[]),
335 route("GET", "/repos/:owner/:name/check-runs/:id/annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), &[]),
336 route("POST", "/repos/:owner/:name/check-runs/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckRun), &[]),
337 route("GET", "/repos/:owner/:name/check-suites/:id", Op::Checks(ChecksOp::GetCheckSuite), &[]),
338 route("POST", "/repos/:owner/:name/check-suites/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckSuite), &[]),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge339 // Rulesets: a repository's, a workspace's, the rules of one branch,
340 // and how they judged pushes and merges.
341 route("GET", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::ListRepoRulesets), &[("include_parents", "include_parents")]),
342 route("POST", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::CreateRepoRuleset), &[]),
343 route("GET", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::GetRepoRuleset), &[]),
344 route("PUT", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::UpdateRepoRuleset), &[]),
345 route("DELETE", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::DeleteRepoRuleset), &[]),
346 route("GET", "/repos/:owner/:name/rules/branches/:branch", Op::Rules(RulesOp::GetBranchRules), &[("target", "target")]),
347 route(
348 "GET",
349 "/repos/:owner/:name/rules/evaluations",
350 Op::Rules(RulesOp::ListRuleEvaluations),
351 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
352 ),
353 route("GET", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::ListWorkspaceRulesets), &[]),
354 route("POST", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::CreateWorkspaceRuleset), &[]),
355 route("GET", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::GetWorkspaceRuleset), &[]),
356 route("PUT", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::UpdateWorkspaceRuleset), &[]),
357 route("DELETE", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::DeleteWorkspaceRuleset), &[]),
358 route(
359 "GET",
360 "/workspaces/:workspace/rules/evaluations",
361 Op::Rules(RulesOp::ListWorkspaceRuleEvaluations),
362 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
363 ),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97364 // Deployments wherever they run, their statuses, and environments.
365 route(
366 "GET",
367 "/repos/:owner/:name/deployments",
368 Op::Deployments(DeploymentsOp::ListDeployments),
369 &[("environment", "environment"), ("ref", "ref"), ("sha", "sha"), ("task", "task"), ("state", "state"), ("source", "source"), ("creator", "creator"), ("page", "page"), ("per_page", "per_page")],
370 ),
371 route("POST", "/repos/:owner/:name/deployments", Op::Deployments(DeploymentsOp::CreateDeployment), &[]),
Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9372 // A g1t Actions job opening a g1t.page build it runs itself.
373 route("POST", "/repos/:owner/:name/deployments/builds", Op::Deployments(DeploymentsOp::StartDeploymentBuild), &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97374 route("GET", "/repos/:owner/:name/deployments/:id", Op::Deployments(DeploymentsOp::GetDeployment), &[]),
375 route("GET", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::ListDeploymentStatuses), &[]),
376 route("POST", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::CreateDeploymentStatus), &[]),
377 route("GET", "/repos/:owner/:name/environments", Op::Deployments(DeploymentsOp::ListEnvironments), &[]),
378 route("GET", "/repos/:owner/:name/environments/:environment", Op::Deployments(DeploymentsOp::GetEnvironment), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'379 // Environments' protection rules, and the runs they hold.
380 route("PUT", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::UpdateEnvironment), &[]),
381 route("DELETE", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::DeleteEnvironment), &[]),
382 route(
383 "GET",
384 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
385 Op::Protection(ProtectionOp::GetPendingDeployments),
386 &[],
387 ),
388 route(
389 "POST",
390 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
391 Op::Protection(ProtectionOp::ReviewPendingDeployments),
392 &[],
393 ),
394 route("POST", "/repos/:owner/:name/actions/runs/:id/approve", Op::Protection(ProtectionOp::ApproveWorkflowRun), &[]),
395 route("GET", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::GetWorkflowPermissions), &[]),
396 route("PUT", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::SetWorkflowPermissions), &[]),
397 route(
398 "GET",
399 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
400 Op::Protection(ProtectionOp::GetForkPrApproval),
401 &[],
402 ),
403 route(
404 "PUT",
405 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
406 Op::Protection(ProtectionOp::SetForkPrApproval),
407 &[],
408 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts409 route("GET", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::GetActionsAccess), &[]),
410 route("PUT", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::SetActionsAccess), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'411 route("POST", "/repos/:owner/:name/dispatches", Op::Protection(ProtectionOp::CreateRepositoryDispatch), &[]),
412 route(
413 "GET",
414 "/workspaces/:workspace/actions/permissions/workflow",
415 Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions),
416 &[],
417 ),
418 route(
419 "PUT",
420 "/workspaces/:workspace/actions/permissions/workflow",
421 Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions),
422 &[],
423 ),
The balance shows what was spent this month and in total424 route("GET", "/repos/:owner/:name/queue", Op::GetMergeQueue, &[]),
425 route(
426 "POST",
427 "/repos/:owner/:name/pulls/:number/messages",
428 Op::MessageAgent,
429 &[],
430 ),
431 route(
432 "POST",
433 "/repos/:owner/:name/pulls/:number/messages/take",
434 Op::TakeMessages,
435 &[],
436 ),
437 route(
438 "POST",
439 "/repos/:owner/:name/messages/:id/answer",
440 Op::AnswerMessage,
441 &[],
442 ),
443 route("POST", "/repos/:owner/:name/memory", Op::Remember, &[]),
444 route(
445 "GET",
446 "/repos/:owner/:name/memory",
447 Op::Recall,
448 &[("q", "query"), ("limit", "limit")],
449 ),
450 route(
451 "GET",
452 "/repos/:owner/:name/events",
453 Op::ListEvents,
454 &[("before", "before")],
455 ),
456 route("GET", "/repos/:owner/:name/labels", Op::ListLabels, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar457 route("POST", "/repos/:owner/:name/labels", Op::CreateLabel, &[]),
458 route("POST", "/repos/:owner/:name/labels/defaults", Op::AddDefaultLabels, &[]),
459 route("PATCH", "/repos/:owner/:name/labels/:label", Op::UpdateLabel, &[]),
460 route("DELETE", "/repos/:owner/:name/labels/:label", Op::DeleteLabel, &[]),
461 route("GET", "/repos/:owner/:name/issues/:number/labels", Op::ListIssueLabels, &[]),
462 route("POST", "/repos/:owner/:name/issues/:number/labels", Op::AddIssueLabels, &[]),
463 route("PUT", "/repos/:owner/:name/issues/:number/labels", Op::SetIssueLabels, &[]),
464 route("DELETE", "/repos/:owner/:name/issues/:number/labels", Op::RemoveIssueLabels, &[]),
465 route("DELETE", "/repos/:owner/:name/issues/:number/labels/:label", Op::RemoveIssueLabels, &[]),
466 route("GET", "/repos/:owner/:name/milestones", Op::ListMilestones, &[("state", "state")]),
467 route("POST", "/repos/:owner/:name/milestones", Op::CreateMilestone, &[]),
468 route("GET", "/repos/:owner/:name/milestones/:milestone", Op::GetMilestone, &[]),
469 route("PATCH", "/repos/:owner/:name/milestones/:milestone", Op::UpdateMilestone, &[]),
470 route("DELETE", "/repos/:owner/:name/milestones/:milestone", Op::DeleteMilestone, &[]),
The balance shows what was spent this month and in total471 route(
472 "GET",
473 "/repos/:owner/:name/issues",
474 Op::ListIssues,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar475 &[("state", "state"), ("label", "label"), ("milestone", "milestone")],
The balance shows what was spent this month and in total476 ),
477 route("POST", "/repos/:owner/:name/issues", Op::CreateIssue, &[]),
478 route(
479 "GET",
480 "/repos/:owner/:name/issues/:number",
481 Op::GetIssue,
482 &[],
483 ),
484 route(
485 "PATCH",
486 "/repos/:owner/:name/issues/:number",
487 Op::UpdateIssue,
488 &[],
489 ),
490 route(
491 "POST",
492 "/repos/:owner/:name/issues/:number/close",
493 Op::CloseIssue,
494 &[],
495 ),
496 route(
497 "POST",
498 "/repos/:owner/:name/issues/:number/reopen",
499 Op::ReopenIssue,
500 &[],
501 ),
502 route(
503 "POST",
504 "/repos/:owner/:name/issues/:number/assign",
505 Op::AssignIssue,
506 &[],
507 ),
508 route(
509 "POST",
510 "/repos/:owner/:name/issues/import",
511 Op::ImportIssue,
512 &[],
513 ),
514 route(
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step515 "POST",
516 "/repos/:owner/:name/issues/delegate",
517 Op::Delegate,
518 &[],
519 ),
520 route(
The balance shows what was spent this month and in total521 "GET",
522 "/repos/:owner/:name/context",
523 Op::GetContext,
524 &[("reference", "reference")],
525 ),
526 route(
527 "GET",
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API528 "/workspaces/:workspace/context/search",
529 Op::SearchContext,
530 &[("q", "query"), ("project", "project"), ("kinds", "kinds"), ("limit", "limit")],
531 ),
532 route(
533 "GET",
534 "/workspaces/:workspace/context/:kind/:id",
535 Op::GetEntity,
536 &[],
537 ),
538 route(
539 "GET",
The balance shows what was spent this month and in total540 "/workspaces/:workspace/integrations",
541 Op::ListIntegrations,
542 &[],
543 ),
544 route(
545 "POST",
546 "/workspaces/:workspace/integrations",
547 Op::ConnectIntegration,
548 &[],
549 ),
550 route(
551 "GET",
552 "/repos/:owner/:name/hooks",
553 Op::ListWebhooks,
554 &[],
555 ),
556 route(
557 "POST",
558 "/repos/:owner/:name/hooks",
559 Op::CreateWebhook,
560 &[],
561 ),
562 route(
563 "PATCH",
564 "/repos/:owner/:name/hooks/:id",
565 Op::UpdateWebhook,
566 &[],
567 ),
568 route(
569 "DELETE",
570 "/repos/:owner/:name/hooks/:id",
571 Op::DeleteWebhook,
572 &[],
573 ),
574 route(
575 "POST",
576 "/repos/:owner/:name/hooks/:id/pings",
577 Op::PingWebhook,
578 &[],
579 ),
580 route(
581 "GET",
582 "/repos/:owner/:name/hooks/:id/deliveries",
583 Op::ListWebhookDeliveries,
584 &[],
585 ),
586 route(
587 "POST",
588 "/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliver",
589 Op::RedeliverWebhook,
590 &[],
591 ),
592 route(
593 "GET",
594 "/workspaces/:workspace/hooks",
595 Op::ListWebhooks,
596 &[],
597 ),
598 route(
599 "POST",
600 "/workspaces/:workspace/hooks",
601 Op::CreateWebhook,
602 &[],
603 ),
604 route(
605 "PATCH",
606 "/workspaces/:workspace/hooks/:id",
607 Op::UpdateWebhook,
608 &[],
609 ),
610 route(
611 "DELETE",
612 "/workspaces/:workspace/hooks/:id",
613 Op::DeleteWebhook,
614 &[],
615 ),
616 route(
617 "POST",
618 "/workspaces/:workspace/hooks/:id/pings",
619 Op::PingWebhook,
620 &[],
621 ),
622 route(
623 "GET",
624 "/workspaces/:workspace/hooks/:id/deliveries",
625 Op::ListWebhookDeliveries,
626 &[],
627 ),
628 route(
629 "POST",
630 "/workspaces/:workspace/hooks/:id/deliveries/:delivery/redeliver",
631 Op::RedeliverWebhook,
632 &[],
633 ),
634 route(
635 "GET",
636 "/workspaces/:workspace/model-routes",
637 Op::GetModelRoutes,
638 &[],
639 ),
640 route(
641 "PUT",
642 "/workspaces/:workspace/model-routes",
643 Op::SetModelRoutes,
644 &[],
645 ),
646 route(
AI Gateway: OpenAI's format, open models, and your own providers647 "PATCH",
648 "/workspaces/:workspace/integrations/:id",
649 Op::UpdateIntegration,
650 &[],
651 ),
652 route(
The balance shows what was spent this month and in total653 "DELETE",
654 "/workspaces/:workspace/integrations/:id",
655 Op::DisconnectIntegration,
656 &[],
657 ),
658 route(
659 "POST",
660 "/workspaces/:workspace/integrations/:id/test",
661 Op::TestIntegration,
662 &[],
663 ),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9664 // Customize: marketplaces, Discover, plugins and connectors. A plugin's
665 // ref is `name@marketplace`, URL-encoded (`@` as `%40`).
666 route("GET", "/workspaces/:workspace/customize/marketplaces", Op::Customize(CustomizeOp::ListMarketplaces), &[]),
667 route("POST", "/workspaces/:workspace/customize/marketplaces", Op::Customize(CustomizeOp::AddMarketplace), &[]),
668 route("DELETE", "/workspaces/:workspace/customize/marketplaces/:marketplace", Op::Customize(CustomizeOp::RemoveMarketplace), &[]),
669 route("POST", "/workspaces/:workspace/customize/marketplaces/:marketplace/refresh", Op::Customize(CustomizeOp::RefreshMarketplace), &[]),
670 route("GET", "/workspaces/:workspace/customize/discover", Op::Customize(CustomizeOp::Discover), &[]),
671 route("GET", "/workspaces/:workspace/customize/plugins", Op::Customize(CustomizeOp::ListPlugins), &[]),
672 route("POST", "/workspaces/:workspace/customize/plugins", Op::Customize(CustomizeOp::InstallPlugin), &[]),
673 route("GET", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::GetPlugin), &[]),
674 route("PATCH", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::SetPluginEnabled), &[]),
675 route("DELETE", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::UninstallPlugin), &[]),
676 route("POST", "/workspaces/:workspace/customize/plugins/:ref/update", Op::Customize(CustomizeOp::UpdatePlugin), &[]),
677 route("GET", "/workspaces/:workspace/customize/agents", Op::Customize(CustomizeOp::ListPluginAgents), &[]),
678 route("GET", "/workspaces/:workspace/customize/connectors", Op::Customize(CustomizeOp::ListConnectors), &[]),
679 route("POST", "/workspaces/:workspace/customize/connectors", Op::Customize(CustomizeOp::AddConnector), &[]),
680 route("GET", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::GetConnector), &[]),
681 route("PATCH", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::SetConnectorKey), &[]),
682 route("DELETE", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::RemoveConnector), &[]),
683 route("POST", "/workspaces/:workspace/customize/connectors/:id/refresh", Op::Customize(CustomizeOp::RefreshConnector), &[]),
684 route("POST", "/workspaces/:workspace/customize/connectors/:id/grants", Op::Customize(CustomizeOp::GrantConnector), &[]),
685 route("DELETE", "/workspaces/:workspace/customize/connectors/:id/grants/:grant", Op::Customize(CustomizeOp::RevokeConnector), &[]),
686 route("GET", "/workspaces/:workspace/customize/bundle", Op::Customize(CustomizeOp::Bundle), &[]),
The balance shows what was spent this month and in total687 route(
688 "GET",
689 "/repos/:owner/:name/actions/workflows",
690 Op::ListWorkflows,
691 &[],
692 ),
693 route(
694 "GET",
695 "/repos/:owner/:name/actions/workflows/:workflow/runs",
696 Op::ListWorkflowRuns,
697 &[("branch", "branch"), ("event", "event"), ("per_page", "limit")],
698 ),
699 route(
700 "POST",
701 "/repos/:owner/:name/actions/workflows/:workflow/dispatches",
702 Op::DispatchWorkflow,
703 &[],
704 ),
705 route(
706 "PATCH",
707 "/repos/:owner/:name/actions/workflows/:workflow",
708 Op::UpdateWorkflow,
709 &[],
710 ),
711 route(
712 "PUT",
713 "/repos/:owner/:name/actions/workflows/:workflow/enable",
714 Op::UpdateWorkflow,
715 &[],
716 ),
717 route(
718 "PUT",
719 "/repos/:owner/:name/actions/workflows/:workflow/disable",
720 Op::UpdateWorkflow,
721 &[],
722 ),
723 route(
724 "GET",
725 "/repos/:owner/:name/actions/runs",
726 Op::ListWorkflowRuns,
727 &[("workflow", "workflow"), ("branch", "branch"), ("event", "event"), ("pull", "pull"), ("head_sha", "sha"), ("per_page", "limit")],
728 ),
729 route(
730 "GET",
731 "/repos/:owner/:name/actions/runs/:id",
732 Op::GetWorkflowRun,
733 &[],
734 ),
735 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)736 "GET",
737 "/repos/:owner/:name/actions/runs/:id/attempts/:attempt",
738 Op::GetWorkflowRun,
739 &[],
740 ),
741 route(
The balance shows what was spent this month and in total742 "POST",
743 "/repos/:owner/:name/actions/runs/:id/cancel",
744 Op::CancelWorkflowRun,
745 &[],
746 ),
747 route(
748 "POST",
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)749 "/repos/:owner/:name/actions/runs/:id/force-cancel",
750 Op::CancelWorkflowRun,
751 &[],
752 ),
753 route(
754 "POST",
The balance shows what was spent this month and in total755 "/repos/:owner/:name/actions/runs/:id/rerun",
756 Op::RerunWorkflowRun,
757 &[],
758 ),
759 route(
760 "POST",
761 "/repos/:owner/:name/actions/runs/:id/rerun-failed-jobs",
762 Op::RerunWorkflowRun,
763 &[],
764 ),
765 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)766 "POST",
767 "/repos/:owner/:name/actions/jobs/:job/rerun",
768 Op::RerunWorkflowRun,
769 &[],
770 ),
771 route(
The balance shows what was spent this month and in total772 "GET",
773 "/repos/:owner/:name/actions/jobs/:job/logs",
774 Op::GetJobLogs,
775 &[("after", "after")],
776 ),
Merge packages: roles, Actions access, source label, soft delete, API777 // Packages, at GitHub's addresses with the workspace in place of the
778 // organization. A name with a slash is one URL-encoded segment.
779 route("GET", "/workspaces/:workspace/packages", Op::Packages(PackagesOp::ListPackages), &[("package_type", "package_type"), ("q", "q"), ("state", "state")]),
780 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::GetPackage), &[]),
781 route("PATCH", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::UpdatePackage), &[]),
782 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::DeletePackage), &[]),
783 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/restore", Op::Packages(PackagesOp::RestorePackage), &[]),
784 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions", Op::Packages(PackagesOp::ListVersions), &[("state", "state")]),
785 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::GetVersion), &[]),
786 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::DeleteVersion), &[]),
787 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id/restore", Op::Packages(PackagesOp::RestoreVersion), &[]),
788 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::LinkPackage), &[]),
789 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::UnlinkPackage), &[]),
790 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::ListAccess), &[]),
791 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::SetAccess), &[]),
792 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::RemoveAccess), &[("username", "username"), ("team", "team")]),
793 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::ListActionsAccess), &[]),
794 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::SetActionsAccess), &[]),
795 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access/:repository", Op::Packages(PackagesOp::RemoveActionsAccess), &[]),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2796 // Artifacts, at GitHub's addresses. `…/zip` answers with a redirect to
797 // a signed link (lib.rs).
798 route("GET", "/repos/:owner/:name/actions/artifacts", Op::Artifacts(ArtifactsOp::ListArtifacts), &[("name", "name"), ("page", "page"), ("per_page", "per_page")]),
799 route("GET", "/repos/:owner/:name/actions/runs/:id/artifacts", Op::Artifacts(ArtifactsOp::ListRunArtifacts), &[("name", "name")]),
800 route("GET", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::GetArtifact), &[]),
801 route("GET", "/repos/:owner/:name/actions/artifacts/:id/zip", Op::Artifacts(ArtifactsOp::DownloadArtifact), &[]),
802 route("DELETE", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::DeleteArtifact), &[]),
803 route("GET", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::GetArtifactRetention), &[]),
804 route("PUT", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::SetArtifactRetention), &[]),
The balance shows what was spent this month and in total805 route(
806 "GET",
807 "/repos/:owner/:name/actions/secrets",
808 Op::ListActionsSecrets,
809 &[],
810 ),
811 route(
812 "PUT",
813 "/repos/:owner/:name/actions/secrets/:setting",
814 Op::SetActionsSecret,
815 &[],
816 ),
817 route(
818 "DELETE",
819 "/repos/:owner/:name/actions/secrets/:setting",
820 Op::DeleteActionsSecret,
821 &[],
822 ),
823 route(
824 "GET",
825 "/repos/:owner/:name/actions/variables",
826 Op::ListActionsVariables,
827 &[],
828 ),
829 route(
830 "POST",
831 "/repos/:owner/:name/actions/variables",
832 Op::SetActionsVariable,
833 &[],
834 ),
835 route(
836 "PATCH",
837 "/repos/:owner/:name/actions/variables/:setting",
838 Op::SetActionsVariable,
839 &[],
840 ),
841 route(
842 "DELETE",
843 "/repos/:owner/:name/actions/variables/:setting",
844 Op::DeleteActionsVariable,
845 &[],
846 ),
847 route(
848 "GET",
849 "/workspaces/:workspace/actions/secrets",
850 Op::ListActionsSecrets,
851 &[],
852 ),
853 route(
854 "PUT",
855 "/workspaces/:workspace/actions/secrets/:setting",
856 Op::SetActionsSecret,
857 &[],
858 ),
859 route(
860 "DELETE",
861 "/workspaces/:workspace/actions/secrets/:setting",
862 Op::DeleteActionsSecret,
863 &[],
864 ),
865 route(
866 "GET",
867 "/workspaces/:workspace/actions/variables",
868 Op::ListActionsVariables,
869 &[],
870 ),
871 route(
872 "POST",
873 "/workspaces/:workspace/actions/variables",
874 Op::SetActionsVariable,
875 &[],
876 ),
877 route(
878 "PATCH",
879 "/workspaces/:workspace/actions/variables/:setting",
880 Op::SetActionsVariable,
881 &[],
882 ),
883 route(
884 "DELETE",
885 "/workspaces/:workspace/actions/variables/:setting",
886 Op::DeleteActionsVariable,
887 &[],
888 ),
Fast pages, required checks on the branch, self-hosted runners, honest incidents889 // Self-hosted runners: a repository's own, or a workspace's.
890 route("GET", "/repos/:owner/:name/actions/runners", Op::ListRunners, &[]),
891 route("POST", "/repos/:owner/:name/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
892 route("DELETE", "/repos/:owner/:name/actions/runners/:id", Op::RemoveRunner, &[]),
893 route("GET", "/repos/:owner/:name/actions/runner-settings", Op::GetRunnerSettings, &[]),
894 route("PATCH", "/repos/:owner/:name/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
895 route("GET", "/workspaces/:workspace/actions/runners", Op::ListRunners, &[]),
896 route("POST", "/workspaces/:workspace/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
897 route("DELETE", "/workspaces/:workspace/actions/runners/:id", Op::RemoveRunner, &[]),
898 route("GET", "/workspaces/:workspace/actions/runner-settings", Op::GetRunnerSettings, &[]),
899 route("PATCH", "/workspaces/:workspace/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
900 route("GET", "/workspaces/:workspace/actions/runner-groups", Op::ListRunnerGroups, &[]),
901 route("POST", "/workspaces/:workspace/actions/runner-groups", Op::CreateRunnerGroup, &[]),
902 route("PATCH", "/workspaces/:workspace/actions/runner-groups/:id", Op::UpdateRunnerGroup, &[]),
903 route("DELETE", "/workspaces/:workspace/actions/runner-groups/:id", Op::DeleteRunnerGroup, &[]),
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.904 // A workspace agent's own computer.
905 route("GET", "/workspaces/:workspace/agents/:agent/computer", Op::GetAgentComputer, &[]),
906 route("POST", "/workspaces/:workspace/agents/:agent/computer/wake", Op::WakeAgentComputer, &[]),
907 route("POST", "/workspaces/:workspace/agents/:agent/computer/sleep", Op::SleepAgentComputer, &[]),
908 route("POST", "/workspaces/:workspace/agents/:agent/computer/reset", Op::ResetAgentComputer, &[]),
909 route("GET", "/workspaces/:workspace/agents/:agent/computer/commands", Op::ListAgentComputerCommands, &[]),
The balance shows what was spent this month and in total910 route("POST", "/repos/:owner/:name/plans", Op::PlanWork, &[]),
911 route("GET", "/repos/:owner/:name/plans/:plan", Op::GetPlan, &[]),
912 route(
913 "POST",
914 "/repos/:owner/:name/plans/:plan/apply",
915 Op::ApplyPlan,
916 &[],
917 ),
918 route(
919 "POST",
920 "/repos/:owner/:name/issues/:number/comments",
921 Op::AddComment,
922 &[],
923 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts924 route("PATCH", "/repos/:owner/:name/issues/comments/:comment_id", Op::EditComment, &[]),
925 route("DELETE", "/repos/:owner/:name/issues/comments/:comment_id", Op::DeleteComment, &[]),
The balance shows what was spent this month and in total926 route(
927 "GET",
928 "/repos/:owner/:name/pulls",
929 Op::ListPullRequests,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar930 &[("state", "state"), ("label", "label"), ("milestone", "milestone"), ("base", "base")],
The balance shows what was spent this month and in total931 ),
932 route(
933 "POST",
934 "/repos/:owner/:name/pulls",
935 Op::CreatePullRequest,
936 &[],
937 ),
938 route(
939 "GET",
940 "/repos/:owner/:name/pulls/:number",
941 Op::GetPullRequest,
942 &[],
943 ),
944 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar945 "PATCH",
946 "/repos/:owner/:name/pulls/:number",
947 Op::UpdatePullRequest,
948 &[],
949 ),
950 route(
The balance shows what was spent this month and in total951 "GET",
952 "/repos/:owner/:name/pulls/:number/changes",
953 Op::GetPullRequestChanges,
954 &[],
955 ),
956 route(
957 "POST",
958 "/repos/:owner/:name/pulls/:number/reviews",
959 Op::ReviewPullRequest,
960 &[],
961 ),
962 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar963 "POST",
964 "/repos/:owner/:name/pulls/:number/requested_reviewers",
965 Op::RequestReviewers,
966 &[],
967 ),
968 route(
969 "DELETE",
970 "/repos/:owner/:name/pulls/:number/requested_reviewers",
971 Op::RemoveRequestedReviewers,
972 &[],
973 ),
974 route(
The balance shows what was spent this month and in total975 "GET",
976 "/repos/:owner/:name/pulls/:number/session",
977 Op::ReadSession,
978 &[("after", "after")],
979 ),
980 route(
981 "POST",
982 "/repos/:owner/:name/pulls/:number/session",
983 Op::RecordSession,
984 &[],
985 ),
986 route(
987 "POST",
988 "/repos/:owner/:name/pulls/:number/ready",
989 Op::MarkPullRequestReady,
990 &[],
991 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts992 route("POST", "/repos/:owner/:name/pulls/:number/draft", Op::ConvertPullRequestToDraft, &[]),
The balance shows what was spent this month and in total993 route(
994 "POST",
995 "/repos/:owner/:name/pulls/:number/close",
996 Op::ClosePullRequest,
997 &[],
998 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts999 route("POST", "/repos/:owner/:name/pulls/:number/reopen", Op::ReopenPullRequest, &[]),
The balance shows what was spent this month and in total1000 route(
1001 "POST",
1002 "/repos/:owner/:name/pulls/:number/merge",
1003 Op::MergePullRequest,
1004 &[],
1005 ),
Merge main into Artifacts Phase 21006 // Artifacts mode's docs, slides, designs and dashboards. Search comes
1007 // before an artifact by id, which it would otherwise match.
1008 route("GET", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::List), &[("tab", "tab"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("q", "q"), ("state", "state"), ("cursor", "cursor"), ("limit", "limit")]),
1009 route("POST", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::Create), &[]),
1010 route("GET", "/workspaces/:workspace/artifacts/search", Op::Folios(FoliosOp::Search), &[("q", "q"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("limit", "limit")]),
1011 route("GET", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Get), &[]),
1012 route("PATCH", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Update), &[]),
1013 route("DELETE", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Trash), &[]),
1014 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/restore", Op::Folios(FoliosOp::Restore), &[]),
1015 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/purge", Op::Folios(FoliosOp::Purge), &[]),
1016 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::GetContent), &[]),
1017 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::Edit), &[]),
1018 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::GetAccess), &[]),
1019 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::SetAccess), &[]),
1020 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/versions", Op::Folios(FoliosOp::ListVersions), &[]),
1021 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/versions/:version_id/restore", Op::Folios(FoliosOp::RestoreVersion), &[]),
1022 route("GET", "/workspaces/:workspace/artifact-templates", Op::Folios(FoliosOp::ListTemplates), &[("kind", "kind")]),
1023 route("GET", "/workspaces/:workspace/artifact-spaces", Op::Folios(FoliosOp::ListSpaces), &[]),
1024 route("POST", "/workspaces/:workspace/datasets/query", Op::Folios(FoliosOp::QueryDataset), &[]),
The balance shows what was spent this month and in total1025];
1026
1027impl Route {
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1028 /// Whether the route answers success with `204` and no body, as
1029 /// GitHub's address for the same does. MCP still answers `true`.
1030 pub fn no_content(&self) -> bool {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r91031 matches!(
1032 self.op,
1033 Op::DeleteComment
1034 | Op::Customize(CustomizeOp::RemoveMarketplace | CustomizeOp::UninstallPlugin | CustomizeOp::RemoveConnector)
1035 )
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1036 }
1037
The balance shows what was spent this month and in total1038 /// The names of the route's path parameters, in order.
1039 pub fn params(&self) -> impl Iterator<Item = &'static str> {
1040 self.path
1041 .split('/')
1042 .filter_map(|segment| segment.strip_prefix(':'))
1043 }
1044
1045 /// The values of the path parameters, if `path` is this route's.
1046 fn matches<'a>(&self, path: &'a str) -> Option<Vec<(&'static str, &'a str)>> {
1047 let mut values = Vec::new();
1048 let mut actual = path.trim_end_matches('/').split('/');
1049 for expected in self.path.split('/') {
1050 let segment = actual.next()?;
1051 match expected.strip_prefix(':') {
1052 Some(name) if !segment.is_empty() => values.push((name, segment)),
1053 Some(_) => return None,
1054 None if expected == segment => {}
1055 None => return None,
1056 }
1057 }
1058 actual.next().is_none().then_some(values)
1059 }
1060}
1061
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1062/// A path segment with its `%XX` escapes decoded; as given when that is not
1063/// UTF-8.
1064fn percent_decoded(segment: &str) -> String {
1065 let bytes = segment.as_bytes();
1066 let mut out = Vec::with_capacity(bytes.len());
1067 let mut i = 0;
1068 while i < bytes.len() {
1069 let escaped = (bytes[i] == b'%')
1070 .then(|| segment.get(i + 1..i + 3))
1071 .flatten()
1072 .filter(|hex| hex.bytes().all(|byte| byte.is_ascii_hexdigit()))
1073 .and_then(|hex| u8::from_str_radix(hex, 16).ok());
1074 match escaped {
1075 Some(byte) => {
1076 out.push(byte);
1077 i += 3;
1078 }
1079 None => {
1080 out.push(bytes[i]);
1081 i += 1;
1082 }
1083 }
1084 }
1085 String::from_utf8(out).unwrap_or_else(|_| segment.to_owned())
1086}
1087
The balance shows what was spent this month and in total1088/// The route for a request, and the operation input it describes.
1089///
1090/// The input is the JSON body, overlaid with the query parameters the route
1091/// reads and then with what the path names: `owner` and `name` become
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1092/// `repo`, as does a team's `repo` with its `workspace`, and `number`
1093/// becomes an integer.
The balance shows what was spent this month and in total1094pub fn resolve(
1095 method: &str,
1096 path: &str,
1097 query: &[(String, String)],
1098 body: Value,
1099) -> Option<(&'static Route, Value)> {
1100 let (route, params) = ROUTES
1101 .iter()
1102 .filter(|route| route.method == method)
1103 .find_map(|route| Some((route, route.matches(path)?)))?;
1104
1105 let mut input = match body {
1106 Value::Object(fields) => fields,
1107 _ => Map::new(),
1108 };
1109 for (name, key) in route.query {
1110 if let Some((_, value)) = query.iter().find(|(query_name, _)| query_name == name) {
1111 input.insert((*key).to_owned(), Value::String(value.clone()));
1112 }
1113 }
1114 let param = |wanted: &str| {
1115 params
1116 .iter()
1117 .find(|(name, _)| *name == wanted)
1118 .map(|(_, value)| *value)
1119 };
1120 if let (Some(owner), Some(name)) = (param("owner"), param("name")) {
1121 input.insert("repo".to_owned(), Value::String(format!("{owner}/{name}")));
1122 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971123 // Every other name the path gives, under that name; the ones below that
1124 // need more (a repository, a number, an encoded name) are set after.
1125 for (key, value) in &params {
1126 if !matches!(*key, "owner" | "name") {
1127 input.insert((*key).to_owned(), Value::String(percent_decoded(value)));
The balance shows what was spent this month and in total1128 }
1129 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1130 // A repository of a team's workspace, named by itself.
1131 if let (Some(workspace), Some(name)) = (param("workspace"), param("repo")) {
1132 input.insert("repo".to_owned(), Value::String(format!("{workspace}/{name}")));
1133 }
1134 // A branch name may hold slashes, sent URL-encoded as one segment, and
1135 // a label's name spaces.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1136 if let Some(branch) = param("branch") {
1137 input.insert("branch".to_owned(), Value::String(percent_decoded(branch)));
1138 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971139 // An environment's name may hold slashes and spaces, URL-encoded.
1140 if let Some(environment) = param("environment") {
1141 input.insert("environment".to_owned(), Value::String(percent_decoded(environment)));
1142 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1143 if let Some(label) = param("label") {
1144 input.insert("label".to_owned(), Value::String(percent_decoded(label)));
1145 }
1146 if let Some(milestone) = param("milestone") {
1147 // Not a number: zero, which no milestone has.
1148 input.insert("milestone".to_owned(), milestone.parse::<u32>().unwrap_or(0).into());
1149 }
The balance shows what was spent this month and in total1150 // GitHub says some things with the path alone.
1151 if route.path.ends_with("/enable") || route.path.ends_with("/disable") {
1152 input.insert("enabled".to_owned(), Value::Bool(route.path.ends_with("/enable")));
1153 }
1154 if route.path.ends_with("/rerun-failed-jobs") {
1155 input.insert("failed_only".to_owned(), Value::Bool(true));
1156 }
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)1157 if route.path.ends_with("/force-cancel") {
1158 input.insert("force".to_owned(), Value::Bool(true));
1159 }
API: notifications over REST and MCP, with notifications scopes1160 // Unsaving and waking a thread are a DELETE of what PUT made.
1161 if route.method == "DELETE" && route.path.ends_with("/saved") {
1162 input.insert("saved".to_owned(), Value::Bool(false));
1163 }
1164 if route.method == "DELETE" && route.path.ends_with("/snooze") {
1165 input.remove("until");
1166 }
The balance shows what was spent this month and in total1167 if let Some(number) = param("number") {
1168 // Not a number: zero, which no issue or pull request has.
1169 input.insert(
1170 "number".to_owned(),
1171 number.parse::<u32>().unwrap_or(0).into(),
1172 );
1173 }
1174 Some((route, Value::Object(input)))
1175}
1176
1177#[cfg(test)]
1178mod tests {
1179 use serde_json::json;
1180
1181 use super::*;
1182
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971183 /// Every name a route's path gives reaches the operation: a name left
1184 /// off the lists above is dropped, and the operation answers that it
1185 /// was not given (the project routes were, until this test).
1186 #[test]
1187 fn every_path_parameter_reaches_the_input() {
1188 for route in ROUTES.iter() {
1189 let names: Vec<&str> = route.path.split('/').filter_map(|part| part.strip_prefix(':')).collect();
1190 if names.is_empty() {
1191 continue;
1192 }
1193 let path: String = route
1194 .path
1195 .split('/')
1196 .map(|part| match part.strip_prefix(':') {
1197 Some("number" | "milestone") => "7".to_owned(),
1198 Some(name) => format!("{name}-x"),
1199 None => part.to_owned(),
1200 })
1201 .collect::<Vec<_>>()
1202 .join("/");
1203 let (found, input) = resolve(route.method, &path, &[], json!({})).unwrap();
1204 // A path two routes could take is checked under the first.
1205 if found.path != route.path {
1206 continue;
1207 }
1208 for name in names {
1209 let key = match name {
1210 "owner" | "name" => "repo",
1211 "repo" if names_has_workspace(route.path) => "repo",
1212 other => other,
1213 };
1214 assert!(
1215 input.get(key).is_some_and(|value| !value.is_null()),
1216 "{} {}: :{name} does not reach the input",
1217 route.method,
1218 route.path
1219 );
1220 }
1221 }
1222 }
1223
1224 fn names_has_workspace(path: &str) -> bool {
1225 path.split('/').any(|part| part == ":workspace")
1226 }
1227
The balance shows what was spent this month and in total1228 #[test]
1229 fn a_path_resolves_to_its_operation_and_input() {
1230 let (route, input) = resolve(
1231 "POST",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1232 "/repos/flagon-io/hello/pulls/14/merge",
The balance shows what was spent this month and in total1233 &[],
1234 json!({ "keep_issue_open": true, "number": 99, "repo": "someone/else" }),
1235 )
1236 .unwrap();
1237 assert_eq!(route.op, Op::MergePullRequest);
1238 // What the path names wins over the body.
1239 assert_eq!(
1240 input,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1241 json!({ "keep_issue_open": true, "number": 14, "repo": "flagon-io/hello" })
The balance shows what was spent this month and in total1242 );
1243 }
1244
1245 #[test]
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1246 fn a_branch_with_slashes_is_one_encoded_segment() {
1247 let (route, input) = resolve(
1248 "POST",
1249 "/repos/flagon-io/hello/branches/feature%2Flogin/rename",
1250 &[],
1251 json!({ "new_name": "feature/sign-in" }),
1252 )
1253 .unwrap();
1254 assert_eq!(route.op, Op::RenameBranch);
1255 assert_eq!(
1256 input,
1257 json!({ "new_name": "feature/sign-in", "branch": "feature/login", "repo": "flagon-io/hello" })
1258 );
1259 assert_eq!(percent_decoded("100%"), "100%");
1260 assert_eq!(percent_decoded("a%2bb%zz"), "a+b%zz");
1261 }
1262
1263 #[test]
1264 fn a_collaborator_is_named_by_username() {
1265 let (route, input) = resolve(
1266 "PATCH",
1267 "/repos/flagon-io/hello/collaborators/ada",
1268 &[],
1269 json!({ "role": "maintain" }),
1270 )
1271 .unwrap();
1272 assert_eq!(route.op, Op::UpdateCollaborator);
1273 assert_eq!(input, json!({ "role": "maintain", "username": "ada", "repo": "flagon-io/hello" }));
1274 let (route, input) = resolve("DELETE", "/user/repository_invitations/rin_1", &[], Value::Null).unwrap();
1275 assert_eq!(route.op, Op::DeclineRepoInvitation);
1276 assert_eq!(input, json!({ "id": "rin_1" }));
1277 }
1278
1279 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1280 fn teams_are_addressed_by_workspace_and_slug() {
1281 let query = [("q".to_owned(), "back".to_owned())];
1282 let (route, input) = resolve("GET", "/workspaces/acme/teams", &query, Value::Null).unwrap();
1283 assert_eq!(route.op, Op::ListTeams);
1284 assert_eq!(input, json!({ "query": "back", "workspace": "acme" }));
1285 let (route, input) = resolve("PATCH", "/workspaces/acme/teams/backend", &[], json!({ "name": "Back end" })).unwrap();
1286 assert_eq!(route.op, Op::UpdateTeam);
1287 assert_eq!(input, json!({ "name": "Back end", "workspace": "acme", "team": "backend" }));
1288 let (route, input) =
1289 resolve("PUT", "/workspaces/acme/teams/backend/members/ana", &[], json!({ "role": "maintainer" })).unwrap();
1290 assert_eq!(route.op, Op::SetTeamMember);
1291 assert_eq!(input, json!({ "role": "maintainer", "workspace": "acme", "team": "backend", "username": "ana" }));
1292 let query = [("include_child_teams".to_owned(), "true".to_owned())];
1293 let (route, input) = resolve("GET", "/workspaces/acme/teams/backend/members", &query, Value::Null).unwrap();
1294 assert_eq!(route.op, Op::ListTeamMembers);
1295 assert_eq!(input, json!({ "include_child_teams": "true", "workspace": "acme", "team": "backend" }));
1296 // A repository is named by itself, in the team's workspace.
1297 let (route, input) =
1298 resolve("PUT", "/workspaces/acme/teams/backend/repos/rocket", &[], json!({ "role": "write" })).unwrap();
1299 assert_eq!(route.op, Op::SetTeamRepo);
1300 assert_eq!(input, json!({ "role": "write", "workspace": "acme", "team": "backend", "repo": "acme/rocket" }));
1301 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1302 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/repos/rocket"), Op::RemoveTeamRepo);
1303 assert_eq!(op("GET", "/workspaces/acme/teams/backend/teams"), Op::ListChildTeams);
1304 assert_eq!(op("GET", "/workspaces/acme/teams/backend/repos"), Op::ListTeamRepos);
1305 assert_eq!(op("PUT", "/workspaces/acme/teams/backend/review_assignment"), Op::SetTeamReviewAssignment);
1306 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend"), Op::DeleteTeam);
1307 assert_eq!(op("POST", "/workspaces/acme/teams"), Op::CreateTeam);
1308 assert_eq!(op("GET", "/workspaces/acme/teams/backend"), Op::GetTeam);
1309 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/members/ana"), Op::RemoveTeamMember);
1310 let (route, input) = resolve("GET", "/workspaces/acme/members/ana/teams", &[], Value::Null).unwrap();
1311 assert_eq!(route.op, Op::ListUserTeams);
1312 assert_eq!(input, json!({ "workspace": "acme", "username": "ana" }));
1313 }
1314
1315 #[test]
1316 fn reviewers_are_requested_and_code_owners_checked_on_a_repository() {
1317 let body = json!({ "reviewers": ["ana"], "team_reviewers": ["backend"] });
1318 let (route, input) = resolve("POST", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body.clone()).unwrap();
1319 assert_eq!(route.op, Op::RequestReviewers);
1320 assert_eq!(
1321 input,
1322 json!({ "reviewers": ["ana"], "team_reviewers": ["backend"], "repo": "acme/rocket", "number": 7 })
1323 );
1324 let (route, _) = resolve("DELETE", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body).unwrap();
1325 assert_eq!(route.op, Op::RemoveRequestedReviewers);
1326 let query = [("ref".to_owned(), "main".to_owned())];
1327 let (route, input) = resolve("GET", "/repos/acme/rocket/codeowners/errors", &query, Value::Null).unwrap();
1328 assert_eq!(route.op, Op::GetCodeownersErrors);
1329 assert_eq!(input, json!({ "ref": "main", "repo": "acme/rocket" }));
1330 }
1331
1332 #[test]
API: notifications over REST and MCP, with notifications scopes1333 fn notifications_are_addressed_as_threads_and_by_issue() {
1334 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1", &[], Value::Null).unwrap();
1335 assert_eq!(route.op, Op::MarkThreadDone);
1336 assert_eq!(input, json!({ "id": "ntf_1" }));
1337 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/saved", &[], Value::Null).unwrap();
1338 assert_eq!(route.op, Op::SaveThread);
1339 assert_eq!(input, json!({ "id": "ntf_1", "saved": false }));
1340 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/snooze", &[], json!({ "until": "x" })).unwrap();
1341 assert_eq!(route.op, Op::SnoozeThread);
1342 assert_eq!(input, json!({ "id": "ntf_1" }));
1343 let query = [("all".to_owned(), "true".to_owned()), ("per_page".to_owned(), "50".to_owned())];
1344 let (route, input) = resolve("GET", "/repos/acme/rocket/notifications", &query, Value::Null).unwrap();
1345 assert_eq!(route.op, Op::ListNotifications);
1346 assert_eq!(input, json!({ "all": "true", "per_page": "50", "repo": "acme/rocket" }));
1347 let (route, input) = resolve("PUT", "/repos/acme/rocket/issues/7/subscription", &[], json!({ "ignored": true })).unwrap();
1348 assert_eq!(route.op, Op::SetThreadSubscription);
1349 assert_eq!(input, json!({ "ignored": true, "number": 7, "repo": "acme/rocket" }));
1350 assert_eq!(resolve("GET", "/user/subscriptions", &[], Value::Null).unwrap().0.op, Op::ListWatchedRepos);
1351 }
1352
1353 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1354 fn labels_and_milestones_are_named_in_the_path() {
1355 let (route, input) = resolve("PATCH", "/repos/acme/web/labels/good%20first%20issue", &[], json!({ "color": "7057ff" })).unwrap();
1356 assert_eq!(route.op, Op::UpdateLabel);
1357 assert_eq!(input, json!({ "color": "7057ff", "label": "good first issue", "repo": "acme/web" }));
1358 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels/bug", &[], Value::Null).unwrap();
1359 assert_eq!(route.op, Op::RemoveIssueLabels);
1360 assert_eq!(input, json!({ "label": "bug", "number": 7, "repo": "acme/web" }));
1361 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels", &[], Value::Null).unwrap();
1362 assert_eq!(route.op, Op::RemoveIssueLabels);
1363 assert_eq!(input, json!({ "number": 7, "repo": "acme/web" }));
1364 let (route, _) = resolve("POST", "/repos/acme/web/labels/defaults", &[], Value::Null).unwrap();
1365 assert_eq!(route.op, Op::AddDefaultLabels);
1366 let (route, input) = resolve("PATCH", "/repos/acme/web/milestones/3", &[], json!({ "state": "closed" })).unwrap();
1367 assert_eq!(route.op, Op::UpdateMilestone);
1368 assert_eq!(input, json!({ "state": "closed", "milestone": 3, "repo": "acme/web" }));
1369 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "base": "release" })).unwrap();
1370 assert_eq!(route.op, Op::UpdatePullRequest);
1371 assert_eq!(input, json!({ "base": "release", "number": 9, "repo": "acme/web" }));
1372 }
1373
1374 #[test]
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1375 fn pull_requests_reopen_and_turn_draft_and_comments_are_named_by_id() {
1376 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1377 assert_eq!(op("POST", "/repos/acme/web/pulls/9/reopen"), Op::ReopenPullRequest);
1378 assert_eq!(op("POST", "/repos/acme/web/pulls/9/draft"), Op::ConvertPullRequestToDraft);
1379 assert_eq!(op("POST", "/repos/acme/web/pulls/9/close"), Op::ClosePullRequest);
1380 // Reopening and closing with a PATCH, as `state`.
1381 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "state": "open" })).unwrap();
1382 assert_eq!(route.op, Op::UpdatePullRequest);
1383 assert_eq!(input, json!({ "state": "open", "number": 9, "repo": "acme/web" }));
1384 let (route, input) =
1385 resolve("PATCH", "/repos/acme/web/issues/comments/cmt_1", &[], json!({ "body": "Better" })).unwrap();
1386 assert_eq!(route.op, Op::EditComment);
1387 assert_eq!(input, json!({ "body": "Better", "comment_id": "cmt_1", "repo": "acme/web" }));
1388 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/comments/cmt_1", &[], Value::Null).unwrap();
1389 assert_eq!(route.op, Op::DeleteComment);
1390 assert_eq!(input, json!({ "comment_id": "cmt_1", "repo": "acme/web" }));
1391 // Still an issue's comments, labels and subscription.
1392 assert_eq!(op("POST", "/repos/acme/web/issues/7/comments"), Op::AddComment);
1393 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/labels"), Op::RemoveIssueLabels);
1394 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/subscription"), Op::DeleteThreadSubscription);
1395 }
1396
1397 #[test]
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1398 fn billing_is_addressed_by_workspace() {
1399 let query = [("from".to_owned(), "2026-10-01".to_owned()), ("products".to_owned(), "agent,sandboxes".to_owned())];
1400 let (route, input) = resolve("GET", "/workspaces/acme/usage", &query, Value::Null).unwrap();
1401 assert_eq!(route.op, Op::GetUsage);
1402 assert_eq!(input, json!({ "from": "2026-10-01", "products": "agent,sandboxes", "workspace": "acme" }));
1403 let (route, input) = resolve("PUT", "/workspaces/acme/budget", &[], json!({ "alerts": [50] })).unwrap();
1404 assert_eq!(route.op, Op::SetBudget);
1405 assert_eq!(input, json!({ "alerts": [50], "workspace": "acme" }));
1406 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1407 assert_eq!(op("GET", "/workspaces/acme/budget"), Op::GetBudget);
1408 assert_eq!(op("GET", "/workspaces/acme/ai_credit"), Op::GetAiCredit);
1409 assert_eq!(op("POST", "/workspaces/acme/ai_credit/checkout"), Op::BuyAiCredit);
1410 assert_eq!(op("GET", "/workspaces/acme/invoices"), Op::ListInvoices);
1411 assert_eq!(op("GET", "/workspaces/acme/billing_details"), Op::GetBillingDetails);
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens1412 assert_eq!(op("GET", "/workspaces/acme/gateway/requests"), Op::ListGatewayRequests);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1413 }
1414
1415 #[test]
Merge checks: statuses and check runs on every commit1416 fn checks_are_at_githubs_addresses() {
1417 let sha = "a".repeat(40);
1418 let body = json!({ "state": "success", "context": "ci/build" });
1419 let (route, input) = resolve("POST", &format!("/repos/acme/web/statuses/{sha}"), &[], body).unwrap();
1420 assert_eq!(route.op, Op::Checks(ChecksOp::CreateCommitStatus));
1421 assert_eq!(input, json!({ "state": "success", "context": "ci/build", "sha": sha, "repo": "acme/web" }));
1422 let (route, input) = resolve("GET", "/repos/acme/web/commits/release%2F1.x/status", &[], Value::Null).unwrap();
1423 assert_eq!(route.op, Op::Checks(ChecksOp::GetCombinedStatus));
1424 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1425 let query = [("check_name".to_owned(), "lint".to_owned())];
1426 let (route, input) = resolve("GET", "/repos/acme/web/commits/main/check-runs", &query, Value::Null).unwrap();
1427 assert_eq!(route.op, Op::Checks(ChecksOp::ListCheckRunsForRef));
1428 assert_eq!(input, json!({ "check_name": "lint", "ref": "main", "repo": "acme/web" }));
1429 let (route, input) = resolve("PATCH", "/repos/acme/web/check-runs/cr_1", &[], json!({ "conclusion": "success" })).unwrap();
1430 assert_eq!(route.op, Op::Checks(ChecksOp::UpdateCheckRun));
1431 assert_eq!(input, json!({ "conclusion": "success", "id": "cr_1", "repo": "acme/web" }));
1432 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1433 assert_eq!(op("POST", "/repos/acme/web/check-runs"), Op::Checks(ChecksOp::CreateCheckRun));
1434 assert_eq!(op("GET", "/repos/acme/web/check-runs/cr_1/annotations"), Op::Checks(ChecksOp::ListCheckRunAnnotations));
1435 assert_eq!(op("POST", "/repos/acme/web/check-suites/cs_1/rerequest"), Op::Checks(ChecksOp::RerequestCheckSuite));
1436 assert_eq!(op("GET", "/repos/acme/web/commits/main/check-suites"), Op::Checks(ChecksOp::ListCheckSuitesForRef));
1437 }
1438
1439 #[test]
The balance shows what was spent this month and in total1440 fn query_parameters_are_renamed() {
1441 let query = [
1442 ("q".to_owned(), "parser".to_owned()),
1443 ("x".to_owned(), "y".to_owned()),
1444 ];
1445 let (route, input) = resolve("GET", "/repos", &query, Value::Null).unwrap();
1446 assert_eq!(route.op, Op::ListRepos);
1447 assert_eq!(input, json!({ "query": "parser" }));
1448 }
1449
1450 #[test]
1451 fn method_and_shape_must_match() {
1452 assert!(resolve("GET", "/repos/a/b/issues/1/close", &[], Value::Null).is_none());
1453 assert!(resolve("GET", "/repos/a", &[], Value::Null).is_none());
1454 assert!(resolve("GET", "/repos/a/b/issues/1/extra", &[], Value::Null).is_none());
1455 assert!(resolve("GET", "/repos/a/b/", &[], Value::Null).is_some());
1456 }
1457
1458 #[test]
1459 fn every_parameter_and_query_name_is_an_input() {
1460 for route in ROUTES {
1461 let properties = route.op.properties();
1462 for (_, key) in route.query {
1463 assert!(properties.contains_key(*key), "{}: {key}", route.path);
1464 }
1465 for name in route.params() {
1466 let covered = matches!(name, "owner" | "name") && properties.contains_key("repo")
1467 || properties.contains_key(name);
1468 assert!(covered, "{}: {name}", route.path);
1469 }
1470 }
1471 }
1472
1473 #[test]
1474 fn every_operation_has_a_route() {
1475 for op in Op::ALL {
1476 assert!(ROUTES.iter().any(|route| route.op == op), "{}", op.name());
1477 }
1478 }
1479}

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