@zhin.js/adapter-github 1.0.0 → 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} +26 -9
- 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 +36 -0
- package/lib/endpoint.js +140 -0
- package/lib/gh-client.d.ts +73 -1
- package/lib/gh-client.js +109 -7
- 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 +9 -4
- package/lib/types.js +1 -2
- 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 -22
- package/plugin.js +38 -0
- package/schema.json +130 -0
- package/src/client.ts +65 -0
- package/src/endpoint.ts +169 -0
- package/src/gh-client.ts +147 -12
- 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 +10 -4
- package/src/webhook.ts +130 -0
- package/src/workspace-manager.ts +168 -0
- package/lib/adapter.d.ts +0 -63
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -415
- 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/bot.d.ts +0 -26
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -160
- package/lib/bot.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 -446
- package/src/agent-prompt.ts +0 -99
- package/src/bot.ts +0 -174
- package/src/register-github-mcp.ts +0 -60
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { executeGithubUnbind } from '../../src/github-tool-handlers.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{}>({
|
|
6
|
+
description: '解除你绑定的 GitHub 账号',
|
|
7
|
+
adapter: 'github',
|
|
8
|
+
inputSchema: z.object({}),
|
|
9
|
+
tags: ['github'],
|
|
10
|
+
async execute(input, context) {
|
|
11
|
+
return executeGithubUnbind({}, context.$client, context.message);
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { executeGithubUnsubscribe } from '../../src/github-tool-handlers.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{ repo: string }>({
|
|
6
|
+
description: '取消订阅 GitHub 仓库的 Webhook 事件',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
repo: z.string().min(1),
|
|
9
|
+
}),
|
|
10
|
+
adapter: 'github',
|
|
11
|
+
tags: ['github'],
|
|
12
|
+
async execute(input, context) {
|
|
13
|
+
return executeGithubUnsubscribe(input, context.$client, context.message);
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { executeGithubWhoami } from '../../src/github-tool-handlers.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{}>({
|
|
6
|
+
description: '查看你绑定的 GitHub 账号信息',
|
|
7
|
+
adapter: 'github',
|
|
8
|
+
inputSchema: z.object({}),
|
|
9
|
+
tags: ['github'],
|
|
10
|
+
async execute(input, context) {
|
|
11
|
+
return executeGithubWhoami({}, context.$client, context.message);
|
|
12
|
+
},
|
|
13
|
+
});
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PluginDatabaseHost } from 'zhin.js';
|
|
2
|
+
import { GhClient } from './gh-client.js';
|
|
3
|
+
import type { ResolvedGithubConfig } from './protocol.js';
|
|
4
|
+
import { WorkspaceManager } from './workspace-manager.js';
|
|
5
|
+
/** GitHub SDK surface exposed to plugin handlers and Agent tools. */
|
|
6
|
+
export declare class GithubClient {
|
|
7
|
+
#private;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly api: GhClient;
|
|
10
|
+
readonly config: ResolvedGithubConfig;
|
|
11
|
+
readonly database?: PluginDatabaseHost | undefined;
|
|
12
|
+
constructor(name: string, api: GhClient, config: ResolvedGithubConfig, database?: PluginDatabaseHost | undefined);
|
|
13
|
+
getUserOrDefaultApi(platform?: string, platformUid?: string): Promise<GhClient>;
|
|
14
|
+
get clientId(): string | null;
|
|
15
|
+
get host(): string | undefined;
|
|
16
|
+
get appSlug(): string | null;
|
|
17
|
+
get installations(): {
|
|
18
|
+
id: number;
|
|
19
|
+
account: {
|
|
20
|
+
login: string;
|
|
21
|
+
type: string;
|
|
22
|
+
};
|
|
23
|
+
target_type: string;
|
|
24
|
+
}[];
|
|
25
|
+
get workspaceManager(): WorkspaceManager;
|
|
26
|
+
}
|
|
27
|
+
export type GithubClientEventMap = Record<string, unknown>;
|
|
28
|
+
declare module '@zhin.js/feature-kit' {
|
|
29
|
+
interface AdapterClientRegistry {
|
|
30
|
+
readonly github: {
|
|
31
|
+
readonly client: GithubClient;
|
|
32
|
+
readonly events: GithubClientEventMap;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export declare const githubClient: import("@zhin.js/adapter").EndpointClientToken<GithubClient, GithubClientEventMap>;
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { defineEndpointClient } from 'zhin.js/adapter';
|
|
3
|
+
import { lookupGithubOauthAccessToken } from './oauth-users.js';
|
|
4
|
+
import { WorkspaceManager } from './workspace-manager.js';
|
|
5
|
+
/** GitHub SDK surface exposed to plugin handlers and Agent tools. */
|
|
6
|
+
export class GithubClient {
|
|
7
|
+
name;
|
|
8
|
+
api;
|
|
9
|
+
config;
|
|
10
|
+
database;
|
|
11
|
+
#workspaceManager;
|
|
12
|
+
constructor(name, api, config, database) {
|
|
13
|
+
this.name = name;
|
|
14
|
+
this.api = api;
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.database = database;
|
|
17
|
+
}
|
|
18
|
+
async getUserOrDefaultApi(platform, platformUid) {
|
|
19
|
+
if (platform && platformUid) {
|
|
20
|
+
const token = await lookupGithubOauthAccessToken(this.database, platform, platformUid);
|
|
21
|
+
if (token)
|
|
22
|
+
return this.api.withToken(token);
|
|
23
|
+
}
|
|
24
|
+
return this.api;
|
|
25
|
+
}
|
|
26
|
+
get clientId() {
|
|
27
|
+
return this.api.clientId || null;
|
|
28
|
+
}
|
|
29
|
+
get host() {
|
|
30
|
+
return this.config.host;
|
|
31
|
+
}
|
|
32
|
+
get appSlug() {
|
|
33
|
+
return this.api.appSlug || null;
|
|
34
|
+
}
|
|
35
|
+
get installations() {
|
|
36
|
+
return this.api.installations || [];
|
|
37
|
+
}
|
|
38
|
+
get workspaceManager() {
|
|
39
|
+
if (!this.#workspaceManager) {
|
|
40
|
+
const root = this.config.workspaceRoot
|
|
41
|
+
?? path.join(process.cwd(), 'data', 'github-workspaces');
|
|
42
|
+
this.#workspaceManager = new WorkspaceManager(this.api, root);
|
|
43
|
+
}
|
|
44
|
+
return this.#workspaceManager;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export const githubClient = defineEndpointClient('github');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
2
|
+
/**
|
|
3
|
+
* GithubEndpoint — lifecycle, outbound send, inbound admit.
|
|
4
|
+
*/
|
|
5
|
+
import { type EndpointSendRequest } from 'zhin.js/adapter';
|
|
6
|
+
import type { HttpHost } from '@zhin.js/host-http';
|
|
7
|
+
import type { CapabilityId, PluginDatabaseHost } from 'zhin.js';
|
|
8
|
+
import { GhClient } from './gh-client.js';
|
|
9
|
+
import { type GithubInboundComment, type ResolvedGithubConfig } from './protocol.js';
|
|
10
|
+
import { GithubClient } from './client.js';
|
|
11
|
+
export interface GithubEndpointOptions {
|
|
12
|
+
readonly id: CapabilityId;
|
|
13
|
+
readonly http?: HttpHost;
|
|
14
|
+
readonly database?: PluginDatabaseHost;
|
|
15
|
+
readonly config: ResolvedGithubConfig;
|
|
16
|
+
readonly createClient?: (config: ResolvedGithubConfig) => GhClient;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* GitHub 是代码协作面(issue/PR),无好友/群/频道等 IM 社交概念,
|
|
20
|
+
* 不适用 EndpointManagement 语义端口;本 endpoint 不暴露该端口。
|
|
21
|
+
*/
|
|
22
|
+
export declare class GithubEndpoint extends Endpoint<GithubClient> {
|
|
23
|
+
#private;
|
|
24
|
+
readonly client: GithubClient;
|
|
25
|
+
constructor(options: GithubEndpointOptions);
|
|
26
|
+
get config(): ResolvedGithubConfig;
|
|
27
|
+
start(): Promise<void>;
|
|
28
|
+
open(): void;
|
|
29
|
+
close(): void;
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
32
|
+
/** Test / internal: admit a parsed comment when open. */
|
|
33
|
+
admit(comment: GithubInboundComment): void;
|
|
34
|
+
admitPlatform(name: string, event: unknown): void;
|
|
35
|
+
}
|
|
36
|
+
export declare function defaultCreateClient(config: ResolvedGithubConfig): GhClient;
|
package/lib/endpoint.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
2
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
3
|
+
import { GhClient } from './gh-client.js';
|
|
4
|
+
import { enrichInboundContent, formatInboundContent, formatOutboundBody, githubInboundConversation, parseChannelId, } from './protocol.js';
|
|
5
|
+
import { registerGithubWebhookRoutes } from './webhook.js';
|
|
6
|
+
import { GithubClient } from './client.js';
|
|
7
|
+
/**
|
|
8
|
+
* GitHub 是代码协作面(issue/PR),无好友/群/频道等 IM 社交概念,
|
|
9
|
+
* 不适用 EndpointManagement 语义端口;本 endpoint 不暴露该端口。
|
|
10
|
+
*/
|
|
11
|
+
export class GithubEndpoint extends Endpoint {
|
|
12
|
+
#logger;
|
|
13
|
+
#options;
|
|
14
|
+
client;
|
|
15
|
+
#routeReleases = [];
|
|
16
|
+
#open = false;
|
|
17
|
+
#started = false;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
super();
|
|
20
|
+
this.#logger = getAdapterLogger('github', options.config.id);
|
|
21
|
+
this.#options = options;
|
|
22
|
+
const api = options.createClient?.(options.config) ?? defaultCreateClient(options.config);
|
|
23
|
+
this.client = new GithubClient(options.config.id, api, options.config, options.database);
|
|
24
|
+
}
|
|
25
|
+
get config() {
|
|
26
|
+
return this.client.config;
|
|
27
|
+
}
|
|
28
|
+
async start() {
|
|
29
|
+
if (this.#started)
|
|
30
|
+
return;
|
|
31
|
+
this.#started = true;
|
|
32
|
+
try {
|
|
33
|
+
const result = await this.client.api.verifyAuth();
|
|
34
|
+
if (!result.ok)
|
|
35
|
+
throw new Error(`GitHub 认证失败: ${result.message}`);
|
|
36
|
+
if (this.client.config.webhookSecret) {
|
|
37
|
+
if (!this.#options.http) {
|
|
38
|
+
throw new TypeError('GitHub webhook_secret requires httpHostToken');
|
|
39
|
+
}
|
|
40
|
+
this.#routeReleases.push(...registerGithubWebhookRoutes(this.#options.http, this));
|
|
41
|
+
this.#logger.debug(formatCompact({
|
|
42
|
+
endpoint: this.client.name,
|
|
43
|
+
op: 'webhook',
|
|
44
|
+
path: this.client.config.webhookPath,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.#logger.debug(formatCompact({
|
|
49
|
+
endpoint: this.client.name,
|
|
50
|
+
op: 'connect',
|
|
51
|
+
mode: 'api-only',
|
|
52
|
+
bot: this.client.api.authenticatedUser,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
await this.stop();
|
|
58
|
+
this.#logger.error('Failed to connect GitHub endpoint:', error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
open() {
|
|
63
|
+
this.#open = true;
|
|
64
|
+
}
|
|
65
|
+
close() {
|
|
66
|
+
this.#open = false;
|
|
67
|
+
}
|
|
68
|
+
async stop() {
|
|
69
|
+
this.#open = false;
|
|
70
|
+
for (const release of this.#routeReleases.splice(0))
|
|
71
|
+
release();
|
|
72
|
+
this.#started = false;
|
|
73
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
74
|
+
}
|
|
75
|
+
async send({ conversation, payload }) {
|
|
76
|
+
const parsed = parseChannelId(conversation.id);
|
|
77
|
+
if (!parsed)
|
|
78
|
+
throw new Error(`无效的 GitHub conversation ID: ${conversation.id}`);
|
|
79
|
+
const text = formatOutboundBody(payload);
|
|
80
|
+
const r = parsed.type === 'issue'
|
|
81
|
+
? await this.client.api.createIssueComment(parsed.repo, parsed.number, text)
|
|
82
|
+
: await this.client.api.createPRComment(parsed.repo, parsed.number, text);
|
|
83
|
+
if (!r.ok)
|
|
84
|
+
throw new Error(`发送失败: ${JSON.stringify(r.data)}`);
|
|
85
|
+
this.#logger.debug(formatCompact({
|
|
86
|
+
op: 'github_send',
|
|
87
|
+
endpoint: this.client.name,
|
|
88
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
89
|
+
messageId: r.data.id,
|
|
90
|
+
}));
|
|
91
|
+
return String(r.data.id);
|
|
92
|
+
}
|
|
93
|
+
/** Test / internal: admit a parsed comment when open. */
|
|
94
|
+
admit(comment) {
|
|
95
|
+
if (!this.#open)
|
|
96
|
+
return;
|
|
97
|
+
const botUser = this.client.config.botLogin
|
|
98
|
+
|| this.client.api.getBotLogin()
|
|
99
|
+
|| this.client.api.authenticatedUser;
|
|
100
|
+
if (botUser && comment.sender === botUser)
|
|
101
|
+
return;
|
|
102
|
+
const conversation = githubInboundConversation(String(this.#options.id), comment);
|
|
103
|
+
const content = enrichInboundContent(formatInboundContent(comment.content), this.client.config, botUser ?? undefined, comment.repo);
|
|
104
|
+
void this.emit('message.receive', {
|
|
105
|
+
conversation,
|
|
106
|
+
message: { conversation, id: comment.id },
|
|
107
|
+
content,
|
|
108
|
+
sender: { id: comment.sender, name: comment.sender },
|
|
109
|
+
endpointId: this.client.name,
|
|
110
|
+
metadata: Object.freeze({
|
|
111
|
+
repo: comment.repo,
|
|
112
|
+
kind: comment.kind,
|
|
113
|
+
createdAt: comment.createdAt,
|
|
114
|
+
}),
|
|
115
|
+
}).catch((err) => {
|
|
116
|
+
this.#logger.warn(formatCompact({
|
|
117
|
+
op: 'github_gateway_receive_failed',
|
|
118
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
119
|
+
error: err instanceof Error ? err.message : String(err),
|
|
120
|
+
}));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
admitPlatform(name, event) {
|
|
124
|
+
if (!this.#open)
|
|
125
|
+
return;
|
|
126
|
+
void this.emitPlatform(name, event).catch((error) => {
|
|
127
|
+
this.#logger.warn(formatCompact({
|
|
128
|
+
op: 'github_platform_event_failed',
|
|
129
|
+
event: name,
|
|
130
|
+
error: error instanceof Error ? error.message : String(error),
|
|
131
|
+
}));
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export function defaultCreateClient(config) {
|
|
136
|
+
const appAuth = config.appId && config.privateKey
|
|
137
|
+
? { appId: config.appId, privateKey: config.privateKey }
|
|
138
|
+
: undefined;
|
|
139
|
+
return new GhClient({ host: config.host, appAuth });
|
|
140
|
+
}
|
package/lib/gh-client.d.ts
CHANGED
|
@@ -13,6 +13,17 @@ export interface AppAuth {
|
|
|
13
13
|
/** PEM 格式私钥内容,或私钥文件路径 */
|
|
14
14
|
privateKey: string;
|
|
15
15
|
}
|
|
16
|
+
export interface GitHubBotIdentity {
|
|
17
|
+
login: string;
|
|
18
|
+
email: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
userId: number;
|
|
21
|
+
}
|
|
22
|
+
export interface GitHubCommitAuthor {
|
|
23
|
+
name: string;
|
|
24
|
+
email: string;
|
|
25
|
+
date?: string;
|
|
26
|
+
}
|
|
16
27
|
export interface GhClientOptions {
|
|
17
28
|
/** GitHub Enterprise 主机名(默认 github.com) */
|
|
18
29
|
host?: string;
|
|
@@ -153,6 +164,64 @@ export declare class GhClient {
|
|
|
153
164
|
status: number;
|
|
154
165
|
data: any;
|
|
155
166
|
}>;
|
|
167
|
+
getFileContent(repo: string, path: string, ref?: string): Promise<{
|
|
168
|
+
ok: boolean;
|
|
169
|
+
status: number;
|
|
170
|
+
data: {
|
|
171
|
+
content: string;
|
|
172
|
+
sha: string;
|
|
173
|
+
encoding: string;
|
|
174
|
+
};
|
|
175
|
+
}>;
|
|
176
|
+
createOrUpdateFile(repo: string, path: string, content: string, message: string, options?: {
|
|
177
|
+
branch?: string;
|
|
178
|
+
sha?: string;
|
|
179
|
+
}): Promise<{
|
|
180
|
+
ok: boolean;
|
|
181
|
+
status: number;
|
|
182
|
+
data: {
|
|
183
|
+
commit: {
|
|
184
|
+
sha: string;
|
|
185
|
+
html_url?: string;
|
|
186
|
+
};
|
|
187
|
+
content: {
|
|
188
|
+
html_url?: string;
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
}>;
|
|
192
|
+
getRef(repo: string, ref: string): Promise<{
|
|
193
|
+
ok: boolean;
|
|
194
|
+
status: number;
|
|
195
|
+
data: {
|
|
196
|
+
ref: string;
|
|
197
|
+
object: {
|
|
198
|
+
sha: string;
|
|
199
|
+
type: string;
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
}>;
|
|
203
|
+
createRef(repo: string, ref: string, sha: string): Promise<{
|
|
204
|
+
ok: boolean;
|
|
205
|
+
status: number;
|
|
206
|
+
data: any;
|
|
207
|
+
}>;
|
|
208
|
+
createPullRequest(repo: string, title: string, head: string, base: string, body?: string): Promise<{
|
|
209
|
+
ok: boolean;
|
|
210
|
+
status: number;
|
|
211
|
+
data: {
|
|
212
|
+
number: number;
|
|
213
|
+
html_url: string;
|
|
214
|
+
head: {
|
|
215
|
+
ref: string;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
}>;
|
|
219
|
+
getBotLogin(): string | null;
|
|
220
|
+
getCommitAuthor(): GitHubCommitAuthor | null;
|
|
221
|
+
getBotIdentitySync(): GitHubBotIdentity | null;
|
|
222
|
+
getBotIdentity(): Promise<GitHubBotIdentity | null>;
|
|
223
|
+
buildCloneUrl(repo: string, token: string): string;
|
|
224
|
+
resolveBotUserId(): Promise<number | null>;
|
|
156
225
|
starRepo(repo: string): Promise<{
|
|
157
226
|
ok: boolean;
|
|
158
227
|
status: number;
|
|
@@ -193,6 +262,10 @@ export declare class GhClient {
|
|
|
193
262
|
get appSlug(): string | null;
|
|
194
263
|
/** App 的 client_id(verifyAuth 后从 /app 获取,用于 Device Flow) */
|
|
195
264
|
private _clientId;
|
|
265
|
+
/** Bot 机器用户 numeric id(/users/{slug}[bot]) */
|
|
266
|
+
private _botUserId;
|
|
267
|
+
/** 确保 App 认证的 Installation Token 对指定 repo 有效,并返回 token */
|
|
268
|
+
ensureInstallationTokenForRepo(repo: string): Promise<string | undefined>;
|
|
196
269
|
get clientId(): string | null;
|
|
197
270
|
/**
|
|
198
271
|
* 获取仓库事件(支持 ETag 条件请求)
|
|
@@ -225,4 +298,3 @@ export declare class GhClient {
|
|
|
225
298
|
scope: string;
|
|
226
299
|
} | null>;
|
|
227
300
|
}
|
|
228
|
-
//# sourceMappingURL=gh-client.d.ts.map
|
package/lib/gh-client.js
CHANGED
|
@@ -143,11 +143,12 @@ export class GhClient {
|
|
|
143
143
|
}
|
|
144
144
|
catch (err) {
|
|
145
145
|
let data;
|
|
146
|
+
const errObj = err instanceof Error ? err : new Error(String(err));
|
|
146
147
|
try {
|
|
147
|
-
data = err.stdout ? JSON.parse(err.stdout) : { message:
|
|
148
|
+
data = err.stdout ? JSON.parse(err.stdout) : { message: errObj.message };
|
|
148
149
|
}
|
|
149
150
|
catch {
|
|
150
|
-
data = { message: err.stderr?.trim() ||
|
|
151
|
+
data = { message: err.stderr?.trim() || errObj.message };
|
|
151
152
|
}
|
|
152
153
|
return { ok: false, status: err.exitCode || 0, data };
|
|
153
154
|
}
|
|
@@ -180,6 +181,7 @@ export class GhClient {
|
|
|
180
181
|
this._appSlug = appData.slug || null;
|
|
181
182
|
if (appData.client_id)
|
|
182
183
|
this._clientId = appData.client_id;
|
|
184
|
+
await this.resolveBotUserId();
|
|
183
185
|
const repos = this._repoToInstallation.size;
|
|
184
186
|
return { ok: true, user: name, message: `GitHub App: ${name} (${this._allInstallations.length} 安装, ${repos} 仓库)` };
|
|
185
187
|
}
|
|
@@ -187,7 +189,7 @@ export class GhClient {
|
|
|
187
189
|
return { ok: false, user: '', message: `App Token 验证失败 (${appRes.status}): ${errBody}` };
|
|
188
190
|
}
|
|
189
191
|
catch (e) {
|
|
190
|
-
return { ok: false, user: '', message: e.message || 'App 认证失败' };
|
|
192
|
+
return { ok: false, user: '', message: (e instanceof Error ? e.message : String(e)) || 'App 认证失败' };
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
// Token 模式(用户绑定)或 gh CLI 默认模式
|
|
@@ -208,7 +210,7 @@ export class GhClient {
|
|
|
208
210
|
return { ok: true, user: login, message: `gh CLI: ${login}` };
|
|
209
211
|
}
|
|
210
212
|
catch (e) {
|
|
211
|
-
return { ok: false, user: '', message: e.message || 'gh 认证检查失败' };
|
|
213
|
+
return { ok: false, user: '', message: (e instanceof Error ? e.message : String(e)) || 'gh 认证检查失败' };
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
216
|
// ── Issue 评论 (聊天核心) ─────────────────────────────────────────
|
|
@@ -264,6 +266,95 @@ export class GhClient {
|
|
|
264
266
|
async deletePRReviewComment(repo, commentId) {
|
|
265
267
|
return this.del(`/repos/${repo}/pulls/comments/${commentId}`);
|
|
266
268
|
}
|
|
269
|
+
// ── Contents / Pulls(Bot 写操作)──────────────────────────────────
|
|
270
|
+
async getFileContent(repo, path, ref) {
|
|
271
|
+
let apiPath = `/repos/${repo}/contents/${path.split('/').map(encodeURIComponent).join('/')}`;
|
|
272
|
+
if (ref)
|
|
273
|
+
apiPath += `?ref=${encodeURIComponent(ref)}`;
|
|
274
|
+
return this.get(apiPath);
|
|
275
|
+
}
|
|
276
|
+
async createOrUpdateFile(repo, path, content, message, options) {
|
|
277
|
+
const body = {
|
|
278
|
+
message,
|
|
279
|
+
content: Buffer.from(content, 'utf-8').toString('base64'),
|
|
280
|
+
};
|
|
281
|
+
if (options?.branch)
|
|
282
|
+
body.branch = options.branch;
|
|
283
|
+
if (options?.sha)
|
|
284
|
+
body.sha = options.sha;
|
|
285
|
+
const apiPath = `/repos/${repo}/contents/${path.split('/').map(encodeURIComponent).join('/')}`;
|
|
286
|
+
return this.put(apiPath, body);
|
|
287
|
+
}
|
|
288
|
+
async getRef(repo, ref) {
|
|
289
|
+
return this.get(`/repos/${repo}/git/ref/${encodeURIComponent(ref)}`);
|
|
290
|
+
}
|
|
291
|
+
async createRef(repo, ref, sha) {
|
|
292
|
+
return this.post(`/repos/${repo}/git/refs`, { ref, sha });
|
|
293
|
+
}
|
|
294
|
+
async createPullRequest(repo, title, head, base, body) {
|
|
295
|
+
return this.post(`/repos/${repo}/pulls`, { title, head, base, body: body ?? '' });
|
|
296
|
+
}
|
|
297
|
+
getBotLogin() {
|
|
298
|
+
if (this._user)
|
|
299
|
+
return this._user;
|
|
300
|
+
if (this._appSlug)
|
|
301
|
+
return `${this._appSlug}[bot]`;
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
getCommitAuthor() {
|
|
305
|
+
const identity = this.getBotIdentitySync();
|
|
306
|
+
if (!identity)
|
|
307
|
+
return null;
|
|
308
|
+
return { name: identity.login, email: identity.email, date: new Date().toISOString() };
|
|
309
|
+
}
|
|
310
|
+
getBotIdentitySync() {
|
|
311
|
+
const slug = this._appSlug;
|
|
312
|
+
const login = this.getBotLogin();
|
|
313
|
+
if (!slug || !login || this._botUserId == null)
|
|
314
|
+
return null;
|
|
315
|
+
return {
|
|
316
|
+
login,
|
|
317
|
+
slug,
|
|
318
|
+
userId: this._botUserId,
|
|
319
|
+
email: `${this._botUserId}+${login}@users.noreply.github.com`,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async getBotIdentity() {
|
|
323
|
+
if (!this._appAuth)
|
|
324
|
+
return null;
|
|
325
|
+
if (this._botUserId == null)
|
|
326
|
+
await this.resolveBotUserId();
|
|
327
|
+
return this.getBotIdentitySync();
|
|
328
|
+
}
|
|
329
|
+
buildCloneUrl(repo, token) {
|
|
330
|
+
const host = this.host || 'github.com';
|
|
331
|
+
return `https://x-access-token:${encodeURIComponent(token)}@${host}/${repo}.git`;
|
|
332
|
+
}
|
|
333
|
+
async resolveBotUserId() {
|
|
334
|
+
if (this._botUserId != null)
|
|
335
|
+
return this._botUserId;
|
|
336
|
+
const slug = this._appSlug;
|
|
337
|
+
if (!slug)
|
|
338
|
+
return null;
|
|
339
|
+
const login = `${slug}[bot]`;
|
|
340
|
+
const baseUrl = this.host ? `https://${this.host}/api/v3` : 'https://api.github.com';
|
|
341
|
+
try {
|
|
342
|
+
const res = await fetch(`${baseUrl}/users/${encodeURIComponent(login)}`, {
|
|
343
|
+
headers: { Accept: 'application/vnd.github+json' },
|
|
344
|
+
});
|
|
345
|
+
if (!res.ok)
|
|
346
|
+
return null;
|
|
347
|
+
const data = (await res.json());
|
|
348
|
+
if (typeof data.id === 'number') {
|
|
349
|
+
this._botUserId = data.id;
|
|
350
|
+
return data.id;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
267
358
|
// ── Star ─────────────────────────────────────────────────────────
|
|
268
359
|
async starRepo(repo) {
|
|
269
360
|
return this.put(`/user/starred/${repo}`);
|
|
@@ -383,6 +474,15 @@ export class GhClient {
|
|
|
383
474
|
get appSlug() { return this._appSlug; }
|
|
384
475
|
/** App 的 client_id(verifyAuth 后从 /app 获取,用于 Device Flow) */
|
|
385
476
|
_clientId = null;
|
|
477
|
+
/** Bot 机器用户 numeric id(/users/{slug}[bot]) */
|
|
478
|
+
_botUserId = null;
|
|
479
|
+
/** 确保 App 认证的 Installation Token 对指定 repo 有效,并返回 token */
|
|
480
|
+
async ensureInstallationTokenForRepo(repo) {
|
|
481
|
+
if (!this._appAuth)
|
|
482
|
+
return this.token;
|
|
483
|
+
await this.ensureTokenForRepo(repo);
|
|
484
|
+
return this.token;
|
|
485
|
+
}
|
|
386
486
|
get clientId() { return this._clientId; }
|
|
387
487
|
// ── 事件轮询 ─────────────────────────────────────────────────────
|
|
388
488
|
/**
|
|
@@ -412,7 +512,8 @@ export class GhClient {
|
|
|
412
512
|
}
|
|
413
513
|
catch (err) {
|
|
414
514
|
// 304 Not Modified — gh 会以非 0 退出码返回
|
|
415
|
-
|
|
515
|
+
const ghErr = err instanceof Error ? err : null;
|
|
516
|
+
if (ghErr?.stderr?.includes('304') || ghErr?.exitCode === 1) {
|
|
416
517
|
return { events: [], etag: etag || null };
|
|
417
518
|
}
|
|
418
519
|
throw err;
|
|
@@ -441,7 +542,9 @@ export class GhClient {
|
|
|
441
542
|
});
|
|
442
543
|
}
|
|
443
544
|
catch (e) {
|
|
444
|
-
|
|
545
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
546
|
+
const errCause = e instanceof Error ? e.cause?.code : undefined;
|
|
547
|
+
throw new Error(`Device Flow 请求失败: 无法连接 ${baseUrl} (${errCause || errMsg})。` +
|
|
445
548
|
`Device Flow 需要访问 github.com(非 api.github.com),请检查网络/代理设置。`);
|
|
446
549
|
}
|
|
447
550
|
if (!res.ok) {
|
|
@@ -491,4 +594,3 @@ export class GhClient {
|
|
|
491
594
|
return null;
|
|
492
595
|
}
|
|
493
596
|
}
|
|
494
|
-
//# sourceMappingURL=gh-client.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub App Bot workflow handlers (Installation Token identity).
|
|
3
|
+
*/
|
|
4
|
+
import type { Message } from 'zhin.js';
|
|
5
|
+
import type { GithubClient } from './client.js';
|
|
6
|
+
export declare function executeGithubPrepareWorkspace(args: {
|
|
7
|
+
repo?: string;
|
|
8
|
+
}, client: GithubClient, commMessage?: Message): Promise<string>;
|
|
9
|
+
export declare function executeGithubPatchFile(args: {
|
|
10
|
+
repo?: string;
|
|
11
|
+
path: string;
|
|
12
|
+
content: string;
|
|
13
|
+
message: string;
|
|
14
|
+
branch?: string;
|
|
15
|
+
}, client: GithubClient, commMessage?: Message): Promise<string>;
|
|
16
|
+
export declare function executeGithubPushBranch(args: {
|
|
17
|
+
repo?: string;
|
|
18
|
+
branch?: string;
|
|
19
|
+
message: string;
|
|
20
|
+
}, client: GithubClient, commMessage?: Message): Promise<string>;
|
|
21
|
+
export declare function executeGithubCreatePr(args: {
|
|
22
|
+
repo?: string;
|
|
23
|
+
title: string;
|
|
24
|
+
body?: string;
|
|
25
|
+
head?: string;
|
|
26
|
+
base?: string;
|
|
27
|
+
}, client: GithubClient, commMessage?: Message): Promise<string>;
|