@inkeep/agents-work-apps 0.0.0-dev-20260203033642
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/LICENSE.md +49 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.js +3 -0
- package/dist/db/runDbClient.d.ts +6 -0
- package/dist/db/runDbClient.js +9 -0
- package/dist/env.d.ts +47 -0
- package/dist/env.js +48 -0
- package/dist/github/config.d.ts +22 -0
- package/dist/github/config.js +79 -0
- package/dist/github/index.d.ts +13 -0
- package/dist/github/index.js +23 -0
- package/dist/github/installation.d.ts +66 -0
- package/dist/github/installation.js +293 -0
- package/dist/github/jwks.d.ts +20 -0
- package/dist/github/jwks.js +85 -0
- package/dist/github/mcp/auth.d.ts +10 -0
- package/dist/github/mcp/auth.js +43 -0
- package/dist/github/mcp/index.d.ts +11 -0
- package/dist/github/mcp/index.js +670 -0
- package/dist/github/mcp/schemas.d.ts +87 -0
- package/dist/github/mcp/schemas.js +69 -0
- package/dist/github/mcp/utils.d.ts +228 -0
- package/dist/github/mcp/utils.js +464 -0
- package/dist/github/oidcToken.d.ts +22 -0
- package/dist/github/oidcToken.js +140 -0
- package/dist/github/routes/setup.d.ts +7 -0
- package/dist/github/routes/setup.js +217 -0
- package/dist/github/routes/tokenExchange.d.ts +7 -0
- package/dist/github/routes/tokenExchange.js +233 -0
- package/dist/github/routes/webhooks.d.ts +12 -0
- package/dist/github/routes/webhooks.js +278 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +3 -0
- package/package.json +65 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { getLogger } from "../../logger.js";
|
|
2
|
+
import runDbClient_default from "../../db/runDbClient.js";
|
|
3
|
+
import { getWebhookSecret, isWebhookConfigured } from "../config.js";
|
|
4
|
+
import { addRepositories, deleteInstallation, getInstallationByGitHubId, removeRepositories, updateInstallationStatusByGitHubId } from "@inkeep/agents-core";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
//#region src/github/routes/webhooks.ts
|
|
9
|
+
const logger = getLogger("github-webhooks");
|
|
10
|
+
function verifyWebhookSignature(payload, signature, secret) {
|
|
11
|
+
if (!signature) return {
|
|
12
|
+
success: false,
|
|
13
|
+
error: "Missing X-Hub-Signature-256 header"
|
|
14
|
+
};
|
|
15
|
+
if (!signature.startsWith("sha256=")) return {
|
|
16
|
+
success: false,
|
|
17
|
+
error: "Invalid signature format"
|
|
18
|
+
};
|
|
19
|
+
const providedSignature = signature.slice(7);
|
|
20
|
+
const hmac = createHmac("sha256", secret);
|
|
21
|
+
hmac.update(payload);
|
|
22
|
+
const expectedSignature = hmac.digest("hex");
|
|
23
|
+
try {
|
|
24
|
+
const providedBuffer = Buffer.from(providedSignature, "hex");
|
|
25
|
+
const expectedBuffer = Buffer.from(expectedSignature, "hex");
|
|
26
|
+
if (providedBuffer.length !== expectedBuffer.length) return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: "Invalid signature"
|
|
29
|
+
};
|
|
30
|
+
if (!timingSafeEqual(providedBuffer, expectedBuffer)) return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: "Invalid signature"
|
|
33
|
+
};
|
|
34
|
+
return { success: true };
|
|
35
|
+
} catch {
|
|
36
|
+
return {
|
|
37
|
+
success: false,
|
|
38
|
+
error: "Invalid signature format"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const app = new Hono();
|
|
43
|
+
app.post("/", async (c) => {
|
|
44
|
+
if (!isWebhookConfigured()) {
|
|
45
|
+
logger.error({}, "GitHub webhook secret not configured");
|
|
46
|
+
return c.json({ error: "GitHub webhook secret not configured" }, 500);
|
|
47
|
+
}
|
|
48
|
+
const rawBody = await c.req.text();
|
|
49
|
+
const signature = c.req.header("X-Hub-Signature-256");
|
|
50
|
+
const eventType = c.req.header("X-GitHub-Event");
|
|
51
|
+
const deliveryId = c.req.header("X-GitHub-Delivery");
|
|
52
|
+
logger.info({
|
|
53
|
+
eventType,
|
|
54
|
+
deliveryId
|
|
55
|
+
}, "Received GitHub webhook");
|
|
56
|
+
const verificationResult = verifyWebhookSignature(rawBody, signature, getWebhookSecret());
|
|
57
|
+
if (!verificationResult.success) {
|
|
58
|
+
logger.warn({
|
|
59
|
+
eventType,
|
|
60
|
+
deliveryId,
|
|
61
|
+
error: verificationResult.error
|
|
62
|
+
}, "Webhook signature verification failed");
|
|
63
|
+
return c.json({ error: verificationResult.error }, 401);
|
|
64
|
+
}
|
|
65
|
+
logger.info({
|
|
66
|
+
eventType,
|
|
67
|
+
deliveryId
|
|
68
|
+
}, "Webhook signature verified");
|
|
69
|
+
if (!eventType) {
|
|
70
|
+
logger.warn({ deliveryId }, "Missing X-GitHub-Event header");
|
|
71
|
+
return c.json({ error: "Missing X-GitHub-Event header" }, 400);
|
|
72
|
+
}
|
|
73
|
+
const payload = JSON.parse(rawBody);
|
|
74
|
+
if (eventType === "installation") return handleInstallationEvent(c, payload, deliveryId);
|
|
75
|
+
if (eventType === "installation_repositories") return handleInstallationRepositoriesEvent(c, payload, deliveryId);
|
|
76
|
+
logger.info({
|
|
77
|
+
eventType,
|
|
78
|
+
deliveryId
|
|
79
|
+
}, "Received unhandled event type, acknowledging");
|
|
80
|
+
return c.json({ received: true }, 200);
|
|
81
|
+
});
|
|
82
|
+
async function handleInstallationEvent(c, payload, deliveryId) {
|
|
83
|
+
const { action, installation } = payload;
|
|
84
|
+
const installationId = String(installation.id);
|
|
85
|
+
logger.info({
|
|
86
|
+
action,
|
|
87
|
+
deliveryId,
|
|
88
|
+
installationId,
|
|
89
|
+
accountLogin: installation.account.login,
|
|
90
|
+
accountType: installation.account.type
|
|
91
|
+
}, "Processing installation webhook event");
|
|
92
|
+
try {
|
|
93
|
+
switch (action) {
|
|
94
|
+
case "created": {
|
|
95
|
+
const existing = await getInstallationByGitHubId(runDbClient_default)(installationId);
|
|
96
|
+
if (existing) if (existing.status === "pending") {
|
|
97
|
+
logger.info({
|
|
98
|
+
installationId,
|
|
99
|
+
existingId: existing.id
|
|
100
|
+
}, "Activating pending installation");
|
|
101
|
+
await updateInstallationStatusByGitHubId(runDbClient_default)({
|
|
102
|
+
gitHubInstallationId: installationId,
|
|
103
|
+
status: "active"
|
|
104
|
+
});
|
|
105
|
+
} else logger.info({
|
|
106
|
+
installationId,
|
|
107
|
+
existingId: existing.id,
|
|
108
|
+
currentStatus: existing.status
|
|
109
|
+
}, "Installation already exists, no action needed");
|
|
110
|
+
else logger.warn({
|
|
111
|
+
installationId,
|
|
112
|
+
accountLogin: installation.account.login
|
|
113
|
+
}, "Received created event for unknown installation - cannot create without tenant association");
|
|
114
|
+
if (existing && payload.repositories && payload.repositories.length > 0) {
|
|
115
|
+
const repos = payload.repositories.map((repo) => ({
|
|
116
|
+
repositoryId: String(repo.id),
|
|
117
|
+
repositoryName: repo.name,
|
|
118
|
+
repositoryFullName: repo.full_name,
|
|
119
|
+
private: repo.private
|
|
120
|
+
}));
|
|
121
|
+
await addRepositories(runDbClient_default)({
|
|
122
|
+
installationId: existing.id,
|
|
123
|
+
repositories: repos
|
|
124
|
+
});
|
|
125
|
+
logger.info({
|
|
126
|
+
installationId,
|
|
127
|
+
repositoryCount: repos.length
|
|
128
|
+
}, "Added initial repositories from installation created event");
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "deleted": {
|
|
133
|
+
const existing = await getInstallationByGitHubId(runDbClient_default)(installationId);
|
|
134
|
+
if (existing) {
|
|
135
|
+
logger.info({
|
|
136
|
+
installationId,
|
|
137
|
+
existingId: existing.id
|
|
138
|
+
}, "Deleting installation");
|
|
139
|
+
await deleteInstallation(runDbClient_default)({
|
|
140
|
+
tenantId: existing.tenantId,
|
|
141
|
+
id: existing.id
|
|
142
|
+
});
|
|
143
|
+
} else logger.warn({ installationId }, "Received deleted event for unknown installation");
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "suspend": {
|
|
147
|
+
const existing = await getInstallationByGitHubId(runDbClient_default)(installationId);
|
|
148
|
+
if (existing) {
|
|
149
|
+
logger.info({
|
|
150
|
+
installationId,
|
|
151
|
+
existingId: existing.id
|
|
152
|
+
}, "Suspending installation");
|
|
153
|
+
await updateInstallationStatusByGitHubId(runDbClient_default)({
|
|
154
|
+
gitHubInstallationId: installationId,
|
|
155
|
+
status: "suspended"
|
|
156
|
+
});
|
|
157
|
+
} else logger.warn({ installationId }, "Received suspend event for unknown installation");
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "unsuspend": {
|
|
161
|
+
const existing = await getInstallationByGitHubId(runDbClient_default)(installationId);
|
|
162
|
+
if (existing) {
|
|
163
|
+
logger.info({
|
|
164
|
+
installationId,
|
|
165
|
+
existingId: existing.id
|
|
166
|
+
}, "Unsuspending installation");
|
|
167
|
+
await updateInstallationStatusByGitHubId(runDbClient_default)({
|
|
168
|
+
gitHubInstallationId: installationId,
|
|
169
|
+
status: "active"
|
|
170
|
+
});
|
|
171
|
+
} else logger.warn({ installationId }, "Received unsuspend event for unknown installation");
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case "new_permissions_accepted":
|
|
175
|
+
logger.info({ installationId }, "New permissions accepted for installation");
|
|
176
|
+
break;
|
|
177
|
+
default: logger.info({
|
|
178
|
+
action,
|
|
179
|
+
deliveryId
|
|
180
|
+
}, "Received unhandled installation action");
|
|
181
|
+
}
|
|
182
|
+
return c.json({
|
|
183
|
+
received: true,
|
|
184
|
+
action
|
|
185
|
+
}, 200);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
logger.error({
|
|
188
|
+
error,
|
|
189
|
+
action,
|
|
190
|
+
installationId,
|
|
191
|
+
deliveryId
|
|
192
|
+
}, "Failed to process installation event");
|
|
193
|
+
return c.json({
|
|
194
|
+
received: true,
|
|
195
|
+
error: "Processing failed"
|
|
196
|
+
}, 200);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function handleInstallationRepositoriesEvent(c, payload, deliveryId) {
|
|
200
|
+
const { action, installation, repositories_added, repositories_removed } = payload;
|
|
201
|
+
const installationId = String(installation.id);
|
|
202
|
+
logger.info({
|
|
203
|
+
action,
|
|
204
|
+
deliveryId,
|
|
205
|
+
installationId,
|
|
206
|
+
accountLogin: installation.account.login,
|
|
207
|
+
addedCount: repositories_added?.length ?? 0,
|
|
208
|
+
removedCount: repositories_removed?.length ?? 0
|
|
209
|
+
}, "Processing installation_repositories webhook event");
|
|
210
|
+
try {
|
|
211
|
+
const existing = await getInstallationByGitHubId(runDbClient_default)(installationId);
|
|
212
|
+
if (!existing) {
|
|
213
|
+
logger.warn({
|
|
214
|
+
installationId,
|
|
215
|
+
accountLogin: installation.account.login
|
|
216
|
+
}, "Received repository event for unknown installation");
|
|
217
|
+
return c.json({
|
|
218
|
+
received: true,
|
|
219
|
+
warning: "Unknown installation"
|
|
220
|
+
}, 200);
|
|
221
|
+
}
|
|
222
|
+
if (existing.status === "deleted") {
|
|
223
|
+
logger.info({ installationId }, "Ignoring repository event for deleted installation");
|
|
224
|
+
return c.json({
|
|
225
|
+
received: true,
|
|
226
|
+
skipped: "Installation deleted"
|
|
227
|
+
}, 200);
|
|
228
|
+
}
|
|
229
|
+
if (action === "added" && repositories_added && repositories_added.length > 0) {
|
|
230
|
+
const repos = repositories_added.map((repo) => ({
|
|
231
|
+
repositoryId: String(repo.id),
|
|
232
|
+
repositoryName: repo.name,
|
|
233
|
+
repositoryFullName: repo.full_name,
|
|
234
|
+
private: repo.private
|
|
235
|
+
}));
|
|
236
|
+
const added = await addRepositories(runDbClient_default)({
|
|
237
|
+
installationId: existing.id,
|
|
238
|
+
repositories: repos
|
|
239
|
+
});
|
|
240
|
+
logger.info({
|
|
241
|
+
installationId,
|
|
242
|
+
addedCount: added.length,
|
|
243
|
+
requestedCount: repos.length
|
|
244
|
+
}, "Added repositories to installation");
|
|
245
|
+
}
|
|
246
|
+
if (action === "removed" && repositories_removed && repositories_removed.length > 0) {
|
|
247
|
+
const repoIds = repositories_removed.map((repo) => String(repo.id));
|
|
248
|
+
const removedCount = await removeRepositories(runDbClient_default)({
|
|
249
|
+
installationId: existing.id,
|
|
250
|
+
repositoryIds: repoIds
|
|
251
|
+
});
|
|
252
|
+
logger.info({
|
|
253
|
+
installationId,
|
|
254
|
+
removedCount,
|
|
255
|
+
requestedCount: repoIds.length
|
|
256
|
+
}, "Removed repositories from installation");
|
|
257
|
+
}
|
|
258
|
+
return c.json({
|
|
259
|
+
received: true,
|
|
260
|
+
action
|
|
261
|
+
}, 200);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
logger.error({
|
|
264
|
+
error,
|
|
265
|
+
action,
|
|
266
|
+
installationId,
|
|
267
|
+
deliveryId
|
|
268
|
+
}, "Failed to process installation_repositories event");
|
|
269
|
+
return c.json({
|
|
270
|
+
received: true,
|
|
271
|
+
error: "Processing failed"
|
|
272
|
+
}, 200);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
var webhooks_default = app;
|
|
276
|
+
|
|
277
|
+
//#endregion
|
|
278
|
+
export { webhooks_default as default, verifyWebhookSignature };
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inkeep/agents-work-apps",
|
|
3
|
+
"version": "0.0.0-dev-20260203033642",
|
|
4
|
+
"description": "First party integrations for Inkeep Agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./github": {
|
|
14
|
+
"types": "./dist/github/index.d.ts",
|
|
15
|
+
"import": "./dist/github/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
20
|
+
"@octokit/auth-app": "^8.1.2",
|
|
21
|
+
"@octokit/rest": "^22.0.1",
|
|
22
|
+
"drizzle-orm": "^0.44.7",
|
|
23
|
+
"fetch-to-node": "^2.1.0",
|
|
24
|
+
"hono": "^4.10.4",
|
|
25
|
+
"jose": "^6.1.0",
|
|
26
|
+
"minimatch": "^10.1.1",
|
|
27
|
+
"@inkeep/agents-core": "0.0.0-dev-20260203033642"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@hono/zod-openapi": "^1.1.5",
|
|
31
|
+
"zod": "^4.1.11"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.11.24",
|
|
35
|
+
"typescript": "^5.9.2",
|
|
36
|
+
"vitest": "^3.2.4"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.18.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org/"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE.md"
|
|
49
|
+
],
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/inkeep/agents.git",
|
|
53
|
+
"directory": "packages/agents-workapps"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsdown",
|
|
57
|
+
"dev": "pnpm build --watch",
|
|
58
|
+
"test": "vitest --run",
|
|
59
|
+
"lint": "biome lint --error-on-warnings",
|
|
60
|
+
"lint:fix": "biome check --write src",
|
|
61
|
+
"format": "biome format --write src",
|
|
62
|
+
"format:check": "biome format src",
|
|
63
|
+
"typecheck": "tsc --noEmit"
|
|
64
|
+
}
|
|
65
|
+
}
|