@zhin.js/adapter-github 1.0.1 → 1.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/CHANGELOG.md +1118 -0
- package/README.md +70 -191
- package/adapters/github.js +51 -0
- package/adapters/github.ts +59 -0
- package/agent/prompt-sections/platform.ts +16 -0
- package/{skills/github/SKILL.md → agent/skills/github.md} +22 -5
- package/agent/tools/bind.ts +13 -0
- package/agent/tools/create_pr.ts +20 -0
- package/agent/tools/install.ts +13 -0
- package/agent/tools/patch_file.ts +19 -0
- package/agent/tools/prepare_workspace.ts +15 -0
- package/agent/tools/push_branch.ts +18 -0
- package/agent/tools/star.ts +16 -0
- package/agent/tools/subscribe.ts +16 -0
- package/agent/tools/subscriptions.ts +13 -0
- package/agent/tools/unbind.ts +13 -0
- package/agent/tools/unsubscribe.ts +15 -0
- package/agent/tools/whoami.ts +13 -0
- package/commands/endpoint/add/[id].js +3 -0
- package/commands/endpoint/add/[id].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +36 -0
- package/lib/client.js +47 -0
- package/lib/endpoint.d.ts +32 -22
- package/lib/endpoint.js +125 -145
- package/lib/gh-client.d.ts +73 -1
- package/lib/gh-client.js +99 -1
- package/lib/github-bot-handlers.d.ts +27 -0
- package/lib/github-bot-handlers.js +76 -0
- package/lib/github-channel-context.d.ts +16 -0
- package/lib/github-channel-context.js +31 -0
- package/lib/github-endpoint-commands.d.ts +1 -0
- package/lib/github-endpoint-commands.js +22 -0
- package/lib/github-runtime-state.d.ts +1 -0
- package/lib/github-runtime-state.js +6 -0
- package/lib/github-tool-handlers.d.ts +18 -0
- package/lib/github-tool-handlers.js +214 -0
- package/lib/index.d.ts +7 -32
- package/lib/index.js +7 -385
- package/lib/oauth-users.d.ts +33 -0
- package/lib/oauth-users.js +38 -0
- package/lib/protocol.d.ts +94 -0
- package/lib/protocol.js +292 -0
- package/lib/types.d.ts +6 -1
- package/lib/types.js +0 -1
- package/lib/webhook.d.ts +14 -0
- package/lib/webhook.js +88 -0
- package/lib/workspace-manager.d.ts +21 -0
- package/lib/workspace-manager.js +154 -0
- package/package.json +77 -23
- package/plugin.js +38 -0
- package/schema.json +130 -0
- package/src/client.ts +65 -0
- package/src/endpoint.ts +145 -150
- package/src/gh-client.ts +131 -0
- package/src/github-bot-handlers.ts +113 -0
- package/src/github-channel-context.ts +46 -0
- package/src/github-endpoint-commands.ts +23 -0
- package/src/github-runtime-state.ts +7 -0
- package/src/github-tool-handlers.ts +252 -0
- package/src/index.ts +48 -431
- package/src/oauth-users.ts +47 -0
- package/src/protocol.ts +425 -0
- package/src/types.ts +6 -0
- package/src/webhook.ts +130 -0
- package/src/workspace-manager.ts +168 -0
- package/lib/adapter.d.ts +0 -64
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -416
- package/lib/adapter.js.map +0 -1
- package/lib/agent-prompt.d.ts +0 -3
- package/lib/agent-prompt.d.ts.map +0 -1
- package/lib/agent-prompt.js +0 -82
- package/lib/agent-prompt.js.map +0 -1
- package/lib/endpoint.d.ts.map +0 -1
- package/lib/endpoint.js.map +0 -1
- package/lib/gh-client.d.ts.map +0 -1
- package/lib/gh-client.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/register-github-mcp.d.ts +0 -6
- package/lib/register-github-mcp.d.ts.map +0 -1
- package/lib/register-github-mcp.js +0 -35
- package/lib/register-github-mcp.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -448
- package/src/agent-prompt.ts +0 -99
- package/src/register-github-mcp.ts +0 -60
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { formatCompact } from '@zhin.js/logger';
|
|
2
|
+
import type { Message } from 'zhin.js';
|
|
3
|
+
import { getCurrentCommMessage } from '@zhin.js/agent/security';
|
|
4
|
+
import { GhClient } from './gh-client.js';
|
|
5
|
+
import type { EventType } from './types.js';
|
|
6
|
+
import type { GithubClient } from './client.js';
|
|
7
|
+
|
|
8
|
+
function oauthModel(client: GithubClient) {
|
|
9
|
+
const db = client.database as {
|
|
10
|
+
models?: Map<string, unknown>;
|
|
11
|
+
} | null | undefined;
|
|
12
|
+
return db?.models?.get('github_oauth_users') as {
|
|
13
|
+
select: () => { where: (q: object) => Promise<any[]> };
|
|
14
|
+
insert: (row: object) => Promise<void>;
|
|
15
|
+
delete: () => { where: (q: object) => Promise<void> };
|
|
16
|
+
} | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function subscriptionsModel(client: GithubClient) {
|
|
20
|
+
const db = client.database as {
|
|
21
|
+
models?: Map<string, unknown>;
|
|
22
|
+
} | null | undefined;
|
|
23
|
+
return db?.models?.get('github_subscriptions') as {
|
|
24
|
+
select: () => { where: (q: object) => Promise<any[]> };
|
|
25
|
+
insert: (row: object) => Promise<void>;
|
|
26
|
+
update: (row: object) => { where: (q: object) => Promise<void> };
|
|
27
|
+
delete: () => { where: (q: object) => Promise<void> };
|
|
28
|
+
} | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function depsLogger() {
|
|
32
|
+
return {
|
|
33
|
+
debug: (...args: unknown[]) => console.debug(...args),
|
|
34
|
+
warn: (...args: unknown[]) => console.warn(...args),
|
|
35
|
+
error: (...args: unknown[]) => console.error(...args),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function executeGithubStar(args: { action: 'star' | 'unstar' | 'check'; repo: string }, client: GithubClient, commMessage?: Message) {
|
|
40
|
+
const msg = commMessage ?? getCurrentCommMessage();
|
|
41
|
+
const gh = await client.getUserOrDefaultApi(msg?.$adapter, msg?.$sender.id);
|
|
42
|
+
switch (args.action) {
|
|
43
|
+
case 'star': {
|
|
44
|
+
const r = await gh.starRepo(args.repo);
|
|
45
|
+
return r.ok ? `⭐ 已 Star ${args.repo}` : `❌ ${r.data?.message || JSON.stringify(r.data)}`;
|
|
46
|
+
}
|
|
47
|
+
case 'unstar': {
|
|
48
|
+
const r = await gh.unstarRepo(args.repo);
|
|
49
|
+
return r.ok ? `💔 已取消 Star ${args.repo}` : `❌ ${r.data?.message || JSON.stringify(r.data)}`;
|
|
50
|
+
}
|
|
51
|
+
case 'check': {
|
|
52
|
+
const starred = await gh.isStarred(args.repo);
|
|
53
|
+
return starred ? `⭐ 已 Star ${args.repo}` : `☆ 尚未 Star ${args.repo}`;
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
return `❌ 未知操作: ${args.action}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function executeGithubBind(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
|
|
61
|
+
const log = depsLogger();
|
|
62
|
+
const msg = commMessage ?? getCurrentCommMessage();
|
|
63
|
+
if (!msg?.$adapter || !msg?.$sender?.id) return '❌ 无法获取当前用户信息';
|
|
64
|
+
|
|
65
|
+
const clientId = client.clientId;
|
|
66
|
+
if (!clientId) return '❌ Endpoint 未配置 GitHub App 或 App 无 client_id,无法进行账号绑定';
|
|
67
|
+
|
|
68
|
+
const model = oauthModel(client);
|
|
69
|
+
if (!model) return '❌ 数据库未就绪';
|
|
70
|
+
|
|
71
|
+
const [existing] = await model.select().where({ platform: msg.$adapter, platform_uid: msg.$sender.id });
|
|
72
|
+
if (existing) {
|
|
73
|
+
return `⚠️ 你已绑定 GitHub 账号: ${existing.github_login}\n如需重新绑定,请先执行 github_unbind`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const host = client.host;
|
|
78
|
+
const codeResp = await GhClient.deviceFlowRequestCode(clientId, host);
|
|
79
|
+
const tokenPromise = GhClient.deviceFlowPollToken(
|
|
80
|
+
clientId, codeResp.device_code, codeResp.interval, codeResp.expires_in, host,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const replyMsg = [
|
|
84
|
+
'🔗 请在浏览器中打开以下链接进行授权:',
|
|
85
|
+
` ${codeResp.verification_uri}`,
|
|
86
|
+
'',
|
|
87
|
+
`📋 输入验证码: **${codeResp.user_code}**`,
|
|
88
|
+
'',
|
|
89
|
+
`⏳ 等待授权中…(${Math.floor(codeResp.expires_in / 60)} 分钟内有效)`,
|
|
90
|
+
].join('\n');
|
|
91
|
+
|
|
92
|
+
tokenPromise.then(async (tokenData) => {
|
|
93
|
+
if (!tokenData) {
|
|
94
|
+
log.warn(formatCompact({ op: 'device_flow', ok: false, platform: msg.$adapter, sender: msg.$sender.id }));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const userGh = new GhClient({ host, token: tokenData.access_token });
|
|
99
|
+
const authResult = await userGh.verifyAuth();
|
|
100
|
+
const login = authResult.ok ? authResult.user : 'unknown';
|
|
101
|
+
|
|
102
|
+
await model.insert({
|
|
103
|
+
id: Date.now(),
|
|
104
|
+
platform: msg.$adapter,
|
|
105
|
+
platform_uid: msg.$sender.id,
|
|
106
|
+
github_login: login,
|
|
107
|
+
access_token: tokenData.access_token,
|
|
108
|
+
created_at: Date.now(),
|
|
109
|
+
});
|
|
110
|
+
log.debug(formatCompact({ op: 'bind', platform: msg.$adapter, sender: msg.$sender.id, login }));
|
|
111
|
+
|
|
112
|
+
if (msg?.$reply) {
|
|
113
|
+
await msg.$reply(`✅ GitHub 账号绑定成功!\n👤 ${login}`);
|
|
114
|
+
}
|
|
115
|
+
}).catch((err) => {
|
|
116
|
+
log.error('GitHub Device Flow 错误:', err);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return replyMsg;
|
|
120
|
+
} catch (e: unknown) {
|
|
121
|
+
return `❌ Device Flow 启动失败: ${e instanceof Error ? e.message : String(e)}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function executeGithubUnbind(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
|
|
126
|
+
const msg = commMessage ?? getCurrentCommMessage();
|
|
127
|
+
if (!msg?.$adapter || !msg?.$sender?.id) return '❌ 无法获取当前用户信息';
|
|
128
|
+
|
|
129
|
+
const model = oauthModel(client);
|
|
130
|
+
if (!model) return '❌ 数据库未就绪';
|
|
131
|
+
|
|
132
|
+
const [existing] = await model.select().where({ platform: msg.$adapter, platform_uid: msg.$sender.id });
|
|
133
|
+
if (!existing) return '📭 你尚未绑定 GitHub 账号';
|
|
134
|
+
|
|
135
|
+
await model.delete().where({ id: existing.id });
|
|
136
|
+
return `✅ 已解除 GitHub 账号绑定: ${existing.github_login}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function executeGithubWhoami(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
|
|
140
|
+
const msg = commMessage ?? getCurrentCommMessage();
|
|
141
|
+
if (!msg?.$adapter || !msg?.$sender?.id) return '❌ 无法获取当前用户信息';
|
|
142
|
+
|
|
143
|
+
const model = oauthModel(client);
|
|
144
|
+
if (!model) return '❌ 数据库未就绪';
|
|
145
|
+
|
|
146
|
+
const [existing] = await model.select().where({ platform: msg.$adapter, platform_uid: msg.$sender.id });
|
|
147
|
+
if (!existing) return '📭 你尚未绑定 GitHub 账号\n🔗 使用 github_bind 绑定你的账号';
|
|
148
|
+
|
|
149
|
+
const userGh = new GhClient({ host: client.host, token: existing.access_token });
|
|
150
|
+
const auth = await userGh.verifyAuth();
|
|
151
|
+
if (auth.ok) {
|
|
152
|
+
return `👤 已绑定 GitHub 账号: ${auth.user}\n📅 绑定时间: ${new Date(existing.created_at).toLocaleString('zh-CN')}`;
|
|
153
|
+
}
|
|
154
|
+
return `⚠️ 已绑定账号 ${existing.github_login},但 Token 已失效\n🔗 请执行 github_unbind 后重新 github_bind`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function executeGithubInstall(client: GithubClient) {
|
|
158
|
+
const slug = client.appSlug;
|
|
159
|
+
if (!slug) return '❌ Endpoint 未配置 GitHub App';
|
|
160
|
+
const host = client.host || 'github.com';
|
|
161
|
+
const installations = client.installations;
|
|
162
|
+
let msg = `🔗 请点击以下链接安装 GitHub App 到你的仓库:\n https://${host}/apps/${slug}/installations/new`;
|
|
163
|
+
if (installations.length) {
|
|
164
|
+
msg += `\n\n📋 当前已安装 (${installations.length}):`;
|
|
165
|
+
for (const inst of installations) {
|
|
166
|
+
msg += `\n • ${inst.account.login} (${inst.account.type})`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return msg;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function executeGithubSubscribe(args: { repo: string; events?: string }, client: GithubClient, commMessage?: Message) {
|
|
173
|
+
const msg = commMessage ?? getCurrentCommMessage();
|
|
174
|
+
if (!msg?.$adapter || !msg?.$sender.id || !msg?.$channel?.id || !msg?.$endpoint) {
|
|
175
|
+
return '❌ 无法获取当前聊天通道信息';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const model = subscriptionsModel(client);
|
|
179
|
+
if (!model) return '❌ 数据库未就绪';
|
|
180
|
+
|
|
181
|
+
const validEvents: EventType[] = ['push', 'issue', 'star', 'fork', 'unstar', 'pull_request'];
|
|
182
|
+
const events: EventType[] = args.events
|
|
183
|
+
? args.events.split(',').map((s) => s.trim()).filter((e): e is EventType => validEvents.includes(e as EventType))
|
|
184
|
+
: validEvents;
|
|
185
|
+
if (!events.length) return `❌ 无效的事件类型,可选: ${validEvents.join(', ')}`;
|
|
186
|
+
|
|
187
|
+
const [existing] = await model.select().where({
|
|
188
|
+
repo: args.repo,
|
|
189
|
+
target_id: msg.$channel?.id,
|
|
190
|
+
adapter: msg.$adapter,
|
|
191
|
+
endpoint: msg.$endpoint,
|
|
192
|
+
});
|
|
193
|
+
if (existing) {
|
|
194
|
+
await model.update({ events, target_type: msg.$channel?.type || 'private' }).where({ id: existing.id });
|
|
195
|
+
return `✅ 已更新订阅 ${args.repo}\n📡 事件: ${events.join(', ')}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await model.insert({
|
|
199
|
+
id: Date.now(),
|
|
200
|
+
repo: args.repo,
|
|
201
|
+
events,
|
|
202
|
+
target_id: msg.$channel?.id,
|
|
203
|
+
target_type: msg.$channel?.type || 'private',
|
|
204
|
+
adapter: msg.$adapter,
|
|
205
|
+
endpoint: msg.$endpoint,
|
|
206
|
+
});
|
|
207
|
+
return `✅ 已订阅 ${args.repo}\n📡 事件: ${events.join(', ')}\n📌 通知将推送到当前通道`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function executeGithubUnsubscribe(args: { repo: string }, client: GithubClient, commMessage?: Message) {
|
|
211
|
+
const msg = commMessage ?? getCurrentCommMessage();
|
|
212
|
+
if (!msg?.$adapter || !msg?.$channel?.id || !msg?.$endpoint) {
|
|
213
|
+
return '❌ 无法获取当前聊天通道信息';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const model = subscriptionsModel(client);
|
|
217
|
+
if (!model) return '❌ 数据库未就绪';
|
|
218
|
+
|
|
219
|
+
const [existing] = await model.select().where({
|
|
220
|
+
repo: args.repo,
|
|
221
|
+
target_id: msg.$channel?.id,
|
|
222
|
+
adapter: msg.$adapter,
|
|
223
|
+
endpoint: msg.$endpoint,
|
|
224
|
+
});
|
|
225
|
+
if (!existing) return `📭 当前通道未订阅 ${args.repo}`;
|
|
226
|
+
|
|
227
|
+
await model.delete().where({ id: existing.id });
|
|
228
|
+
return `✅ 已取消订阅 ${args.repo}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function executeGithubSubscriptions(_args: Record<string, never>, client: GithubClient, commMessage?: Message) {
|
|
232
|
+
const msg = commMessage ?? getCurrentCommMessage();
|
|
233
|
+
if (!msg?.$adapter || !msg?.$channel?.id || !msg?.$endpoint) {
|
|
234
|
+
return '❌ 无法获取当前聊天通道信息';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const model = subscriptionsModel(client);
|
|
238
|
+
if (!model) return '❌ 数据库未就绪';
|
|
239
|
+
|
|
240
|
+
const subs = await model.select().where({
|
|
241
|
+
target_id: msg.$channel?.id,
|
|
242
|
+
adapter: msg.$adapter,
|
|
243
|
+
endpoint: msg.$endpoint,
|
|
244
|
+
});
|
|
245
|
+
if (!subs?.length) return '📭 当前通道没有任何 GitHub 订阅';
|
|
246
|
+
|
|
247
|
+
return `📋 当前通道订阅 (${subs.length}):\n\n` +
|
|
248
|
+
subs.map((s: any) => {
|
|
249
|
+
const events = Array.isArray(s.events) ? s.events : [];
|
|
250
|
+
return ` 📦 ${s.repo}\n 📡 ${events.join(', ') || '(无事件)'}`;
|
|
251
|
+
}).join('\n\n');
|
|
252
|
+
}
|