@ryuu-reinzz/ryuu-cli 0.5.0 → 0.5.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/CHANGELOG.md +47 -0
- package/docs/github.md +470 -0
- package/package.json +1 -1
- package/plugins/github/README.md +158 -0
- package/plugins/github/api.js +78 -0
- package/plugins/github/auth.js +45 -0
- package/plugins/github/backend.js +191 -0
- package/plugins/github/command.js +307 -0
- package/plugins/github/config.js +209 -0
- package/plugins/github/credentials.js +161 -0
- package/plugins/github/errors.js +49 -0
- package/plugins/github/index.js +41 -0
- package/plugins/github/manager.js +613 -0
- package/plugins/github/tool-helpers.js +259 -0
- package/plugins/github/tools-account.js +64 -0
- package/plugins/github/tools-actions.js +126 -0
- package/plugins/github/tools-issues.js +214 -0
- package/plugins/github/tools-pulls.js +267 -0
- package/plugins/github/tools-releases.js +87 -0
- package/plugins/github/tools-repos.js +430 -0
- package/plugins/github/tools.js +25 -0
- package/plugins/index.js +3 -1
- package/src/cli/commands.js +71 -4
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { defineTool } from '../../src/tools/registry.js';
|
|
2
|
+
import { assertDestructiveAllowed, limitItems, pageQuery, pickPull, repoBase, text } from './tool-helpers.js';
|
|
3
|
+
|
|
4
|
+
const REPO_PROPS = {
|
|
5
|
+
owner: { type: 'string', description: 'Pemilik repository.' },
|
|
6
|
+
repo: { type: 'string', description: 'Nama repository.' },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function createPullTools(plugin) {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'github_list_pull_requests',
|
|
13
|
+
description:
|
|
14
|
+
'Daftar pull request pada repository dengan filter status, branch, dan urutan. Membutuhkan permission Pull requests read.',
|
|
15
|
+
risk: 'read',
|
|
16
|
+
schema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
...REPO_PROPS,
|
|
20
|
+
state: { type: 'string', enum: ['open', 'closed', 'all'], description: 'Status PR. Default open.' },
|
|
21
|
+
head: { type: 'string', description: 'Filter berdasarkan branch sumber, format owner:branch.' },
|
|
22
|
+
base: { type: 'string', description: 'Filter berdasarkan branch tujuan.' },
|
|
23
|
+
sort: { type: 'string', enum: ['created', 'updated', 'popularity', 'long-running'], description: 'Urutan hasil.' },
|
|
24
|
+
direction: { type: 'string', enum: ['asc', 'desc'], description: 'Arah urutan.' },
|
|
25
|
+
per_page: { type: 'integer', minimum: 1, maximum: 100, description: 'Jumlah per halaman (default 30).' },
|
|
26
|
+
page: { type: 'integer', minimum: 1, description: 'Halaman (default 1).' },
|
|
27
|
+
},
|
|
28
|
+
required: ['owner', 'repo'],
|
|
29
|
+
},
|
|
30
|
+
async handler(args) {
|
|
31
|
+
const api = await plugin.requireClient();
|
|
32
|
+
const pulls = await api.paginate(`${repoBase(args)}/pulls`, {
|
|
33
|
+
query: {
|
|
34
|
+
...pageQuery(args),
|
|
35
|
+
state: args.state || 'open',
|
|
36
|
+
...(args.head ? { head: args.head } : {}),
|
|
37
|
+
...(args.base ? { base: args.base } : {}),
|
|
38
|
+
...(args.sort ? { sort: args.sort } : {}),
|
|
39
|
+
...(args.direction ? { direction: args.direction } : {}),
|
|
40
|
+
},
|
|
41
|
+
maxPages: 2,
|
|
42
|
+
maxItems: 100,
|
|
43
|
+
});
|
|
44
|
+
return { ok: true, count: pulls.length, pull_requests: limitItems(pulls, 100).map(pickPull) };
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
|
|
48
|
+
defineTool({
|
|
49
|
+
name: 'github_get_pull_request',
|
|
50
|
+
description:
|
|
51
|
+
'Ambil detail satu pull request: branch asal/tujuan, status merge, jumlah file berubah, dan isi deskripsi.',
|
|
52
|
+
risk: 'read',
|
|
53
|
+
schema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
...REPO_PROPS,
|
|
57
|
+
pull_number: { type: 'integer', minimum: 1, description: 'Nomor pull request.' },
|
|
58
|
+
},
|
|
59
|
+
required: ['owner', 'repo', 'pull_number'],
|
|
60
|
+
},
|
|
61
|
+
async handler(args) {
|
|
62
|
+
const api = await plugin.requireClient();
|
|
63
|
+
const pull = await api.get(`${repoBase(args)}/pulls/${Number(args.pull_number)}`);
|
|
64
|
+
return { ok: true, pull_request: pickPull(pull) };
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
|
|
68
|
+
defineTool({
|
|
69
|
+
name: 'github_create_pull_request',
|
|
70
|
+
description:
|
|
71
|
+
'Buat pull request baru dari branch head ke branch base. Membutuhkan permission Pull requests write dan Contents read. Selalu meminta approval.',
|
|
72
|
+
risk: 'write',
|
|
73
|
+
schema: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
...REPO_PROPS,
|
|
77
|
+
title: { type: 'string', description: 'Judul pull request.' },
|
|
78
|
+
head: { type: 'string', description: 'Branch sumber, contoh: feature/login atau owner:feature/login.' },
|
|
79
|
+
base: { type: 'string', description: 'Branch tujuan, contoh: main.' },
|
|
80
|
+
body: { type: 'string', description: 'Deskripsi dalam Markdown.' },
|
|
81
|
+
draft: { type: 'boolean', description: 'Buat sebagai draft.' },
|
|
82
|
+
maintainer_can_modify: { type: 'boolean', description: 'Izinkan maintainer mengubah branch head.' },
|
|
83
|
+
},
|
|
84
|
+
required: ['owner', 'repo', 'title', 'head', 'base'],
|
|
85
|
+
},
|
|
86
|
+
async handler(args) {
|
|
87
|
+
const api = await plugin.requireClient();
|
|
88
|
+
const body = {
|
|
89
|
+
title: String(args.title),
|
|
90
|
+
head: String(args.head),
|
|
91
|
+
base: String(args.base),
|
|
92
|
+
...(args.body !== undefined ? { body: String(args.body) } : {}),
|
|
93
|
+
...(args.draft !== undefined ? { draft: Boolean(args.draft) } : {}),
|
|
94
|
+
...(args.maintainer_can_modify !== undefined
|
|
95
|
+
? { maintainer_can_modify: Boolean(args.maintainer_can_modify) }
|
|
96
|
+
: {}),
|
|
97
|
+
};
|
|
98
|
+
const pull = await api.post(`${repoBase(args)}/pulls`, body);
|
|
99
|
+
return { ok: true, pull_request: pickPull(pull) };
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
defineTool({
|
|
104
|
+
name: 'github_update_pull_request',
|
|
105
|
+
description:
|
|
106
|
+
'Perbarui judul, deskripsi, status, atau branch tujuan pull request. Membutuhkan permission Pull requests write serta approval.',
|
|
107
|
+
risk: 'write',
|
|
108
|
+
schema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
...REPO_PROPS,
|
|
112
|
+
pull_number: { type: 'integer', minimum: 1, description: 'Nomor pull request.' },
|
|
113
|
+
title: { type: 'string', description: 'Judul baru.' },
|
|
114
|
+
body: { type: 'string', description: 'Deskripsi baru.' },
|
|
115
|
+
state: { type: 'string', enum: ['open', 'closed'], description: 'Status baru.' },
|
|
116
|
+
base: { type: 'string', description: 'Branch tujuan baru.' },
|
|
117
|
+
},
|
|
118
|
+
required: ['owner', 'repo', 'pull_number'],
|
|
119
|
+
},
|
|
120
|
+
async handler(args) {
|
|
121
|
+
const api = await plugin.requireClient();
|
|
122
|
+
const body = {};
|
|
123
|
+
for (const key of ['title', 'body', 'state', 'base']) {
|
|
124
|
+
if (args[key] !== undefined) body[key] = args[key];
|
|
125
|
+
}
|
|
126
|
+
if (Object.keys(body).length === 0) throw new Error('Tidak ada perubahan yang diminta.');
|
|
127
|
+
const pull = await api.patch(`${repoBase(args)}/pulls/${Number(args.pull_number)}`, body);
|
|
128
|
+
return { ok: true, pull_request: pickPull(pull) };
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
|
|
132
|
+
defineTool({
|
|
133
|
+
name: 'github_merge_pull_request',
|
|
134
|
+
description:
|
|
135
|
+
'Merge pull request. Operasi berdampak besar: butuh permission Pull requests write, selalu meminta approval, dan dapat diblokir oleh confirmDestructive. Sertakan sha bila ingin memastikan head belum berubah.',
|
|
136
|
+
risk: 'destructive',
|
|
137
|
+
requiresApproval: true,
|
|
138
|
+
schema: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
...REPO_PROPS,
|
|
142
|
+
pull_number: { type: 'integer', minimum: 1, description: 'Nomor pull request.' },
|
|
143
|
+
merge_method: { type: 'string', enum: ['merge', 'squash', 'rebase'], description: 'Metode merge.' },
|
|
144
|
+
commit_title: { type: 'string', description: 'Judul commit merge.' },
|
|
145
|
+
commit_message: { type: 'string', description: 'Pesan commit merge.' },
|
|
146
|
+
sha: { type: 'string', description: 'Head SHA yang diharapkan (opsional, untuk keamanan).' },
|
|
147
|
+
},
|
|
148
|
+
required: ['owner', 'repo', 'pull_number'],
|
|
149
|
+
},
|
|
150
|
+
async handler(args) {
|
|
151
|
+
await assertDestructiveAllowed(plugin, 'Merge pull request');
|
|
152
|
+
const api = await plugin.requireClient();
|
|
153
|
+
const body = {};
|
|
154
|
+
for (const key of ['merge_method', 'commit_title', 'commit_message', 'sha']) {
|
|
155
|
+
if (args[key] !== undefined) body[key] = args[key];
|
|
156
|
+
}
|
|
157
|
+
const result = await api.put(`${repoBase(args)}/pulls/${Number(args.pull_number)}/merge`, body);
|
|
158
|
+
return {
|
|
159
|
+
ok: Boolean(result?.merged),
|
|
160
|
+
merged: Boolean(result?.merged),
|
|
161
|
+
message: text(result?.message, 500),
|
|
162
|
+
sha: result?.sha || null,
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
|
|
167
|
+
defineTool({
|
|
168
|
+
name: 'github_list_pull_request_files',
|
|
169
|
+
description: 'Daftar file yang berubah pada sebuah pull request beserta jumlah baris ditambah/dihapus.',
|
|
170
|
+
risk: 'read',
|
|
171
|
+
schema: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
...REPO_PROPS,
|
|
175
|
+
pull_number: { type: 'integer', minimum: 1, description: 'Nomor pull request.' },
|
|
176
|
+
per_page: { type: 'integer', minimum: 1, maximum: 100, description: 'Jumlah per halaman (default 30).' },
|
|
177
|
+
page: { type: 'integer', minimum: 1, description: 'Halaman (default 1).' },
|
|
178
|
+
},
|
|
179
|
+
required: ['owner', 'repo', 'pull_number'],
|
|
180
|
+
},
|
|
181
|
+
async handler(args) {
|
|
182
|
+
const api = await plugin.requireClient();
|
|
183
|
+
const files = await api.paginate(`${repoBase(args)}/pulls/${Number(args.pull_number)}/files`, {
|
|
184
|
+
query: pageQuery(args),
|
|
185
|
+
maxPages: 3,
|
|
186
|
+
maxItems: 300,
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
count: files.length,
|
|
191
|
+
files: limitItems(files, 300).map((file) => ({
|
|
192
|
+
filename: file.filename,
|
|
193
|
+
status: file.status,
|
|
194
|
+
additions: file.additions,
|
|
195
|
+
deletions: file.deletions,
|
|
196
|
+
changes: file.changes,
|
|
197
|
+
patch: text(file.patch, 4000),
|
|
198
|
+
})),
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
|
|
203
|
+
defineTool({
|
|
204
|
+
name: 'github_review_pull_request',
|
|
205
|
+
description:
|
|
206
|
+
'Kirim review pada pull request: APPROVE, REQUEST_CHANGES, atau COMMENT, dengan komentar per baris opsional. Membutuhkan permission Pull requests write dan approval.',
|
|
207
|
+
risk: 'write',
|
|
208
|
+
schema: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: {
|
|
211
|
+
...REPO_PROPS,
|
|
212
|
+
pull_number: { type: 'integer', minimum: 1, description: 'Nomor pull request.' },
|
|
213
|
+
body: { type: 'string', description: 'Ringkasan review.' },
|
|
214
|
+
event: {
|
|
215
|
+
type: 'string',
|
|
216
|
+
enum: ['APPROVE', 'REQUEST_CHANGES', 'COMMENT'],
|
|
217
|
+
description: 'Jenis review.',
|
|
218
|
+
},
|
|
219
|
+
comments: {
|
|
220
|
+
type: 'array',
|
|
221
|
+
maxItems: 50,
|
|
222
|
+
description: 'Komentar per baris: path, position (atau line), dan body.',
|
|
223
|
+
items: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
path: { type: 'string' },
|
|
227
|
+
position: { type: 'integer', minimum: 1 },
|
|
228
|
+
line: { type: 'integer', minimum: 1 },
|
|
229
|
+
body: { type: 'string' },
|
|
230
|
+
},
|
|
231
|
+
required: ['path', 'body'],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
required: ['owner', 'repo', 'pull_number', 'event'],
|
|
236
|
+
},
|
|
237
|
+
async handler(args) {
|
|
238
|
+
const api = await plugin.requireClient();
|
|
239
|
+
const body = { event: String(args.event) };
|
|
240
|
+
if (args.body !== undefined) body.body = String(args.body);
|
|
241
|
+
if (Array.isArray(args.comments)) {
|
|
242
|
+
body.comments = args.comments.slice(0, 50).map((comment) => ({
|
|
243
|
+
path: String(comment.path),
|
|
244
|
+
body: String(comment.body),
|
|
245
|
+
...(comment.position !== undefined ? { position: Number(comment.position) } : {}),
|
|
246
|
+
...(comment.line !== undefined ? { line: Number(comment.line) } : {}),
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
if (body.event === 'REQUEST_CHANGES' && !body.body) {
|
|
250
|
+
throw new Error('Review REQUEST_CHANGES membutuhkan body.');
|
|
251
|
+
}
|
|
252
|
+
const review = await api.post(`${repoBase(args)}/pulls/${Number(args.pull_number)}/reviews`, body);
|
|
253
|
+
return {
|
|
254
|
+
ok: true,
|
|
255
|
+
review: {
|
|
256
|
+
id: review.id,
|
|
257
|
+
user: review.user?.login || null,
|
|
258
|
+
state: review.state,
|
|
259
|
+
body: text(review.body, 2000),
|
|
260
|
+
html_url: review.html_url || null,
|
|
261
|
+
submitted_at: review.submitted_at || null,
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
];
|
|
267
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineTool } from '../../src/tools/registry.js';
|
|
2
|
+
import { limitItems, pageQuery, pickRelease, repoBase } from './tool-helpers.js';
|
|
3
|
+
|
|
4
|
+
const REPO_PROPS = {
|
|
5
|
+
owner: { type: 'string', description: 'Pemilik repository.' },
|
|
6
|
+
repo: { type: 'string', description: 'Nama repository.' },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function createReleaseTools(plugin) {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'github_list_releases',
|
|
13
|
+
description: 'Daftar release repository, termasuk draft dan prerelease. Membutuhkan permission Contents read.',
|
|
14
|
+
risk: 'read',
|
|
15
|
+
schema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
...REPO_PROPS,
|
|
19
|
+
per_page: { type: 'integer', minimum: 1, maximum: 100, description: 'Jumlah per halaman (default 30).' },
|
|
20
|
+
page: { type: 'integer', minimum: 1, description: 'Halaman (default 1).' },
|
|
21
|
+
},
|
|
22
|
+
required: ['owner', 'repo'],
|
|
23
|
+
},
|
|
24
|
+
async handler(args) {
|
|
25
|
+
const api = await plugin.requireClient();
|
|
26
|
+
const releases = await api.paginate(`${repoBase(args)}/releases`, {
|
|
27
|
+
query: pageQuery(args),
|
|
28
|
+
maxPages: 2,
|
|
29
|
+
maxItems: 100,
|
|
30
|
+
});
|
|
31
|
+
return { ok: true, count: releases.length, releases: limitItems(releases, 100).map(pickRelease) };
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
|
|
35
|
+
defineTool({
|
|
36
|
+
name: 'github_get_release',
|
|
37
|
+
description: 'Ambil satu release berdasarkan id numerik GitHub (bukan tag), termasuk catatan rilis dan tautan unduhan.',
|
|
38
|
+
risk: 'read',
|
|
39
|
+
schema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
...REPO_PROPS,
|
|
43
|
+
release_id: { type: 'integer', minimum: 1, description: 'ID numerik release.' },
|
|
44
|
+
},
|
|
45
|
+
required: ['owner', 'repo', 'release_id'],
|
|
46
|
+
},
|
|
47
|
+
async handler(args) {
|
|
48
|
+
const api = await plugin.requireClient();
|
|
49
|
+
const release = await api.get(`${repoBase(args)}/releases/${Number(args.release_id)}`);
|
|
50
|
+
return { ok: true, release: pickRelease(release) };
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
defineTool({
|
|
55
|
+
name: 'github_create_release',
|
|
56
|
+
description:
|
|
57
|
+
'Buat release dari tag yang sudah ada atau tag baru pada commit tertentu. Membutuhkan permission Contents write serta approval pengguna.',
|
|
58
|
+
risk: 'write',
|
|
59
|
+
schema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
...REPO_PROPS,
|
|
63
|
+
tag_name: { type: 'string', description: 'Nama tag, contoh: v1.2.0.' },
|
|
64
|
+
target_commitish: { type: 'string', description: 'Branch atau SHA target bila tag belum ada.' },
|
|
65
|
+
name: { type: 'string', description: 'Judul release.' },
|
|
66
|
+
body: { type: 'string', description: 'Catatan rilis dalam Markdown.' },
|
|
67
|
+
draft: { type: 'boolean', description: 'Simpan sebagai draft.' },
|
|
68
|
+
prerelease: { type: 'boolean', description: 'Tandai sebagai prerelease.' },
|
|
69
|
+
generate_release_notes: { type: 'boolean', description: 'Minta GitHub membuat catatan rilis otomatis.' },
|
|
70
|
+
},
|
|
71
|
+
required: ['owner', 'repo', 'tag_name'],
|
|
72
|
+
},
|
|
73
|
+
async handler(args) {
|
|
74
|
+
const api = await plugin.requireClient();
|
|
75
|
+
const body = { tag_name: String(args.tag_name) };
|
|
76
|
+
for (const key of ['target_commitish', 'name', 'body']) {
|
|
77
|
+
if (args[key] !== undefined) body[key] = args[key];
|
|
78
|
+
}
|
|
79
|
+
for (const key of ['draft', 'prerelease', 'generate_release_notes']) {
|
|
80
|
+
if (args[key] !== undefined) body[key] = Boolean(args[key]);
|
|
81
|
+
}
|
|
82
|
+
const release = await api.post(`${repoBase(args)}/releases`, body);
|
|
83
|
+
return { ok: true, release: pickRelease(release) };
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
];
|
|
87
|
+
}
|