Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
1,527 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.

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

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