@github-tools/sdk 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1649 @@
1
+ import { createGithubTools } from "./index.mjs";
2
+ import { ToolLoopAgent, tool } from "ai";
3
+ import { z } from "zod";
4
+ import { Octokit } from "octokit";
5
+
6
+ //#region src/client.ts
7
+ function createOctokit(token) {
8
+ return new Octokit({ auth: token });
9
+ }
10
+
11
+ //#endregion
12
+ //#region src/tools/repository.ts
13
+ async function getRepositoryStep({ token, owner, repo }) {
14
+ "use step";
15
+ const { data } = await createOctokit(token).rest.repos.get({
16
+ owner,
17
+ repo
18
+ });
19
+ return {
20
+ name: data.name,
21
+ fullName: data.full_name,
22
+ description: data.description,
23
+ url: data.html_url,
24
+ defaultBranch: data.default_branch,
25
+ stars: data.stargazers_count,
26
+ forks: data.forks_count,
27
+ openIssues: data.open_issues_count,
28
+ language: data.language,
29
+ private: data.private,
30
+ createdAt: data.created_at,
31
+ updatedAt: data.updated_at
32
+ };
33
+ }
34
+ const getRepository = (token) => tool({
35
+ description: "Get information about a GitHub repository including description, stars, forks, language, and default branch",
36
+ inputSchema: z.object({
37
+ owner: z.string().describe("Repository owner (user or organization)"),
38
+ repo: z.string().describe("Repository name")
39
+ }),
40
+ execute: async (args) => getRepositoryStep({
41
+ token,
42
+ ...args
43
+ })
44
+ });
45
+ async function listBranchesStep({ token, owner, repo, perPage }) {
46
+ "use step";
47
+ const { data } = await createOctokit(token).rest.repos.listBranches({
48
+ owner,
49
+ repo,
50
+ per_page: perPage
51
+ });
52
+ return data.map((branch) => ({
53
+ name: branch.name,
54
+ sha: branch.commit.sha,
55
+ protected: branch.protected
56
+ }));
57
+ }
58
+ const listBranches = (token) => tool({
59
+ description: "List branches in a GitHub repository",
60
+ inputSchema: z.object({
61
+ owner: z.string().describe("Repository owner"),
62
+ repo: z.string().describe("Repository name"),
63
+ perPage: z.number().optional().default(30).describe("Number of branches to return (max 100)")
64
+ }),
65
+ execute: async (args) => listBranchesStep({
66
+ token,
67
+ ...args
68
+ })
69
+ });
70
+ async function getFileContentStep({ token, owner, repo, path, ref }) {
71
+ "use step";
72
+ const { data } = await createOctokit(token).rest.repos.getContent({
73
+ owner,
74
+ repo,
75
+ path,
76
+ ref
77
+ });
78
+ if (Array.isArray(data)) return {
79
+ type: "directory",
80
+ entries: data.map((e) => ({
81
+ name: e.name,
82
+ type: e.type,
83
+ path: e.path
84
+ }))
85
+ };
86
+ if (data.type !== "file") return {
87
+ type: data.type,
88
+ path: data.path
89
+ };
90
+ const content = Buffer.from(data.content, "base64").toString("utf-8");
91
+ return {
92
+ type: "file",
93
+ path: data.path,
94
+ sha: data.sha,
95
+ size: data.size,
96
+ content
97
+ };
98
+ }
99
+ const getFileContent = (token) => tool({
100
+ description: "Get the content of a file from a GitHub repository",
101
+ inputSchema: z.object({
102
+ owner: z.string().describe("Repository owner"),
103
+ repo: z.string().describe("Repository name"),
104
+ path: z.string().describe("Path to the file in the repository"),
105
+ ref: z.string().optional().describe("Branch, tag, or commit SHA (defaults to the default branch)")
106
+ }),
107
+ execute: async (args) => getFileContentStep({
108
+ token,
109
+ ...args
110
+ })
111
+ });
112
+ async function createBranchStep({ token, owner, repo, branch, from }) {
113
+ "use step";
114
+ const octokit = createOctokit(token);
115
+ let sha = from;
116
+ if (!sha || !sha.match(/^[0-9a-f]{40}$/i)) {
117
+ const { data: ref } = await octokit.rest.git.getRef({
118
+ owner,
119
+ repo,
120
+ ref: `heads/${from || (await octokit.rest.repos.get({
121
+ owner,
122
+ repo
123
+ })).data.default_branch}`
124
+ });
125
+ sha = ref.object.sha;
126
+ }
127
+ const { data } = await octokit.rest.git.createRef({
128
+ owner,
129
+ repo,
130
+ ref: `refs/heads/${branch}`,
131
+ sha
132
+ });
133
+ return {
134
+ ref: data.ref,
135
+ sha: data.object.sha,
136
+ url: data.url
137
+ };
138
+ }
139
+ const createBranch = (token, { needsApproval = true } = {}) => tool({
140
+ description: "Create a new branch in a GitHub repository from an existing branch or commit SHA",
141
+ needsApproval,
142
+ inputSchema: z.object({
143
+ owner: z.string().describe("Repository owner"),
144
+ repo: z.string().describe("Repository name"),
145
+ branch: z.string().describe("Name for the new branch"),
146
+ from: z.string().optional().describe("Source branch name or commit SHA to branch from (defaults to the default branch)")
147
+ }),
148
+ execute: async (args) => createBranchStep({
149
+ token,
150
+ ...args
151
+ })
152
+ });
153
+ async function forkRepositoryStep({ token, owner, repo, organization, name }) {
154
+ "use step";
155
+ const { data } = await createOctokit(token).rest.repos.createFork({
156
+ owner,
157
+ repo,
158
+ organization,
159
+ name
160
+ });
161
+ return {
162
+ name: data.name,
163
+ fullName: data.full_name,
164
+ url: data.html_url,
165
+ cloneUrl: data.clone_url,
166
+ sshUrl: data.ssh_url,
167
+ defaultBranch: data.default_branch,
168
+ private: data.private,
169
+ parent: data.parent ? {
170
+ fullName: data.parent.full_name,
171
+ url: data.parent.html_url
172
+ } : null
173
+ };
174
+ }
175
+ const forkRepository = (token, { needsApproval = true } = {}) => tool({
176
+ description: "Fork a GitHub repository to the authenticated user account or a specified organization",
177
+ needsApproval,
178
+ inputSchema: z.object({
179
+ owner: z.string().describe("Repository owner to fork from"),
180
+ repo: z.string().describe("Repository name to fork"),
181
+ organization: z.string().optional().describe("Organization to fork into (omit to fork to your personal account)"),
182
+ name: z.string().optional().describe("Name for the forked repository (defaults to the original name)")
183
+ }),
184
+ execute: async (args) => forkRepositoryStep({
185
+ token,
186
+ ...args
187
+ })
188
+ });
189
+ async function createRepositoryStep({ token, name, description, isPrivate, autoInit, gitignoreTemplate, licenseTemplate, org }) {
190
+ "use step";
191
+ const octokit = createOctokit(token);
192
+ const params = {
193
+ name,
194
+ description,
195
+ private: isPrivate,
196
+ auto_init: autoInit,
197
+ gitignore_template: gitignoreTemplate,
198
+ license_template: licenseTemplate
199
+ };
200
+ const { data } = org ? await octokit.rest.repos.createInOrg({
201
+ org,
202
+ ...params
203
+ }) : await octokit.rest.repos.createForAuthenticatedUser(params);
204
+ return {
205
+ name: data.name,
206
+ fullName: data.full_name,
207
+ description: data.description,
208
+ url: data.html_url,
209
+ cloneUrl: data.clone_url,
210
+ sshUrl: data.ssh_url,
211
+ defaultBranch: data.default_branch,
212
+ private: data.private,
213
+ createdAt: data.created_at
214
+ };
215
+ }
216
+ const createRepository = (token, { needsApproval = true } = {}) => tool({
217
+ description: "Create a new GitHub repository for the authenticated user or a specified organization",
218
+ needsApproval,
219
+ inputSchema: z.object({
220
+ name: z.string().describe("Repository name"),
221
+ description: z.string().optional().describe("A short description of the repository"),
222
+ isPrivate: z.boolean().optional().default(false).describe("Whether the repository is private"),
223
+ autoInit: z.boolean().optional().default(false).describe("Create an initial commit with a README"),
224
+ gitignoreTemplate: z.string().optional().describe("Gitignore template to use (e.g. \"Node\", \"Python\")"),
225
+ licenseTemplate: z.string().optional().describe("License keyword (e.g. \"mit\", \"apache-2.0\")"),
226
+ org: z.string().optional().describe("Organization to create the repository in (omit for personal repo)")
227
+ }),
228
+ execute: async (args) => createRepositoryStep({
229
+ token,
230
+ ...args
231
+ })
232
+ });
233
+ async function createOrUpdateFileStep({ token, owner, repo, path, message, content, branch, sha }) {
234
+ "use step";
235
+ const octokit = createOctokit(token);
236
+ const encoded = Buffer.from(content).toString("base64");
237
+ const { data } = await octokit.rest.repos.createOrUpdateFileContents({
238
+ owner,
239
+ repo,
240
+ path,
241
+ message,
242
+ content: encoded,
243
+ branch,
244
+ sha
245
+ });
246
+ return {
247
+ path: data.content?.path,
248
+ sha: data.content?.sha,
249
+ commitSha: data.commit.sha,
250
+ commitUrl: data.commit.html_url
251
+ };
252
+ }
253
+ const createOrUpdateFile = (token, { needsApproval = true } = {}) => tool({
254
+ description: "Create or update a file in a GitHub repository. Provide the SHA when updating an existing file.",
255
+ needsApproval,
256
+ inputSchema: z.object({
257
+ owner: z.string().describe("Repository owner"),
258
+ repo: z.string().describe("Repository name"),
259
+ path: z.string().describe("Path to the file in the repository"),
260
+ message: z.string().describe("Commit message"),
261
+ content: z.string().describe("File content (plain text, will be base64-encoded automatically)"),
262
+ branch: z.string().optional().describe("Branch to commit to (defaults to the default branch)"),
263
+ sha: z.string().optional().describe("SHA of the file being replaced (required when updating an existing file)")
264
+ }),
265
+ execute: async (args) => createOrUpdateFileStep({
266
+ token,
267
+ ...args
268
+ })
269
+ });
270
+
271
+ //#endregion
272
+ //#region src/tools/pull-requests.ts
273
+ async function listPullRequestsStep({ token, owner, repo, state, perPage }) {
274
+ "use step";
275
+ const { data } = await createOctokit(token).rest.pulls.list({
276
+ owner,
277
+ repo,
278
+ state,
279
+ per_page: perPage
280
+ });
281
+ return data.map((pr) => ({
282
+ number: pr.number,
283
+ title: pr.title,
284
+ state: pr.state,
285
+ url: pr.html_url,
286
+ author: pr.user?.login,
287
+ branch: pr.head.ref,
288
+ base: pr.base.ref,
289
+ draft: pr.draft,
290
+ createdAt: pr.created_at,
291
+ updatedAt: pr.updated_at
292
+ }));
293
+ }
294
+ const listPullRequests = (token) => tool({
295
+ description: "List pull requests for a GitHub repository",
296
+ inputSchema: z.object({
297
+ owner: z.string().describe("Repository owner"),
298
+ repo: z.string().describe("Repository name"),
299
+ state: z.enum([
300
+ "open",
301
+ "closed",
302
+ "all"
303
+ ]).optional().default("open").describe("Filter by state"),
304
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)")
305
+ }),
306
+ execute: async (args) => listPullRequestsStep({
307
+ token,
308
+ ...args
309
+ })
310
+ });
311
+ async function getPullRequestStep({ token, owner, repo, pullNumber }) {
312
+ "use step";
313
+ const { data } = await createOctokit(token).rest.pulls.get({
314
+ owner,
315
+ repo,
316
+ pull_number: pullNumber
317
+ });
318
+ return {
319
+ number: data.number,
320
+ title: data.title,
321
+ body: data.body,
322
+ state: data.state,
323
+ url: data.html_url,
324
+ author: data.user?.login,
325
+ branch: data.head.ref,
326
+ base: data.base.ref,
327
+ draft: data.draft,
328
+ merged: data.merged,
329
+ mergeable: data.mergeable,
330
+ additions: data.additions,
331
+ deletions: data.deletions,
332
+ changedFiles: data.changed_files,
333
+ createdAt: data.created_at,
334
+ updatedAt: data.updated_at,
335
+ mergedAt: data.merged_at
336
+ };
337
+ }
338
+ const getPullRequest = (token) => tool({
339
+ description: "Get detailed information about a specific pull request",
340
+ inputSchema: z.object({
341
+ owner: z.string().describe("Repository owner"),
342
+ repo: z.string().describe("Repository name"),
343
+ pullNumber: z.number().describe("Pull request number")
344
+ }),
345
+ execute: async (args) => getPullRequestStep({
346
+ token,
347
+ ...args
348
+ })
349
+ });
350
+ async function createPullRequestStep({ token, owner, repo, title, body, head, base, draft }) {
351
+ "use step";
352
+ const { data } = await createOctokit(token).rest.pulls.create({
353
+ owner,
354
+ repo,
355
+ title,
356
+ body,
357
+ head,
358
+ base,
359
+ draft
360
+ });
361
+ return {
362
+ number: data.number,
363
+ title: data.title,
364
+ url: data.html_url,
365
+ state: data.state,
366
+ draft: data.draft,
367
+ branch: data.head.ref,
368
+ base: data.base.ref
369
+ };
370
+ }
371
+ const createPullRequest = (token, { needsApproval = true } = {}) => tool({
372
+ description: "Create a new pull request in a GitHub repository",
373
+ needsApproval,
374
+ inputSchema: z.object({
375
+ owner: z.string().describe("Repository owner"),
376
+ repo: z.string().describe("Repository name"),
377
+ title: z.string().describe("Pull request title"),
378
+ body: z.string().optional().describe("Pull request description (supports Markdown)"),
379
+ head: z.string().describe("Branch containing the changes (format: branch or username:branch)"),
380
+ base: z.string().describe("Branch to merge into"),
381
+ draft: z.boolean().optional().default(false).describe("Create as draft pull request")
382
+ }),
383
+ execute: async (args) => createPullRequestStep({
384
+ token,
385
+ ...args
386
+ })
387
+ });
388
+ async function mergePullRequestStep({ token, owner, repo, pullNumber, commitTitle, commitMessage, mergeMethod }) {
389
+ "use step";
390
+ const { data } = await createOctokit(token).rest.pulls.merge({
391
+ owner,
392
+ repo,
393
+ pull_number: pullNumber,
394
+ commit_title: commitTitle,
395
+ commit_message: commitMessage,
396
+ merge_method: mergeMethod
397
+ });
398
+ return {
399
+ merged: data.merged,
400
+ message: data.message,
401
+ sha: data.sha
402
+ };
403
+ }
404
+ const mergePullRequest = (token, { needsApproval = true } = {}) => tool({
405
+ description: "Merge a pull request",
406
+ needsApproval,
407
+ inputSchema: z.object({
408
+ owner: z.string().describe("Repository owner"),
409
+ repo: z.string().describe("Repository name"),
410
+ pullNumber: z.number().describe("Pull request number"),
411
+ commitTitle: z.string().optional().describe("Title for the automatic merge commit"),
412
+ commitMessage: z.string().optional().describe("Extra detail to append to automatic commit message"),
413
+ mergeMethod: z.enum([
414
+ "merge",
415
+ "squash",
416
+ "rebase"
417
+ ]).optional().default("merge").describe("Merge strategy")
418
+ }),
419
+ execute: async (args) => mergePullRequestStep({
420
+ token,
421
+ ...args
422
+ })
423
+ });
424
+ async function addPullRequestCommentStep({ token, owner, repo, pullNumber, body }) {
425
+ "use step";
426
+ const { data } = await createOctokit(token).rest.issues.createComment({
427
+ owner,
428
+ repo,
429
+ issue_number: pullNumber,
430
+ body
431
+ });
432
+ return {
433
+ id: data.id,
434
+ url: data.html_url,
435
+ body: data.body,
436
+ author: data.user?.login,
437
+ createdAt: data.created_at
438
+ };
439
+ }
440
+ const addPullRequestComment = (token, { needsApproval = true } = {}) => tool({
441
+ description: "Add a comment to a pull request",
442
+ needsApproval,
443
+ inputSchema: z.object({
444
+ owner: z.string().describe("Repository owner"),
445
+ repo: z.string().describe("Repository name"),
446
+ pullNumber: z.number().describe("Pull request number"),
447
+ body: z.string().describe("Comment text (supports Markdown)")
448
+ }),
449
+ execute: async (args) => addPullRequestCommentStep({
450
+ token,
451
+ ...args
452
+ })
453
+ });
454
+ async function listPullRequestFilesStep({ token, owner, repo, pullNumber, perPage, page }) {
455
+ "use step";
456
+ const { data } = await createOctokit(token).rest.pulls.listFiles({
457
+ owner,
458
+ repo,
459
+ pull_number: pullNumber,
460
+ per_page: perPage,
461
+ page
462
+ });
463
+ return data.map((file) => ({
464
+ filename: file.filename,
465
+ status: file.status,
466
+ additions: file.additions,
467
+ deletions: file.deletions,
468
+ changes: file.changes,
469
+ patch: file.patch
470
+ }));
471
+ }
472
+ const listPullRequestFiles = (token) => tool({
473
+ description: "List files changed in a pull request, including diff status and patch content",
474
+ inputSchema: z.object({
475
+ owner: z.string().describe("Repository owner"),
476
+ repo: z.string().describe("Repository name"),
477
+ pullNumber: z.number().describe("Pull request number"),
478
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)"),
479
+ page: z.number().optional().default(1).describe("Page number for pagination")
480
+ }),
481
+ execute: async (args) => listPullRequestFilesStep({
482
+ token,
483
+ ...args
484
+ })
485
+ });
486
+ async function listPullRequestReviewsStep({ token, owner, repo, pullNumber, perPage, page }) {
487
+ "use step";
488
+ const { data } = await createOctokit(token).rest.pulls.listReviews({
489
+ owner,
490
+ repo,
491
+ pull_number: pullNumber,
492
+ per_page: perPage,
493
+ page
494
+ });
495
+ return data.map((review) => ({
496
+ id: review.id,
497
+ state: review.state,
498
+ body: review.body,
499
+ author: review.user?.login,
500
+ url: review.html_url,
501
+ submittedAt: review.submitted_at
502
+ }));
503
+ }
504
+ const listPullRequestReviews = (token) => tool({
505
+ description: "List reviews on a pull request (approvals, change requests, and comments)",
506
+ inputSchema: z.object({
507
+ owner: z.string().describe("Repository owner"),
508
+ repo: z.string().describe("Repository name"),
509
+ pullNumber: z.number().describe("Pull request number"),
510
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)"),
511
+ page: z.number().optional().default(1).describe("Page number for pagination")
512
+ }),
513
+ execute: async (args) => listPullRequestReviewsStep({
514
+ token,
515
+ ...args
516
+ })
517
+ });
518
+ async function createPullRequestReviewStep({ token, owner, repo, pullNumber, body, event, comments }) {
519
+ "use step";
520
+ const { data } = await createOctokit(token).rest.pulls.createReview({
521
+ owner,
522
+ repo,
523
+ pull_number: pullNumber,
524
+ body,
525
+ event,
526
+ comments
527
+ });
528
+ return {
529
+ id: data.id,
530
+ state: data.state,
531
+ body: data.body,
532
+ url: data.html_url,
533
+ author: data.user?.login,
534
+ submittedAt: data.submitted_at
535
+ };
536
+ }
537
+ const createPullRequestReview = (token, { needsApproval = true } = {}) => tool({
538
+ description: "Submit a pull request review — approve, request changes, or comment with optional inline comments on specific lines",
539
+ needsApproval,
540
+ inputSchema: z.object({
541
+ owner: z.string().describe("Repository owner"),
542
+ repo: z.string().describe("Repository name"),
543
+ pullNumber: z.number().describe("Pull request number"),
544
+ body: z.string().optional().describe("Review body text (supports Markdown)"),
545
+ event: z.enum([
546
+ "APPROVE",
547
+ "REQUEST_CHANGES",
548
+ "COMMENT"
549
+ ]).describe("Review action: approve, request changes, or comment"),
550
+ comments: z.array(z.object({
551
+ path: z.string().describe("File path relative to the repository root"),
552
+ body: z.string().describe("Inline comment text"),
553
+ line: z.number().optional().describe("Line number in the file to comment on"),
554
+ side: z.enum(["LEFT", "RIGHT"]).optional().describe("Which side of the diff to comment on (LEFT = base, RIGHT = head)")
555
+ })).optional().describe("Inline review comments on specific files and lines")
556
+ }),
557
+ execute: async (args) => createPullRequestReviewStep({
558
+ token,
559
+ ...args
560
+ })
561
+ });
562
+
563
+ //#endregion
564
+ //#region src/tools/issues.ts
565
+ async function listIssuesStep({ token, owner, repo, state, labels, perPage }) {
566
+ "use step";
567
+ const { data } = await createOctokit(token).rest.issues.listForRepo({
568
+ owner,
569
+ repo,
570
+ state,
571
+ labels,
572
+ per_page: perPage
573
+ });
574
+ return data.filter((issue) => !issue.pull_request).map((issue) => ({
575
+ number: issue.number,
576
+ title: issue.title,
577
+ state: issue.state,
578
+ url: issue.html_url,
579
+ author: issue.user?.login,
580
+ labels: issue.labels.map((l) => typeof l === "string" ? l : l.name),
581
+ createdAt: issue.created_at,
582
+ updatedAt: issue.updated_at
583
+ }));
584
+ }
585
+ const listIssues = (token) => tool({
586
+ description: "List issues for a GitHub repository (excludes pull requests)",
587
+ inputSchema: z.object({
588
+ owner: z.string().describe("Repository owner"),
589
+ repo: z.string().describe("Repository name"),
590
+ state: z.enum([
591
+ "open",
592
+ "closed",
593
+ "all"
594
+ ]).optional().default("open").describe("Filter by state"),
595
+ labels: z.string().optional().describe("Comma-separated list of label names to filter by"),
596
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)")
597
+ }),
598
+ execute: async (args) => listIssuesStep({
599
+ token,
600
+ ...args
601
+ })
602
+ });
603
+ async function getIssueStep({ token, owner, repo, issueNumber }) {
604
+ "use step";
605
+ const { data } = await createOctokit(token).rest.issues.get({
606
+ owner,
607
+ repo,
608
+ issue_number: issueNumber
609
+ });
610
+ return {
611
+ number: data.number,
612
+ title: data.title,
613
+ body: data.body,
614
+ state: data.state,
615
+ url: data.html_url,
616
+ author: data.user?.login,
617
+ assignees: data.assignees?.map((a) => a.login),
618
+ labels: data.labels.map((l) => typeof l === "string" ? l : l.name),
619
+ comments: data.comments,
620
+ createdAt: data.created_at,
621
+ updatedAt: data.updated_at,
622
+ closedAt: data.closed_at
623
+ };
624
+ }
625
+ const getIssue = (token) => tool({
626
+ description: "Get detailed information about a specific issue",
627
+ inputSchema: z.object({
628
+ owner: z.string().describe("Repository owner"),
629
+ repo: z.string().describe("Repository name"),
630
+ issueNumber: z.number().describe("Issue number")
631
+ }),
632
+ execute: async (args) => getIssueStep({
633
+ token,
634
+ ...args
635
+ })
636
+ });
637
+ async function createIssueStep({ token, owner, repo, title, body, labels, assignees }) {
638
+ "use step";
639
+ const { data } = await createOctokit(token).rest.issues.create({
640
+ owner,
641
+ repo,
642
+ title,
643
+ body,
644
+ labels,
645
+ assignees
646
+ });
647
+ return {
648
+ number: data.number,
649
+ title: data.title,
650
+ url: data.html_url,
651
+ state: data.state,
652
+ labels: data.labels.map((l) => typeof l === "string" ? l : l.name)
653
+ };
654
+ }
655
+ const createIssue = (token, { needsApproval = true } = {}) => tool({
656
+ description: "Create a new issue in a GitHub repository",
657
+ needsApproval,
658
+ inputSchema: z.object({
659
+ owner: z.string().describe("Repository owner"),
660
+ repo: z.string().describe("Repository name"),
661
+ title: z.string().describe("Issue title"),
662
+ body: z.string().optional().describe("Issue description (supports Markdown)"),
663
+ labels: z.array(z.string()).optional().describe("Labels to apply to the issue"),
664
+ assignees: z.array(z.string()).optional().describe("GitHub usernames to assign to the issue")
665
+ }),
666
+ execute: async (args) => createIssueStep({
667
+ token,
668
+ ...args
669
+ })
670
+ });
671
+ async function addIssueCommentStep({ token, owner, repo, issueNumber, body }) {
672
+ "use step";
673
+ const { data } = await createOctokit(token).rest.issues.createComment({
674
+ owner,
675
+ repo,
676
+ issue_number: issueNumber,
677
+ body
678
+ });
679
+ return {
680
+ id: data.id,
681
+ url: data.html_url,
682
+ body: data.body,
683
+ author: data.user?.login,
684
+ createdAt: data.created_at
685
+ };
686
+ }
687
+ const addIssueComment = (token, { needsApproval = true } = {}) => tool({
688
+ description: "Add a comment to a GitHub issue",
689
+ needsApproval,
690
+ inputSchema: z.object({
691
+ owner: z.string().describe("Repository owner"),
692
+ repo: z.string().describe("Repository name"),
693
+ issueNumber: z.number().describe("Issue number"),
694
+ body: z.string().describe("Comment text (supports Markdown)")
695
+ }),
696
+ execute: async (args) => addIssueCommentStep({
697
+ token,
698
+ ...args
699
+ })
700
+ });
701
+ async function closeIssueStep({ token, owner, repo, issueNumber, stateReason }) {
702
+ "use step";
703
+ const { data } = await createOctokit(token).rest.issues.update({
704
+ owner,
705
+ repo,
706
+ issue_number: issueNumber,
707
+ state: "closed",
708
+ state_reason: stateReason
709
+ });
710
+ return {
711
+ number: data.number,
712
+ title: data.title,
713
+ state: data.state,
714
+ url: data.html_url,
715
+ closedAt: data.closed_at
716
+ };
717
+ }
718
+ const closeIssue = (token, { needsApproval = true } = {}) => tool({
719
+ description: "Close an open GitHub issue",
720
+ needsApproval,
721
+ inputSchema: z.object({
722
+ owner: z.string().describe("Repository owner"),
723
+ repo: z.string().describe("Repository name"),
724
+ issueNumber: z.number().describe("Issue number to close"),
725
+ stateReason: z.enum(["completed", "not_planned"]).optional().default("completed").describe("Reason for closing")
726
+ }),
727
+ execute: async (args) => closeIssueStep({
728
+ token,
729
+ ...args
730
+ })
731
+ });
732
+ async function listLabelsStep({ token, owner, repo, perPage, page }) {
733
+ "use step";
734
+ const { data } = await createOctokit(token).rest.issues.listLabelsForRepo({
735
+ owner,
736
+ repo,
737
+ per_page: perPage,
738
+ page
739
+ });
740
+ return data.map((label) => ({
741
+ name: label.name,
742
+ color: label.color,
743
+ description: label.description
744
+ }));
745
+ }
746
+ const listLabels = (token) => tool({
747
+ description: "List labels available in a GitHub repository",
748
+ inputSchema: z.object({
749
+ owner: z.string().describe("Repository owner"),
750
+ repo: z.string().describe("Repository name"),
751
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)"),
752
+ page: z.number().optional().default(1).describe("Page number for pagination")
753
+ }),
754
+ execute: async (args) => listLabelsStep({
755
+ token,
756
+ ...args
757
+ })
758
+ });
759
+ async function addLabelsStep({ token, owner, repo, issueNumber, labels }) {
760
+ "use step";
761
+ const { data } = await createOctokit(token).rest.issues.addLabels({
762
+ owner,
763
+ repo,
764
+ issue_number: issueNumber,
765
+ labels
766
+ });
767
+ return data.map((label) => ({
768
+ name: label.name,
769
+ color: label.color,
770
+ description: label.description
771
+ }));
772
+ }
773
+ const addLabels = (token, { needsApproval = true } = {}) => tool({
774
+ description: "Add labels to an issue or pull request",
775
+ needsApproval,
776
+ inputSchema: z.object({
777
+ owner: z.string().describe("Repository owner"),
778
+ repo: z.string().describe("Repository name"),
779
+ issueNumber: z.number().describe("Issue or pull request number"),
780
+ labels: z.array(z.string()).describe("Label names to add")
781
+ }),
782
+ execute: async (args) => addLabelsStep({
783
+ token,
784
+ ...args
785
+ })
786
+ });
787
+ async function removeLabelStep({ token, owner, repo, issueNumber, label }) {
788
+ "use step";
789
+ await createOctokit(token).rest.issues.removeLabel({
790
+ owner,
791
+ repo,
792
+ issue_number: issueNumber,
793
+ name: label
794
+ });
795
+ return {
796
+ removed: true,
797
+ label,
798
+ issueNumber
799
+ };
800
+ }
801
+ const removeLabel = (token, { needsApproval = true } = {}) => tool({
802
+ description: "Remove a label from an issue or pull request",
803
+ needsApproval,
804
+ inputSchema: z.object({
805
+ owner: z.string().describe("Repository owner"),
806
+ repo: z.string().describe("Repository name"),
807
+ issueNumber: z.number().describe("Issue or pull request number"),
808
+ label: z.string().describe("Label name to remove")
809
+ }),
810
+ execute: async (args) => removeLabelStep({
811
+ token,
812
+ ...args
813
+ })
814
+ });
815
+
816
+ //#endregion
817
+ //#region src/tools/search.ts
818
+ async function searchCodeStep({ token, query, perPage }) {
819
+ "use step";
820
+ const { data } = await createOctokit(token).rest.search.code({
821
+ q: query,
822
+ per_page: perPage
823
+ });
824
+ return {
825
+ totalCount: data.total_count,
826
+ items: data.items.map((item) => ({
827
+ name: item.name,
828
+ path: item.path,
829
+ url: item.html_url,
830
+ repository: item.repository.full_name,
831
+ sha: item.sha
832
+ }))
833
+ };
834
+ }
835
+ const searchCode = (token) => tool({
836
+ description: "Search for code in GitHub repositories. Use qualifiers like \"repo:owner/name\" to scope the search.",
837
+ inputSchema: z.object({
838
+ query: z.string().describe("Search query. Supports GitHub search qualifiers, e.g. \"useState repo:facebook/react\""),
839
+ perPage: z.number().optional().default(10).describe("Number of results to return (max 30)")
840
+ }),
841
+ execute: async (args) => searchCodeStep({
842
+ token,
843
+ ...args
844
+ })
845
+ });
846
+ async function searchRepositoriesStep({ token, query, perPage, sort, order }) {
847
+ "use step";
848
+ const { data } = await createOctokit(token).rest.search.repos({
849
+ q: query,
850
+ per_page: perPage,
851
+ sort,
852
+ order
853
+ });
854
+ return {
855
+ totalCount: data.total_count,
856
+ items: data.items.map((repo) => ({
857
+ name: repo.name,
858
+ fullName: repo.full_name,
859
+ description: repo.description,
860
+ url: repo.html_url,
861
+ stars: repo.stargazers_count,
862
+ forks: repo.forks_count,
863
+ language: repo.language,
864
+ topics: repo.topics
865
+ }))
866
+ };
867
+ }
868
+ const searchRepositories = (token) => tool({
869
+ description: "Search for GitHub repositories by keyword, topic, language, or other qualifiers",
870
+ inputSchema: z.object({
871
+ query: z.string().describe("Search query. Supports GitHub search qualifiers, e.g. \"nuxt language:typescript stars:>1000\""),
872
+ perPage: z.number().optional().default(10).describe("Number of results to return (max 30)"),
873
+ sort: z.enum([
874
+ "stars",
875
+ "forks",
876
+ "help-wanted-issues",
877
+ "updated"
878
+ ]).optional().describe("Sort field"),
879
+ order: z.enum(["asc", "desc"]).optional().default("desc").describe("Sort order")
880
+ }),
881
+ execute: async (args) => searchRepositoriesStep({
882
+ token,
883
+ ...args
884
+ })
885
+ });
886
+
887
+ //#endregion
888
+ //#region src/tools/commits.ts
889
+ const BLAME_QUERY = `
890
+ query ($owner: String!, $name: String!, $expression: String!, $path: String!) {
891
+ repository(owner: $owner, name: $name) {
892
+ object(expression: $expression) {
893
+ ... on Commit {
894
+ oid
895
+ blame(path: $path) {
896
+ ranges {
897
+ startingLine
898
+ endingLine
899
+ age
900
+ commit {
901
+ oid
902
+ abbreviatedOid
903
+ messageHeadline
904
+ authoredDate
905
+ url
906
+ author {
907
+ name
908
+ email
909
+ user {
910
+ login
911
+ }
912
+ }
913
+ }
914
+ }
915
+ }
916
+ }
917
+ }
918
+ }
919
+ }
920
+ `;
921
+ async function listCommitsStep({ token, owner, repo, path, sha, author, since, until, perPage }) {
922
+ "use step";
923
+ const { data } = await createOctokit(token).rest.repos.listCommits({
924
+ owner,
925
+ repo,
926
+ path,
927
+ sha,
928
+ author,
929
+ since,
930
+ until,
931
+ per_page: perPage
932
+ });
933
+ return data.map((commit) => ({
934
+ sha: commit.sha,
935
+ message: commit.commit.message,
936
+ author: commit.commit.author?.name,
937
+ authorLogin: commit.author?.login,
938
+ date: commit.commit.author?.date,
939
+ url: commit.html_url
940
+ }));
941
+ }
942
+ const listCommits = (token) => tool({
943
+ description: "List commits for a GitHub repository. Filter by file path to see commits that touched a file. For line-by-line attribution at a given ref, use getBlame instead.",
944
+ inputSchema: z.object({
945
+ owner: z.string().describe("Repository owner"),
946
+ repo: z.string().describe("Repository name"),
947
+ path: z.string().optional().describe("Only commits containing this file path"),
948
+ sha: z.string().optional().describe("Branch name or commit SHA to start listing from"),
949
+ author: z.string().optional().describe("GitHub username or email to filter commits by"),
950
+ since: z.string().optional().describe("Only commits after this date (ISO 8601 format)"),
951
+ until: z.string().optional().describe("Only commits before this date (ISO 8601 format)"),
952
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)")
953
+ }),
954
+ execute: async (args) => listCommitsStep({
955
+ token,
956
+ ...args
957
+ })
958
+ });
959
+ async function getCommitStep({ token, owner, repo, ref }) {
960
+ "use step";
961
+ const { data } = await createOctokit(token).rest.repos.getCommit({
962
+ owner,
963
+ repo,
964
+ ref
965
+ });
966
+ return {
967
+ sha: data.sha,
968
+ message: data.commit.message,
969
+ author: data.commit.author?.name,
970
+ authorLogin: data.author?.login,
971
+ date: data.commit.author?.date,
972
+ url: data.html_url,
973
+ stats: data.stats ? {
974
+ additions: data.stats.additions,
975
+ deletions: data.stats.deletions,
976
+ total: data.stats.total
977
+ } : null,
978
+ files: data.files?.map((file) => ({
979
+ filename: file.filename,
980
+ status: file.status,
981
+ additions: file.additions,
982
+ deletions: file.deletions,
983
+ patch: file.patch
984
+ }))
985
+ };
986
+ }
987
+ const getCommit = (token) => tool({
988
+ description: "Get detailed information about a specific commit, including the list of files changed with additions and deletions",
989
+ inputSchema: z.object({
990
+ owner: z.string().describe("Repository owner"),
991
+ repo: z.string().describe("Repository name"),
992
+ ref: z.string().describe("Commit SHA, branch name, or tag")
993
+ }),
994
+ execute: async (args) => getCommitStep({
995
+ token,
996
+ ...args
997
+ })
998
+ });
999
+ async function getBlameStep({ token, owner, repo, path, ref, line, lineStart, lineEnd }) {
1000
+ "use step";
1001
+ const octokit = createOctokit(token);
1002
+ let expression = ref;
1003
+ if (!expression) {
1004
+ const { data } = await octokit.rest.repos.get({
1005
+ owner,
1006
+ repo
1007
+ });
1008
+ expression = data.default_branch;
1009
+ }
1010
+ const data = await octokit.graphql(BLAME_QUERY, {
1011
+ owner,
1012
+ name: repo,
1013
+ expression,
1014
+ path
1015
+ });
1016
+ if (!data.repository) return { error: `Repository not found: ${owner}/${repo}` };
1017
+ const obj = data.repository.object;
1018
+ if (!obj?.oid || !obj?.blame) return { error: `Ref "${expression}" did not resolve to a commit for this repository (or the path is invalid). Pass a branch name, tag, or full commit SHA.` };
1019
+ let ranges = obj.blame.ranges.map((r) => ({
1020
+ startingLine: r.startingLine,
1021
+ endingLine: r.endingLine,
1022
+ age: r.age,
1023
+ commit: {
1024
+ sha: r.commit.oid,
1025
+ abbreviatedSha: r.commit.abbreviatedOid,
1026
+ messageHeadline: r.commit.messageHeadline,
1027
+ authoredDate: r.commit.authoredDate,
1028
+ url: r.commit.url,
1029
+ authorName: r.commit.author?.name ?? null,
1030
+ authorEmail: r.commit.author?.email ?? null,
1031
+ authorLogin: r.commit.author?.user?.login ?? null
1032
+ }
1033
+ }));
1034
+ if (line != null) ranges = ranges.filter((r) => line >= r.startingLine && line <= r.endingLine);
1035
+ else if (lineStart != null || lineEnd != null) {
1036
+ const start = lineStart ?? 1;
1037
+ const end = lineEnd ?? Number.MAX_SAFE_INTEGER;
1038
+ ranges = ranges.filter((r) => r.endingLine >= start && r.startingLine <= end);
1039
+ }
1040
+ return {
1041
+ ref: expression,
1042
+ tipSha: obj.oid,
1043
+ path,
1044
+ rangeCount: ranges.length,
1045
+ ranges
1046
+ };
1047
+ }
1048
+ const getBlame = (token) => tool({
1049
+ description: "Line-level git blame for a file at a commit-like ref (branch, tag, or SHA). Returns contiguous ranges mapping lines to the commits that last modified them — use this to see who introduced a line and when (GitHub GraphQL API).",
1050
+ inputSchema: z.object({
1051
+ owner: z.string().describe("Repository owner"),
1052
+ repo: z.string().describe("Repository name"),
1053
+ path: z.string().describe("Path to the file in the repository"),
1054
+ ref: z.string().optional().describe("Branch name, tag, or commit SHA (defaults to the repository default branch)"),
1055
+ line: z.number().int().positive().optional().describe("If set, only return blame ranges that include this line number"),
1056
+ lineStart: z.number().int().positive().optional().describe("When used with lineEnd, only return ranges overlapping this window"),
1057
+ lineEnd: z.number().int().positive().optional().describe("When used with lineStart, only return ranges overlapping this window")
1058
+ }),
1059
+ execute: async (args) => getBlameStep({
1060
+ token,
1061
+ ...args
1062
+ })
1063
+ });
1064
+
1065
+ //#endregion
1066
+ //#region src/tools/gists.ts
1067
+ async function listGistsStep({ token, username, perPage, page }) {
1068
+ "use step";
1069
+ const octokit = createOctokit(token);
1070
+ const { data } = username ? await octokit.rest.gists.listForUser({
1071
+ username,
1072
+ per_page: perPage,
1073
+ page
1074
+ }) : await octokit.rest.gists.list({
1075
+ per_page: perPage,
1076
+ page
1077
+ });
1078
+ return data.map((gist) => ({
1079
+ id: gist.id,
1080
+ description: gist.description,
1081
+ public: gist.public,
1082
+ url: gist.html_url,
1083
+ files: Object.keys(gist.files ?? {}),
1084
+ owner: gist.owner?.login,
1085
+ comments: gist.comments,
1086
+ createdAt: gist.created_at,
1087
+ updatedAt: gist.updated_at
1088
+ }));
1089
+ }
1090
+ const listGists = (token) => tool({
1091
+ description: "List gists for the authenticated user or a specific user",
1092
+ inputSchema: z.object({
1093
+ username: z.string().optional().describe("GitHub username — omit to list your own gists"),
1094
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)"),
1095
+ page: z.number().optional().default(1).describe("Page number for pagination")
1096
+ }),
1097
+ execute: async (args) => listGistsStep({
1098
+ token,
1099
+ ...args
1100
+ })
1101
+ });
1102
+ async function getGistStep({ token, gistId }) {
1103
+ "use step";
1104
+ const { data } = await createOctokit(token).rest.gists.get({ gist_id: gistId });
1105
+ return {
1106
+ id: data.id,
1107
+ description: data.description,
1108
+ public: data.public,
1109
+ url: data.html_url,
1110
+ owner: data.owner?.login,
1111
+ files: Object.values(data.files ?? {}).map((file) => ({
1112
+ filename: file?.filename,
1113
+ language: file?.language,
1114
+ size: file?.size,
1115
+ content: file?.content
1116
+ })),
1117
+ comments: data.comments,
1118
+ createdAt: data.created_at,
1119
+ updatedAt: data.updated_at
1120
+ };
1121
+ }
1122
+ const getGist = (token) => tool({
1123
+ description: "Get a gist by ID, including file contents",
1124
+ inputSchema: z.object({ gistId: z.string().describe("Gist ID") }),
1125
+ execute: async (args) => getGistStep({
1126
+ token,
1127
+ ...args
1128
+ })
1129
+ });
1130
+ async function listGistCommentsStep({ token, gistId, perPage, page }) {
1131
+ "use step";
1132
+ const { data } = await createOctokit(token).rest.gists.listComments({
1133
+ gist_id: gistId,
1134
+ per_page: perPage,
1135
+ page
1136
+ });
1137
+ return data.map((comment) => ({
1138
+ id: comment.id,
1139
+ body: comment.body,
1140
+ author: comment.user?.login,
1141
+ url: comment.url,
1142
+ createdAt: comment.created_at,
1143
+ updatedAt: comment.updated_at
1144
+ }));
1145
+ }
1146
+ const listGistComments = (token) => tool({
1147
+ description: "List comments on a gist",
1148
+ inputSchema: z.object({
1149
+ gistId: z.string().describe("Gist ID"),
1150
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)"),
1151
+ page: z.number().optional().default(1).describe("Page number for pagination")
1152
+ }),
1153
+ execute: async (args) => listGistCommentsStep({
1154
+ token,
1155
+ ...args
1156
+ })
1157
+ });
1158
+ async function createGistStep({ token, description, files, isPublic }) {
1159
+ "use step";
1160
+ const { data } = await createOctokit(token).rest.gists.create({
1161
+ description,
1162
+ files,
1163
+ public: isPublic
1164
+ });
1165
+ return {
1166
+ id: data.id,
1167
+ description: data.description,
1168
+ public: data.public,
1169
+ url: data.html_url,
1170
+ files: Object.keys(data.files ?? {}),
1171
+ owner: data.owner?.login
1172
+ };
1173
+ }
1174
+ const createGist = (token, { needsApproval = true } = {}) => tool({
1175
+ description: "Create a new gist with one or more files",
1176
+ needsApproval,
1177
+ inputSchema: z.object({
1178
+ description: z.string().optional().describe("Gist description"),
1179
+ files: z.record(z.string(), z.object({ content: z.string().describe("File content") })).describe("Files to include, keyed by filename"),
1180
+ isPublic: z.boolean().optional().default(false).describe("Whether the gist is public")
1181
+ }),
1182
+ execute: async (args) => createGistStep({
1183
+ token,
1184
+ ...args
1185
+ })
1186
+ });
1187
+ async function updateGistStep({ token, gistId, description, files, filesToDelete }) {
1188
+ "use step";
1189
+ const octokit = createOctokit(token);
1190
+ const fileUpdates = {};
1191
+ if (files) Object.assign(fileUpdates, files);
1192
+ if (filesToDelete) for (const name of filesToDelete) fileUpdates[name] = null;
1193
+ const { data } = await octokit.rest.gists.update({
1194
+ gist_id: gistId,
1195
+ description,
1196
+ files: fileUpdates
1197
+ });
1198
+ return {
1199
+ id: data.id,
1200
+ description: data.description,
1201
+ url: data.html_url,
1202
+ files: Object.keys(data.files ?? {})
1203
+ };
1204
+ }
1205
+ const updateGist = (token, { needsApproval = true } = {}) => tool({
1206
+ description: "Update an existing gist — edit description, update files, or remove files",
1207
+ needsApproval,
1208
+ inputSchema: z.object({
1209
+ gistId: z.string().describe("Gist ID"),
1210
+ description: z.string().optional().describe("New gist description"),
1211
+ files: z.record(z.string(), z.object({ content: z.string().describe("New file content") })).optional().describe("Files to add or update, keyed by filename"),
1212
+ filesToDelete: z.array(z.string()).optional().describe("Filenames to remove from the gist")
1213
+ }),
1214
+ execute: async (args) => updateGistStep({
1215
+ token,
1216
+ ...args
1217
+ })
1218
+ });
1219
+ async function deleteGistStep({ token, gistId }) {
1220
+ "use step";
1221
+ await createOctokit(token).rest.gists.delete({ gist_id: gistId });
1222
+ return {
1223
+ deleted: true,
1224
+ gistId
1225
+ };
1226
+ }
1227
+ const deleteGist = (token, { needsApproval = true } = {}) => tool({
1228
+ description: "Delete a gist permanently",
1229
+ needsApproval,
1230
+ inputSchema: z.object({ gistId: z.string().describe("Gist ID to delete") }),
1231
+ execute: async (args) => deleteGistStep({
1232
+ token,
1233
+ ...args
1234
+ })
1235
+ });
1236
+ async function createGistCommentStep({ token, gistId, body }) {
1237
+ "use step";
1238
+ const { data } = await createOctokit(token).rest.gists.createComment({
1239
+ gist_id: gistId,
1240
+ body
1241
+ });
1242
+ return {
1243
+ id: data.id,
1244
+ url: data.url,
1245
+ body: data.body,
1246
+ author: data.user?.login,
1247
+ createdAt: data.created_at
1248
+ };
1249
+ }
1250
+ const createGistComment = (token, { needsApproval = true } = {}) => tool({
1251
+ description: "Add a comment to a gist",
1252
+ needsApproval,
1253
+ inputSchema: z.object({
1254
+ gistId: z.string().describe("Gist ID"),
1255
+ body: z.string().describe("Comment text (supports Markdown)")
1256
+ }),
1257
+ execute: async (args) => createGistCommentStep({
1258
+ token,
1259
+ ...args
1260
+ })
1261
+ });
1262
+
1263
+ //#endregion
1264
+ //#region src/tools/workflows.ts
1265
+ async function listWorkflowsStep({ token, owner, repo, perPage, page }) {
1266
+ "use step";
1267
+ const { data } = await createOctokit(token).rest.actions.listRepoWorkflows({
1268
+ owner,
1269
+ repo,
1270
+ per_page: perPage,
1271
+ page
1272
+ });
1273
+ return {
1274
+ totalCount: data.total_count,
1275
+ workflows: data.workflows.map((wf) => ({
1276
+ id: wf.id,
1277
+ name: wf.name,
1278
+ path: wf.path,
1279
+ state: wf.state,
1280
+ url: wf.html_url,
1281
+ createdAt: wf.created_at,
1282
+ updatedAt: wf.updated_at
1283
+ }))
1284
+ };
1285
+ }
1286
+ const listWorkflows = (token) => tool({
1287
+ description: "List GitHub Actions workflows in a repository",
1288
+ inputSchema: z.object({
1289
+ owner: z.string().describe("Repository owner"),
1290
+ repo: z.string().describe("Repository name"),
1291
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)"),
1292
+ page: z.number().optional().default(1).describe("Page number for pagination")
1293
+ }),
1294
+ execute: async (args) => listWorkflowsStep({
1295
+ token,
1296
+ ...args
1297
+ })
1298
+ });
1299
+ async function listWorkflowRunsStep({ token, owner, repo, workflowId, branch, event, status, perPage, page }) {
1300
+ "use step";
1301
+ const octokit = createOctokit(token);
1302
+ const { data } = workflowId ? await octokit.rest.actions.listWorkflowRuns({
1303
+ owner,
1304
+ repo,
1305
+ workflow_id: workflowId,
1306
+ per_page: perPage,
1307
+ page,
1308
+ ...branch && { branch },
1309
+ ...event && { event },
1310
+ ...status && { status }
1311
+ }) : await octokit.rest.actions.listWorkflowRunsForRepo({
1312
+ owner,
1313
+ repo,
1314
+ per_page: perPage,
1315
+ page,
1316
+ ...branch && { branch },
1317
+ ...event && { event },
1318
+ ...status && { status }
1319
+ });
1320
+ return {
1321
+ totalCount: data.total_count,
1322
+ runs: data.workflow_runs.map((run) => ({
1323
+ id: run.id,
1324
+ name: run.name,
1325
+ status: run.status,
1326
+ conclusion: run.conclusion,
1327
+ branch: run.head_branch,
1328
+ event: run.event,
1329
+ url: run.html_url,
1330
+ actor: run.actor?.login,
1331
+ createdAt: run.created_at,
1332
+ updatedAt: run.updated_at,
1333
+ runNumber: run.run_number,
1334
+ runAttempt: run.run_attempt
1335
+ }))
1336
+ };
1337
+ }
1338
+ const listWorkflowRuns = (token) => tool({
1339
+ description: "List workflow runs for a repository, optionally filtered by workflow, branch, status, or event",
1340
+ inputSchema: z.object({
1341
+ owner: z.string().describe("Repository owner"),
1342
+ repo: z.string().describe("Repository name"),
1343
+ workflowId: z.union([z.string(), z.number()]).optional().describe("Workflow ID or filename (e.g. \"ci.yml\") to filter by"),
1344
+ branch: z.string().optional().describe("Branch name to filter by"),
1345
+ event: z.string().optional().describe("Event type to filter by (e.g. \"push\", \"pull_request\")"),
1346
+ status: z.enum([
1347
+ "completed",
1348
+ "action_required",
1349
+ "cancelled",
1350
+ "failure",
1351
+ "neutral",
1352
+ "skipped",
1353
+ "stale",
1354
+ "success",
1355
+ "timed_out",
1356
+ "in_progress",
1357
+ "queued",
1358
+ "requested",
1359
+ "waiting",
1360
+ "pending"
1361
+ ]).optional().describe("Status to filter by"),
1362
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)"),
1363
+ page: z.number().optional().default(1).describe("Page number for pagination")
1364
+ }),
1365
+ execute: async (args) => listWorkflowRunsStep({
1366
+ token,
1367
+ ...args
1368
+ })
1369
+ });
1370
+ async function getWorkflowRunStep({ token, owner, repo, runId }) {
1371
+ "use step";
1372
+ const { data } = await createOctokit(token).rest.actions.getWorkflowRun({
1373
+ owner,
1374
+ repo,
1375
+ run_id: runId
1376
+ });
1377
+ return {
1378
+ id: data.id,
1379
+ name: data.name,
1380
+ status: data.status,
1381
+ conclusion: data.conclusion,
1382
+ branch: data.head_branch,
1383
+ sha: data.head_sha,
1384
+ event: data.event,
1385
+ url: data.html_url,
1386
+ actor: data.actor?.login,
1387
+ runNumber: data.run_number,
1388
+ runAttempt: data.run_attempt,
1389
+ createdAt: data.created_at,
1390
+ updatedAt: data.updated_at,
1391
+ runStartedAt: data.run_started_at
1392
+ };
1393
+ }
1394
+ const getWorkflowRun = (token) => tool({
1395
+ description: "Get details of a specific workflow run including status, timing, and trigger info",
1396
+ inputSchema: z.object({
1397
+ owner: z.string().describe("Repository owner"),
1398
+ repo: z.string().describe("Repository name"),
1399
+ runId: z.number().describe("Workflow run ID")
1400
+ }),
1401
+ execute: async (args) => getWorkflowRunStep({
1402
+ token,
1403
+ ...args
1404
+ })
1405
+ });
1406
+ async function listWorkflowJobsStep({ token, owner, repo, runId, filter, perPage, page }) {
1407
+ "use step";
1408
+ const { data } = await createOctokit(token).rest.actions.listJobsForWorkflowRun({
1409
+ owner,
1410
+ repo,
1411
+ run_id: runId,
1412
+ filter,
1413
+ per_page: perPage,
1414
+ page
1415
+ });
1416
+ return {
1417
+ totalCount: data.total_count,
1418
+ jobs: data.jobs.map((job) => ({
1419
+ id: job.id,
1420
+ name: job.name,
1421
+ status: job.status,
1422
+ conclusion: job.conclusion,
1423
+ url: job.html_url,
1424
+ startedAt: job.started_at,
1425
+ completedAt: job.completed_at,
1426
+ runnerName: job.runner_name,
1427
+ steps: job.steps?.map((step) => ({
1428
+ name: step.name,
1429
+ status: step.status,
1430
+ conclusion: step.conclusion,
1431
+ number: step.number,
1432
+ startedAt: step.started_at,
1433
+ completedAt: step.completed_at
1434
+ }))
1435
+ }))
1436
+ };
1437
+ }
1438
+ const listWorkflowJobs = (token) => tool({
1439
+ description: "List jobs for a workflow run, including step-level status and timing",
1440
+ inputSchema: z.object({
1441
+ owner: z.string().describe("Repository owner"),
1442
+ repo: z.string().describe("Repository name"),
1443
+ runId: z.number().describe("Workflow run ID"),
1444
+ filter: z.enum(["latest", "all"]).optional().default("latest").describe("Filter by the latest attempt or all attempts"),
1445
+ perPage: z.number().optional().default(30).describe("Number of results to return (max 100)"),
1446
+ page: z.number().optional().default(1).describe("Page number for pagination")
1447
+ }),
1448
+ execute: async (args) => listWorkflowJobsStep({
1449
+ token,
1450
+ ...args
1451
+ })
1452
+ });
1453
+ async function triggerWorkflowStep({ token, owner, repo, workflowId, ref, inputs }) {
1454
+ "use step";
1455
+ await createOctokit(token).rest.actions.createWorkflowDispatch({
1456
+ owner,
1457
+ repo,
1458
+ workflow_id: workflowId,
1459
+ ref,
1460
+ inputs
1461
+ });
1462
+ return {
1463
+ triggered: true,
1464
+ workflowId,
1465
+ ref
1466
+ };
1467
+ }
1468
+ const triggerWorkflow = (token, { needsApproval = true } = {}) => tool({
1469
+ description: "Trigger a workflow via workflow_dispatch event",
1470
+ needsApproval,
1471
+ inputSchema: z.object({
1472
+ owner: z.string().describe("Repository owner"),
1473
+ repo: z.string().describe("Repository name"),
1474
+ workflowId: z.union([z.string(), z.number()]).describe("Workflow ID or filename (e.g. \"deploy.yml\")"),
1475
+ ref: z.string().describe("Git ref (branch or tag) to run the workflow on"),
1476
+ inputs: z.record(z.string(), z.string()).optional().describe("Input parameters defined in the workflow_dispatch trigger")
1477
+ }),
1478
+ execute: async (args) => triggerWorkflowStep({
1479
+ token,
1480
+ ...args
1481
+ })
1482
+ });
1483
+ async function cancelWorkflowRunStep({ token, owner, repo, runId }) {
1484
+ "use step";
1485
+ await createOctokit(token).rest.actions.cancelWorkflowRun({
1486
+ owner,
1487
+ repo,
1488
+ run_id: runId
1489
+ });
1490
+ return {
1491
+ cancelled: true,
1492
+ runId
1493
+ };
1494
+ }
1495
+ const cancelWorkflowRun = (token, { needsApproval = true } = {}) => tool({
1496
+ description: "Cancel an in-progress workflow run",
1497
+ needsApproval,
1498
+ inputSchema: z.object({
1499
+ owner: z.string().describe("Repository owner"),
1500
+ repo: z.string().describe("Repository name"),
1501
+ runId: z.number().describe("Workflow run ID to cancel")
1502
+ }),
1503
+ execute: async (args) => cancelWorkflowRunStep({
1504
+ token,
1505
+ ...args
1506
+ })
1507
+ });
1508
+ async function rerunWorkflowRunStep({ token, owner, repo, runId, onlyFailedJobs }) {
1509
+ "use step";
1510
+ const octokit = createOctokit(token);
1511
+ if (onlyFailedJobs) await octokit.rest.actions.reRunWorkflowFailedJobs({
1512
+ owner,
1513
+ repo,
1514
+ run_id: runId
1515
+ });
1516
+ else await octokit.rest.actions.reRunWorkflow({
1517
+ owner,
1518
+ repo,
1519
+ run_id: runId
1520
+ });
1521
+ return {
1522
+ rerun: true,
1523
+ runId,
1524
+ onlyFailedJobs
1525
+ };
1526
+ }
1527
+ const rerunWorkflowRun = (token, { needsApproval = true } = {}) => tool({
1528
+ description: "Re-run a workflow run, optionally only the failed jobs",
1529
+ needsApproval,
1530
+ inputSchema: z.object({
1531
+ owner: z.string().describe("Repository owner"),
1532
+ repo: z.string().describe("Repository name"),
1533
+ runId: z.number().describe("Workflow run ID to re-run"),
1534
+ onlyFailedJobs: z.boolean().optional().default(false).describe("Only re-run failed jobs instead of the entire workflow")
1535
+ }),
1536
+ execute: async (args) => rerunWorkflowRunStep({
1537
+ token,
1538
+ ...args
1539
+ })
1540
+ });
1541
+
1542
+ //#endregion
1543
+ //#region src/agents.ts
1544
+ const SHARED_RULES = `When a tool execution is denied by the user, do not retry it. Briefly acknowledge the decision and move on.`;
1545
+ const DEFAULT_INSTRUCTIONS = `You are a helpful GitHub assistant. You can read and explore repositories, issues, pull requests, commits, code, gists, and workflows. You can also create issues, pull requests, comments, gists, trigger workflows, and update files when asked.
1546
+
1547
+ ${SHARED_RULES}`;
1548
+ const PRESET_INSTRUCTIONS = {
1549
+ "code-review": `You are a code review assistant. Your job is to review pull requests thoroughly and provide constructive feedback.
1550
+
1551
+ When reviewing a PR:
1552
+ - Read the PR description and changed files carefully
1553
+ - To trace why a specific line exists or who last touched it, use getBlame on the file path and ref (branch or merge commit), then follow up with getCommit if you need the full patch
1554
+ - Check for bugs, logic errors, and edge cases
1555
+ - Suggest improvements when you spot issues
1556
+ - Be constructive — explain why something is a problem and how to fix it
1557
+ - Use listPullRequestFiles to see exactly which files changed before diving into details
1558
+ - Use createPullRequestReview to submit a formal review with inline comments on specific lines
1559
+ - Post your review as PR comments when asked
1560
+
1561
+ ${SHARED_RULES}`,
1562
+ "issue-triage": `You are an issue triage assistant. Your job is to help manage and organize GitHub issues.
1563
+
1564
+ When triaging issues:
1565
+ - Read issue descriptions carefully to understand the problem
1566
+ - Identify duplicates when possible
1567
+ - Help categorize and prioritize issues
1568
+ - Respond to users with clear, helpful information
1569
+ - Use listLabels to see available labels, then addLabels and removeLabel to categorize issues
1570
+ - Create new issues when asked, with clear titles and descriptions
1571
+
1572
+ ${SHARED_RULES}`,
1573
+ "ci-ops": `You are a CI/CD operations assistant. Your job is to help monitor and manage GitHub Actions workflows.
1574
+
1575
+ When working with workflows:
1576
+ - Check workflow run status and report failures clearly
1577
+ - Inspect job steps to identify exactly where a run failed
1578
+ - Re-run failed workflows when asked
1579
+ - Trigger workflow dispatches with the correct inputs and branch
1580
+ - Be careful with cancel and re-run operations — confirm the target run
1581
+ - Summarize run history and trends when asked
1582
+
1583
+ ${SHARED_RULES}`,
1584
+ "repo-explorer": `You are a repository explorer. Your job is to help users understand codebases and find information across GitHub repositories.
1585
+
1586
+ When exploring repos:
1587
+ - Answer questions about code structure and organization
1588
+ - Use getBlame when the user asks about history or ownership of specific lines in a file
1589
+ - Summarize recent activity (commits, PRs, issues)
1590
+ - Find specific files, functions, or patterns in code
1591
+ - Explain how different parts of the codebase work together
1592
+ - You have read-only access — you cannot make changes
1593
+
1594
+ ${SHARED_RULES}`,
1595
+ "maintainer": `You are a repository maintainer assistant. You have full access to manage repositories, issues, pull requests, gists, and workflows.
1596
+
1597
+ When maintaining repos:
1598
+ - Be careful with write operations — review before acting
1599
+ - Create well-structured issues and PRs with clear descriptions
1600
+ - Use merge strategies appropriate for the repository
1601
+ - Keep commit messages clean and descriptive
1602
+ - When closing issues, provide a clear reason
1603
+
1604
+ ${SHARED_RULES}`
1605
+ };
1606
+ function resolveInstructions(options) {
1607
+ const defaultPrompt = options.preset && !Array.isArray(options.preset) ? PRESET_INSTRUCTIONS[options.preset] : DEFAULT_INSTRUCTIONS;
1608
+ if (options.instructions) return options.instructions;
1609
+ if (options.additionalInstructions) return `${defaultPrompt}\n\n${options.additionalInstructions}`;
1610
+ return defaultPrompt;
1611
+ }
1612
+ /**
1613
+ * Create a pre-configured GitHub agent powered by the AI SDK's `ToolLoopAgent`.
1614
+ *
1615
+ * Returns a `ToolLoopAgent` instance with `.generate()` and `.stream()` methods.
1616
+ *
1617
+ * @example
1618
+ * ```ts
1619
+ * import { createGithubAgent } from '@github-tools/sdk'
1620
+ *
1621
+ * const agent = createGithubAgent({
1622
+ * model: 'anthropic/claude-sonnet-4.6',
1623
+ * token: process.env.GITHUB_TOKEN!,
1624
+ * preset: 'code-review',
1625
+ * })
1626
+ *
1627
+ * const result = await agent.generate({ prompt: 'Review PR #42 on vercel/ai' })
1628
+ * ```
1629
+ */
1630
+ function createGithubAgent({ token, preset, requireApproval, instructions, additionalInstructions, ...agentOptions }) {
1631
+ const tools = createGithubTools({
1632
+ token,
1633
+ requireApproval,
1634
+ preset
1635
+ });
1636
+ return new ToolLoopAgent({
1637
+ ...agentOptions,
1638
+ tools,
1639
+ instructions: resolveInstructions({
1640
+ preset,
1641
+ instructions,
1642
+ additionalInstructions
1643
+ })
1644
+ });
1645
+ }
1646
+
1647
+ //#endregion
1648
+ export { addPullRequestComment as A, createRepository as B, addLabels as C, listIssues as D, getIssue as E, listPullRequestReviews as F, createOctokit as G, getFileContent as H, listPullRequests as I, mergePullRequest as L, createPullRequestReview as M, getPullRequest as N, listLabels as O, listPullRequestFiles as P, createBranch as R, addIssueComment as S, createIssue as T, getRepository as U, forkRepository as V, listBranches as W, getBlame as _, listWorkflowJobs as a, searchCode as b, rerunWorkflowRun as c, createGistComment as d, deleteGist as f, updateGist as g, listGists as h, getWorkflowRun as i, createPullRequest as j, removeLabel as k, triggerWorkflow as l, listGistComments as m, resolveInstructions as n, listWorkflowRuns as o, getGist as p, cancelWorkflowRun as r, listWorkflows as s, createGithubAgent as t, createGist as u, getCommit as v, closeIssue as w, searchRepositories as x, listCommits as y, createOrUpdateFile as z };
1649
+ //# sourceMappingURL=agents-D9aKsd_7.mjs.map