@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,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 listIssues = SlateTool.create(spec, {
|
|
7
|
+
name: 'List Issues',
|
|
8
|
+
key: 'list_issues',
|
|
9
|
+
description: `List issues in a GitHub repository with filtering and sorting options.
|
|
10
|
+
Filter by state, labels, assignee, milestone, and since date. Note: GitHub's API returns pull requests alongside issues — this tool filters them out.`,
|
|
11
|
+
tags: {
|
|
12
|
+
readOnly: true
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
.input(
|
|
16
|
+
z.object({
|
|
17
|
+
owner: z.string().describe('Repository owner (user or organization)'),
|
|
18
|
+
repo: z.string().describe('Repository name'),
|
|
19
|
+
state: z
|
|
20
|
+
.enum(['open', 'closed', 'all'])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Filter by state (default: open)'),
|
|
23
|
+
labels: z.string().optional().describe('Comma-separated label names to filter by'),
|
|
24
|
+
assignee: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Filter by assignee username, or "none" for unassigned'),
|
|
28
|
+
sort: z.enum(['created', 'updated', 'comments']).optional().describe('Sort field'),
|
|
29
|
+
direction: z.enum(['asc', 'desc']).optional().describe('Sort direction'),
|
|
30
|
+
since: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Only issues updated after this ISO 8601 timestamp'),
|
|
34
|
+
perPage: z.number().optional().describe('Results per page (max 100)'),
|
|
35
|
+
page: z.number().optional().describe('Page number')
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
.output(
|
|
39
|
+
z.object({
|
|
40
|
+
issues: z.array(
|
|
41
|
+
z.object({
|
|
42
|
+
issueNumber: z.number().describe('Issue number'),
|
|
43
|
+
title: z.string().describe('Issue title'),
|
|
44
|
+
state: z.string().describe('Issue state'),
|
|
45
|
+
author: z.string().describe('Issue author login'),
|
|
46
|
+
assignees: z.array(z.string()).describe('Assigned usernames'),
|
|
47
|
+
labels: z.array(z.string()).describe('Label names'),
|
|
48
|
+
commentsCount: z.number().describe('Number of comments'),
|
|
49
|
+
createdAt: z.string().describe('Creation timestamp'),
|
|
50
|
+
updatedAt: z.string().describe('Last update timestamp'),
|
|
51
|
+
htmlUrl: z.string().describe('URL to the issue on GitHub')
|
|
52
|
+
})
|
|
53
|
+
),
|
|
54
|
+
totalCount: z.number().describe('Number of issues returned')
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
.handleInvocation(async ctx => {
|
|
58
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
59
|
+
let items = await client.listIssues(ctx.input.owner, ctx.input.repo, {
|
|
60
|
+
state: ctx.input.state,
|
|
61
|
+
labels: ctx.input.labels,
|
|
62
|
+
assignee: ctx.input.assignee,
|
|
63
|
+
sort: ctx.input.sort,
|
|
64
|
+
direction: ctx.input.direction,
|
|
65
|
+
since: ctx.input.since,
|
|
66
|
+
perPage: ctx.input.perPage,
|
|
67
|
+
page: ctx.input.page
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
let issues = items
|
|
71
|
+
.filter((i: any) => !i.pull_request)
|
|
72
|
+
.map((i: any) => ({
|
|
73
|
+
issueNumber: i.number,
|
|
74
|
+
title: i.title,
|
|
75
|
+
state: i.state,
|
|
76
|
+
author: i.user.login,
|
|
77
|
+
assignees: (i.assignees ?? []).map((a: any) => a.login),
|
|
78
|
+
labels: (i.labels ?? []).map((l: any) => l.name),
|
|
79
|
+
commentsCount: i.comments,
|
|
80
|
+
createdAt: i.created_at,
|
|
81
|
+
updatedAt: i.updated_at,
|
|
82
|
+
htmlUrl: i.html_url
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
output: { issues, totalCount: issues.length },
|
|
87
|
+
message: `Found **${issues.length}** issues in **${ctx.input.owner}/${ctx.input.repo}** (state: ${ctx.input.state ?? 'open'}).`
|
|
88
|
+
};
|
|
89
|
+
})
|
|
90
|
+
.build();
|
|
@@ -0,0 +1,82 @@
|
|
|
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 listPullRequests = SlateTool.create(spec, {
|
|
7
|
+
name: 'List Pull Requests',
|
|
8
|
+
key: 'list_pull_requests',
|
|
9
|
+
description: `List pull requests in a GitHub repository with filtering by state, head/base branch, and sorting options.`,
|
|
10
|
+
tags: {
|
|
11
|
+
readOnly: 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
|
+
state: z
|
|
19
|
+
.enum(['open', 'closed', 'all'])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Filter by state (default: open)'),
|
|
22
|
+
head: z.string().optional().describe('Filter by head branch (format: "user:branch")'),
|
|
23
|
+
base: z.string().optional().describe('Filter by base branch name'),
|
|
24
|
+
sort: z
|
|
25
|
+
.enum(['created', 'updated', 'popularity', 'long-running'])
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Sort field'),
|
|
28
|
+
direction: z.enum(['asc', 'desc']).optional().describe('Sort direction'),
|
|
29
|
+
perPage: z.number().optional().describe('Results per page (max 100)'),
|
|
30
|
+
page: z.number().optional().describe('Page number')
|
|
31
|
+
})
|
|
32
|
+
)
|
|
33
|
+
.output(
|
|
34
|
+
z.object({
|
|
35
|
+
pullRequests: z.array(
|
|
36
|
+
z.object({
|
|
37
|
+
pullNumber: z.number().describe('Pull request number'),
|
|
38
|
+
title: z.string().describe('PR title'),
|
|
39
|
+
state: z.string().describe('PR state'),
|
|
40
|
+
author: z.string().describe('PR author login'),
|
|
41
|
+
head: z.string().describe('Head branch ref'),
|
|
42
|
+
base: z.string().describe('Base branch ref'),
|
|
43
|
+
draft: z.boolean().describe('Whether the PR is a draft'),
|
|
44
|
+
createdAt: z.string().describe('Creation timestamp'),
|
|
45
|
+
updatedAt: z.string().describe('Last update timestamp'),
|
|
46
|
+
htmlUrl: z.string().describe('URL to the PR on GitHub')
|
|
47
|
+
})
|
|
48
|
+
),
|
|
49
|
+
totalCount: z.number().describe('Number of pull requests returned')
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
.handleInvocation(async ctx => {
|
|
53
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
54
|
+
let prs = await client.listPullRequests(ctx.input.owner, ctx.input.repo, {
|
|
55
|
+
state: ctx.input.state,
|
|
56
|
+
head: ctx.input.head,
|
|
57
|
+
base: ctx.input.base,
|
|
58
|
+
sort: ctx.input.sort,
|
|
59
|
+
direction: ctx.input.direction,
|
|
60
|
+
perPage: ctx.input.perPage,
|
|
61
|
+
page: ctx.input.page
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
let pullRequests = prs.map((pr: any) => ({
|
|
65
|
+
pullNumber: pr.number,
|
|
66
|
+
title: pr.title,
|
|
67
|
+
state: pr.state,
|
|
68
|
+
author: pr.user.login,
|
|
69
|
+
head: pr.head.ref,
|
|
70
|
+
base: pr.base.ref,
|
|
71
|
+
draft: pr.draft,
|
|
72
|
+
createdAt: pr.created_at,
|
|
73
|
+
updatedAt: pr.updated_at,
|
|
74
|
+
htmlUrl: pr.html_url
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
output: { pullRequests, totalCount: pullRequests.length },
|
|
79
|
+
message: `Found **${pullRequests.length}** pull requests in **${ctx.input.owner}/${ctx.input.repo}** (state: ${ctx.input.state ?? 'open'}).`
|
|
80
|
+
};
|
|
81
|
+
})
|
|
82
|
+
.build();
|
|
@@ -0,0 +1,102 @@
|
|
|
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 listRepositories = SlateTool.create(spec, {
|
|
7
|
+
name: 'List Repositories',
|
|
8
|
+
key: 'list_repositories',
|
|
9
|
+
description: `List repositories for the authenticated user or a specific organization.
|
|
10
|
+
Supports filtering by type, sorting, and pagination.`,
|
|
11
|
+
tags: {
|
|
12
|
+
readOnly: true
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
.input(
|
|
16
|
+
z.object({
|
|
17
|
+
org: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe(
|
|
21
|
+
'Organization name. If omitted, lists repositories for the authenticated user.'
|
|
22
|
+
),
|
|
23
|
+
type: z
|
|
24
|
+
.enum(['all', 'owner', 'public', 'private', 'member'])
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Filter by repository type'),
|
|
27
|
+
sort: z
|
|
28
|
+
.enum(['created', 'updated', 'pushed', 'full_name'])
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Sort field'),
|
|
31
|
+
direction: z.enum(['asc', 'desc']).optional().describe('Sort direction'),
|
|
32
|
+
perPage: z.number().optional().describe('Results per page (max 100)'),
|
|
33
|
+
page: z.number().optional().describe('Page number')
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
.output(
|
|
37
|
+
z.object({
|
|
38
|
+
repositories: z.array(
|
|
39
|
+
z.object({
|
|
40
|
+
repositoryId: z.number().describe('Unique repository ID'),
|
|
41
|
+
name: z.string().describe('Repository name'),
|
|
42
|
+
fullName: z.string().describe('Full name in owner/repo format'),
|
|
43
|
+
owner: z.string().describe('Repository owner login'),
|
|
44
|
+
private: z.boolean().describe('Whether the repository is private'),
|
|
45
|
+
description: z.string().nullable().describe('Repository description'),
|
|
46
|
+
htmlUrl: z.string().describe('URL to the repository on GitHub'),
|
|
47
|
+
defaultBranch: z.string().describe('Default branch name'),
|
|
48
|
+
language: z.string().nullable().describe('Primary programming language'),
|
|
49
|
+
stargazersCount: z.number().describe('Number of stars'),
|
|
50
|
+
forksCount: z.number().describe('Number of forks'),
|
|
51
|
+
updatedAt: z.string().describe('Last update timestamp')
|
|
52
|
+
})
|
|
53
|
+
),
|
|
54
|
+
totalCount: z.number().describe('Number of repositories returned')
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
.handleInvocation(async ctx => {
|
|
58
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
59
|
+
|
|
60
|
+
let repos: any[];
|
|
61
|
+
if (ctx.input.org) {
|
|
62
|
+
repos = await client.listOrgRepos(ctx.input.org, {
|
|
63
|
+
type: ctx.input.type,
|
|
64
|
+
sort: ctx.input.sort,
|
|
65
|
+
direction: ctx.input.direction,
|
|
66
|
+
perPage: ctx.input.perPage,
|
|
67
|
+
page: ctx.input.page
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
repos = await client.listRepositories({
|
|
71
|
+
type: ctx.input.type,
|
|
72
|
+
sort: ctx.input.sort,
|
|
73
|
+
direction: ctx.input.direction,
|
|
74
|
+
perPage: ctx.input.perPage,
|
|
75
|
+
page: ctx.input.page
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let repositories = repos.map((r: any) => ({
|
|
80
|
+
repositoryId: r.id,
|
|
81
|
+
name: r.name,
|
|
82
|
+
fullName: r.full_name,
|
|
83
|
+
owner: r.owner.login,
|
|
84
|
+
private: r.private,
|
|
85
|
+
description: r.description,
|
|
86
|
+
htmlUrl: r.html_url,
|
|
87
|
+
defaultBranch: r.default_branch,
|
|
88
|
+
language: r.language,
|
|
89
|
+
stargazersCount: r.stargazers_count,
|
|
90
|
+
forksCount: r.forks_count,
|
|
91
|
+
updatedAt: r.updated_at
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
output: {
|
|
96
|
+
repositories,
|
|
97
|
+
totalCount: repositories.length
|
|
98
|
+
},
|
|
99
|
+
message: `Found **${repositories.length}** repositories${ctx.input.org ? ` in org **${ctx.input.org}**` : ''}.`
|
|
100
|
+
};
|
|
101
|
+
})
|
|
102
|
+
.build();
|
|
@@ -0,0 +1,88 @@
|
|
|
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 manageCollaborators = SlateTool.create(spec, {
|
|
7
|
+
name: 'Manage Collaborators',
|
|
8
|
+
key: 'manage_collaborators',
|
|
9
|
+
description: `List, add, or remove collaborators on a GitHub repository. Control access permissions for individual users.`
|
|
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', 'add', 'remove']).describe('Action to perform'),
|
|
16
|
+
username: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Username of the collaborator (required for add/remove)'),
|
|
20
|
+
permission: z
|
|
21
|
+
.enum(['pull', 'triage', 'push', 'maintain', 'admin'])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Permission level (for add action, default: push)'),
|
|
24
|
+
perPage: z.number().optional().describe('Results per page (for list)'),
|
|
25
|
+
page: z.number().optional().describe('Page number (for list)')
|
|
26
|
+
})
|
|
27
|
+
)
|
|
28
|
+
.output(
|
|
29
|
+
z.object({
|
|
30
|
+
collaborators: z
|
|
31
|
+
.array(
|
|
32
|
+
z.object({
|
|
33
|
+
username: z.string().describe('Collaborator username'),
|
|
34
|
+
avatarUrl: z.string().describe('Avatar URL'),
|
|
35
|
+
permissions: z
|
|
36
|
+
.record(z.string(), z.boolean())
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Permission map')
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('List of collaborators'),
|
|
43
|
+
added: z.boolean().optional().describe('Whether the collaborator was added'),
|
|
44
|
+
removed: z.boolean().optional().describe('Whether the collaborator was removed')
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
.handleInvocation(async ctx => {
|
|
48
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
49
|
+
let { owner, repo, action, username } = ctx.input;
|
|
50
|
+
|
|
51
|
+
if (action === 'list') {
|
|
52
|
+
let collabs = await client.listCollaborators(owner, repo, {
|
|
53
|
+
perPage: ctx.input.perPage,
|
|
54
|
+
page: ctx.input.page
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
output: {
|
|
58
|
+
collaborators: collabs.map((c: any) => ({
|
|
59
|
+
username: c.login,
|
|
60
|
+
avatarUrl: c.avatar_url,
|
|
61
|
+
permissions: c.permissions
|
|
62
|
+
}))
|
|
63
|
+
},
|
|
64
|
+
message: `Found **${collabs.length}** collaborators in **${owner}/${repo}**.`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!username) throw new Error('username is required for add/remove actions.');
|
|
69
|
+
|
|
70
|
+
if (action === 'add') {
|
|
71
|
+
await client.addCollaborator(owner, repo, username, ctx.input.permission);
|
|
72
|
+
return {
|
|
73
|
+
output: { added: true },
|
|
74
|
+
message: `Added **${username}** as collaborator to **${owner}/${repo}** with ${ctx.input.permission ?? 'push'} permission.`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (action === 'remove') {
|
|
79
|
+
await client.removeCollaborator(owner, repo, username);
|
|
80
|
+
return {
|
|
81
|
+
output: { removed: true },
|
|
82
|
+
message: `Removed **${username}** from **${owner}/${repo}** collaborators.`
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw new Error(`Unknown action: ${action}`);
|
|
87
|
+
})
|
|
88
|
+
.build();
|
|
@@ -0,0 +1,117 @@
|
|
|
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 manageFileContent = SlateTool.create(spec, {
|
|
7
|
+
name: 'Manage File Content',
|
|
8
|
+
key: 'manage_file_content',
|
|
9
|
+
description: `Read, create, update, or delete a file in a GitHub repository.
|
|
10
|
+
- **read**: Retrieve file contents (decoded from Base64).
|
|
11
|
+
- **write**: Create or update a file. Provide Base64-encoded content and a commit message.
|
|
12
|
+
- **delete**: Delete a file. Requires the file's current SHA and a commit message.`,
|
|
13
|
+
instructions: [
|
|
14
|
+
'For read operations, only owner, repo, path, and optionally ref are needed.',
|
|
15
|
+
'For write operations, file content must be Base64-encoded.',
|
|
16
|
+
'For update/delete, provide the current SHA of the file to avoid conflicts.'
|
|
17
|
+
]
|
|
18
|
+
})
|
|
19
|
+
.input(
|
|
20
|
+
z.object({
|
|
21
|
+
owner: z.string().describe('Repository owner'),
|
|
22
|
+
repo: z.string().describe('Repository name'),
|
|
23
|
+
path: z.string().describe('File path relative to the repo root'),
|
|
24
|
+
action: z.enum(['read', 'write', 'delete']).describe('Operation to perform'),
|
|
25
|
+
ref: z.string().optional().describe('Branch/tag/commit to read from (only for read)'),
|
|
26
|
+
content: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Base64-encoded file content (required for write)'),
|
|
30
|
+
commitMessage: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Commit message (required for write/delete)'),
|
|
34
|
+
sha: z.string().optional().describe('Current file SHA (required for update/delete)'),
|
|
35
|
+
branch: z.string().optional().describe('Branch to commit to (for write/delete)')
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
.output(
|
|
39
|
+
z.object({
|
|
40
|
+
path: z.string().describe('File path'),
|
|
41
|
+
sha: z.string().optional().describe('File SHA'),
|
|
42
|
+
content: z.string().optional().describe('Decoded file content (for read)'),
|
|
43
|
+
size: z.number().optional().describe('File size in bytes'),
|
|
44
|
+
htmlUrl: z.string().optional().describe('URL to the file on GitHub'),
|
|
45
|
+
commitSha: z.string().optional().describe('Commit SHA (for write/delete)')
|
|
46
|
+
})
|
|
47
|
+
)
|
|
48
|
+
.handleInvocation(async ctx => {
|
|
49
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
50
|
+
let { owner, repo, path, action } = ctx.input;
|
|
51
|
+
|
|
52
|
+
if (action === 'read') {
|
|
53
|
+
let file = await client.getContent(owner, repo, path, ctx.input.ref);
|
|
54
|
+
|
|
55
|
+
let content: string | undefined;
|
|
56
|
+
if (file.content && file.encoding === 'base64') {
|
|
57
|
+
content = atob(file.content);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
output: {
|
|
62
|
+
path: file.path,
|
|
63
|
+
sha: file.sha,
|
|
64
|
+
content,
|
|
65
|
+
size: file.size,
|
|
66
|
+
htmlUrl: file.html_url
|
|
67
|
+
},
|
|
68
|
+
message: `Read file **${file.path}** (${file.size} bytes) from **${owner}/${repo}**.`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (action === 'write') {
|
|
73
|
+
if (!ctx.input.content || !ctx.input.commitMessage) {
|
|
74
|
+
throw new Error('content and commitMessage are required for write operations.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let result = await client.createOrUpdateFile(owner, repo, path, {
|
|
78
|
+
message: ctx.input.commitMessage,
|
|
79
|
+
content: ctx.input.content,
|
|
80
|
+
sha: ctx.input.sha,
|
|
81
|
+
branch: ctx.input.branch
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
output: {
|
|
86
|
+
path: result.content.path,
|
|
87
|
+
sha: result.content.sha,
|
|
88
|
+
htmlUrl: result.content.html_url,
|
|
89
|
+
commitSha: result.commit.sha
|
|
90
|
+
},
|
|
91
|
+
message: `${ctx.input.sha ? 'Updated' : 'Created'} file **${path}** in **${owner}/${repo}** (commit: \`${result.commit.sha.slice(0, 7)}\`).`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (action === 'delete') {
|
|
96
|
+
if (!ctx.input.sha || !ctx.input.commitMessage) {
|
|
97
|
+
throw new Error('sha and commitMessage are required for delete operations.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let result = await client.deleteFile(owner, repo, path, {
|
|
101
|
+
message: ctx.input.commitMessage,
|
|
102
|
+
sha: ctx.input.sha,
|
|
103
|
+
branch: ctx.input.branch
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
output: {
|
|
108
|
+
path,
|
|
109
|
+
commitSha: result.commit.sha
|
|
110
|
+
},
|
|
111
|
+
message: `Deleted file **${path}** from **${owner}/${repo}** (commit: \`${result.commit.sha.slice(0, 7)}\`).`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new Error(`Unknown action: ${action}`);
|
|
116
|
+
})
|
|
117
|
+
.build();
|
|
@@ -0,0 +1,183 @@
|
|
|
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 manageGist = SlateTool.create(spec, {
|
|
7
|
+
name: 'Manage Gist',
|
|
8
|
+
key: 'manage_gist',
|
|
9
|
+
description: `Create, read, update, or delete GitHub Gists (code snippets).
|
|
10
|
+
- **create**: Create a new gist with one or more files.
|
|
11
|
+
- **get**: Retrieve a gist by ID.
|
|
12
|
+
- **update**: Update a gist's description or files.
|
|
13
|
+
- **delete**: Delete a gist.
|
|
14
|
+
- **list**: List gists for the authenticated user.`
|
|
15
|
+
})
|
|
16
|
+
.input(
|
|
17
|
+
z.object({
|
|
18
|
+
action: z
|
|
19
|
+
.enum(['create', 'get', 'update', 'delete', 'list'])
|
|
20
|
+
.describe('Action to perform'),
|
|
21
|
+
gistId: z.string().optional().describe('Gist ID (required for get, update, delete)'),
|
|
22
|
+
description: z.string().optional().describe('Gist description (for create/update)'),
|
|
23
|
+
public: z
|
|
24
|
+
.boolean()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Whether the gist is public (for create, default: false)'),
|
|
27
|
+
files: z
|
|
28
|
+
.record(
|
|
29
|
+
z.string(),
|
|
30
|
+
z.object({
|
|
31
|
+
content: z.string().optional().describe('File content'),
|
|
32
|
+
filename: z.string().optional().describe('New filename (for rename)')
|
|
33
|
+
})
|
|
34
|
+
)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe(
|
|
37
|
+
'Files to include. For create, content is required. For update, set value to null to delete a file.'
|
|
38
|
+
),
|
|
39
|
+
perPage: z.number().optional().describe('Results per page (for list)'),
|
|
40
|
+
page: z.number().optional().describe('Page number (for list)')
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
.output(
|
|
44
|
+
z.object({
|
|
45
|
+
gistId: z.string().optional().describe('Gist ID'),
|
|
46
|
+
htmlUrl: z.string().optional().describe('URL to the gist'),
|
|
47
|
+
description: z.string().nullable().optional().describe('Gist description'),
|
|
48
|
+
public: z.boolean().optional().describe('Whether the gist is public'),
|
|
49
|
+
files: z
|
|
50
|
+
.array(
|
|
51
|
+
z.object({
|
|
52
|
+
filename: z.string(),
|
|
53
|
+
language: z.string().nullable(),
|
|
54
|
+
size: z.number()
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('Files in the gist'),
|
|
59
|
+
gists: z
|
|
60
|
+
.array(
|
|
61
|
+
z.object({
|
|
62
|
+
gistId: z.string(),
|
|
63
|
+
htmlUrl: z.string(),
|
|
64
|
+
description: z.string().nullable(),
|
|
65
|
+
public: z.boolean(),
|
|
66
|
+
fileCount: z.number(),
|
|
67
|
+
createdAt: z.string(),
|
|
68
|
+
updatedAt: z.string()
|
|
69
|
+
})
|
|
70
|
+
)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('List of gists'),
|
|
73
|
+
deleted: z.boolean().optional().describe('Whether the gist was deleted')
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
.handleInvocation(async ctx => {
|
|
77
|
+
let client = new GitHubClient(ctx.auth.token);
|
|
78
|
+
let { action, gistId } = ctx.input;
|
|
79
|
+
|
|
80
|
+
if (action === 'list') {
|
|
81
|
+
let gists = await client.listGists({
|
|
82
|
+
perPage: ctx.input.perPage,
|
|
83
|
+
page: ctx.input.page
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
output: {
|
|
87
|
+
gists: gists.map((g: any) => ({
|
|
88
|
+
gistId: g.id,
|
|
89
|
+
htmlUrl: g.html_url,
|
|
90
|
+
description: g.description,
|
|
91
|
+
public: g.public,
|
|
92
|
+
fileCount: Object.keys(g.files).length,
|
|
93
|
+
createdAt: g.created_at,
|
|
94
|
+
updatedAt: g.updated_at
|
|
95
|
+
}))
|
|
96
|
+
},
|
|
97
|
+
message: `Found **${gists.length}** gists.`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (action === 'get') {
|
|
102
|
+
if (!gistId) throw new Error('gistId is required for get action.');
|
|
103
|
+
let gist = await client.getGist(gistId);
|
|
104
|
+
return {
|
|
105
|
+
output: {
|
|
106
|
+
gistId: gist.id,
|
|
107
|
+
htmlUrl: gist.html_url,
|
|
108
|
+
description: gist.description,
|
|
109
|
+
public: gist.public,
|
|
110
|
+
files: Object.values(gist.files).map((f: any) => ({
|
|
111
|
+
filename: f.filename,
|
|
112
|
+
language: f.language,
|
|
113
|
+
size: f.size
|
|
114
|
+
}))
|
|
115
|
+
},
|
|
116
|
+
message: `Retrieved gist **${gist.id}** — ${gist.html_url}`
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (action === 'create') {
|
|
121
|
+
if (!ctx.input.files) throw new Error('files are required for create action.');
|
|
122
|
+
let filesPayload: Record<string, { content: string }> = {};
|
|
123
|
+
for (let [name, file] of Object.entries(ctx.input.files)) {
|
|
124
|
+
let f = file as { content?: string; filename?: string };
|
|
125
|
+
if (f.content) {
|
|
126
|
+
filesPayload[name] = { content: f.content };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let gist = await client.createGist({
|
|
130
|
+
description: ctx.input.description,
|
|
131
|
+
public: ctx.input.public,
|
|
132
|
+
files: filesPayload
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
output: {
|
|
136
|
+
gistId: gist.id,
|
|
137
|
+
htmlUrl: gist.html_url,
|
|
138
|
+
description: gist.description,
|
|
139
|
+
public: gist.public,
|
|
140
|
+
files: Object.values(gist.files).map((f: any) => ({
|
|
141
|
+
filename: f.filename,
|
|
142
|
+
language: f.language,
|
|
143
|
+
size: f.size
|
|
144
|
+
}))
|
|
145
|
+
},
|
|
146
|
+
message: `Created gist **${gist.id}** — ${gist.html_url}`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (action === 'update') {
|
|
151
|
+
if (!gistId) throw new Error('gistId is required for update action.');
|
|
152
|
+
let gist = await client.updateGist(gistId, {
|
|
153
|
+
description: ctx.input.description,
|
|
154
|
+
files: ctx.input.files as any
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
output: {
|
|
158
|
+
gistId: gist.id,
|
|
159
|
+
htmlUrl: gist.html_url,
|
|
160
|
+
description: gist.description,
|
|
161
|
+
public: gist.public,
|
|
162
|
+
files: Object.values(gist.files).map((f: any) => ({
|
|
163
|
+
filename: f.filename,
|
|
164
|
+
language: f.language,
|
|
165
|
+
size: f.size
|
|
166
|
+
}))
|
|
167
|
+
},
|
|
168
|
+
message: `Updated gist **${gist.id}** — ${gist.html_url}`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (action === 'delete') {
|
|
173
|
+
if (!gistId) throw new Error('gistId is required for delete action.');
|
|
174
|
+
await client.deleteGist(gistId);
|
|
175
|
+
return {
|
|
176
|
+
output: { deleted: true },
|
|
177
|
+
message: `Deleted gist **${gistId}**.`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
throw new Error(`Unknown action: ${action}`);
|
|
182
|
+
})
|
|
183
|
+
.build();
|