@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/dist/index.js CHANGED
@@ -1,28 +1,16 @@
1
1
  import { definePlugin, shouldHandleInvoke, agentOutput, uiWidget, } from '@meetopenbot/plugin-sdk';
2
- import { Octokit } from '@octokit/rest';
3
- import { generateText, stepCountIs, tool } from 'ai';
4
- import { createOpenAI } from '@ai-sdk/openai';
5
- import { z } from 'zod';
6
- const listReposSchema = z.object({
7
- limit: z.number().optional().describe('Maximum number of repositories to return'),
8
- visibility: z.enum(['all', 'public', 'private']).optional().describe('Filter by repository visibility'),
9
- sort: z.enum(['created', 'updated', 'pushed', 'full_name']).optional().describe('How to sort the repositories'),
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 your GitHub repositories',
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 getCredentials = async () => {
25
+ const getGithubToken = async () => {
82
26
  const config = context.config;
83
- const env = process.env;
84
27
  const variables = await context.storage.getVariables();
85
- const getVal = (key, envKey) => {
86
- if (config[key])
87
- return config[key];
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 { githubToken, openaiApiKey } = await getCredentials();
116
- if (!openaiApiKey) {
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: 'github-config-form',
128
- title: 'OpenAI Configuration',
129
- description: 'Enter your OpenAI API key to get started.',
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: 'openaiApiKey',
133
- label: 'OpenAI API Key',
134
- type: 'text',
135
- placeholder: 'sk-...',
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 Configuration',
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
- const { text, steps } = await generateText({
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
- tools: {
159
- list_repos: tool({
160
- description: 'List repositories for the authenticated user',
161
- inputSchema: listReposSchema,
162
- execute: async ({ limit, visibility, sort }) => {
163
- try {
164
- const result = await octokit.repos.listForAuthenticatedUser({
165
- per_page: limit || 20,
166
- visibility: visibility || 'all',
167
- sort: sort || 'updated',
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: text,
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: ${error.message}`,
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 !== 'github-config-form')
96
+ if (event.data?.widgetId !== GITHUB_TOKEN_WIDGET_ID)
255
97
  return;
256
- const { openaiApiKey } = event.data.values || {};
257
- if (openaiApiKey) {
258
- await context.storage.createVariable({
259
- key: 'OPENAI_API_KEY',
260
- value: openaiApiKey,
261
- secret: true,
262
- });
263
- yield agentOutput({
264
- agentId: context.agentId,
265
- content: "OpenAI API key saved! I'm now ready to help you with GitHub.",
266
- threadId: event.meta?.threadId,
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.1",
4
- "description": "Manage your GitHub repositories, issues, and pull requests from OpenBot",
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/openai": "^3.0.64",
17
- "@meetopenbot/plugin-sdk": "^0.1.2",
18
- "@octokit/rest": "^22.0.0",
19
- "ai": "^6.0.185",
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
  }