g1t/packages/contracts/src/scopes.ts

300 lines13,568 bytesCodeBlame

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.

Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1/**
2 * Scopes: what an access token may do on its owner's behalf. Mirrors
3 * `crates/contracts/src/scopes.rs`, which is the source of truth; a Rust
4 * test keeps the tables here the same.
5 *
6 * A token reaches whatever its owner can reach (a workspace's token, that
7 * workspace); what a request may do is the intersection of the owner's
8 * role and the token's scopes.
9 */
10
11export type ScopeResource =
12 | "repo"
13 | "code"
14 | "issues"
15 | "pull_requests"
16 | "agents"
17 | "workflows"
18 | "memory"
19 | "account"
20 | "workspace"
21 | "access"
22 | "webhooks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents23 | "secrets"
24 | "runners";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step25
26export type ScopeLevel = "read" | "write" | "run" | "admin";
27
28/** Every scope, grouped by resource, least first. */
29export const SCOPES = [
30 { scope: "repo:read", description: "See repositories, their settings, labels and timelines, and search" },
31 { scope: "repo:write", description: "Create repositories, rename branches and change how pull requests merge" },
32 { scope: "repo:admin", description: "Rename, archive, transfer, delete or change who can see a repository" },
33 { scope: "code:read", description: "Clone and fetch private repositories with git" },
34 { scope: "code:write", description: "Push commits with git" },
35 { scope: "issues:read", description: "Read issues, comments and plans" },
36 { scope: "issues:write", description: "Open, edit, close and comment on issues" },
37 { scope: "pull_requests:read", description: "Read pull requests, their changes, sessions and merge queues" },
38 { scope: "pull_requests:write", description: "Open, review, close and merge pull requests" },
39 { scope: "agents:run", description: "Put g1t agents to work and message them, which uses the workspace's money" },
40 { scope: "workflows:read", description: "Read workflows, runs and logs" },
41 { scope: "workflows:write", description: "Run, cancel, rerun and turn workflows on or off" },
42 { scope: "memory:read", description: "Recall memory and search the workspace's context" },
43 { scope: "memory:write", description: "Save memory for the next agent" },
44 { scope: "account:read", description: "Read your email addresses, invites and invitations" },
45 { scope: "account:write", description: "Change your email addresses, make invites and answer invitations" },
46 { scope: "workspace:read", description: "Read workspace invites, integrations and model routes" },
47 { scope: "workspace:admin", description: "Create and delete workspaces, invite members, connect integrations" },
48 { scope: "access:read", description: "See who has access to repositories" },
49 { scope: "access:admin", description: "Give and take away access to repositories" },
50 { scope: "webhooks:read", description: "See webhooks and their deliveries" },
51 { scope: "webhooks:admin", description: "Create, change and delete webhooks" },
52 { scope: "secrets:read", description: "List secrets (never their values) and read variables" },
53 { scope: "secrets:admin", description: "Set and delete secrets and variables" },
Fast pages, required checks on the branch, self-hosted runners, honest incidents54 { scope: "runners:read", description: "See self-hosted runners, their groups and where agents run" },
55 { scope: "runners:admin", description: "Register and remove self-hosted runners, change their groups and settings" },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step56] as const;
57
58export type Scope = (typeof SCOPES)[number]["scope"];
59
60/** Resources in the order settings show them, with their names for people. */
61export const SCOPE_RESOURCES: { resource: ScopeResource; label: string }[] = [
62 { resource: "repo", label: "Repositories" },
63 { resource: "code", label: "Code" },
64 { resource: "issues", label: "Issues" },
65 { resource: "pull_requests", label: "Pull requests" },
66 { resource: "agents", label: "g1t agents" },
67 { resource: "workflows", label: "Workflows" },
68 { resource: "memory", label: "Memory and context" },
69 { resource: "account", label: "Your account" },
70 { resource: "workspace", label: "Workspaces" },
71 { resource: "access", label: "Who has access" },
72 { resource: "webhooks", label: "Webhooks" },
73 { resource: "secrets", label: "Secrets and variables" },
Fast pages, required checks on the branch, self-hosted runners, honest incidents74 { resource: "runners", label: "Self-hosted runners" },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step75];
76
77const LEVEL_ORDER: Record<ScopeLevel, number> = { read: 0, write: 1, run: 2, admin: 3 };
78
79export function scopeResource(scope: Scope): ScopeResource {
80 return scope.split(":")[0] as ScopeResource;
81}
82
83export function scopeLevel(scope: Scope): ScopeLevel {
84 return scope.split(":")[1] as ScopeLevel;
85}
86
87export function isScope(text: string): text is Scope {
88 return SCOPES.some((row) => row.scope === text);
89}
90
91/** Changes that are hard to undo, or decide who can reach what. */
92export function isDangerous(scope: Scope): boolean {
93 return scopeLevel(scope) === "admin";
94}
95
96export function describeScope(scope: Scope): string {
97 return SCOPES.find((row) => row.scope === scope)?.description ?? scope;
98}
99
100/** Whether holding `held` gives `needed`: the same resource, at its level or lower. */
101export function scopeIncludes(held: Scope, needed: Scope): boolean {
102 return (
103 scopeResource(held) === scopeResource(needed) &&
104 LEVEL_ORDER[scopeLevel(held)] >= LEVEL_ORDER[scopeLevel(needed)]
105 );
106}
107
108/** The levels a resource has, least first. */
109export function levelsOf(resource: ScopeResource): ScopeLevel[] {
110 return SCOPES.filter((row) => scopeResource(row.scope) === resource).map((row) => scopeLevel(row.scope));
111}
112
113/** Scopes from text separated by spaces or commas, in table order; unknown ones are left out. */
114export function parseScopes(text: string): Scope[] {
115 const given = new Set(text.split(/[\s,]+/).map((part) => part.trim().toLowerCase()));
116 return SCOPES.map((row) => row.scope).filter((scope) => given.has(scope));
117}
118
119/** What a token stores for full access. */
120export const FULL_ACCESS = "*";
121
122export type PresetId = "read_only" | "agent" | "ci" | "full";
123
124/** Starting points for choosing scopes. `*` is full access. */
125export const PRESET_SCOPES = {
126 read_only: [
Fast pages, required checks on the branch, self-hosted runners, honest incidents127 "repo:read", "code:read", "issues:read", "pull_requests:read", "workflows:read", "memory:read", "account:read", "workspace:read", "access:read", "webhooks:read", "secrets:read", "runners:read",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step128 ] as const,
129 agent: [
130 "repo:read", "code:read", "code:write", "issues:read", "issues:write", "pull_requests:read", "pull_requests:write", "agents:run", "workflows:read", "memory:read", "memory:write", "account:read", "workspace:read", "access:read", "webhooks:read", "secrets:read",
131 ] as const,
132 ci: [
133 "repo:read", "code:read", "code:write", "workflows:read", "workflows:write",
134 ] as const,
135 full: [
136 "*",
137 ] as const,
138};
139
140export const PRESETS: { id: PresetId; label: string; description: string }[] = [
141 { id: "read_only", label: "Read only", description: "Read everything you can read; change nothing." },
142 { id: "agent", label: "Agent", description: "Read everything, work on issues and pull requests, push code and run g1t agents." },
143 { id: "ci", label: "CI", description: "Clone and push code, and run workflows." },
144 { id: "full", label: "Full access", description: "Everything you can do, including deleting repositories and changing who has access." },
145];
146
147/** The scopes of a preset, or null for full access. */
148export function presetScopes(id: PresetId): Scope[] | null {
149 if (id === "full") return null;
150 return [...PRESET_SCOPES[id]] as Scope[];
151}
152
153/** What an OAuth client gets when it asks for nothing in particular. */
154export const OAUTH_DEFAULT_SCOPES: Scope[] = [...PRESET_SCOPES.agent];
155
156/** The operation each scope gates, by the API's operation names. */
157export const OPERATION_SCOPES = [
158 ["list_emails", "account:read"],
159 ["add_email", "account:write"],
160 ["remove_email", "account:write"],
161 ["update_email_settings", "account:write"],
162 ["list_invites", "account:read"],
163 ["create_invite", "account:write"],
164 ["revoke_invite", "account:write"],
165 ["list_my_repo_invitations", "account:read"],
166 ["accept_repo_invitation", "account:write"],
167 ["decline_repo_invitation", "account:write"],
168 ["create_workspace", "workspace:admin"],
169 ["delete_workspace", "workspace:admin"],
170 ["list_workspace_invites", "workspace:read"],
171 ["invite_member", "workspace:admin"],
172 ["revoke_workspace_invite", "workspace:admin"],
173 ["list_integrations", "workspace:read"],
174 ["connect_integration", "workspace:admin"],
175 ["disconnect_integration", "workspace:admin"],
176 ["test_integration", "workspace:admin"],
177 ["get_model_routes", "workspace:read"],
178 ["set_model_routes", "workspace:admin"],
179 ["list_repos", "repo:read"],
180 ["get_repo", "repo:read"],
181 ["search", "repo:read"],
182 ["list_events", "repo:read"],
183 ["list_labels", "repo:read"],
184 ["get_repo_settings", "repo:read"],
Fast pages, required checks on the branch, self-hosted runners, honest incidents185 ["list_check_names", "repo:read"],
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step186 ["list_deleted_repos", "repo:read"],
187 ["create_repo", "repo:write"],
188 ["update_repo", "repo:write"],
189 ["update_repo_settings", "repo:write"],
190 ["rename_branch", "repo:write"],
191 ["rename_repo", "repo:admin"],
192 ["transfer_repo", "repo:admin"],
193 ["archive_repo", "repo:admin"],
194 ["unarchive_repo", "repo:admin"],
195 ["set_repo_visibility", "repo:admin"],
196 ["delete_repo", "repo:admin"],
197 ["restore_repo", "repo:admin"],
198 ["purge_repo", "repo:admin"],
199 ["list_issues", "issues:read"],
200 ["get_issue", "issues:read"],
201 ["get_plan", "issues:read"],
202 ["create_issue", "issues:write"],
203 ["update_issue", "issues:write"],
204 ["close_issue", "issues:write"],
205 ["reopen_issue", "issues:write"],
206 ["add_comment", "issues:write"],
207 ["import_issue", "issues:write"],
208 ["apply_plan", "issues:write"],
209 ["list_pull_requests", "pull_requests:read"],
210 ["get_pull_request", "pull_requests:read"],
211 ["get_pull_request_changes", "pull_requests:read"],
212 ["read_session", "pull_requests:read"],
213 ["get_merge_queue", "pull_requests:read"],
214 ["create_pull_request", "pull_requests:write"],
215 ["record_session", "pull_requests:write"],
216 ["mark_pull_request_ready", "pull_requests:write"],
217 ["close_pull_request", "pull_requests:write"],
218 ["review_pull_request", "pull_requests:write"],
219 ["merge_pull_request", "pull_requests:write"],
220 ["assign_issue", "agents:run"],
221 ["delegate", "agents:run"],
222 ["plan_work", "agents:run"],
223 ["message_agent", "agents:run"],
224 ["answer_message", "agents:run"],
225 ["take_messages", "agents:run"],
226 ["list_workflows", "workflows:read"],
227 ["list_workflow_runs", "workflows:read"],
228 ["get_workflow_run", "workflows:read"],
229 ["get_job_logs", "workflows:read"],
230 ["dispatch_workflow", "workflows:write"],
231 ["cancel_workflow_run", "workflows:write"],
232 ["rerun_workflow_run", "workflows:write"],
233 ["update_workflow", "workflows:write"],
234 ["recall", "memory:read"],
235 ["search_context", "memory:read"],
236 ["get_entity", "memory:read"],
237 ["get_context", "memory:read"],
238 ["remember", "memory:write"],
239 ["list_collaborators", "access:read"],
240 ["get_collaborator_permission", "access:read"],
241 ["list_repo_invitations", "access:read"],
242 ["list_outside_collaborators", "access:read"],
243 ["add_collaborator", "access:admin"],
244 ["update_collaborator", "access:admin"],
245 ["remove_collaborator", "access:admin"],
246 ["revoke_repo_invitation", "access:admin"],
247 ["set_base_permission", "access:admin"],
248 ["list_webhooks", "webhooks:read"],
249 ["list_webhook_deliveries", "webhooks:read"],
250 ["create_webhook", "webhooks:admin"],
251 ["update_webhook", "webhooks:admin"],
252 ["delete_webhook", "webhooks:admin"],
253 ["ping_webhook", "webhooks:admin"],
254 ["redeliver_webhook", "webhooks:admin"],
255 ["list_actions_secrets", "secrets:read"],
256 ["list_actions_variables", "secrets:read"],
257 ["set_actions_secret", "secrets:admin"],
258 ["delete_actions_secret", "secrets:admin"],
259 ["set_actions_variable", "secrets:admin"],
260 ["delete_actions_variable", "secrets:admin"],
Fast pages, required checks on the branch, self-hosted runners, honest incidents261 // Self-hosted runners.
262 ["list_runners", "runners:read"],
263 ["list_runner_groups", "runners:read"],
264 ["get_runner_settings", "runners:read"],
265 ["create_runner_registration_token", "runners:admin"],
266 ["remove_runner", "runners:admin"],
267 ["create_runner_group", "runners:admin"],
268 ["update_runner_group", "runners:admin"],
269 ["delete_runner_group", "runners:admin"],
270 ["update_runner_settings", "runners:admin"],
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step271] as const;
272
273/**
274 * The scopes a token or grant holds, as stored: null for full access, or
275 * the list. `legacy` marks a token made before scopes, which has full
276 * access until someone narrows it.
277 */
278export type TokenScopes = {
279 scopes: Scope[] | null;
280 legacy: boolean;
281};
282
283/**
284 * How settings group scopes into a checklist: each group's scopes, least
285 * first. Admin scopes are not here; they are under "Dangerous" on their
286 * own (see `DANGEROUS_SCOPES`). Every other scope is in exactly one group.
287 */
288export const SCOPE_GROUPS: { id: string; label: string; scopes: Scope[] }[] = [
289 { id: "code", label: "Repositories & code", scopes: ["repo:read", "repo:write", "code:read", "code:write"] },
290 { id: "work", label: "Issues & pull requests", scopes: ["issues:read", "issues:write", "pull_requests:read", "pull_requests:write"] },
291 { id: "agents", label: "Agents", scopes: ["agents:run"] },
292 { id: "workflows", label: "Workflows", scopes: ["workflows:read", "workflows:write"] },
293 { id: "memory", label: "Memory & search", scopes: ["memory:read", "memory:write"] },
294 { id: "account", label: "Account", scopes: ["account:read", "account:write"] },
295 { id: "workspace", label: "Workspace", scopes: ["workspace:read", "access:read", "webhooks:read", "secrets:read"] },
Fast pages, required checks on the branch, self-hosted runners, honest incidents296 { id: "runners", label: "Runners", scopes: ["runners:read"] },
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step297];
298
299/** The admin scopes, shown under "Dangerous" behind a warning. */
300export const DANGEROUS_SCOPES: Scope[] = SCOPES.map((row) => row.scope).filter(isDangerous);