@slates-integrations/github 0.2.0-rc.2
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.
- package/README.md +109 -0
- package/docs/SPEC.md +215 -0
- package/logo.png +0 -0
- package/package.json +18 -0
- package/slate.json +22 -0
- package/src/auth.ts +257 -0
- package/src/config.ts +4 -0
- package/src/index.ts +78 -0
- package/src/lib/client.ts +974 -0
- package/src/spec.ts +13 -0
- package/src/tools/comment-on-issue.ts +46 -0
- package/src/tools/create-commit-status.ts +97 -0
- package/src/tools/create-release.ts +69 -0
- package/src/tools/create-repository.ts +70 -0
- package/src/tools/get-issue.ts +67 -0
- package/src/tools/get-repository.ts +75 -0
- package/src/tools/get-user.ts +64 -0
- package/src/tools/index.ts +24 -0
- package/src/tools/list-branches.ts +54 -0
- package/src/tools/list-commits.ts +70 -0
- package/src/tools/list-issues.ts +90 -0
- package/src/tools/list-pull-requests.ts +82 -0
- package/src/tools/list-repositories.ts +102 -0
- package/src/tools/manage-collaborators.ts +88 -0
- package/src/tools/manage-file-content.ts +117 -0
- package/src/tools/manage-gist.ts +183 -0
- package/src/tools/manage-issue.ts +94 -0
- package/src/tools/manage-labels.ts +93 -0
- package/src/tools/manage-pull-request.ts +111 -0
- package/src/tools/manage-workflow.ts +242 -0
- package/src/tools/merge-pull-request.ts +61 -0
- package/src/tools/review-pull-request.ts +90 -0
- package/src/tools/search.ts +72 -0
- package/src/tools/star-repository.ts +57 -0
- package/src/tools/update-repository.ts +56 -0
- package/src/triggers/index.ts +8 -0
- package/src/triggers/issue-comment.ts +94 -0
- package/src/triggers/issues.ts +111 -0
- package/src/triggers/pull-request-review.ts +95 -0
- package/src/triggers/pull-request.ts +123 -0
- package/src/triggers/push.ts +117 -0
- package/src/triggers/release.ts +103 -0
- package/src/triggers/star.ts +71 -0
- package/src/triggers/workflow-run.ts +112 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { GitHubClient } from '../lib/client';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
export let manageIssue = SlateTool.create(spec, {
|
|
7
|
+
name: 'Manage Issue',
|
|
8
|
+
key: 'manage_issue',
|
|
9
|
+
description: `Create a new issue or update an existing one in a GitHub repository.
|
|
10
|
+
When creating: provide title and optionally body, labels, assignees, and milestone.
|
|
11
|
+
When updating: provide the issue number along with fields to change (title, body, state, labels, assignees).`,
|
|
12
|
+
instructions: [
|
|
13
|
+
'To create a new issue, omit issueNumber and provide at least a title.',
|
|
14
|
+
'To update an existing issue, provide the issueNumber along with the fields to change.',
|
|
15
|
+
'To close an issue, set state to "closed". To reopen, set state to "open".'
|
|
16
|
+
]
|
|
17
|
+
})
|
|
18
|
+
.input(
|
|
19
|
+
z.object({
|
|
20
|
+
owner: z.string().describe('Repository owner (user or organization)'),
|
|
21
|
+
repo: z.string().describe('Repository name'),
|
|
22
|
+
issueNumber: z
|
|
23
|
+
.number()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Issue number to update. Omit to create a new issue.'),
|
|
26
|
+
title: z.string().optional().describe('Issue title (required when creating)'),
|
|
27
|
+
body: z.string().optional().describe('Issue body in Markdown'),
|
|
28
|
+
state: z.enum(['open', 'closed']).optional().describe('Issue state (only for updates)'),
|
|
29
|
+
stateReason: z
|
|
30
|
+
.enum(['completed', 'not_planned', 'reopened'])
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Reason for state change'),
|
|
33
|
+
labels: z.array(z.string()).optional().describe('Labels to set on the issue'),
|
|
34
|
+
assignees: z.array(z.string()).optional().describe('Usernames to assign'),
|
|
35
|
+
milestone: z
|
|
36
|
+
.number()
|
|
37
|
+
.nullable()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Milestone number to set, or null to remove')
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
.output(
|
|
43
|
+
z.object({
|
|
44
|
+
issueNumber: z.number().describe('Issue number'),
|
|
45
|
+
issueId: z.number().describe('Unique issue ID'),
|
|
46
|
+
title: z.string().describe('Issue title'),
|
|
47
|
+
state: z.string().describe('Issue state (open/closed)'),
|
|
48
|
+
htmlUrl: z.string().describe('URL to the issue on GitHub'),
|
|
49
|
+
author: z.string().describe('Issue author login'),
|
|
50
|
+
assignees: z.array(z.string()).describe('Assigned usernames'),
|
|
51
|
+
labels: z.array(z.string()).describe('Label names'),
|
|
52
|
+
createdAt: z.string().describe('Creation timestamp'),
|
|
53
|
+
updatedAt: z.string().describe('Last update timestamp')
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
.handleInvocation(async ctx => {
|
|
57
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
58
|
+
let { owner, repo, issueNumber, ...data } = ctx.input;
|
|
59
|
+
let issue: any;
|
|
60
|
+
|
|
61
|
+
if (issueNumber) {
|
|
62
|
+
issue = await client.updateIssue(owner, repo, issueNumber, data);
|
|
63
|
+
} else {
|
|
64
|
+
if (!data.title) {
|
|
65
|
+
throw new Error('Title is required when creating a new issue.');
|
|
66
|
+
}
|
|
67
|
+
issue = await client.createIssue(owner, repo, {
|
|
68
|
+
title: data.title,
|
|
69
|
+
body: data.body,
|
|
70
|
+
assignees: data.assignees,
|
|
71
|
+
labels: data.labels,
|
|
72
|
+
milestone: data.milestone ?? undefined
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
output: {
|
|
78
|
+
issueNumber: issue.number,
|
|
79
|
+
issueId: issue.id,
|
|
80
|
+
title: issue.title,
|
|
81
|
+
state: issue.state,
|
|
82
|
+
htmlUrl: issue.html_url,
|
|
83
|
+
author: issue.user.login,
|
|
84
|
+
assignees: (issue.assignees ?? []).map((a: any) => a.login),
|
|
85
|
+
labels: (issue.labels ?? []).map((l: any) => l.name),
|
|
86
|
+
createdAt: issue.created_at,
|
|
87
|
+
updatedAt: issue.updated_at
|
|
88
|
+
},
|
|
89
|
+
message: issueNumber
|
|
90
|
+
? `Updated issue **#${issue.number}** in **${owner}/${repo}** — ${issue.html_url}`
|
|
91
|
+
: `Created issue **#${issue.number}**: "${issue.title}" in **${owner}/${repo}** — ${issue.html_url}`
|
|
92
|
+
};
|
|
93
|
+
})
|
|
94
|
+
.build();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { GitHubClient } from '../lib/client';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
export let manageLabels = SlateTool.create(spec, {
|
|
7
|
+
name: 'Manage Labels',
|
|
8
|
+
key: 'manage_labels',
|
|
9
|
+
description: `List existing labels or create a new label in a GitHub repository. Labels can be applied to issues and pull requests for categorization.`
|
|
10
|
+
})
|
|
11
|
+
.input(
|
|
12
|
+
z.object({
|
|
13
|
+
owner: z.string().describe('Repository owner'),
|
|
14
|
+
repo: z.string().describe('Repository name'),
|
|
15
|
+
action: z.enum(['list', 'create']).describe('Action to perform'),
|
|
16
|
+
name: z.string().optional().describe('Label name (required for create)'),
|
|
17
|
+
color: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Label color hex code without "#" (e.g., "ff0000")'),
|
|
21
|
+
description: z.string().optional().describe('Label description'),
|
|
22
|
+
perPage: z.number().optional().describe('Results per page for list'),
|
|
23
|
+
page: z.number().optional().describe('Page number for list')
|
|
24
|
+
})
|
|
25
|
+
)
|
|
26
|
+
.output(
|
|
27
|
+
z.object({
|
|
28
|
+
labels: z
|
|
29
|
+
.array(
|
|
30
|
+
z.object({
|
|
31
|
+
labelId: z.number().describe('Label ID'),
|
|
32
|
+
name: z.string().describe('Label name'),
|
|
33
|
+
color: z.string().describe('Label color hex'),
|
|
34
|
+
description: z.string().nullable().describe('Label description')
|
|
35
|
+
})
|
|
36
|
+
)
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('List of labels (for list action)'),
|
|
39
|
+
label: z
|
|
40
|
+
.object({
|
|
41
|
+
labelId: z.number().describe('Label ID'),
|
|
42
|
+
name: z.string().describe('Label name'),
|
|
43
|
+
color: z.string().describe('Label color hex'),
|
|
44
|
+
description: z.string().nullable().describe('Label description')
|
|
45
|
+
})
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Created label (for create action)')
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
.handleInvocation(async ctx => {
|
|
51
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
52
|
+
let { owner, repo, action } = ctx.input;
|
|
53
|
+
|
|
54
|
+
if (action === 'list') {
|
|
55
|
+
let labels = await client.listLabels(owner, repo, {
|
|
56
|
+
perPage: ctx.input.perPage,
|
|
57
|
+
page: ctx.input.page
|
|
58
|
+
});
|
|
59
|
+
let mapped = labels.map((l: any) => ({
|
|
60
|
+
labelId: l.id,
|
|
61
|
+
name: l.name,
|
|
62
|
+
color: l.color,
|
|
63
|
+
description: l.description
|
|
64
|
+
}));
|
|
65
|
+
return {
|
|
66
|
+
output: { labels: mapped },
|
|
67
|
+
message: `Found **${mapped.length}** labels in **${owner}/${repo}**.`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!ctx.input.name) {
|
|
72
|
+
throw new Error('name is required for create action.');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let label = await client.createLabel(owner, repo, {
|
|
76
|
+
name: ctx.input.name,
|
|
77
|
+
color: ctx.input.color,
|
|
78
|
+
description: ctx.input.description
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
output: {
|
|
83
|
+
label: {
|
|
84
|
+
labelId: label.id,
|
|
85
|
+
name: label.name,
|
|
86
|
+
color: label.color,
|
|
87
|
+
description: label.description
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
message: `Created label **${label.name}** in **${owner}/${repo}**.`
|
|
91
|
+
};
|
|
92
|
+
})
|
|
93
|
+
.build();
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { GitHubClient } from '../lib/client';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
export let managePullRequest = SlateTool.create(spec, {
|
|
7
|
+
name: 'Manage Pull Request',
|
|
8
|
+
key: 'manage_pull_request',
|
|
9
|
+
description: `Create a new pull request or update an existing one.
|
|
10
|
+
When creating: provide head branch, base branch, and title.
|
|
11
|
+
When updating: provide the pull request number along with fields to change.`,
|
|
12
|
+
instructions: [
|
|
13
|
+
'To create a new PR, omit pullNumber and provide head, base, and title.',
|
|
14
|
+
'To update an existing PR, provide pullNumber along with fields to change.',
|
|
15
|
+
'To close a PR without merging, set state to "closed".'
|
|
16
|
+
]
|
|
17
|
+
})
|
|
18
|
+
.input(
|
|
19
|
+
z.object({
|
|
20
|
+
owner: z.string().describe('Repository owner (user or organization)'),
|
|
21
|
+
repo: z.string().describe('Repository name'),
|
|
22
|
+
pullNumber: z
|
|
23
|
+
.number()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Pull request number to update. Omit to create a new PR.'),
|
|
26
|
+
title: z.string().optional().describe('PR title (required when creating)'),
|
|
27
|
+
body: z.string().optional().describe('PR description in Markdown'),
|
|
28
|
+
head: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe(
|
|
32
|
+
'Head branch (required when creating). Use "fork_owner:branch" for cross-repo PRs.'
|
|
33
|
+
),
|
|
34
|
+
base: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Base branch to merge into (required when creating)'),
|
|
38
|
+
draft: z.boolean().optional().describe('Create as draft PR'),
|
|
39
|
+
state: z.enum(['open', 'closed']).optional().describe('PR state (only for updates)'),
|
|
40
|
+
maintainerCanModify: z
|
|
41
|
+
.boolean()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Allow maintainers to push to the head branch')
|
|
44
|
+
})
|
|
45
|
+
)
|
|
46
|
+
.output(
|
|
47
|
+
z.object({
|
|
48
|
+
pullNumber: z.number().describe('Pull request number'),
|
|
49
|
+
pullRequestId: z.number().describe('Unique PR ID'),
|
|
50
|
+
title: z.string().describe('PR title'),
|
|
51
|
+
state: z.string().describe('PR state'),
|
|
52
|
+
htmlUrl: z.string().describe('URL to the PR on GitHub'),
|
|
53
|
+
author: z.string().describe('PR author login'),
|
|
54
|
+
head: z.string().describe('Head branch ref'),
|
|
55
|
+
base: z.string().describe('Base branch ref'),
|
|
56
|
+
draft: z.boolean().describe('Whether the PR is a draft'),
|
|
57
|
+
merged: z.boolean().describe('Whether the PR has been merged'),
|
|
58
|
+
mergeable: z.boolean().nullable().describe('Whether the PR is mergeable'),
|
|
59
|
+
createdAt: z.string().describe('Creation timestamp'),
|
|
60
|
+
updatedAt: z.string().describe('Last update timestamp')
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
.handleInvocation(async ctx => {
|
|
64
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
65
|
+
let { owner, repo, pullNumber, ...data } = ctx.input;
|
|
66
|
+
let pr: any;
|
|
67
|
+
|
|
68
|
+
if (pullNumber) {
|
|
69
|
+
pr = await client.updatePullRequest(owner, repo, pullNumber, {
|
|
70
|
+
title: data.title,
|
|
71
|
+
body: data.body,
|
|
72
|
+
state: data.state,
|
|
73
|
+
base: data.base,
|
|
74
|
+
maintainerCanModify: data.maintainerCanModify
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
if (!data.title || !data.head || !data.base) {
|
|
78
|
+
throw new Error('title, head, and base are required when creating a pull request.');
|
|
79
|
+
}
|
|
80
|
+
pr = await client.createPullRequest(owner, repo, {
|
|
81
|
+
title: data.title,
|
|
82
|
+
head: data.head,
|
|
83
|
+
base: data.base,
|
|
84
|
+
body: data.body,
|
|
85
|
+
draft: data.draft,
|
|
86
|
+
maintainerCanModify: data.maintainerCanModify
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
output: {
|
|
92
|
+
pullNumber: pr.number,
|
|
93
|
+
pullRequestId: pr.id,
|
|
94
|
+
title: pr.title,
|
|
95
|
+
state: pr.state,
|
|
96
|
+
htmlUrl: pr.html_url,
|
|
97
|
+
author: pr.user.login,
|
|
98
|
+
head: pr.head.ref,
|
|
99
|
+
base: pr.base.ref,
|
|
100
|
+
draft: pr.draft,
|
|
101
|
+
merged: pr.merged,
|
|
102
|
+
mergeable: pr.mergeable ?? null,
|
|
103
|
+
createdAt: pr.created_at,
|
|
104
|
+
updatedAt: pr.updated_at
|
|
105
|
+
},
|
|
106
|
+
message: pullNumber
|
|
107
|
+
? `Updated PR **#${pr.number}** in **${owner}/${repo}** — ${pr.html_url}`
|
|
108
|
+
: `Created PR **#${pr.number}**: "${pr.title}" (${pr.head.ref} → ${pr.base.ref}) — ${pr.html_url}`
|
|
109
|
+
};
|
|
110
|
+
})
|
|
111
|
+
.build();
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { GitHubClient } from '../lib/client';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
export let manageWorkflow = SlateTool.create(spec, {
|
|
7
|
+
name: 'Manage Workflow',
|
|
8
|
+
key: 'manage_workflow',
|
|
9
|
+
description: `Interact with GitHub Actions workflows: list workflows, list runs, trigger a workflow dispatch, cancel or rerun a workflow run, and view run jobs.`,
|
|
10
|
+
instructions: [
|
|
11
|
+
'Use action "list_workflows" to list all workflows in a repository.',
|
|
12
|
+
'Use "list_runs" to see recent workflow runs, optionally filtered by workflow, branch, event, or status.',
|
|
13
|
+
'Use "trigger" to dispatch a workflow_dispatch event. The workflow must support the workflow_dispatch trigger.',
|
|
14
|
+
'Use "cancel" or "rerun" with a runId.',
|
|
15
|
+
'Use "get_run" to get details of a specific run.',
|
|
16
|
+
'Use "list_jobs" to see jobs of a specific run.'
|
|
17
|
+
]
|
|
18
|
+
})
|
|
19
|
+
.input(
|
|
20
|
+
z.object({
|
|
21
|
+
owner: z.string().describe('Repository owner'),
|
|
22
|
+
repo: z.string().describe('Repository name'),
|
|
23
|
+
action: z
|
|
24
|
+
.enum([
|
|
25
|
+
'list_workflows',
|
|
26
|
+
'list_runs',
|
|
27
|
+
'get_run',
|
|
28
|
+
'trigger',
|
|
29
|
+
'cancel',
|
|
30
|
+
'rerun',
|
|
31
|
+
'list_jobs'
|
|
32
|
+
])
|
|
33
|
+
.describe('Workflow action to perform'),
|
|
34
|
+
workflowId: z
|
|
35
|
+
.union([z.number(), z.string()])
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Workflow ID or filename (e.g., "ci.yml")'),
|
|
38
|
+
runId: z
|
|
39
|
+
.number()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Workflow run ID (for get_run, cancel, rerun, list_jobs)'),
|
|
42
|
+
ref: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Git ref to trigger the workflow on (for trigger action)'),
|
|
46
|
+
inputs: z
|
|
47
|
+
.record(z.string(), z.string())
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Workflow inputs (for trigger action)'),
|
|
50
|
+
branch: z.string().optional().describe('Filter runs by branch'),
|
|
51
|
+
event: z.string().optional().describe('Filter runs by event type'),
|
|
52
|
+
status: z
|
|
53
|
+
.string()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe('Filter runs by status (e.g., "completed", "in_progress", "queued")'),
|
|
56
|
+
perPage: z.number().optional().describe('Results per page'),
|
|
57
|
+
page: z.number().optional().describe('Page number')
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
.output(
|
|
61
|
+
z.object({
|
|
62
|
+
workflows: z
|
|
63
|
+
.array(
|
|
64
|
+
z.object({
|
|
65
|
+
workflowId: z.number(),
|
|
66
|
+
name: z.string(),
|
|
67
|
+
path: z.string(),
|
|
68
|
+
state: z.string()
|
|
69
|
+
})
|
|
70
|
+
)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('List of workflows'),
|
|
73
|
+
runs: z
|
|
74
|
+
.array(
|
|
75
|
+
z.object({
|
|
76
|
+
runId: z.number(),
|
|
77
|
+
name: z.string().nullable(),
|
|
78
|
+
status: z.string().nullable(),
|
|
79
|
+
conclusion: z.string().nullable(),
|
|
80
|
+
headBranch: z.string().nullable(),
|
|
81
|
+
event: z.string(),
|
|
82
|
+
htmlUrl: z.string(),
|
|
83
|
+
createdAt: z.string()
|
|
84
|
+
})
|
|
85
|
+
)
|
|
86
|
+
.optional()
|
|
87
|
+
.describe('List of workflow runs'),
|
|
88
|
+
run: z
|
|
89
|
+
.object({
|
|
90
|
+
runId: z.number(),
|
|
91
|
+
name: z.string().nullable(),
|
|
92
|
+
status: z.string().nullable(),
|
|
93
|
+
conclusion: z.string().nullable(),
|
|
94
|
+
headBranch: z.string().nullable(),
|
|
95
|
+
headSha: z.string(),
|
|
96
|
+
event: z.string(),
|
|
97
|
+
htmlUrl: z.string(),
|
|
98
|
+
createdAt: z.string(),
|
|
99
|
+
updatedAt: z.string()
|
|
100
|
+
})
|
|
101
|
+
.optional()
|
|
102
|
+
.describe('Single workflow run details'),
|
|
103
|
+
jobs: z
|
|
104
|
+
.array(
|
|
105
|
+
z.object({
|
|
106
|
+
jobId: z.number(),
|
|
107
|
+
name: z.string(),
|
|
108
|
+
status: z.string(),
|
|
109
|
+
conclusion: z.string().nullable(),
|
|
110
|
+
startedAt: z.string().nullable(),
|
|
111
|
+
completedAt: z.string().nullable()
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
.optional()
|
|
115
|
+
.describe('List of jobs in a run'),
|
|
116
|
+
triggered: z.boolean().optional().describe('Whether the workflow was triggered'),
|
|
117
|
+
cancelled: z.boolean().optional().describe('Whether the run was cancelled'),
|
|
118
|
+
rerunStarted: z.boolean().optional().describe('Whether the rerun was started')
|
|
119
|
+
})
|
|
120
|
+
)
|
|
121
|
+
.handleInvocation(async ctx => {
|
|
122
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
123
|
+
let { owner, repo, action } = ctx.input;
|
|
124
|
+
|
|
125
|
+
if (action === 'list_workflows') {
|
|
126
|
+
let data = await client.listWorkflows(owner, repo, {
|
|
127
|
+
perPage: ctx.input.perPage,
|
|
128
|
+
page: ctx.input.page
|
|
129
|
+
});
|
|
130
|
+
let workflows = data.workflows.map((w: any) => ({
|
|
131
|
+
workflowId: w.id,
|
|
132
|
+
name: w.name,
|
|
133
|
+
path: w.path,
|
|
134
|
+
state: w.state
|
|
135
|
+
}));
|
|
136
|
+
return {
|
|
137
|
+
output: { workflows },
|
|
138
|
+
message: `Found **${workflows.length}** workflows in **${owner}/${repo}**.`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (action === 'list_runs') {
|
|
143
|
+
let data = await client.listWorkflowRuns(owner, repo, {
|
|
144
|
+
workflowId: ctx.input.workflowId,
|
|
145
|
+
branch: ctx.input.branch,
|
|
146
|
+
event: ctx.input.event,
|
|
147
|
+
status: ctx.input.status,
|
|
148
|
+
perPage: ctx.input.perPage,
|
|
149
|
+
page: ctx.input.page
|
|
150
|
+
});
|
|
151
|
+
let runs = data.workflow_runs.map((r: any) => ({
|
|
152
|
+
runId: r.id,
|
|
153
|
+
name: r.name,
|
|
154
|
+
status: r.status,
|
|
155
|
+
conclusion: r.conclusion,
|
|
156
|
+
headBranch: r.head_branch,
|
|
157
|
+
event: r.event,
|
|
158
|
+
htmlUrl: r.html_url,
|
|
159
|
+
createdAt: r.created_at
|
|
160
|
+
}));
|
|
161
|
+
return {
|
|
162
|
+
output: { runs },
|
|
163
|
+
message: `Found **${runs.length}** workflow runs in **${owner}/${repo}**.`
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (action === 'get_run') {
|
|
168
|
+
if (!ctx.input.runId) throw new Error('runId is required for get_run.');
|
|
169
|
+
let r = await client.getWorkflowRun(owner, repo, ctx.input.runId);
|
|
170
|
+
return {
|
|
171
|
+
output: {
|
|
172
|
+
run: {
|
|
173
|
+
runId: r.id,
|
|
174
|
+
name: r.name,
|
|
175
|
+
status: r.status,
|
|
176
|
+
conclusion: r.conclusion,
|
|
177
|
+
headBranch: r.head_branch,
|
|
178
|
+
headSha: r.head_sha,
|
|
179
|
+
event: r.event,
|
|
180
|
+
htmlUrl: r.html_url,
|
|
181
|
+
createdAt: r.created_at,
|
|
182
|
+
updatedAt: r.updated_at
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
message: `Workflow run **#${r.id}**: ${r.status}${r.conclusion ? ` (${r.conclusion})` : ''} — ${r.html_url}`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (action === 'trigger') {
|
|
190
|
+
if (!ctx.input.workflowId) throw new Error('workflowId is required for trigger.');
|
|
191
|
+
if (!ctx.input.ref) throw new Error('ref is required for trigger.');
|
|
192
|
+
await client.triggerWorkflowDispatch(owner, repo, ctx.input.workflowId, {
|
|
193
|
+
ref: ctx.input.ref,
|
|
194
|
+
inputs: ctx.input.inputs as Record<string, string> | undefined
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
output: { triggered: true },
|
|
198
|
+
message: `Triggered workflow **${ctx.input.workflowId}** on ref \`${ctx.input.ref}\` in **${owner}/${repo}**.`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (action === 'cancel') {
|
|
203
|
+
if (!ctx.input.runId) throw new Error('runId is required for cancel.');
|
|
204
|
+
await client.cancelWorkflowRun(owner, repo, ctx.input.runId);
|
|
205
|
+
return {
|
|
206
|
+
output: { cancelled: true },
|
|
207
|
+
message: `Cancelled workflow run **#${ctx.input.runId}** in **${owner}/${repo}**.`
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (action === 'rerun') {
|
|
212
|
+
if (!ctx.input.runId) throw new Error('runId is required for rerun.');
|
|
213
|
+
await client.rerunWorkflow(owner, repo, ctx.input.runId);
|
|
214
|
+
return {
|
|
215
|
+
output: { rerunStarted: true },
|
|
216
|
+
message: `Re-running workflow run **#${ctx.input.runId}** in **${owner}/${repo}**.`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (action === 'list_jobs') {
|
|
221
|
+
if (!ctx.input.runId) throw new Error('runId is required for list_jobs.');
|
|
222
|
+
let data = await client.listWorkflowRunJobs(owner, repo, ctx.input.runId, {
|
|
223
|
+
perPage: ctx.input.perPage,
|
|
224
|
+
page: ctx.input.page
|
|
225
|
+
});
|
|
226
|
+
let jobs = data.jobs.map((j: any) => ({
|
|
227
|
+
jobId: j.id,
|
|
228
|
+
name: j.name,
|
|
229
|
+
status: j.status,
|
|
230
|
+
conclusion: j.conclusion,
|
|
231
|
+
startedAt: j.started_at,
|
|
232
|
+
completedAt: j.completed_at
|
|
233
|
+
}));
|
|
234
|
+
return {
|
|
235
|
+
output: { jobs },
|
|
236
|
+
message: `Found **${jobs.length}** jobs in workflow run **#${ctx.input.runId}**.`
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error(`Unknown action: ${action}`);
|
|
241
|
+
})
|
|
242
|
+
.build();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { GitHubClient } from '../lib/client';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
export let mergePullRequest = SlateTool.create(spec, {
|
|
7
|
+
name: 'Merge Pull Request',
|
|
8
|
+
key: 'merge_pull_request',
|
|
9
|
+
description: `Merge a pull request using the specified merge method (merge commit, squash, or rebase).`,
|
|
10
|
+
tags: {
|
|
11
|
+
destructive: true
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
.input(
|
|
15
|
+
z.object({
|
|
16
|
+
owner: z.string().describe('Repository owner (user or organization)'),
|
|
17
|
+
repo: z.string().describe('Repository name'),
|
|
18
|
+
pullNumber: z.number().describe('Pull request number'),
|
|
19
|
+
commitTitle: z.string().optional().describe('Title for the merge commit'),
|
|
20
|
+
commitMessage: z.string().optional().describe('Extra detail for the merge commit'),
|
|
21
|
+
mergeMethod: z
|
|
22
|
+
.enum(['merge', 'squash', 'rebase'])
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Merge method (default: merge)'),
|
|
25
|
+
sha: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('SHA that head must match to allow merge (safety check)')
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
.output(
|
|
32
|
+
z.object({
|
|
33
|
+
merged: z.boolean().describe('Whether the merge was successful'),
|
|
34
|
+
sha: z.string().describe('SHA of the merge commit'),
|
|
35
|
+
message: z.string().describe('Merge result message')
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
.handleInvocation(async ctx => {
|
|
39
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
40
|
+
let result = await client.mergePullRequest(
|
|
41
|
+
ctx.input.owner,
|
|
42
|
+
ctx.input.repo,
|
|
43
|
+
ctx.input.pullNumber,
|
|
44
|
+
{
|
|
45
|
+
commitTitle: ctx.input.commitTitle,
|
|
46
|
+
commitMessage: ctx.input.commitMessage,
|
|
47
|
+
mergeMethod: ctx.input.mergeMethod,
|
|
48
|
+
sha: ctx.input.sha
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
output: {
|
|
54
|
+
merged: result.merged,
|
|
55
|
+
sha: result.sha,
|
|
56
|
+
message: result.message
|
|
57
|
+
},
|
|
58
|
+
message: `Merged PR **#${ctx.input.pullNumber}** in **${ctx.input.owner}/${ctx.input.repo}** using ${ctx.input.mergeMethod ?? 'merge'} method.`
|
|
59
|
+
};
|
|
60
|
+
})
|
|
61
|
+
.build();
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { SlateTool } from 'slates';
|
|
2
|
+
import { GitHubClient } from '../lib/client';
|
|
3
|
+
import { spec } from '../spec';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
export let reviewPullRequest = SlateTool.create(spec, {
|
|
7
|
+
name: 'Review Pull Request',
|
|
8
|
+
key: 'review_pull_request',
|
|
9
|
+
description: `Submit a review on a pull request with an approve, request changes, or comment action.
|
|
10
|
+
Optionally include inline comments on specific files and lines. Can also request reviewers.`,
|
|
11
|
+
instructions: [
|
|
12
|
+
'Set action to "request_reviewers" to request reviewers instead of submitting a review.',
|
|
13
|
+
'For inline comments, provide the file path and position (line in the diff).'
|
|
14
|
+
]
|
|
15
|
+
})
|
|
16
|
+
.input(
|
|
17
|
+
z.object({
|
|
18
|
+
owner: z.string().describe('Repository owner (user or organization)'),
|
|
19
|
+
repo: z.string().describe('Repository name'),
|
|
20
|
+
pullNumber: z.number().describe('Pull request number'),
|
|
21
|
+
action: z
|
|
22
|
+
.enum(['APPROVE', 'REQUEST_CHANGES', 'COMMENT', 'request_reviewers'])
|
|
23
|
+
.describe('Review action or request reviewers'),
|
|
24
|
+
body: z.string().optional().describe('Review body in Markdown'),
|
|
25
|
+
comments: z
|
|
26
|
+
.array(
|
|
27
|
+
z.object({
|
|
28
|
+
path: z.string().describe('File path relative to repo root'),
|
|
29
|
+
position: z.number().optional().describe('Position in the diff'),
|
|
30
|
+
body: z.string().describe('Comment body')
|
|
31
|
+
})
|
|
32
|
+
)
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Inline review comments'),
|
|
35
|
+
reviewers: z
|
|
36
|
+
.array(z.string())
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Usernames to request review from (only for request_reviewers action)'),
|
|
39
|
+
teamReviewers: z
|
|
40
|
+
.array(z.string())
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Team slugs to request review from (only for request_reviewers action)')
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
.output(
|
|
46
|
+
z.object({
|
|
47
|
+
reviewId: z.number().optional().describe('Review ID (when submitting a review)'),
|
|
48
|
+
state: z.string().optional().describe('Review state'),
|
|
49
|
+
htmlUrl: z.string().optional().describe('URL to the review on GitHub'),
|
|
50
|
+
requestedReviewers: z
|
|
51
|
+
.array(z.string())
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Requested reviewer usernames')
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
.handleInvocation(async ctx => {
|
|
57
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
58
|
+
let { owner, repo, pullNumber, action } = ctx.input;
|
|
59
|
+
|
|
60
|
+
if (action === 'request_reviewers') {
|
|
61
|
+
let result = await client.requestReviewers(owner, repo, pullNumber, {
|
|
62
|
+
reviewers: ctx.input.reviewers,
|
|
63
|
+
teamReviewers: ctx.input.teamReviewers
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
let requested = (result.requested_reviewers ?? []).map((r: any) => r.login);
|
|
67
|
+
return {
|
|
68
|
+
output: {
|
|
69
|
+
requestedReviewers: requested
|
|
70
|
+
},
|
|
71
|
+
message: `Requested reviews from ${requested.join(', ')} on PR **#${pullNumber}**.`
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let review = await client.createPullRequestReview(owner, repo, pullNumber, {
|
|
76
|
+
body: ctx.input.body,
|
|
77
|
+
event: action,
|
|
78
|
+
comments: ctx.input.comments
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
output: {
|
|
83
|
+
reviewId: review.id,
|
|
84
|
+
state: review.state,
|
|
85
|
+
htmlUrl: review.html_url
|
|
86
|
+
},
|
|
87
|
+
message: `Submitted **${review.state}** review on PR **#${pullNumber}** in **${owner}/${repo}**.`
|
|
88
|
+
};
|
|
89
|
+
})
|
|
90
|
+
.build();
|