forges 0.0.0 → 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.
Files changed (152) hide show
  1. package/LICENCE +21 -0
  2. package/README.md +72 -0
  3. package/dist/azure-devops/index.d.mts +2 -0
  4. package/dist/azure-devops/index.mjs +1 -0
  5. package/dist/azure-devops-DwQvL4TV.mjs +1355 -0
  6. package/dist/bitbucket/index.d.mts +2 -0
  7. package/dist/bitbucket/index.mjs +1 -0
  8. package/dist/bitbucket-CpcfJlYi.mjs +1171 -0
  9. package/dist/crypto-CQNEyYHD.mjs +156 -0
  10. package/dist/cursor-origin/index.d.mts +2 -0
  11. package/dist/cursor-origin/index.mjs +1 -0
  12. package/dist/cursor-origin-BhqYx2oW.mjs +980 -0
  13. package/dist/define-B2_4ob2i.d.mts +307 -0
  14. package/dist/define-D3yEXtKK.mjs +2276 -0
  15. package/dist/env.d.mts +46 -0
  16. package/dist/env.mjs +283 -0
  17. package/dist/fake/index.d.mts +227 -0
  18. package/dist/fake/index.mjs +1001 -0
  19. package/dist/fetch-D4pSRvvA.d.mts +1723 -0
  20. package/dist/forgejo/index.d.mts +2 -0
  21. package/dist/forgejo/index.mjs +1 -0
  22. package/dist/forgejo-BCefbHUN.mjs +2005 -0
  23. package/dist/gitea/index.d.mts +2 -0
  24. package/dist/gitea/index.mjs +22 -0
  25. package/dist/gitee/index.d.mts +2 -0
  26. package/dist/gitee/index.mjs +1 -0
  27. package/dist/gitee-CA-f7Rs8.mjs +1272 -0
  28. package/dist/github/index.d.mts +2 -0
  29. package/dist/github/index.mjs +1 -0
  30. package/dist/github-B0umL8_f.mjs +3259 -0
  31. package/dist/gitlab/index.d.mts +2 -0
  32. package/dist/gitlab/index.mjs +1 -0
  33. package/dist/gitlab-Bsr__Z0C.mjs +2378 -0
  34. package/dist/graphql-D7uGzZTf.mjs +366 -0
  35. package/dist/index-BQ6VS31s.d.mts +32 -0
  36. package/dist/index-C-jz9Mo3.d.mts +16 -0
  37. package/dist/index-C4fYzmK3.d.mts +102 -0
  38. package/dist/index-CQiIX4SH.d.mts +27 -0
  39. package/dist/index-CTj5gQzl.d.mts +35 -0
  40. package/dist/index-CliBWzOF.d.mts +23 -0
  41. package/dist/index-Cz_y-t3d.d.mts +36 -0
  42. package/dist/index-DEt25E1w.d.mts +77 -0
  43. package/dist/index-DIuS0EOy.d.mts +43 -0
  44. package/dist/index-uSpsMCrC.d.mts +26 -0
  45. package/dist/index.d.mts +41 -0
  46. package/dist/index.mjs +13 -0
  47. package/dist/kit.d.mts +183 -0
  48. package/dist/kit.mjs +4 -0
  49. package/dist/provider-CoZ7cCxp.d.mts +1439 -0
  50. package/dist/provider-Ed1YhkQN.mjs +169 -0
  51. package/dist/pushin/index.d.mts +2 -0
  52. package/dist/pushin/index.mjs +1 -0
  53. package/dist/pushin-CkxgJ2cU.mjs +343 -0
  54. package/dist/schema/Actor.json +96 -0
  55. package/dist/schema/AlertSupport.json +49 -0
  56. package/dist/schema/AuthKind.json +15 -0
  57. package/dist/schema/Branch.json +31 -0
  58. package/dist/schema/ChangedFile.json +58 -0
  59. package/dist/schema/Check.json +169 -0
  60. package/dist/schema/CheckRef.json +114 -0
  61. package/dist/schema/CheckState.json +14 -0
  62. package/dist/schema/ChecksSummary.json +34 -0
  63. package/dist/schema/CiJob.json +186 -0
  64. package/dist/schema/CiJobRef.json +132 -0
  65. package/dist/schema/CiRun.json +234 -0
  66. package/dist/schema/CiRunRef.json +103 -0
  67. package/dist/schema/CodeFragment.json +19 -0
  68. package/dist/schema/CodeMatch.json +128 -0
  69. package/dist/schema/Collaborator.json +169 -0
  70. package/dist/schema/Comment.json +336 -0
  71. package/dist/schema/CommentRef.json +158 -0
  72. package/dist/schema/Commit.json +307 -0
  73. package/dist/schema/CommitRef.json +104 -0
  74. package/dist/schema/CommitSignature.json +118 -0
  75. package/dist/schema/Comparison.json +355 -0
  76. package/dist/schema/Cursor.json +21 -0
  77. package/dist/schema/EventAction.json +33 -0
  78. package/dist/schema/EventDetail.json +649 -0
  79. package/dist/schema/EventKind.json +26 -0
  80. package/dist/schema/FileContent.json +73 -0
  81. package/dist/schema/FileStatus.json +15 -0
  82. package/dist/schema/ForgeCapabilities.json +737 -0
  83. package/dist/schema/ForgeEvent.json +789 -0
  84. package/dist/schema/ForgeInstance.json +7 -0
  85. package/dist/schema/ForgeKind.json +32 -0
  86. package/dist/schema/ForgeWarning.json +49 -0
  87. package/dist/schema/ForgeWarningCode.json +7 -0
  88. package/dist/schema/GetManyResult.json +699 -0
  89. package/dist/schema/Installation.json +131 -0
  90. package/dist/schema/InstallationToken.json +31 -0
  91. package/dist/schema/KnownForgeKind.json +19 -0
  92. package/dist/schema/Label.json +22 -0
  93. package/dist/schema/MergeMethod.json +14 -0
  94. package/dist/schema/Milestone.json +40 -0
  95. package/dist/schema/Notification.json +498 -0
  96. package/dist/schema/NotificationReason.json +28 -0
  97. package/dist/schema/NotificationRef.json +60 -0
  98. package/dist/schema/NotificationSubject.json +376 -0
  99. package/dist/schema/PerKind.json +49 -0
  100. package/dist/schema/PullBranches.json +125 -0
  101. package/dist/schema/PushCommit.json +24 -0
  102. package/dist/schema/Reaction.json +148 -0
  103. package/dist/schema/ReactionContent.json +17 -0
  104. package/dist/schema/ReactionSummary.json +70 -0
  105. package/dist/schema/Release.json +286 -0
  106. package/dist/schema/ReleaseAsset.json +169 -0
  107. package/dist/schema/ReleaseAssetRef.json +138 -0
  108. package/dist/schema/ReleaseRef.json +107 -0
  109. package/dist/schema/Repo.json +354 -0
  110. package/dist/schema/RepoFeatures.json +35 -0
  111. package/dist/schema/RepoPermissions.json +35 -0
  112. package/dist/schema/RepoRef.json +77 -0
  113. package/dist/schema/RepoRole.json +15 -0
  114. package/dist/schema/RepoVisibility.json +12 -0
  115. package/dist/schema/Review.json +371 -0
  116. package/dist/schema/ReviewComment.json +280 -0
  117. package/dist/schema/ReviewCommentInput.json +34 -0
  118. package/dist/schema/ReviewRef.json +158 -0
  119. package/dist/schema/ReviewState.json +15 -0
  120. package/dist/schema/Reviewer.json +133 -0
  121. package/dist/schema/SecurityAlert.json +219 -0
  122. package/dist/schema/SecurityAlertKind.json +14 -0
  123. package/dist/schema/SecurityAlertRef.json +122 -0
  124. package/dist/schema/SecuritySeverity.json +14 -0
  125. package/dist/schema/SourceKind.json +12 -0
  126. package/dist/schema/SubscriptionState.json +12 -0
  127. package/dist/schema/Support.json +23 -0
  128. package/dist/schema/Tag.json +25 -0
  129. package/dist/schema/TextLimits.json +18 -0
  130. package/dist/schema/Thread.json +653 -0
  131. package/dist/schema/ThreadKind.json +14 -0
  132. package/dist/schema/ThreadRef.json +131 -0
  133. package/dist/schema/ThreadStack.json +146 -0
  134. package/dist/schema/ThreadState.json +13 -0
  135. package/dist/schema/TreeEntry.json +46 -0
  136. package/dist/schema/TreeEntryType.json +13 -0
  137. package/dist/schema/UpsertCommentResult.json +355 -0
  138. package/dist/schema/User.json +123 -0
  139. package/dist/schema/Webhook.json +188 -0
  140. package/dist/schema/WebhookDeliveryRecord.json +173 -0
  141. package/dist/schema/WebhookDeliveryRef.json +135 -0
  142. package/dist/schema/WebhookRef.json +105 -0
  143. package/dist/schema/index.d.mts +4 -0
  144. package/dist/schema/index.mjs +3219 -0
  145. package/dist/tangled/index.d.mts +2 -0
  146. package/dist/tangled/index.mjs +1 -0
  147. package/dist/tangled-ChdPmiv-.mjs +1570 -0
  148. package/dist/testing/index.d.mts +123 -0
  149. package/dist/testing/index.mjs +242 -0
  150. package/dist/webhooks-rn9-8lxT.mjs +70 -0
  151. package/package.json +115 -3
  152. package/dist/.gitkeep +0 -0
@@ -0,0 +1,980 @@
1
+ import { E as summariseChecks, Nt as soleMergeMethod, O as toDate, Pt as toMergeError, S as phased, T as resolveToken, _t as ForgeError, c as checkRunState, d as forgeIterable, dt as fromBase64, f as getManyConcurrently, ft as toFileContent, g as iteratePages, j as toWarning, jt as UnsupportedOperationError, k as toFileStatus, n as perKind, r as verb, rt as createFetcher, t as defineForgeProvider, v as memo, w as requireThread } from "./define-D3yEXtKK.mjs";
2
+ import { a as sha256Hex, n as headerValue, o as signEdDsaJwt, t as bodyText, u as verifyEd25519 } from "./crypto-CQNEyYHD.mjs";
3
+ import { i as refEvent } from "./webhooks-rn9-8lxT.mjs";
4
+ //#region src/cursor-origin/auth.ts
5
+ /** Origin caps installation tokens at the lifetime of the JWT that minted them. */
6
+ const JWT_LIFETIME_SECONDS = 300;
7
+ const REFRESH_MARGIN_MS = 6e4;
8
+ function createOriginAppCredentials(auth, createFetcher) {
9
+ let jwt;
10
+ const tokens = /* @__PURE__ */ new Map();
11
+ const appId = String(auth.appId);
12
+ async function appJwt() {
13
+ if (jwt && jwt.expiresAt > Date.now() + REFRESH_MARGIN_MS) return jwt.value;
14
+ const now = Math.floor(Date.now() / 1e3);
15
+ const value = await signEdDsaJwt(auth.privateKey, {
16
+ iss: appId,
17
+ aud: "origin-apps",
18
+ iat: now,
19
+ exp: now + JWT_LIFETIME_SECONDS
20
+ }, appId);
21
+ jwt = {
22
+ value,
23
+ expiresAt: (now + JWT_LIFETIME_SECONDS) * 1e3
24
+ };
25
+ return value;
26
+ }
27
+ const fetcher = createFetcher(async () => ({ authorization: `Bearer ${await appJwt()}` }));
28
+ async function installationTokenDetails(installationId, options) {
29
+ const cached = tokens.get(installationId);
30
+ if (cached && cached.expiresAt.getTime() > Date.now() + REFRESH_MARGIN_MS) return cached;
31
+ const { data } = await fetcher.json(`/app/installations/${installationId}/access_tokens`, {
32
+ method: "POST",
33
+ json: {},
34
+ signal: options?.signal
35
+ });
36
+ const token = {
37
+ token: data.token,
38
+ expiresAt: new Date(data.expiresAt),
39
+ permissions: {}
40
+ };
41
+ tokens.set(installationId, token);
42
+ return token;
43
+ }
44
+ return {
45
+ appJwt,
46
+ installationTokenDetails,
47
+ installationToken: async (installationId, options) => (await installationTokenDetails(installationId, options)).token
48
+ };
49
+ }
50
+ //#endregion
51
+ //#region src/cursor-origin/normalise.ts
52
+ const FORGE = "cursor-origin";
53
+ function toActor(instance, actor) {
54
+ if (actor?.user) {
55
+ const user = actor.user;
56
+ return {
57
+ forge: FORGE,
58
+ instance,
59
+ login: user.handle ?? user.email ?? user.id,
60
+ id: user.id,
61
+ name: user.displayName,
62
+ typeRaw: user.performedVia?.app ? "user_via_app" : "user",
63
+ isBotHint: Boolean(user.performedVia?.app)
64
+ };
65
+ }
66
+ if (actor?.app) return {
67
+ forge: FORGE,
68
+ instance,
69
+ login: actor.app.displayName ?? actor.app.id,
70
+ id: actor.app.id,
71
+ name: actor.app.displayName,
72
+ typeRaw: "app",
73
+ isBotHint: true
74
+ };
75
+ if (actor?.serviceAccount) return {
76
+ forge: FORGE,
77
+ instance,
78
+ login: actor.serviceAccount.id,
79
+ id: actor.serviceAccount.id,
80
+ typeRaw: "service_account",
81
+ isBotHint: true
82
+ };
83
+ }
84
+ function toRepoRef(instance, repo) {
85
+ return {
86
+ forge: FORGE,
87
+ instance,
88
+ owner: repo.owner.slug,
89
+ name: repo.name,
90
+ externalId: repo.id
91
+ };
92
+ }
93
+ function toRepo(instance, raw) {
94
+ return {
95
+ ref: toRepoRef(instance, raw),
96
+ defaultBranch: raw.defaultBranch,
97
+ visibility: raw.visibility ?? "private",
98
+ visibilityRaw: raw.visibility,
99
+ isFork: false,
100
+ isArchived: false,
101
+ topics: [],
102
+ cloneUrls: raw.cloneUrl ? { https: raw.cloneUrl } : void 0,
103
+ createdAt: toDate(raw.createdAt),
104
+ updatedAt: toDate(raw.updatedAt),
105
+ pushedAt: toDate(raw.pushedAt),
106
+ raw
107
+ };
108
+ }
109
+ function threadState(raw) {
110
+ return raw.merged ? "merged" : raw.state === "open" ? "open" : raw.state === "closed" ? "closed" : "unknown";
111
+ }
112
+ function toThread(ref, raw) {
113
+ const parent = raw.stack?.parentPullRequest;
114
+ return {
115
+ ref: {
116
+ ...ref,
117
+ kind: "pull_request",
118
+ number: raw.number,
119
+ externalId: raw.id
120
+ },
121
+ kind: "pull_request",
122
+ title: raw.title,
123
+ body: raw.body || void 0,
124
+ state: threadState(raw),
125
+ stateRaw: raw.merged ? "merged" : raw.state,
126
+ isDraft: raw.draft ?? false,
127
+ author: toActor(ref.instance, raw.author),
128
+ assignees: [],
129
+ reviewers: [],
130
+ labels: (raw.labels ?? []).map((label) => ({
131
+ name: label.name,
132
+ colour: label.color,
133
+ description: label.description || void 0
134
+ })),
135
+ createdAt: toDate(raw.createdAt),
136
+ updatedAt: toDate(raw.updatedAt),
137
+ closedAt: toDate(raw.closedAt),
138
+ lastActivityAt: toDate(raw.updatedAt),
139
+ branches: raw.head && raw.base ? {
140
+ head: {
141
+ ref: raw.head.ref,
142
+ sha: raw.head.sha
143
+ },
144
+ base: {
145
+ ref: raw.base.ref,
146
+ sha: raw.base.sha
147
+ },
148
+ mergeCommitSha: raw.mergeCommitSha
149
+ } : void 0,
150
+ ...raw.stack ? { stack: {
151
+ id: raw.stack.id,
152
+ ...parent ? { parent: {
153
+ forge: FORGE,
154
+ instance: ref.instance,
155
+ repo: ref.repo,
156
+ kind: "pull_request",
157
+ number: parent.number,
158
+ externalId: parent.id
159
+ } } : {}
160
+ } } : {},
161
+ raw
162
+ };
163
+ }
164
+ /** General-discussion comments have no diff anchor; anchored ones are review comments. */
165
+ function isConversationComment(raw) {
166
+ return !raw.thread?.path;
167
+ }
168
+ function toComment(thread, raw) {
169
+ return {
170
+ ref: {
171
+ forge: FORGE,
172
+ instance: thread.instance,
173
+ thread,
174
+ id: raw.id
175
+ },
176
+ body: raw.body,
177
+ author: toActor(thread.instance, raw.author),
178
+ createdAt: toDate(raw.createdAt),
179
+ updatedAt: toDate(raw.updatedAt),
180
+ raw
181
+ };
182
+ }
183
+ function toCommentEvent(thread, raw) {
184
+ const anchored = !isConversationComment(raw);
185
+ const actor = toActor(thread.instance, raw.author);
186
+ return {
187
+ forge: FORGE,
188
+ instance: thread.instance,
189
+ id: raw.id,
190
+ kind: anchored ? "review_comment" : "comment",
191
+ kindRaw: anchored ? "pull_request.review_comment" : "pull_request.comment",
192
+ summary: `${actor?.login ?? "someone"} ${anchored ? `commented on ${raw.thread.path}` : "commented"}`,
193
+ occurredAt: toDate(raw.createdAt) ?? /* @__PURE__ */ new Date(0),
194
+ actor,
195
+ repo: thread.repo,
196
+ thread,
197
+ source: "poll",
198
+ payload: raw
199
+ };
200
+ }
201
+ const VERDICTS = {
202
+ approve: "approved",
203
+ request_changes: "changes_requested",
204
+ comment: "commented"
205
+ };
206
+ function toReviewEvent(thread, raw, source = "poll") {
207
+ const actor = toActor(thread.instance, raw.author);
208
+ return {
209
+ forge: FORGE,
210
+ instance: thread.instance,
211
+ id: raw.id,
212
+ kind: "review",
213
+ kindRaw: raw.dismissal ? "pull_request.review.dismissed" : `pull_request.review.${raw.verdict}`,
214
+ summary: `${actor?.login ?? "someone"} ${raw.dismissal ? "dismissed a review" : `reviewed (${VERDICTS[raw.verdict] ?? raw.verdict})`}`,
215
+ occurredAt: toDate(raw.submittedAt) ?? /* @__PURE__ */ new Date(0),
216
+ actor,
217
+ repo: thread.repo,
218
+ thread,
219
+ source,
220
+ payload: raw
221
+ };
222
+ }
223
+ function toCheck(repo, raw) {
224
+ return {
225
+ ref: {
226
+ forge: FORGE,
227
+ instance: repo.instance,
228
+ repo,
229
+ id: raw.id,
230
+ type: "check_run"
231
+ },
232
+ name: raw.name,
233
+ state: checkRunState(raw.status, raw.conclusion),
234
+ stateRaw: raw.status,
235
+ conclusionRaw: raw.conclusion,
236
+ url: raw.detailsUrl,
237
+ startedAt: toDate(raw.startedAt),
238
+ completedAt: toDate(raw.completedAt),
239
+ raw
240
+ };
241
+ }
242
+ function toReview(thread, raw) {
243
+ return {
244
+ ref: {
245
+ forge: FORGE,
246
+ instance: thread.instance,
247
+ thread,
248
+ id: raw.id
249
+ },
250
+ author: toActor(thread.instance, raw.author),
251
+ state: raw.dismissal ? "dismissed" : VERDICTS[raw.verdict] ?? "unknown",
252
+ stateRaw: raw.verdict,
253
+ body: raw.body || void 0,
254
+ submittedAt: toDate(raw.submittedAt),
255
+ comments: false,
256
+ raw
257
+ };
258
+ }
259
+ function toChangedFile(raw) {
260
+ return {
261
+ path: raw.filename,
262
+ previousPath: raw.previousFilename,
263
+ status: toFileStatus(raw.status),
264
+ statusRaw: raw.status,
265
+ additions: raw.additions,
266
+ deletions: raw.deletions,
267
+ patch: raw.patch
268
+ };
269
+ }
270
+ function toCommit(repo, raw, files) {
271
+ return {
272
+ ref: {
273
+ forge: FORGE,
274
+ instance: repo.instance,
275
+ repo,
276
+ sha: raw.sha
277
+ },
278
+ sha: raw.sha,
279
+ message: raw.commit?.message ?? "",
280
+ author: {
281
+ name: raw.commit?.author?.name,
282
+ email: raw.commit?.author?.email,
283
+ date: toDate(raw.commit?.author?.date)
284
+ },
285
+ committer: {
286
+ name: raw.commit?.committer?.name,
287
+ email: raw.commit?.committer?.email,
288
+ date: toDate(raw.commit?.committer?.date)
289
+ },
290
+ parents: raw.parents?.flatMap((parent) => parent.sha ? [parent.sha] : []) ?? [],
291
+ ...raw.stats ? { stats: {
292
+ additions: raw.stats.additions ?? 0,
293
+ deletions: raw.stats.deletions ?? 0,
294
+ total: raw.stats.total
295
+ } } : {},
296
+ ...files ? { files } : {},
297
+ raw
298
+ };
299
+ }
300
+ function toBranch(raw) {
301
+ return {
302
+ name: raw.name,
303
+ sha: raw.commit?.sha ?? "",
304
+ raw
305
+ };
306
+ }
307
+ /** Origin lists tags as refs, so the name is the ref minus its `refs/tags/` prefix. */
308
+ function toTag(raw) {
309
+ return {
310
+ name: raw.ref.replace(/^refs\/tags\//, ""),
311
+ sha: raw.object?.sha ?? "",
312
+ raw
313
+ };
314
+ }
315
+ function toTreeEntry(raw) {
316
+ return {
317
+ path: raw.path,
318
+ type: raw.mode === "120000" ? "symlink" : raw.type === "tree" ? "directory" : raw.type === "commit" ? "submodule" : "file",
319
+ sha: raw.sha,
320
+ size: raw.size,
321
+ mode: raw.mode
322
+ };
323
+ }
324
+ //#endregion
325
+ //#region src/cursor-origin/webhooks.ts
326
+ /** Deliveries older or newer than this are refused, as Origin documents. */
327
+ const TOLERANCE_SECONDS = 300;
328
+ const KEYS_TTL_MS = 6e5;
329
+ /**
330
+ * Verifies a delivery: an Ed25519 signature (`v1ed,<base64>`) over the UTF-8
331
+ * hex SHA-256 of `<webhook-id>.<webhook-timestamp>.<body>`, against any
332
+ * active key. Origin signatures carry no key id.
333
+ */
334
+ async function verifyOriginSignature(delivery, keys, now = Date.now()) {
335
+ const id = headerValue(delivery.headers, "webhook-id");
336
+ const timestamp = Number(headerValue(delivery.headers, "webhook-timestamp"));
337
+ const signature = headerValue(delivery.headers, "webhook-signature")?.split(/\s+/).find((value) => value.startsWith("v1ed,"));
338
+ if (!id || !signature || !Number.isInteger(timestamp) || Math.abs(Math.floor(now / 1e3) - timestamp) > TOLERANCE_SECONDS) return false;
339
+ const digest = await sha256Hex(`${id}.${timestamp}.${bodyText(delivery.body)}`);
340
+ for (const key of await keys()) if (await verifyEd25519(key, digest, signature.slice(5))) return true;
341
+ return false;
342
+ }
343
+ const PULL_KINDS = {
344
+ "pull_request.created": "state_change",
345
+ "pull_request.published": "state_change",
346
+ "pull_request.reopened": "state_change",
347
+ "pull_request.closed": "state_change",
348
+ "pull_request.merged": "state_change",
349
+ "pull_request.comment.created": "comment",
350
+ "pull_request.comment.reaction.added": "reaction",
351
+ "pull_request.comment.reaction.removed": "reaction",
352
+ "pull_request.label.added": "label",
353
+ "pull_request.label.removed": "label",
354
+ "pull_request.reviewer.added": "assignment",
355
+ "pull_request.reviewer.removed": "assignment",
356
+ "pull_request.reviewer.rerequested": "assignment",
357
+ "pull_request.head_ref.pushed": "commit"
358
+ };
359
+ function translateOriginWebhook(instance, delivery) {
360
+ const envelope = JSON.parse(bodyText(delivery.body));
361
+ const { event } = envelope;
362
+ const payload = event.payload ?? {};
363
+ const repo = payload.repository ? toRepoRef(instance, payload.repository) : void 0;
364
+ const pull = payload.pullRequest;
365
+ const thread = pull && repo ? {
366
+ forge: FORGE,
367
+ instance,
368
+ repo,
369
+ kind: "pull_request",
370
+ number: pull.number,
371
+ externalId: pull.id
372
+ } : void 0;
373
+ const base = {
374
+ forge: FORGE,
375
+ instance,
376
+ id: envelope.deliveryId ?? event.id,
377
+ kindRaw: event.type,
378
+ occurredAt: toDate(event.eventTime) ?? /* @__PURE__ */ new Date(),
379
+ repo,
380
+ thread,
381
+ installationId: envelope.installationId,
382
+ source: "webhook",
383
+ payload: envelope
384
+ };
385
+ if (event.type.startsWith("pull_request.review.") && payload.review && thread) return [{
386
+ ...toReviewEvent(thread, payload.review, "webhook"),
387
+ ...base
388
+ }];
389
+ if (thread) {
390
+ const actor = toActor(instance, payload.comment?.author ?? payload.actor ?? payload.createdBy ?? pull?.author);
391
+ const kind = payload.comment?.thread?.path ? "review_comment" : PULL_KINDS[event.type] ?? "other";
392
+ return [{
393
+ ...base,
394
+ kind,
395
+ actor,
396
+ summary: `${actor?.login ?? "someone"}: ${event.type.replace(/^pull_request\./, "").replaceAll(".", " ")} on #${thread.number}`
397
+ }];
398
+ }
399
+ if (event.type === "repository.pushed") {
400
+ const pusher = toActor(instance, payload.pusher);
401
+ const who = pusher?.login ?? "someone";
402
+ return (payload.refUpdates ?? []).map((update, index) => {
403
+ const event = refEvent(who, {
404
+ ref: update.ref,
405
+ before: update.before,
406
+ after: update.after,
407
+ created: update.created,
408
+ deleted: update.deleted,
409
+ forced: update.forced,
410
+ commits: update.headCommit?.sha ? [{
411
+ sha: update.headCommit.sha,
412
+ message: update.headCommit.message ?? ""
413
+ }] : []
414
+ });
415
+ return {
416
+ ...base,
417
+ id: `${base.id}:${index}`,
418
+ actor: pusher,
419
+ ...event,
420
+ ...event.kind === "push" ? { summary: `${who} pushed to ${update.ref.replace(/^refs\/(?:heads|tags)\//, "")}` } : {}
421
+ };
422
+ });
423
+ }
424
+ if (event.type.startsWith("installation.")) return [{
425
+ ...base,
426
+ kind: "installation",
427
+ installationId: payload.installation?.id ?? envelope.installationId,
428
+ detail: {
429
+ type: "installation",
430
+ actionRaw: event.type,
431
+ installationId: payload.installation?.id ?? envelope.installationId,
432
+ added: (payload.installation?.repositories ?? []).map((reference) => toRepoRef(instance, reference)),
433
+ removed: []
434
+ },
435
+ summary: `Installation ${event.type.slice(13)}`
436
+ }];
437
+ return [{
438
+ ...base,
439
+ kind: "other",
440
+ summary: event.type
441
+ }];
442
+ }
443
+ const ORIGIN_WEBHOOK_EVENTS = [
444
+ {
445
+ kind: "comment",
446
+ action: "created"
447
+ },
448
+ {
449
+ kind: "review",
450
+ action: "submitted"
451
+ },
452
+ {
453
+ kind: "state_change",
454
+ action: "opened"
455
+ },
456
+ {
457
+ kind: "state_change",
458
+ action: "closed"
459
+ },
460
+ {
461
+ kind: "state_change",
462
+ action: "merged"
463
+ },
464
+ { kind: "installation" }
465
+ ];
466
+ const cursorOriginWebhooks = ({ instance, baseUrl, createFetcher }) => {
467
+ const keysFetcher = createFetcher({ baseUrl });
468
+ const signingKeys = memo(async () => (await keysFetcher.json("/keys")).data.keys ?? [], KEYS_TTL_MS);
469
+ return {
470
+ events: ORIGIN_WEBHOOK_EVENTS,
471
+ verify: (delivery) => verifyOriginSignature(delivery, signingKeys),
472
+ translate: (delivery) => translateOriginWebhook(instance, delivery)
473
+ };
474
+ };
475
+ //#endregion
476
+ //#region src/cursor-origin/index.ts
477
+ /** The Origin API version this provider was built against. See `docs/content/4.providers/cursor-origin.md`. */
478
+ const ORIGIN_API_VERSION = "v1alpha1";
479
+ const PULL = { pull_request: "experimental" };
480
+ const UNVERIFIED = { pull_request: "unverified" };
481
+ const PAGE_SIZE = 50;
482
+ function installationId(installation) {
483
+ return typeof installation === "string" ? installation : installation.id;
484
+ }
485
+ function setupOrigin({ options, baseUrl, instance, origin: context, fetcher, createFetcher, derive, state: credentials }) {
486
+ const repoPath = (repo) => `/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}`;
487
+ const pullPath = (ref) => `${repoPath(ref.repo)}/pulls/${ref.number}`;
488
+ async function tokenPage(path, field, listOptions, from = fetcher) {
489
+ const { data } = await from.json(path, {
490
+ query: {
491
+ ...listOptions.query,
492
+ pageSize: Math.min(listOptions.perPage ?? PAGE_SIZE, 100),
493
+ pageToken: listOptions.cursor?.token
494
+ },
495
+ signal: listOptions.signal
496
+ });
497
+ const next = data.nextPageToken;
498
+ return {
499
+ items: data[field] ?? [],
500
+ cursor: next ? { token: next } : void 0
501
+ };
502
+ }
503
+ function all(path, field, options = {}) {
504
+ return Array.fromAsync(iteratePages((page) => tokenPage(path, field, {
505
+ ...page,
506
+ signal: options.signal
507
+ })));
508
+ }
509
+ async function headChecks(repo, sha, signal) {
510
+ return (await all(`${repoPath(repo)}/commits/${sha}/check-runs`, "checkRuns", { signal })).map((raw) => toCheck(repo, raw));
511
+ }
512
+ async function get(thread, options) {
513
+ const ref = requireThread(thread, context);
514
+ const { data } = await fetcher.json(pullPath(ref), { signal: options?.signal });
515
+ const result = toThread(ref, data);
516
+ const sha = data.version?.headSha ?? data.head?.sha;
517
+ if (sha) try {
518
+ result.checks = summariseChecks((await headChecks(ref.repo, sha, options?.signal)).map((check) => check.state));
519
+ } catch (error) {
520
+ if (!(error instanceof ForgeError)) throw error;
521
+ result.warnings = [...result.warnings ?? [], toWarning("checks_unreadable", error, ref.number)];
522
+ }
523
+ return result;
524
+ }
525
+ const VERDICTS = {
526
+ approve: "approve",
527
+ request_changes: "request_changes",
528
+ comment: "comment"
529
+ };
530
+ async function createReview(thread, input, options) {
531
+ const ref = requireThread(thread, context);
532
+ if (!input.event) throw new UnsupportedOperationError("Cursor Origin has no pending reviews; pass an event", context);
533
+ if (input.comments?.length) throw new UnsupportedOperationError("Cursor Origin takes inline comments as their own requests, not with a review", context);
534
+ const { data } = await fetcher.json(`${pullPath(ref)}/reviews`, {
535
+ method: "POST",
536
+ json: {
537
+ verdict: VERDICTS[input.event],
538
+ body: input.body ?? ""
539
+ },
540
+ signal: options?.signal
541
+ });
542
+ return toReview(ref, data);
543
+ }
544
+ async function setThreadResolved(id, resolved, options) {
545
+ await fetcher.raw(`/pulls/threads/${encodeURIComponent(id)}`, {
546
+ method: "PATCH",
547
+ json: { resolved },
548
+ signal: options?.signal
549
+ });
550
+ }
551
+ async function listPage(repo, query = {}) {
552
+ if (query.kind === "issue" || query.kind === "discussion") return {
553
+ items: [],
554
+ warnings: [{
555
+ code: "kind_unsupported",
556
+ message: `Cursor Origin has pull requests only, no ${query.kind}s`
557
+ }]
558
+ };
559
+ const warnings = [];
560
+ if (!query.cursor) {
561
+ if (query.labels?.length || query.assignee) warnings.push({
562
+ code: "filter_unsupported",
563
+ message: "Cursor Origin cannot filter pull requests by label or assignee; those filters were ignored"
564
+ });
565
+ if (query.sort === "comments") warnings.push({
566
+ code: "sort_unsupported",
567
+ message: "Cursor Origin cannot sort by comment count; sorted by creation time"
568
+ });
569
+ }
570
+ const page = await tokenPage(`${repoPath(repo)}/pulls`, "pullRequests", {
571
+ ...query,
572
+ query: {
573
+ state: query.state === "merged" ? "closed" : query.state ?? "open",
574
+ author: query.author,
575
+ sortBy: query.sort === "updated" ? "updated" : "created",
576
+ direction: query.direction ?? "desc",
577
+ since: query.createdAfter?.toISOString()
578
+ }
579
+ });
580
+ const since = query.since?.getTime();
581
+ return {
582
+ items: page.items.map((raw) => toThread({
583
+ forge: FORGE,
584
+ instance,
585
+ repo,
586
+ kind: "pull_request",
587
+ number: raw.number
588
+ }, raw)).filter((thread) => (since === void 0 || (thread.updatedAt?.getTime() ?? 0) >= since) && (query.state !== "merged" || thread.state === "merged")),
589
+ cursor: page.cursor,
590
+ ...warnings.length ? { warnings } : {}
591
+ };
592
+ }
593
+ async function commentsPage(thread, listOptions = {}) {
594
+ const ref = requireThread(thread, context);
595
+ const page = await tokenPage(`${pullPath(ref)}/comments`, "comments", listOptions);
596
+ return {
597
+ items: page.items.filter(isConversationComment).map((raw) => toComment(ref, raw)),
598
+ cursor: page.cursor
599
+ };
600
+ }
601
+ async function eventsPage(thread, listOptions = {}) {
602
+ const ref = requireThread(thread, context);
603
+ const [comments, reviews] = await Promise.all([all(`${pullPath(ref)}/comments`, "comments", listOptions), all(`${pullPath(ref)}/reviews`, "reviews", listOptions)]);
604
+ return { items: [...comments.map((raw) => toCommentEvent(ref, raw)), ...reviews.filter((review) => review.submittedAt).map((raw) => toReviewEvent(ref, raw))].sort((a, b) => a.occurredAt.getTime() - b.occurredAt.getTime()) };
605
+ }
606
+ async function reposPage(listOptions = {}, from = fetcher) {
607
+ const page = await tokenPage("/installation/repos", "repositories", listOptions, from);
608
+ return {
609
+ items: page.items.map((raw) => toRepo(instance, raw)),
610
+ cursor: page.cursor
611
+ };
612
+ }
613
+ /** A user token has no single repository listing, so each namespace the user belongs to is listed in turn. */
614
+ async function userReposPage(listOptions = {}) {
615
+ const namespaces = await all("/namespaces", "namespaces", listOptions);
616
+ if (!namespaces.length) return { items: [] };
617
+ return phased(namespaces.map((namespace) => async (cursor) => {
618
+ const page = await tokenPage(`/repos/${encodeURIComponent(namespace.slug)}`, "repositories", {
619
+ ...listOptions,
620
+ cursor
621
+ });
622
+ return {
623
+ items: page.items.map((raw) => toRepo(instance, raw)),
624
+ cursor: page.cursor
625
+ };
626
+ }), listOptions.cursor);
627
+ }
628
+ function createInstallationsApi(app) {
629
+ const appFetcher = createFetcher({
630
+ baseUrl,
631
+ authHeaders: async () => ({ authorization: `Bearer ${await app.appJwt()}` })
632
+ });
633
+ const auth = options.auth;
634
+ const toInstallation = (raw) => ({
635
+ forge: FORGE,
636
+ instance,
637
+ id: raw.id,
638
+ targetTypeRaw: raw.target?.type,
639
+ repositorySelectionRaw: raw.repoSelectionMode,
640
+ ...raw.target ? { account: {
641
+ forge: FORGE,
642
+ instance,
643
+ login: raw.target.slug,
644
+ id: raw.target.id ?? raw.target.slug,
645
+ typeRaw: raw.target.type,
646
+ isBotHint: false
647
+ } } : {},
648
+ raw
649
+ });
650
+ const provider = (installation) => derive({
651
+ ...options,
652
+ auth: {
653
+ ...auth,
654
+ installationId: installationId(installation)
655
+ }
656
+ }, app);
657
+ const listPage = async (listOptions = {}) => {
658
+ const page = await tokenPage("/app/installations", "installations", listOptions, appFetcher);
659
+ return {
660
+ items: page.items.map(toInstallation),
661
+ cursor: page.cursor
662
+ };
663
+ };
664
+ return {
665
+ listPage,
666
+ get: async (installation, options) => toInstallation((await appFetcher.json(`/app/installations/${installationId(installation)}`, { signal: options?.signal })).data),
667
+ token: (installation, options) => app.installationTokenDetails(installationId(installation), options),
668
+ reposPage(installation, listOptions) {
669
+ const id = installationId(installation);
670
+ return reposPage(listOptions, createFetcher({
671
+ baseUrl,
672
+ authHeaders: async () => ({ authorization: `Bearer ${await app.installationToken(id)}` })
673
+ }));
674
+ },
675
+ provider,
676
+ providers: () => forgeIterable(async function* () {
677
+ for await (const installation of iteratePages(listPage)) yield {
678
+ installation,
679
+ provider: provider(installation)
680
+ };
681
+ })
682
+ };
683
+ }
684
+ const auth = options.auth;
685
+ const repoAccess = auth?.type === "token" || auth?.type === "app" && auth.installationId !== void 0;
686
+ return {
687
+ checks: { list: verb("unverified", async (repo, sha, options) => ({ items: await headChecks(repo, sha, options?.signal) })) },
688
+ contents: {
689
+ file: verb("unverified", async (repo, path, fileOptions = {}) => {
690
+ const { data } = await fetcher.json(`${repoPath(repo)}/contents`, {
691
+ query: {
692
+ path,
693
+ ref: fileOptions.ref
694
+ },
695
+ signal: fileOptions.signal
696
+ });
697
+ const file = {
698
+ path: data.path,
699
+ sha: data.sha,
700
+ size: data.size === void 0 ? void 0 : Number(data.size)
701
+ };
702
+ const content = data.content ?? (data.sha ? (await fetcher.json(`${repoPath(repo)}/git/blobs/${data.sha}`, { signal: fileOptions.signal })).data.content : "");
703
+ return toFileContent(fromBase64(content), file, fileOptions, context);
704
+ }),
705
+ treePage: verb("unverified", async (repo, treeOptions = {}) => {
706
+ const ref = treeOptions.ref ?? "HEAD";
707
+ const { data } = await fetcher.json(`${repoPath(repo)}/git/trees/${encodeURIComponent(ref)}`, {
708
+ query: { recursive: treeOptions.recursive ? "true" : void 0 },
709
+ signal: treeOptions.signal
710
+ });
711
+ const prefix = treeOptions.path?.replace(/^\/|\/$/g, "");
712
+ return {
713
+ items: (data.tree ?? []).filter((entry) => !prefix || entry.path.startsWith(`${prefix}/`)).map(toTreeEntry),
714
+ ...data.truncated ? { warnings: [{
715
+ code: "tree_truncated",
716
+ message: "Origin truncated the tree; read subtrees non-recursively instead",
717
+ subject: ref
718
+ }] } : {}
719
+ };
720
+ }),
721
+ branchesPage: verb("unverified", async (repo, listOptions = {}) => {
722
+ const page = await tokenPage(`${repoPath(repo)}/branches`, "branches", listOptions);
723
+ return {
724
+ items: page.items.map(toBranch),
725
+ cursor: page.cursor
726
+ };
727
+ }),
728
+ tagsPage: verb("unverified", async (repo, listOptions = {}) => {
729
+ const { data } = await fetcher.json(`${repoPath(repo)}/git/matching-refs`, {
730
+ query: { ref: "tags/" },
731
+ signal: listOptions.signal
732
+ });
733
+ return { items: (data ?? []).map(toTag) };
734
+ }),
735
+ resolveRef: verb("unverified", async (repo, ref, options) => (await fetcher.json(`${repoPath(repo)}/commits/${encodeURIComponent(ref)}`, { signal: options?.signal })).data.sha),
736
+ commitsPage: verb("unverified", async (repo, query = {}) => {
737
+ const page = await tokenPage(`${repoPath(repo)}/commits`, "commits", {
738
+ ...query,
739
+ query: {
740
+ sha: query.ref,
741
+ authorEmails: query.author,
742
+ since: query.since?.toISOString()
743
+ }
744
+ });
745
+ return {
746
+ items: page.items.map((raw) => toCommit(repo, raw)),
747
+ cursor: page.cursor
748
+ };
749
+ }),
750
+ commit: verb("unverified", async (repo, sha, options) => {
751
+ const [commit, files] = await Promise.all([fetcher.json(`${repoPath(repo)}/commits/${sha}`, { signal: options?.signal }), all(`${repoPath(repo)}/commits/${sha}/files`, "files", options)]);
752
+ return toCommit(repo, commit.data, files.map(toChangedFile));
753
+ }),
754
+ compare: verb("unverified", async (repo, base, head, options) => {
755
+ const basehead = encodeURIComponent(`${base}...${head}`);
756
+ const [comparison, files] = await Promise.all([fetcher.json(`${repoPath(repo)}/compare/${basehead}`, { signal: options?.signal }), all(`${repoPath(repo)}/compare/${basehead}/files`, "files", options)]);
757
+ return {
758
+ base,
759
+ head,
760
+ aheadBy: comparison.data.aheadBy,
761
+ behindBy: comparison.data.behindBy,
762
+ mergeBaseSha: comparison.data.mergeBaseCommit?.sha,
763
+ commits: [],
764
+ files: files.map(toChangedFile),
765
+ raw: comparison.data
766
+ };
767
+ })
768
+ },
769
+ traits: {
770
+ eventKinds: "native",
771
+ authKinds: [
772
+ "token",
773
+ "app",
774
+ "anonymous"
775
+ ]
776
+ },
777
+ repos: {
778
+ get: verb("experimental", async (ref, options) => toRepo(instance, (await fetcher.json(repoPath(ref), { signal: options?.signal })).data)),
779
+ listPage: verb(repoAccess && "unverified", async (listOptions = {}) => {
780
+ if (auth?.type === "app") {
781
+ if (auth.installationId === void 0) throw new UnsupportedOperationError("An app without an installation has no repositories; use installations.repos()", context);
782
+ return reposPage(listOptions);
783
+ }
784
+ return userReposPage(listOptions);
785
+ })
786
+ },
787
+ installations: credentials && auth?.type === "app" && auth.installationId === void 0 ? verb("unverified", createInstallationsApi(credentials)) : void 0,
788
+ threads: {
789
+ get: perKind(PULL, get),
790
+ getMany: verb("experimental", (refs, options) => getManyConcurrently(refs, get, options)),
791
+ listPage: perKind(PULL, listPage),
792
+ eventsPage: verb("experimental", eventsPage),
793
+ filesPage: verb("unverified", async (thread, listOptions = {}) => {
794
+ const page = await tokenPage(`${pullPath(requireThread(thread, context))}/files`, "files", listOptions);
795
+ return {
796
+ items: page.items.map(toChangedFile),
797
+ cursor: page.cursor
798
+ };
799
+ }),
800
+ commitsPage: verb("unverified", async (thread, listOptions = {}) => {
801
+ const page = await tokenPage(`${pullPath(requireThread(thread, context))}/commits`, "commits", listOptions);
802
+ return {
803
+ items: page.items.map((raw) => toCommit(thread.repo, raw)),
804
+ cursor: page.cursor
805
+ };
806
+ }),
807
+ commentsPage: perKind(PULL, commentsPage),
808
+ comment: perKind(PULL, async (thread, body, options) => {
809
+ const ref = requireThread(thread, context);
810
+ return toComment(ref, (await fetcher.json(`${pullPath(ref)}/comments`, {
811
+ method: "POST",
812
+ json: { body },
813
+ signal: options?.signal
814
+ })).data);
815
+ }),
816
+ editComment: perKind(UNVERIFIED, async (ref, body, options) => toComment(ref.thread, (await fetcher.json(`${repoPath(ref.thread.repo)}/pulls/comments/${ref.id}`, {
817
+ method: "PATCH",
818
+ json: { body },
819
+ signal: options?.signal
820
+ })).data)),
821
+ deleteComment: perKind(UNVERIFIED, async (ref, options) => {
822
+ await fetcher.raw(`${repoPath(ref.thread.repo)}/pulls/comments/${ref.id}`, {
823
+ method: "DELETE",
824
+ signal: options?.signal
825
+ });
826
+ }),
827
+ create: perKind(UNVERIFIED, async (repo, input, options) => {
828
+ if (input.assignees?.length) throw new UnsupportedOperationError("Cursor Origin pull requests have no assignees", context);
829
+ if (!input.head || !input.base) throw new UnsupportedOperationError("Creating a pull request needs head and base branches", context);
830
+ const { data } = await fetcher.json(`${repoPath(repo)}/pulls`, {
831
+ method: "POST",
832
+ json: {
833
+ title: input.title,
834
+ body: input.body ?? "",
835
+ head: input.head,
836
+ base: input.base,
837
+ draft: input.draft ?? false
838
+ },
839
+ signal: options?.signal
840
+ });
841
+ const ref = {
842
+ forge: FORGE,
843
+ instance,
844
+ repo,
845
+ kind: "pull_request",
846
+ number: data.number
847
+ };
848
+ if (input.labels?.length) await fetcher.raw(`${pullPath(ref)}/labels`, {
849
+ method: "PUT",
850
+ json: { labels: input.labels },
851
+ signal: options?.signal
852
+ });
853
+ return toThread(ref, data);
854
+ }),
855
+ update: perKind(UNVERIFIED, async (thread, input, options) => {
856
+ const ref = requireThread(thread, context);
857
+ return toThread(ref, (await fetcher.json(pullPath(ref), {
858
+ method: "PATCH",
859
+ json: input,
860
+ signal: options?.signal
861
+ })).data);
862
+ }),
863
+ close: perKind(PULL, async (thread, options) => {
864
+ await fetcher.raw(pullPath(requireThread(thread, context)), {
865
+ method: "PATCH",
866
+ json: { state: "closed" },
867
+ signal: options?.signal
868
+ });
869
+ }),
870
+ reopen: perKind(PULL, async (thread, options) => {
871
+ await fetcher.raw(pullPath(requireThread(thread, context)), {
872
+ method: "PATCH",
873
+ json: { state: "open" },
874
+ signal: options?.signal
875
+ });
876
+ }),
877
+ setLabels: perKind(UNVERIFIED, async (thread, labels, options) => {
878
+ await fetcher.raw(`${pullPath(requireThread(thread, context))}/labels`, {
879
+ method: "PUT",
880
+ json: { labels },
881
+ signal: options?.signal
882
+ });
883
+ }),
884
+ requestReview: perKind(UNVERIFIED, async (thread, reviewers, options) => {
885
+ const users = reviewers.map((reviewer) => typeof reviewer === "string" ? reviewer : reviewer.id);
886
+ await fetcher.raw(`${pullPath(requireThread(thread, context))}/requested_reviewers`, {
887
+ method: "POST",
888
+ json: { users },
889
+ signal: options?.signal
890
+ });
891
+ }),
892
+ merge: verb("experimental", async (thread, mergeOptions = {}, hooks = {}) => {
893
+ const ref = requireThread(thread, context);
894
+ if (mergeOptions.whenChecksPass) throw new UnsupportedOperationError("Cursor Origin has no merge queue or auto-merge", context);
895
+ if (mergeOptions.title) throw new UnsupportedOperationError("Cursor Origin does not take a merge commit title", context);
896
+ if (mergeOptions.message) throw new UnsupportedOperationError("Cursor Origin does not take a merge commit message", context);
897
+ if (mergeOptions.method && mergeOptions.method !== "merge" && mergeOptions.method !== "squash") throw new UnsupportedOperationError(`Cursor Origin does not support the ${mergeOptions.method} merge method`, context);
898
+ let method = mergeOptions.method;
899
+ if (!method) {
900
+ const { data: repo } = await fetcher.json(repoPath(ref.repo), { signal: mergeOptions.signal });
901
+ method = soleMergeMethod({
902
+ merge: repo.allowMergeCommit,
903
+ squash: repo.allowSquashMerge
904
+ }, context);
905
+ }
906
+ await hooks.beforeMerge?.();
907
+ await fetcher.raw(`${pullPath(ref)}/merge`, {
908
+ method: "POST",
909
+ json: {
910
+ mergeMethod: method,
911
+ expectedHeadSha: mergeOptions.sha
912
+ },
913
+ mapError: toMergeError,
914
+ signal: mergeOptions.signal
915
+ });
916
+ }),
917
+ reviewsPage: verb("experimental", async (thread, listOptions = {}) => {
918
+ const ref = requireThread(thread, context);
919
+ const page = await tokenPage(`${pullPath(ref)}/reviews`, "reviews", listOptions);
920
+ return {
921
+ items: page.items.map((raw) => toReview(ref, raw)),
922
+ cursor: page.cursor
923
+ };
924
+ }),
925
+ createReview: verb("unverified", createReview),
926
+ reviewThreads: verb("unverified", {
927
+ resolveReviewThread: (_thread, id, options) => setThreadResolved(id, true, options),
928
+ unresolveReviewThread: (_thread, id, options) => setThreadResolved(id, false, options)
929
+ }),
930
+ checks: perKind(PULL, async (thread, options) => {
931
+ const ref = requireThread(thread, context);
932
+ const { data } = await fetcher.json(pullPath(ref), { signal: options?.signal });
933
+ const sha = data.version?.headSha ?? data.head?.sha;
934
+ return { items: sha ? await headChecks(ref.repo, sha, options?.signal) : [] };
935
+ })
936
+ },
937
+ webhooks: {},
938
+ scopes: () => ({ note: "An Origin app installation token; Origin has no per-verb scopes" })
939
+ };
940
+ }
941
+ const ORIGIN = {
942
+ forge: FORGE,
943
+ experimental: true,
944
+ anonymous: "webhooks",
945
+ baseUrl: "https://api.cursor.com",
946
+ apiPath: "/v1/origin",
947
+ instance: (host) => host === "api.cursor.com" ? "origin.cursor.com" : host,
948
+ headers: { accept: "application/json" },
949
+ prepare: ({ options, state, baseUrl, headers }) => options.auth?.type === "app" ? state ?? createOriginAppCredentials(options.auth, (authHeaders) => createFetcherFor(baseUrl, headers, options, authHeaders)) : void 0,
950
+ authHeaders: ({ options, state }) => {
951
+ const auth = options.auth;
952
+ if (!auth || auth.type === "anonymous") return;
953
+ if (auth.type === "token") return async () => ({ authorization: `Bearer ${await resolveToken(auth)}` });
954
+ const app = state;
955
+ return auth.installationId === void 0 ? async () => ({ authorization: `Bearer ${await app.appJwt()}` }) : async () => ({ authorization: `Bearer ${await app.installationToken(String(auth.installationId))}` });
956
+ },
957
+ setup: setupOrigin
958
+ };
959
+ /**
960
+ * Creates a Cursor Origin provider. Experimental: built against the
961
+ * `v1alpha1` API, which Cursor publishes as Early Beta.
962
+ */
963
+ const cursorOrigin = /* @__PURE__ */ defineForgeProvider({
964
+ ...ORIGIN,
965
+ webhooks: cursorOriginWebhooks
966
+ });
967
+ /** `cursorOrigin()` without webhook ingestion, for bundles that never receive a delivery. */
968
+ const cursorOriginLite = /* @__PURE__ */ defineForgeProvider(ORIGIN);
969
+ function createFetcherFor(baseUrl, headers, options, authHeaders) {
970
+ return createFetcher({
971
+ baseUrl,
972
+ headers,
973
+ fetch: options.fetch,
974
+ timeout: options.timeout,
975
+ authHeaders,
976
+ context: { forge: FORGE }
977
+ });
978
+ }
979
+ //#endregion
980
+ export { cursorOrigin as n, cursorOriginLite as r, ORIGIN_API_VERSION as t };