@meetopenbot/github 0.0.1 → 0.1.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.
- package/README.md +17 -39
- package/dist/agent.js +188 -0
- package/dist/diff.js +346 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +54 -324
- package/package.json +25 -13
- package/src/index.ts +0 -418
- package/tsconfig.json +0 -15
package/dist/index.js
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
import { definePlugin, shouldHandleInvoke, agentOutput, uiWidget, } from '@meetopenbot/plugin-sdk';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
const getRepoSchema = z.object({
|
|
12
|
-
owner: z.string().describe('The owner of the repository (user or organization)'),
|
|
13
|
-
repo: z.string().describe('The name of the repository'),
|
|
14
|
-
});
|
|
15
|
-
const createRepoSchema = z.object({
|
|
16
|
-
name: z.string().describe('The name of the repository'),
|
|
17
|
-
org: z.string().optional().describe('The organization to create the repository in. If not provided, it will be created for the authenticated user.'),
|
|
18
|
-
description: z.string().optional().describe('A short description of the repository'),
|
|
19
|
-
private: z.boolean().optional().describe('Whether the repository is private'),
|
|
20
|
-
autoInit: z.boolean().optional().describe('Whether to create an initial commit with an empty README'),
|
|
21
|
-
});
|
|
2
|
+
import { runGithubAgent } from './agent.js';
|
|
3
|
+
const GITHUB_TOKEN_VAR = 'GITHUB_TOKEN';
|
|
4
|
+
const GITHUB_TOKEN_WIDGET_ID = 'github-token-form';
|
|
5
|
+
function readVariable(variables, key) {
|
|
6
|
+
const stored = variables[key];
|
|
7
|
+
if (typeof stored === 'string')
|
|
8
|
+
return stored || undefined;
|
|
9
|
+
return stored?.value || undefined;
|
|
10
|
+
}
|
|
22
11
|
export default definePlugin({
|
|
23
|
-
id: 'github',
|
|
24
12
|
name: 'GitHub',
|
|
25
|
-
description: 'Manage
|
|
13
|
+
description: 'Manage GitHub repositories, issues, and pull requests',
|
|
26
14
|
configSchema: {
|
|
27
15
|
type: 'object',
|
|
28
16
|
properties: {
|
|
@@ -31,80 +19,17 @@ export default definePlugin({
|
|
|
31
19
|
description: 'GitHub Personal Access Token',
|
|
32
20
|
format: 'password',
|
|
33
21
|
},
|
|
34
|
-
openaiApiKey: {
|
|
35
|
-
type: 'string',
|
|
36
|
-
description: 'OpenAI API Key (optional if provided via environment)',
|
|
37
|
-
format: 'password',
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
required: ['githubToken'],
|
|
41
|
-
},
|
|
42
|
-
toolDefinitions: {
|
|
43
|
-
list_repos: {
|
|
44
|
-
description: 'List repositories for the authenticated user',
|
|
45
|
-
inputSchema: {
|
|
46
|
-
type: 'object',
|
|
47
|
-
properties: {
|
|
48
|
-
limit: { type: 'number', description: 'Maximum number of repositories to return' },
|
|
49
|
-
visibility: { type: 'string', enum: ['all', 'public', 'private'], description: 'Filter by repository visibility' },
|
|
50
|
-
sort: { type: 'string', enum: ['created', 'updated', 'pushed', 'full_name'], description: 'How to sort the repositories' },
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
get_repo: {
|
|
55
|
-
description: 'Get details of a specific repository',
|
|
56
|
-
inputSchema: {
|
|
57
|
-
type: 'object',
|
|
58
|
-
properties: {
|
|
59
|
-
owner: { type: 'string', description: 'The owner of the repository' },
|
|
60
|
-
repo: { type: 'string', description: 'The name of the repository' },
|
|
61
|
-
},
|
|
62
|
-
required: ['owner', 'repo'],
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
create_repo: {
|
|
66
|
-
description: 'Create a new repository',
|
|
67
|
-
inputSchema: {
|
|
68
|
-
type: 'object',
|
|
69
|
-
properties: {
|
|
70
|
-
name: { type: 'string', description: 'The name of the repository' },
|
|
71
|
-
org: { type: 'string', description: 'The organization to create the repository in' },
|
|
72
|
-
description: { type: 'string', description: 'A short description of the repository' },
|
|
73
|
-
private: { type: 'boolean', description: 'Whether the repository is private' },
|
|
74
|
-
autoInit: { type: 'boolean', description: 'Whether to create an initial commit with an empty README' },
|
|
75
|
-
},
|
|
76
|
-
required: ['name'],
|
|
77
|
-
},
|
|
78
22
|
},
|
|
79
23
|
},
|
|
80
24
|
factory: (context) => {
|
|
81
|
-
const
|
|
25
|
+
const getGithubToken = async () => {
|
|
82
26
|
const config = context.config;
|
|
83
|
-
const env = process.env;
|
|
84
27
|
const variables = await context.storage.getVariables();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (env[envKey])
|
|
89
|
-
return env[envKey];
|
|
90
|
-
const v = variables[envKey];
|
|
91
|
-
return typeof v === 'string' ? v : v?.value;
|
|
92
|
-
};
|
|
93
|
-
return {
|
|
94
|
-
githubToken: config.githubToken,
|
|
95
|
-
openaiApiKey: getVal('openaiApiKey', 'OPENAI_API_KEY'),
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
const getOctokit = (githubToken) => {
|
|
99
|
-
return new Octokit({
|
|
100
|
-
auth: githubToken,
|
|
101
|
-
headers: {
|
|
102
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
103
|
-
},
|
|
104
|
-
});
|
|
28
|
+
return (config.githubToken ||
|
|
29
|
+
process.env.GITHUB_TOKEN ||
|
|
30
|
+
readVariable(variables, GITHUB_TOKEN_VAR));
|
|
105
31
|
};
|
|
106
32
|
return (builder) => {
|
|
107
|
-
// Handle agent:invoke for natural language queries
|
|
108
33
|
builder.on('agent:invoke', async function* (event) {
|
|
109
34
|
if (!shouldHandleInvoke(event, context.agentId))
|
|
110
35
|
return;
|
|
@@ -112,272 +37,77 @@ export default definePlugin({
|
|
|
112
37
|
const threadId = event.meta?.threadId;
|
|
113
38
|
if (!userMessage)
|
|
114
39
|
return;
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
117
|
-
yield agentOutput({
|
|
118
|
-
agentId: context.agentId,
|
|
119
|
-
content: 'I need an OpenAI API key to help you manage GitHub. Please provide it below:',
|
|
120
|
-
threadId,
|
|
121
|
-
});
|
|
40
|
+
const githubToken = await getGithubToken();
|
|
41
|
+
if (!githubToken) {
|
|
122
42
|
yield uiWidget({
|
|
123
43
|
agentId: context.agentId,
|
|
124
44
|
threadId,
|
|
125
45
|
widget: {
|
|
126
46
|
kind: 'form',
|
|
127
|
-
widgetId:
|
|
128
|
-
title: '
|
|
129
|
-
description: 'Enter
|
|
47
|
+
widgetId: GITHUB_TOKEN_WIDGET_ID,
|
|
48
|
+
title: 'GitHub Access Token',
|
|
49
|
+
description: 'Enter a GitHub Personal Access Token with repo scope to continue.',
|
|
130
50
|
fields: [
|
|
131
51
|
{
|
|
132
|
-
id: '
|
|
133
|
-
label: '
|
|
134
|
-
type: '
|
|
135
|
-
placeholder: '
|
|
52
|
+
id: 'githubToken',
|
|
53
|
+
label: 'GitHub Access Token',
|
|
54
|
+
type: 'password',
|
|
55
|
+
placeholder: 'ghp_...',
|
|
136
56
|
required: true,
|
|
137
57
|
},
|
|
138
58
|
],
|
|
139
|
-
submitLabel: 'Save
|
|
59
|
+
submitLabel: 'Save Token',
|
|
140
60
|
},
|
|
141
61
|
});
|
|
142
62
|
return;
|
|
143
63
|
}
|
|
144
|
-
const octokit = getOctokit(githubToken);
|
|
145
|
-
const openai = createOpenAI({ apiKey: openaiApiKey });
|
|
146
64
|
try {
|
|
147
|
-
|
|
148
|
-
model: openai('gpt-4o'),
|
|
149
|
-
stopWhen: stepCountIs(5),
|
|
150
|
-
system: `You are a GitHub management assistant. Help users manage their repositories.
|
|
151
|
-
- Use list_repos to see the authenticated user's repositories.
|
|
152
|
-
- Use get_repo to get details about a repository.
|
|
153
|
-
- Use create_repo to create a new repository.
|
|
154
|
-
When a tool requires an owner and repo, parse them from the "owner/repo" format the user provides.
|
|
155
|
-
If a tool returns an error, explain it to the user or try an alternative approach if appropriate.
|
|
156
|
-
Be concise and helpful.`,
|
|
65
|
+
for await (const chunk of runGithubAgent({
|
|
157
66
|
prompt: userMessage,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return result.data;
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
return { error: error.message, status: error.status };
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
}),
|
|
176
|
-
get_repo: tool({
|
|
177
|
-
description: 'Get details of a specific repository',
|
|
178
|
-
inputSchema: getRepoSchema,
|
|
179
|
-
execute: async ({ owner, repo }) => {
|
|
180
|
-
try {
|
|
181
|
-
const result = await octokit.repos.get({ owner, repo });
|
|
182
|
-
return result.data;
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
return { error: error.message, status: error.status };
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
}),
|
|
189
|
-
create_repo: tool({
|
|
190
|
-
description: 'Create a new repository',
|
|
191
|
-
inputSchema: createRepoSchema,
|
|
192
|
-
execute: async ({ name, org, description, private: isPrivate, autoInit }) => {
|
|
193
|
-
try {
|
|
194
|
-
const params = {
|
|
195
|
-
name,
|
|
196
|
-
description,
|
|
197
|
-
private: isPrivate,
|
|
198
|
-
auto_init: autoInit,
|
|
199
|
-
};
|
|
200
|
-
const result = org
|
|
201
|
-
? await octokit.repos.createInOrg({ ...params, org })
|
|
202
|
-
: await octokit.repos.createForAuthenticatedUser(params);
|
|
203
|
-
return result.data;
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
return {
|
|
207
|
-
error: error.message,
|
|
208
|
-
status: error.status,
|
|
209
|
-
details: error.response?.data,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
}),
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
if (text.trim()) {
|
|
67
|
+
githubToken,
|
|
68
|
+
})) {
|
|
69
|
+
if (chunk.kind === 'widget') {
|
|
70
|
+
yield uiWidget({
|
|
71
|
+
agentId: context.agentId,
|
|
72
|
+
threadId,
|
|
73
|
+
widget: chunk.widget,
|
|
74
|
+
meta: event.meta,
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
217
78
|
yield agentOutput({
|
|
218
79
|
agentId: context.agentId,
|
|
219
|
-
content:
|
|
80
|
+
content: chunk.content,
|
|
220
81
|
threadId,
|
|
82
|
+
meta: event.meta,
|
|
221
83
|
});
|
|
222
84
|
}
|
|
223
|
-
for (const step of steps) {
|
|
224
|
-
for (const toolResult of step.toolResults) {
|
|
225
|
-
if (toolResult.toolName === 'list_repos') {
|
|
226
|
-
const repos = toolResult.output;
|
|
227
|
-
yield uiWidget({
|
|
228
|
-
agentId: context.agentId,
|
|
229
|
-
threadId,
|
|
230
|
-
widget: {
|
|
231
|
-
kind: 'list',
|
|
232
|
-
title: 'GitHub Repositories',
|
|
233
|
-
items: (repos || []).map((r) => ({
|
|
234
|
-
id: String(r.id),
|
|
235
|
-
label: r.full_name,
|
|
236
|
-
description: r.description || (r.private ? 'Private' : 'Public'),
|
|
237
|
-
status: 'done',
|
|
238
|
-
})),
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
85
|
}
|
|
245
86
|
catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
246
88
|
yield agentOutput({
|
|
247
89
|
agentId: context.agentId,
|
|
248
|
-
content: `I encountered an error: ${
|
|
90
|
+
content: `I encountered an error: ${message}`,
|
|
249
91
|
threadId,
|
|
250
92
|
});
|
|
251
93
|
}
|
|
252
94
|
});
|
|
253
95
|
builder.on('client:ui:widget:response', async function* (event) {
|
|
254
|
-
if (event.data?.widgetId !==
|
|
96
|
+
if (event.data?.widgetId !== GITHUB_TOKEN_WIDGET_ID)
|
|
255
97
|
return;
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
// Handle tool calls
|
|
271
|
-
builder.on('action:list_repos', async function* (event) {
|
|
272
|
-
const { githubToken } = await getCredentials();
|
|
273
|
-
const octokit = getOctokit(githubToken);
|
|
274
|
-
const { limit, visibility, sort } = (event.data || {});
|
|
275
|
-
try {
|
|
276
|
-
const result = await octokit.repos.listForAuthenticatedUser({
|
|
277
|
-
per_page: limit || 20,
|
|
278
|
-
visibility: visibility || 'all',
|
|
279
|
-
sort: sort || 'updated',
|
|
280
|
-
});
|
|
281
|
-
yield uiWidget({
|
|
282
|
-
agentId: context.agentId,
|
|
283
|
-
threadId: event.meta?.threadId,
|
|
284
|
-
widget: {
|
|
285
|
-
kind: 'list',
|
|
286
|
-
title: 'GitHub Repositories',
|
|
287
|
-
items: result.data.map((r) => ({
|
|
288
|
-
id: String(r.id),
|
|
289
|
-
label: r.full_name,
|
|
290
|
-
description: r.description || (r.private ? 'Private' : 'Public'),
|
|
291
|
-
status: 'done',
|
|
292
|
-
})),
|
|
293
|
-
},
|
|
294
|
-
});
|
|
295
|
-
yield {
|
|
296
|
-
type: 'action:list_repos:result',
|
|
297
|
-
data: {
|
|
298
|
-
...result.data,
|
|
299
|
-
output: `Successfully listed ${result.data.length} repositories.`,
|
|
300
|
-
},
|
|
301
|
-
meta: event.meta,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
yield {
|
|
306
|
-
type: 'action:list_repos:result',
|
|
307
|
-
data: {
|
|
308
|
-
error: error.message,
|
|
309
|
-
status: error.status,
|
|
310
|
-
details: error.response?.data,
|
|
311
|
-
output: `Failed to list repositories: ${error.message}`,
|
|
312
|
-
},
|
|
313
|
-
meta: event.meta,
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
builder.on('action:get_repo', async function* (event) {
|
|
318
|
-
const { githubToken } = await getCredentials();
|
|
319
|
-
const octokit = getOctokit(githubToken);
|
|
320
|
-
const { owner, repo } = (event.data || {});
|
|
321
|
-
try {
|
|
322
|
-
const result = await octokit.repos.get({ owner, repo });
|
|
323
|
-
yield {
|
|
324
|
-
type: 'action:get_repo:result',
|
|
325
|
-
data: {
|
|
326
|
-
...result.data,
|
|
327
|
-
output: `Successfully retrieved details for ${owner}/${repo}.`,
|
|
328
|
-
},
|
|
329
|
-
meta: event.meta,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
catch (error) {
|
|
333
|
-
yield {
|
|
334
|
-
type: 'action:get_repo:result',
|
|
335
|
-
data: {
|
|
336
|
-
error: error.message,
|
|
337
|
-
status: error.status,
|
|
338
|
-
details: error.response?.data,
|
|
339
|
-
output: `Failed to get repository ${owner}/${repo}: ${error.message}`,
|
|
340
|
-
},
|
|
341
|
-
meta: event.meta,
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
builder.on('action:create_repo', async function* (event) {
|
|
346
|
-
console.log('action:create_repo', event);
|
|
347
|
-
const { githubToken } = await getCredentials();
|
|
348
|
-
const octokit = getOctokit(githubToken);
|
|
349
|
-
const { name, org, description, private: isPrivate, autoInit } = (event.data || {});
|
|
350
|
-
try {
|
|
351
|
-
const params = {
|
|
352
|
-
name,
|
|
353
|
-
description,
|
|
354
|
-
private: isPrivate,
|
|
355
|
-
auto_init: autoInit,
|
|
356
|
-
};
|
|
357
|
-
const result = org
|
|
358
|
-
? await octokit.repos.createInOrg({ ...params, org })
|
|
359
|
-
: await octokit.repos.createForAuthenticatedUser(params);
|
|
360
|
-
yield {
|
|
361
|
-
type: 'action:create_repo:result',
|
|
362
|
-
data: {
|
|
363
|
-
...result.data,
|
|
364
|
-
output: `Successfully created repository ${result.data.full_name}.`,
|
|
365
|
-
},
|
|
366
|
-
meta: event.meta,
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
catch (error) {
|
|
370
|
-
yield {
|
|
371
|
-
type: 'action:create_repo:result',
|
|
372
|
-
data: {
|
|
373
|
-
error: error.message,
|
|
374
|
-
status: error.status,
|
|
375
|
-
details: error.response?.data,
|
|
376
|
-
output: `Failed to create repository ${name}: ${error.message}`,
|
|
377
|
-
},
|
|
378
|
-
meta: event.meta,
|
|
379
|
-
};
|
|
380
|
-
}
|
|
98
|
+
const githubToken = event.data.values?.githubToken;
|
|
99
|
+
if (typeof githubToken !== 'string' || !githubToken.trim())
|
|
100
|
+
return;
|
|
101
|
+
await context.storage.createVariable({
|
|
102
|
+
key: GITHUB_TOKEN_VAR,
|
|
103
|
+
value: githubToken.trim(),
|
|
104
|
+
secret: true,
|
|
105
|
+
});
|
|
106
|
+
yield agentOutput({
|
|
107
|
+
agentId: context.agentId,
|
|
108
|
+
content: 'GitHub access token saved. Retry your last request.',
|
|
109
|
+
threadId: event.meta?.threadId,
|
|
110
|
+
});
|
|
381
111
|
});
|
|
382
112
|
};
|
|
383
113
|
},
|
package/package.json
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetopenbot/github",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "Manage
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Manage GitHub repositories, issues, and pull requests from OpenBot",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"start": "node dist/index.js",
|
|
10
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
-
},
|
|
6
|
+
"main": "./dist/index.js",
|
|
12
7
|
"publishConfig": {
|
|
13
8
|
"access": "public"
|
|
14
9
|
},
|
|
15
10
|
"dependencies": {
|
|
16
|
-
"@ai-sdk/
|
|
17
|
-
"@
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"zod": "^4.4.3"
|
|
11
|
+
"@ai-sdk/mcp": "^2.0.14",
|
|
12
|
+
"@ai-sdk/openai": "^4.0.15",
|
|
13
|
+
"ai": "^7.0.29",
|
|
14
|
+
"@meetopenbot/plugin-sdk": "^0.2.0"
|
|
21
15
|
},
|
|
22
16
|
"devDependencies": {
|
|
23
17
|
"@types/node": "^25.9.1",
|
|
24
18
|
"ts-node": "^10.9.2",
|
|
25
19
|
"typescript": "^6.0.3"
|
|
20
|
+
},
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc && node ../../scripts/write-plugin-declaration.mjs",
|
|
34
|
+
"start": "node dist/index.js",
|
|
35
|
+
"dev": "tsc --watch --preserveWatchOutput",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "node --experimental-strip-types --test src/diff.test.ts"
|
|
26
38
|
}
|
|
27
39
|
}
|