patchwork-os 0.2.0-alpha.34 → 0.2.0-alpha.35

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.
Files changed (117) hide show
  1. package/README.md +142 -88
  2. package/deploy/bootstrap-new-vps.sh +12 -12
  3. package/deploy/bootstrap-vps.sh +6 -3
  4. package/deploy/deploy-landing.sh +59 -2
  5. package/dist/bridge.js +32 -1
  6. package/dist/bridge.js.map +1 -1
  7. package/dist/commands/recipe.js +18 -1
  8. package/dist/commands/recipe.js.map +1 -1
  9. package/dist/commands/recipeInstall.d.ts +79 -1
  10. package/dist/commands/recipeInstall.js +241 -13
  11. package/dist/commands/recipeInstall.js.map +1 -1
  12. package/dist/connectors/asana.d.ts +198 -0
  13. package/dist/connectors/asana.js +680 -0
  14. package/dist/connectors/asana.js.map +1 -0
  15. package/dist/connectors/baseConnector.d.ts +16 -0
  16. package/dist/connectors/baseConnector.js +106 -24
  17. package/dist/connectors/baseConnector.js.map +1 -1
  18. package/dist/connectors/discord.d.ts +150 -0
  19. package/dist/connectors/discord.js +544 -0
  20. package/dist/connectors/discord.js.map +1 -0
  21. package/dist/connectors/github.js +11 -4
  22. package/dist/connectors/github.js.map +1 -1
  23. package/dist/connectors/gitlab.d.ts +180 -0
  24. package/dist/connectors/gitlab.js +582 -0
  25. package/dist/connectors/gitlab.js.map +1 -0
  26. package/dist/connectors/gmail.js +11 -0
  27. package/dist/connectors/gmail.js.map +1 -1
  28. package/dist/connectors/googleDrive.d.ts +34 -0
  29. package/dist/connectors/googleDrive.js +305 -0
  30. package/dist/connectors/googleDrive.js.map +1 -0
  31. package/dist/connectors/linear.js +23 -4
  32. package/dist/connectors/linear.js.map +1 -1
  33. package/dist/connectors/pagerduty.d.ts +160 -0
  34. package/dist/connectors/pagerduty.js +464 -0
  35. package/dist/connectors/pagerduty.js.map +1 -0
  36. package/dist/connectors/slack.d.ts +1 -1
  37. package/dist/connectors/slack.js +3 -1
  38. package/dist/connectors/slack.js.map +1 -1
  39. package/dist/featureFlags.d.ts +17 -11
  40. package/dist/featureFlags.js +52 -47
  41. package/dist/featureFlags.js.map +1 -1
  42. package/dist/index.js +255 -127
  43. package/dist/index.js.map +1 -1
  44. package/dist/recipeOrchestration.d.ts +7 -0
  45. package/dist/recipeOrchestration.js +149 -28
  46. package/dist/recipeOrchestration.js.map +1 -1
  47. package/dist/recipes/captureForRunlog.d.ts +27 -0
  48. package/dist/recipes/captureForRunlog.js +128 -0
  49. package/dist/recipes/captureForRunlog.js.map +1 -0
  50. package/dist/recipes/chainedRunner.d.ts +39 -3
  51. package/dist/recipes/chainedRunner.js +183 -28
  52. package/dist/recipes/chainedRunner.js.map +1 -1
  53. package/dist/recipes/detectSilentFail.d.ts +34 -0
  54. package/dist/recipes/detectSilentFail.js +105 -0
  55. package/dist/recipes/detectSilentFail.js.map +1 -0
  56. package/dist/recipes/manifest.js +21 -6
  57. package/dist/recipes/manifest.js.map +1 -1
  58. package/dist/recipes/replayRun.d.ts +62 -0
  59. package/dist/recipes/replayRun.js +97 -0
  60. package/dist/recipes/replayRun.js.map +1 -0
  61. package/dist/recipes/scheduler.js +102 -11
  62. package/dist/recipes/scheduler.js.map +1 -1
  63. package/dist/recipes/schemaGenerator.js +3 -3
  64. package/dist/recipes/schemaGenerator.js.map +1 -1
  65. package/dist/recipes/toolRegistry.d.ts +5 -0
  66. package/dist/recipes/toolRegistry.js +9 -0
  67. package/dist/recipes/toolRegistry.js.map +1 -1
  68. package/dist/recipes/tools/asana.d.ts +16 -0
  69. package/dist/recipes/tools/asana.js +524 -0
  70. package/dist/recipes/tools/asana.js.map +1 -0
  71. package/dist/recipes/tools/discord.d.ts +18 -0
  72. package/dist/recipes/tools/discord.js +254 -0
  73. package/dist/recipes/tools/discord.js.map +1 -0
  74. package/dist/recipes/tools/github.js +29 -4
  75. package/dist/recipes/tools/github.js.map +1 -1
  76. package/dist/recipes/tools/gitlab.d.ts +11 -0
  77. package/dist/recipes/tools/gitlab.js +285 -0
  78. package/dist/recipes/tools/gitlab.js.map +1 -0
  79. package/dist/recipes/tools/gmail.d.ts +1 -1
  80. package/dist/recipes/tools/gmail.js +230 -6
  81. package/dist/recipes/tools/gmail.js.map +1 -1
  82. package/dist/recipes/tools/googleDrive.d.ts +1 -0
  83. package/dist/recipes/tools/googleDrive.js +55 -0
  84. package/dist/recipes/tools/googleDrive.js.map +1 -0
  85. package/dist/recipes/tools/index.d.ts +6 -0
  86. package/dist/recipes/tools/index.js +6 -0
  87. package/dist/recipes/tools/index.js.map +1 -1
  88. package/dist/recipes/tools/linear.d.ts +2 -1
  89. package/dist/recipes/tools/linear.js +222 -1
  90. package/dist/recipes/tools/linear.js.map +1 -1
  91. package/dist/recipes/tools/meetingNotes.d.ts +21 -0
  92. package/dist/recipes/tools/meetingNotes.js +701 -0
  93. package/dist/recipes/tools/meetingNotes.js.map +1 -0
  94. package/dist/recipes/tools/pagerduty.d.ts +15 -0
  95. package/dist/recipes/tools/pagerduty.js +451 -0
  96. package/dist/recipes/tools/pagerduty.js.map +1 -0
  97. package/dist/recipes/tools/slack.js +8 -2
  98. package/dist/recipes/tools/slack.js.map +1 -1
  99. package/dist/recipes/yamlRunner.d.ts +23 -2
  100. package/dist/recipes/yamlRunner.js +263 -58
  101. package/dist/recipes/yamlRunner.js.map +1 -1
  102. package/dist/recipesHttp.d.ts +32 -0
  103. package/dist/recipesHttp.js +310 -1
  104. package/dist/recipesHttp.js.map +1 -1
  105. package/dist/runLog.d.ts +64 -2
  106. package/dist/runLog.js +116 -2
  107. package/dist/runLog.js.map +1 -1
  108. package/dist/server.d.ts +8 -0
  109. package/dist/server.js +331 -9
  110. package/dist/server.js.map +1 -1
  111. package/dist/streamableHttp.d.ts +31 -1
  112. package/dist/streamableHttp.js +20 -2
  113. package/dist/streamableHttp.js.map +1 -1
  114. package/dist/tools/slackPostMessage.js +1 -1
  115. package/dist/tools/slackPostMessage.js.map +1 -1
  116. package/package.json +19 -4
  117. package/templates/recipes/project-health-check.yaml +1 -1
@@ -0,0 +1,582 @@
1
+ /**
2
+ * GitLab connector — read-only: projects, issues, merge requests, current user.
3
+ *
4
+ * OAuth 2.0 Authorization Code Grant. GitLab is a confidential client (bridge
5
+ * holds client secret), so PKCE is not used here. Refresh tokens are issued;
6
+ * access tokens default to 2 hours.
7
+ *
8
+ * Auth: standard OAuth 2.0 with `client_id` + `client_secret` + `redirect_uri`.
9
+ * - Env vars: GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET (mirrors asana/discord)
10
+ * - Optional: GITLAB_BASE_URL — override base for self-hosted GitLab.
11
+ * Default https://gitlab.com. `/api/v4` is appended internally.
12
+ * - Stored: getSecretJsonSync("gitlab") → GitLabTokens
13
+ * - Header: Authorization: Bearer <access_token>
14
+ *
15
+ * Read-only scopes: read_user read_api read_repository.
16
+ *
17
+ * Read tools: getCurrentUser, listProjects, listIssues, getIssue,
18
+ * listMergeRequests.
19
+ * Write methods (createIssue, createMergeRequestNote) are deferred to a
20
+ * follow-up PR.
21
+ *
22
+ * HTTP routes (wired in src/server.ts):
23
+ * GET /connections/gitlab/auth — redirect to GitLab consent
24
+ * GET /connections/gitlab/callback — exchange code for tokens
25
+ * POST /connections/gitlab/test — ping GitLab API
26
+ * DELETE /connections/gitlab — best-effort revoke + clear local
27
+ *
28
+ * Extends BaseConnector for unified auth, retry, rate-limit, error handling.
29
+ * Token refresh is delegated to BaseConnector.refreshToken() via apiCall.
30
+ */
31
+ import crypto from "node:crypto";
32
+ import { BaseConnector, } from "./baseConnector.js";
33
+ import { escHtml } from "./htmlEscape.js";
34
+ import { deleteSecretJsonSync, getSecretJsonSync, storeSecretJsonSync, } from "./tokenStorage.js";
35
+ const SCOPES = ["read_user", "read_api", "read_repository"];
36
+ // ── Config ───────────────────────────────────────────────────────────────────
37
+ function clientId() {
38
+ return process.env.GITLAB_CLIENT_ID ?? "";
39
+ }
40
+ function clientSecret() {
41
+ return process.env.GITLAB_CLIENT_SECRET ?? "";
42
+ }
43
+ /**
44
+ * Base host for the GitLab instance, e.g. `https://gitlab.com` (default) or
45
+ * `https://gitlab.example.com` for self-hosted. Trailing slash trimmed; the
46
+ * `/api/v4` path is appended internally so users only configure host.
47
+ */
48
+ export function gitlabBaseUrl() {
49
+ return (process.env.GITLAB_BASE_URL ?? "https://gitlab.com").replace(/\/$/, "");
50
+ }
51
+ function apiBase() {
52
+ return `${gitlabBaseUrl()}/api/v4`;
53
+ }
54
+ function authorizeUrl() {
55
+ return `${gitlabBaseUrl()}/oauth/authorize`;
56
+ }
57
+ function tokenUrl() {
58
+ return `${gitlabBaseUrl()}/oauth/token`;
59
+ }
60
+ function revokeUrl() {
61
+ return `${gitlabBaseUrl()}/oauth/revoke`;
62
+ }
63
+ function redirectUri() {
64
+ const base = (process.env.PATCHWORK_BRIDGE_URL ??
65
+ `http://localhost:${process.env.PATCHWORK_BRIDGE_PORT ?? "3101"}`).replace(/\/$/, "");
66
+ return `${base}/connections/gitlab/callback`;
67
+ }
68
+ function isConfigured() {
69
+ return Boolean(clientId() && clientSecret());
70
+ }
71
+ // ── Token persistence ────────────────────────────────────────────────────────
72
+ export function loadTokens() {
73
+ return getSecretJsonSync("gitlab");
74
+ }
75
+ export function saveTokens(tokens) {
76
+ storeSecretJsonSync("gitlab", tokens);
77
+ }
78
+ export function clearTokens() {
79
+ try {
80
+ deleteSecretJsonSync("gitlab");
81
+ }
82
+ catch {
83
+ // ignore
84
+ }
85
+ }
86
+ export function isConnected() {
87
+ return loadTokens() !== null;
88
+ }
89
+ // ── State (CSRF) ─────────────────────────────────────────────────────────────
90
+ const pendingStates = new Set();
91
+ const STATE_TTL_MS = 5 * 60 * 1000;
92
+ function generateState() {
93
+ const state = crypto.randomBytes(32).toString("hex");
94
+ pendingStates.add(state);
95
+ setTimeout(() => pendingStates.delete(state), STATE_TTL_MS);
96
+ return state;
97
+ }
98
+ function consumeState(state) {
99
+ if (!pendingStates.has(state))
100
+ return false;
101
+ pendingStates.delete(state);
102
+ return true;
103
+ }
104
+ // ── Connector class ──────────────────────────────────────────────────────────
105
+ export class GitLabConnector extends BaseConnector {
106
+ providerName = "gitlab";
107
+ getOAuthConfig() {
108
+ const tokens = loadTokens();
109
+ const id = clientId() || tokens?._client_id || "";
110
+ const secret = clientSecret() || tokens?._client_secret || "";
111
+ if (!id || !secret)
112
+ return null;
113
+ return {
114
+ clientId: id,
115
+ clientSecret: secret,
116
+ tokenEndpoint: tokenUrl(),
117
+ scopes: SCOPES,
118
+ };
119
+ }
120
+ async authenticate() {
121
+ const tokens = loadTokens();
122
+ if (!tokens) {
123
+ throw new Error("GitLab not connected. Visit /connections/gitlab/auth to authorize.");
124
+ }
125
+ return {
126
+ token: tokens.access_token,
127
+ refreshToken: tokens.refresh_token,
128
+ expiresAt: tokens.expires_at ? new Date(tokens.expires_at) : undefined,
129
+ scopes: tokens.scope ? tokens.scope.split(" ") : SCOPES,
130
+ };
131
+ }
132
+ /**
133
+ * Persist refreshed tokens after BaseConnector.refreshToken() updates
134
+ * `this.auth`. Mirror to our GitLab-specific JSON so loadTokens() keeps
135
+ * working for HTTP probes.
136
+ */
137
+ async saveTokens() {
138
+ await super.saveTokens();
139
+ if (!this.auth)
140
+ return;
141
+ const existing = loadTokens();
142
+ saveTokens({
143
+ access_token: this.auth.token,
144
+ refresh_token: this.auth.refreshToken,
145
+ expires_at: this.auth.expiresAt
146
+ ? this.auth.expiresAt.getTime()
147
+ : undefined,
148
+ scope: this.auth.scopes?.join(" "),
149
+ token_type: existing?.token_type ?? "Bearer",
150
+ _client_id: existing?._client_id,
151
+ _client_secret: existing?._client_secret,
152
+ _base_url: existing?._base_url,
153
+ username: existing?.username,
154
+ user_id: existing?.user_id,
155
+ email: existing?.email,
156
+ connected_at: existing?.connected_at ?? new Date().toISOString(),
157
+ });
158
+ }
159
+ async healthCheck() {
160
+ try {
161
+ const result = await this.apiCall(async (token) => {
162
+ const res = await fetch(`${apiBase()}/user`, {
163
+ headers: this.buildHeaders(token),
164
+ });
165
+ if (!res.ok)
166
+ throw res;
167
+ return res.json();
168
+ });
169
+ if ("error" in result)
170
+ return { ok: false, error: result.error };
171
+ return { ok: true };
172
+ }
173
+ catch (err) {
174
+ return { ok: false, error: this.normalizeError(err) };
175
+ }
176
+ }
177
+ normalizeError(error) {
178
+ if (error instanceof Response) {
179
+ const s = error.status;
180
+ if (s === 401)
181
+ return {
182
+ code: "auth_expired",
183
+ message: "GitLab authentication failed — token expired or revoked",
184
+ retryable: true,
185
+ suggestedAction: "Reconnect via /connections/gitlab/auth",
186
+ };
187
+ if (s === 403)
188
+ return {
189
+ code: "permission_denied",
190
+ message: "Insufficient GitLab permissions for this resource",
191
+ retryable: false,
192
+ };
193
+ if (s === 404)
194
+ return {
195
+ code: "not_found",
196
+ message: "GitLab resource not found",
197
+ retryable: false,
198
+ };
199
+ if (s === 429) {
200
+ const reset = error.headers.get("ratelimit-reset");
201
+ const retryAfter = error.headers.get("retry-after");
202
+ return {
203
+ code: "rate_limited",
204
+ message: `GitLab API rate limit exceeded${retryAfter ? ` (retry after ${retryAfter}s)` : ""}`,
205
+ retryable: true,
206
+ suggestedAction: retryAfter
207
+ ? `Wait ${retryAfter}s and retry`
208
+ : "Wait and retry",
209
+ providerDetail: {
210
+ ...(retryAfter ? { retryAfter } : {}),
211
+ ...(reset ? { reset } : {}),
212
+ },
213
+ };
214
+ }
215
+ return {
216
+ code: "provider_error",
217
+ message: `GitLab API error: HTTP ${s}`,
218
+ retryable: s >= 500,
219
+ };
220
+ }
221
+ if (error instanceof Error) {
222
+ if (error.message.includes("ENOTFOUND") ||
223
+ error.message.includes("ECONNREFUSED")) {
224
+ return {
225
+ code: "network_error",
226
+ message: `Cannot connect to GitLab: ${error.message}`,
227
+ retryable: true,
228
+ };
229
+ }
230
+ }
231
+ return {
232
+ code: "provider_error",
233
+ message: error instanceof Error ? error.message : String(error),
234
+ retryable: false,
235
+ };
236
+ }
237
+ getStatus() {
238
+ const tokens = loadTokens();
239
+ return {
240
+ id: "gitlab",
241
+ status: tokens ? "connected" : "disconnected",
242
+ lastSync: tokens?.connected_at,
243
+ workspace: tokens?.username,
244
+ };
245
+ }
246
+ // ── API Methods ────────────────────────────────────────────────────────────
247
+ async getCurrentUser() {
248
+ const result = await this.apiCall(async (token) => {
249
+ const res = await fetch(`${apiBase()}/user`, {
250
+ headers: this.buildHeaders(token),
251
+ });
252
+ this.captureRateLimit(res);
253
+ if (!res.ok)
254
+ throw res;
255
+ return res.json();
256
+ });
257
+ if ("error" in result)
258
+ throw new Error(result.error.message);
259
+ return result.data;
260
+ }
261
+ async listProjects(params = {}) {
262
+ const limit = Math.min(Math.max(params.limit ?? 50, 1), 100);
263
+ const result = await this.apiCall(async (token) => {
264
+ const qs = new URLSearchParams({
265
+ per_page: String(limit),
266
+ // Default to membership=true so users only see their projects unless
267
+ // they explicitly opt out (e.g. for public-search use cases).
268
+ membership: String(params.membership ?? true),
269
+ });
270
+ if (params.owned)
271
+ qs.set("owned", "true");
272
+ if (params.search)
273
+ qs.set("search", params.search);
274
+ const res = await fetch(`${apiBase()}/projects?${qs}`, {
275
+ headers: this.buildHeaders(token),
276
+ });
277
+ this.captureRateLimit(res);
278
+ if (!res.ok)
279
+ throw res;
280
+ return res.json();
281
+ });
282
+ if ("error" in result)
283
+ throw new Error(result.error.message);
284
+ return result.data;
285
+ }
286
+ async listIssues(params = {}) {
287
+ if (params.state && !["opened", "closed", "all"].includes(params.state)) {
288
+ throw new Error(`listIssues: invalid state "${params.state}" (expected opened|closed|all)`);
289
+ }
290
+ const limit = Math.min(Math.max(params.limit ?? 50, 1), 100);
291
+ const result = await this.apiCall(async (token) => {
292
+ const qs = new URLSearchParams({ per_page: String(limit) });
293
+ if (params.state)
294
+ qs.set("state", params.state);
295
+ let url;
296
+ if (params.projectId !== undefined && params.projectId !== "") {
297
+ url = `${apiBase()}/projects/${encodeURIComponent(String(params.projectId))}/issues?${qs}`;
298
+ }
299
+ else {
300
+ // Per-user issues endpoint. `assigned_to_me` is the default when
301
+ // `assignedToMe: true`; otherwise it returns issues created by user.
302
+ if (params.assignedToMe)
303
+ qs.set("scope", "assigned_to_me");
304
+ url = `${apiBase()}/issues?${qs}`;
305
+ }
306
+ const res = await fetch(url, { headers: this.buildHeaders(token) });
307
+ this.captureRateLimit(res);
308
+ if (!res.ok)
309
+ throw res;
310
+ return res.json();
311
+ });
312
+ if ("error" in result)
313
+ throw new Error(result.error.message);
314
+ return result.data;
315
+ }
316
+ async getIssue(projectId, issueIid) {
317
+ if (projectId === undefined || projectId === "" || !issueIid) {
318
+ throw new Error("getIssue requires projectId and issueIid");
319
+ }
320
+ const result = await this.apiCall(async (token) => {
321
+ const res = await fetch(`${apiBase()}/projects/${encodeURIComponent(String(projectId))}/issues/${issueIid}`, { headers: this.buildHeaders(token) });
322
+ this.captureRateLimit(res);
323
+ if (!res.ok)
324
+ throw res;
325
+ return res.json();
326
+ });
327
+ if ("error" in result)
328
+ throw new Error(result.error.message);
329
+ return result.data;
330
+ }
331
+ async listMergeRequests(params = {}) {
332
+ if (params.state &&
333
+ !["opened", "closed", "merged", "all"].includes(params.state)) {
334
+ throw new Error(`listMergeRequests: invalid state "${params.state}" (expected opened|closed|merged|all)`);
335
+ }
336
+ const limit = Math.min(Math.max(params.limit ?? 50, 1), 100);
337
+ const result = await this.apiCall(async (token) => {
338
+ const qs = new URLSearchParams({ per_page: String(limit) });
339
+ if (params.state)
340
+ qs.set("state", params.state);
341
+ if (params.scope)
342
+ qs.set("scope", params.scope);
343
+ let url;
344
+ if (params.projectId !== undefined && params.projectId !== "") {
345
+ url = `${apiBase()}/projects/${encodeURIComponent(String(params.projectId))}/merge_requests?${qs}`;
346
+ }
347
+ else {
348
+ url = `${apiBase()}/merge_requests?${qs}`;
349
+ }
350
+ const res = await fetch(url, { headers: this.buildHeaders(token) });
351
+ this.captureRateLimit(res);
352
+ if (!res.ok)
353
+ throw res;
354
+ return res.json();
355
+ });
356
+ if ("error" in result)
357
+ throw new Error(result.error.message);
358
+ return result.data;
359
+ }
360
+ // ── Helpers ────────────────────────────────────────────────────────────────
361
+ buildHeaders(token) {
362
+ return {
363
+ Authorization: `Bearer ${token}`,
364
+ Accept: "application/json",
365
+ "Content-Type": "application/json",
366
+ };
367
+ }
368
+ captureRateLimit(res) {
369
+ this.updateRateLimitFromHeaders({
370
+ "x-ratelimit-remaining": res.headers.get("ratelimit-remaining") ??
371
+ res.headers.get("x-ratelimit-remaining") ??
372
+ undefined,
373
+ "x-ratelimit-reset": res.headers.get("ratelimit-reset") ??
374
+ res.headers.get("x-ratelimit-reset") ??
375
+ undefined,
376
+ "retry-after": res.headers.get("retry-after") ?? undefined,
377
+ });
378
+ }
379
+ }
380
+ // ── Singleton ────────────────────────────────────────────────────────────────
381
+ let _instance = null;
382
+ function resetGitLabConnector() {
383
+ _instance = null;
384
+ }
385
+ export function getGitLabConnector() {
386
+ if (!_instance) {
387
+ _instance = new GitLabConnector();
388
+ }
389
+ return _instance;
390
+ }
391
+ export { getGitLabConnector as gitlab };
392
+ /**
393
+ * GET /connections/gitlab/auth — redirect to GitLab consent screen.
394
+ */
395
+ export function handleGitLabAuthorize() {
396
+ if (!isConfigured()) {
397
+ return {
398
+ status: 503,
399
+ contentType: "application/json",
400
+ body: JSON.stringify({
401
+ ok: false,
402
+ error: "GitLab connector not configured. Set GITLAB_CLIENT_ID and GITLAB_CLIENT_SECRET.",
403
+ }),
404
+ };
405
+ }
406
+ const state = generateState();
407
+ const params = new URLSearchParams({
408
+ client_id: clientId(),
409
+ redirect_uri: redirectUri(),
410
+ response_type: "code",
411
+ scope: SCOPES.join(" "),
412
+ state,
413
+ });
414
+ return {
415
+ status: 302,
416
+ body: "",
417
+ redirect: `${authorizeUrl()}?${params.toString()}`,
418
+ };
419
+ }
420
+ /**
421
+ * GET /connections/gitlab/callback — exchange code for tokens.
422
+ */
423
+ export async function handleGitLabCallback(code, state, error) {
424
+ if (error) {
425
+ return {
426
+ status: 400,
427
+ contentType: "text/html",
428
+ body: `<html><body><h2>GitLab connect failed</h2><pre>${escHtml(error)}</pre></body></html>`,
429
+ };
430
+ }
431
+ if (!code || !state) {
432
+ return {
433
+ status: 400,
434
+ contentType: "text/html",
435
+ body: `<html><body><h2>GitLab connect failed</h2><pre>missing code or state</pre></body></html>`,
436
+ };
437
+ }
438
+ if (!consumeState(state)) {
439
+ return {
440
+ status: 400,
441
+ contentType: "text/html",
442
+ body: `<html><body><h2>GitLab connect failed</h2><pre>invalid or expired state</pre></body></html>`,
443
+ };
444
+ }
445
+ try {
446
+ const params = new URLSearchParams({
447
+ grant_type: "authorization_code",
448
+ code,
449
+ redirect_uri: redirectUri(),
450
+ client_id: clientId(),
451
+ client_secret: clientSecret(),
452
+ });
453
+ const res = await fetch(tokenUrl(), {
454
+ method: "POST",
455
+ headers: {
456
+ "Content-Type": "application/x-www-form-urlencoded",
457
+ Accept: "application/json",
458
+ },
459
+ body: params.toString(),
460
+ });
461
+ if (!res.ok) {
462
+ const body = await res.text();
463
+ throw new Error(`Token exchange HTTP ${res.status}: ${body}`);
464
+ }
465
+ const json = (await res.json());
466
+ if (!json.access_token) {
467
+ throw new Error("Token exchange returned no access_token");
468
+ }
469
+ // Best-effort fetch of user info so the dashboard can show "connected as X".
470
+ let username;
471
+ let userId;
472
+ let userEmail;
473
+ try {
474
+ const userRes = await fetch(`${apiBase()}/user`, {
475
+ headers: { Authorization: `Bearer ${json.access_token}` },
476
+ });
477
+ if (userRes.ok) {
478
+ const u = (await userRes.json());
479
+ username = u.name ?? u.username;
480
+ userId = u.id;
481
+ userEmail = u.email;
482
+ }
483
+ }
484
+ catch {
485
+ // best-effort
486
+ }
487
+ const expiresAt = typeof json.expires_in === "number" && json.expires_in > 0
488
+ ? Date.now() + json.expires_in * 1000
489
+ : undefined;
490
+ saveTokens({
491
+ access_token: json.access_token,
492
+ refresh_token: json.refresh_token,
493
+ expires_at: expiresAt,
494
+ scope: json.scope,
495
+ token_type: json.token_type ?? "Bearer",
496
+ _client_id: clientId() || undefined,
497
+ _client_secret: clientSecret() || undefined,
498
+ _base_url: gitlabBaseUrl(),
499
+ username,
500
+ user_id: userId,
501
+ email: userEmail,
502
+ connected_at: new Date().toISOString(),
503
+ });
504
+ resetGitLabConnector();
505
+ return {
506
+ status: 200,
507
+ contentType: "text/html",
508
+ body: `<html><body><h2>GitLab connected${username ? ` as ${escHtml(username)}` : ""}</h2><script>try { window.opener.postMessage('patchwork:gitlab:connected', '*'); } catch(_) {} window.close();</script></body></html>`,
509
+ };
510
+ }
511
+ catch (err) {
512
+ return {
513
+ status: 400,
514
+ contentType: "text/html",
515
+ body: `<html><body><h2>GitLab connect failed</h2><pre>${escHtml(err instanceof Error ? err.message : String(err))}</pre></body></html>`,
516
+ };
517
+ }
518
+ }
519
+ /**
520
+ * POST /connections/gitlab/test — verify stored token works.
521
+ */
522
+ export async function handleGitLabTest() {
523
+ const tokens = loadTokens();
524
+ if (!tokens) {
525
+ return {
526
+ status: 400,
527
+ contentType: "application/json",
528
+ body: JSON.stringify({ ok: false, error: "GitLab not connected" }),
529
+ };
530
+ }
531
+ try {
532
+ const connector = getGitLabConnector();
533
+ const check = await connector.healthCheck();
534
+ return {
535
+ status: check.ok ? 200 : 401,
536
+ contentType: "application/json",
537
+ body: JSON.stringify(check.ok
538
+ ? { ok: true, username: tokens.username }
539
+ : { ok: false, error: check.error?.message }),
540
+ };
541
+ }
542
+ catch (err) {
543
+ return {
544
+ status: 500,
545
+ contentType: "application/json",
546
+ body: JSON.stringify({
547
+ ok: false,
548
+ error: err instanceof Error ? err.message : String(err),
549
+ }),
550
+ };
551
+ }
552
+ }
553
+ /**
554
+ * DELETE /connections/gitlab — best-effort revoke at GitLab + drop locally.
555
+ */
556
+ export async function handleGitLabDisconnect() {
557
+ const tokens = loadTokens();
558
+ if (tokens?.access_token && isConfigured()) {
559
+ try {
560
+ await fetch(revokeUrl(), {
561
+ method: "POST",
562
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
563
+ body: new URLSearchParams({
564
+ token: tokens.access_token,
565
+ client_id: clientId(),
566
+ client_secret: clientSecret(),
567
+ }).toString(),
568
+ });
569
+ }
570
+ catch {
571
+ // ignore — still drop local tokens
572
+ }
573
+ }
574
+ clearTokens();
575
+ resetGitLabConnector();
576
+ return {
577
+ status: 200,
578
+ contentType: "application/json",
579
+ body: JSON.stringify({ ok: true }),
580
+ };
581
+ }
582
+ //# sourceMappingURL=gitlab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlab.js","sourceRoot":"","sources":["../../src/connectors/gitlab.ts"],"names":[],"mappings":"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"}
@@ -188,22 +188,28 @@ export async function handleConnectionsList() {
188
188
  const { getStatus: getSentryStatus } = await import("./sentry.js");
189
189
  const { getStatus: getLinearStatus } = await import("./linear.js");
190
190
  const { getStatus: getCalendarStatus } = await import("./googleCalendar.js");
191
+ const { getStatus: getDriveStatus } = await import("./googleDrive.js");
191
192
  const { isConnected: isSlackConnected, getProfile: getSlackProfile } = await import("./slack.js");
192
193
  const gh = getGitHubStatus();
193
194
  const sentry = getSentryStatus();
194
195
  const linear = getLinearStatus();
195
196
  const calendar = getCalendarStatus();
197
+ const drive = getDriveStatus();
196
198
  const slackConnected = isSlackConnected();
197
199
  const slackProfile = getSlackProfile();
198
200
  // Token-paste connectors expose loadTokens() which returns null when
199
201
  // no credentials are stored. Probe each one so the dashboard reflects
200
202
  // their actual state instead of always-disconnected.
201
203
  const tokenPasteProbes = [
204
+ { id: "asana", mod: () => import("./asana.js") },
202
205
  { id: "notion", mod: () => import("./notion.js") },
203
206
  { id: "confluence", mod: () => import("./confluence.js") },
204
207
  { id: "datadog", mod: () => import("./datadog.js") },
208
+ { id: "discord", mod: () => import("./discord.js") },
209
+ { id: "gitlab", mod: () => import("./gitlab.js") },
205
210
  { id: "hubspot", mod: () => import("./hubspot.js") },
206
211
  { id: "intercom", mod: () => import("./intercom.js") },
212
+ { id: "pagerduty", mod: () => import("./pagerduty.js") },
207
213
  { id: "stripe", mod: () => import("./stripe.js") },
208
214
  { id: "zendesk", mod: () => import("./zendesk.js") },
209
215
  { id: "jira", mod: () => import("./jira.js") },
@@ -254,6 +260,11 @@ export async function handleConnectionsList() {
254
260
  status: calendar.status,
255
261
  lastSync: calendar.lastSync,
256
262
  },
263
+ {
264
+ id: "google-drive",
265
+ status: drive.status,
266
+ lastSync: drive.lastSync,
267
+ },
257
268
  {
258
269
  id: "slack",
259
270
  status: slackConnected ? "connected" : "disconnected",