@smwb/onreel-git-mcp 0.0.0-stage → 0.2.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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-3YP22YLA.js +50 -0
  3. package/dist/chunk-3YP22YLA.js.map +1 -0
  4. package/dist/chunk-4ZGRXIVA.js +259 -0
  5. package/dist/chunk-4ZGRXIVA.js.map +1 -0
  6. package/dist/chunk-C6PHNSDC.js +112 -0
  7. package/dist/chunk-C6PHNSDC.js.map +1 -0
  8. package/dist/chunk-GPRMI4PG.js +421 -0
  9. package/dist/chunk-GPRMI4PG.js.map +1 -0
  10. package/dist/chunk-L7CPE2SO.js +166 -0
  11. package/dist/chunk-L7CPE2SO.js.map +1 -0
  12. package/dist/contract/contract.const.d.ts +190 -0
  13. package/dist/contract/contract.lib.d.ts +22 -0
  14. package/dist/contract/contract.schema.d.ts +279 -0
  15. package/dist/contract/contract.types.d.ts +23 -0
  16. package/dist/files/files.const.d.ts +6 -0
  17. package/dist/files/files.lib.d.ts +14 -0
  18. package/dist/files/files.types.d.ts +13 -0
  19. package/dist/files/index.d.ts +3 -0
  20. package/dist/files/index.js +163 -0
  21. package/dist/files/index.js.map +1 -0
  22. package/dist/git/git.const.d.ts +4 -0
  23. package/dist/git/git.lib.d.ts +90 -0
  24. package/dist/git/git.types.d.ts +33 -0
  25. package/dist/git/index.d.ts +7 -0
  26. package/dist/git/index.js +97 -0
  27. package/dist/git/index.js.map +1 -0
  28. package/dist/git/paths.const.d.ts +5 -0
  29. package/dist/git/paths.lib.d.ts +39 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.js +117 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/kernel/log.types.d.ts +5 -0
  34. package/dist/kernel/redact.lib.d.ts +25 -0
  35. package/dist/kernel/store.types.d.ts +109 -0
  36. package/dist/kernel/umask.lib.d.ts +7 -0
  37. package/dist/mcp/args.lib.d.ts +17 -0
  38. package/dist/mcp/envelope.lib.d.ts +20 -0
  39. package/dist/mcp/index.d.ts +8 -0
  40. package/dist/mcp/index.js +744 -0
  41. package/dist/mcp/index.js.map +1 -0
  42. package/dist/mcp/mcp.const.d.ts +23 -0
  43. package/dist/mcp/mcp.schema.d.ts +64 -0
  44. package/dist/mcp/mcp.types.d.ts +125 -0
  45. package/dist/mcp/principal.lib.d.ts +22 -0
  46. package/dist/mcp/server.lib.d.ts +3 -0
  47. package/dist/mcp/tools.lib.d.ts +16 -0
  48. package/dist/tracker/builtin.lib.d.ts +1 -0
  49. package/dist/tracker/github.lib.d.ts +2 -0
  50. package/dist/tracker/gitlab.lib.d.ts +2 -0
  51. package/dist/tracker/http.lib.d.ts +4 -0
  52. package/dist/tracker/index.d.ts +15 -0
  53. package/dist/tracker/index.js +840 -0
  54. package/dist/tracker/index.js.map +1 -0
  55. package/dist/tracker/jira.lib.d.ts +2 -0
  56. package/dist/tracker/local.lib.d.ts +2 -0
  57. package/dist/tracker/mapping.const.d.ts +26 -0
  58. package/dist/tracker/registry.lib.d.ts +5 -0
  59. package/dist/tracker/secrets.lib.d.ts +7 -0
  60. package/dist/tracker/sourcecraft.lib.d.ts +2 -0
  61. package/dist/tracker/tracker.const.d.ts +10 -0
  62. package/dist/tracker/tracker.lib.d.ts +21 -0
  63. package/dist/tracker/tracker.types.d.ts +77 -0
  64. package/dist/worktree/index.d.ts +4 -0
  65. package/dist/worktree/index.js +203 -0
  66. package/dist/worktree/index.js.map +1 -0
  67. package/dist/worktree/lifecycle.const.d.ts +2 -0
  68. package/dist/worktree/lifecycle.lib.d.ts +30 -0
  69. package/dist/worktree/lifecycle.types.d.ts +14 -0
  70. package/package.json +74 -4
  71. package/README.md +0 -3
@@ -0,0 +1,840 @@
1
+ import {
2
+ defaultSecretSink,
3
+ redactSecretText
4
+ } from "../chunk-L7CPE2SO.js";
5
+
6
+ // src/tracker/tracker.const.ts
7
+ var TRACKER_TIMEOUT_MS = 15e3;
8
+ var TRACKER_PAGE_LIMIT_DEFAULT = 50;
9
+ var TRACKER_ERROR_BODY_MAX = 300;
10
+ var TRACKER_SECRETS_FILE = "tracker-secrets.json";
11
+ var TRACKER_LOCAL_AUTHOR = "owner";
12
+
13
+ // src/tracker/tracker.lib.ts
14
+ var TrackerError = class extends Error {
15
+ failure;
16
+ constructor(failure, message) {
17
+ super(message);
18
+ this.name = "TrackerError";
19
+ this.failure = failure;
20
+ }
21
+ };
22
+ function toTimestamp(value) {
23
+ if (typeof value !== "string") return void 0;
24
+ const ms = Date.parse(value);
25
+ return Number.isFinite(ms) && ms >= 0 ? ms : void 0;
26
+ }
27
+ function text(value) {
28
+ return typeof value === "string" && value !== "" ? value : void 0;
29
+ }
30
+ function clipText(value, max) {
31
+ return value.length <= max ? value : value.slice(0, max);
32
+ }
33
+ function stringList(value) {
34
+ if (!Array.isArray(value)) return [];
35
+ return [...new Set(value.filter((item) => typeof item === "string" && item !== ""))];
36
+ }
37
+ function isRecord(value) {
38
+ return typeof value === "object" && value !== null && !Array.isArray(value);
39
+ }
40
+ function lookup(table, key, fallback) {
41
+ return typeof key === "string" && Object.hasOwn(table, key) ? table[key] : fallback;
42
+ }
43
+ function requireTarget(target, hint) {
44
+ if (target === void 0) throw new TrackerError("invalid_request", `\u041D\u0435 \u0437\u0430\u0434\u0430\u043D target \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F: ${hint}.`);
45
+ return target;
46
+ }
47
+ function nameOf(table, category) {
48
+ return Object.entries(table).find(([, value]) => value === category)?.[0];
49
+ }
50
+ function priorityName(table, priority) {
51
+ return Object.entries(table).find(([, value]) => value === priority)?.[0];
52
+ }
53
+
54
+ // src/tracker/mapping.const.ts
55
+ var SOURCECRAFT_STATUSES = {
56
+ open: "open",
57
+ paused: "open",
58
+ inProgress: "in_progress",
59
+ closed: "done",
60
+ declined: "declined",
61
+ duplicate: "declined"
62
+ };
63
+ var SOURCECRAFT_STATUS_SLUGS = {
64
+ open: "open",
65
+ in_progress: "inProgress",
66
+ done: "closed",
67
+ declined: "declined"
68
+ };
69
+ var SOURCECRAFT_PRIORITIES = {
70
+ trivial: "trivial",
71
+ minor: "minor",
72
+ normal: "normal",
73
+ critical: "critical",
74
+ blocker: "blocker"
75
+ };
76
+ var JIRA_STATUS_CATEGORIES = {
77
+ new: "open",
78
+ indeterminate: "in_progress",
79
+ done: "done"
80
+ };
81
+ var JIRA_PRIORITIES = {
82
+ lowest: "trivial",
83
+ low: "minor",
84
+ medium: "normal",
85
+ high: "critical",
86
+ highest: "blocker"
87
+ };
88
+ var JIRA_PRIORITY_NAMES = {
89
+ trivial: "Lowest",
90
+ minor: "Low",
91
+ normal: "Medium",
92
+ critical: "High",
93
+ blocker: "Highest"
94
+ };
95
+ var JIRA_ISSUE_TYPE = "Task";
96
+ var GITHUB_CLOSE_REASONS = {
97
+ completed: "done",
98
+ not_planned: "declined",
99
+ duplicate: "declined"
100
+ };
101
+ var GITLAB_PRIORITY_LABELS = {
102
+ "priority::trivial": "trivial",
103
+ "priority::minor": "minor",
104
+ "priority::normal": "normal",
105
+ "priority::critical": "critical",
106
+ "priority::blocker": "blocker"
107
+ };
108
+ var GITLAB_IN_PROGRESS_LABEL = "status::in-progress";
109
+ var GITHUB_IN_PROGRESS_LABEL = "in progress";
110
+ var GITHUB_PRIORITY_LABELS = {
111
+ "priority: trivial": "trivial",
112
+ "priority: minor": "minor",
113
+ "priority: normal": "normal",
114
+ "priority: critical": "critical",
115
+ "priority: blocker": "blocker"
116
+ };
117
+ var ALL_STATUSES = ["open", "in_progress", "done", "declined"];
118
+
119
+ // src/tracker/http.lib.ts
120
+ async function requestJson(url, request) {
121
+ let response;
122
+ try {
123
+ response = await request.fetch(url, {
124
+ method: request.method ?? "GET",
125
+ headers: { accept: "application/json", ...request.body === void 0 ? {} : { "content-type": "application/json" }, ...request.headers },
126
+ ...request.body === void 0 ? {} : { body: JSON.stringify(request.body) },
127
+ signal: AbortSignal.timeout(TRACKER_TIMEOUT_MS)
128
+ });
129
+ } catch (error) {
130
+ throw new TrackerError("failed", redactSecretText(`\u0422\u0440\u0435\u043A\u0435\u0440 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B (${hostOf(url)}): ${error instanceof Error ? error.message : String(error)}`));
131
+ }
132
+ const raw = await response.text();
133
+ if (!response.ok) {
134
+ if (response.status === 404 && request.missingIsIssue === true) throw new TrackerError("unknown_issue", "\u0417\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0442\u0440\u0435\u043A\u0435\u0440\u0435.");
135
+ const detail = redactSecretText(raw.replace(/\s+/g, " ").trim()).slice(0, TRACKER_ERROR_BODY_MAX);
136
+ throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)} \u043E\u0442\u0432\u0435\u0442\u0438\u043B ${String(response.status)}${detail === "" ? "" : `: ${detail}`}.`);
137
+ }
138
+ if (raw === "") return { body: void 0, headers: response.headers };
139
+ try {
140
+ return { body: JSON.parse(raw), headers: response.headers };
141
+ } catch {
142
+ throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)} \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435 JSON.`);
143
+ }
144
+ }
145
+ function hostOf(url) {
146
+ try {
147
+ return new URL(url).host;
148
+ } catch {
149
+ return "\u0442\u0440\u0435\u043A\u0435\u0440";
150
+ }
151
+ }
152
+ function baseUrl(value, fallback) {
153
+ const raw = (value ?? fallback).replace(/\/+$/, "");
154
+ let parsed;
155
+ try {
156
+ parsed = new URL(raw);
157
+ } catch {
158
+ throw new TrackerError("invalid_request", "\u0410\u0434\u0440\u0435\u0441 \u0442\u0440\u0435\u043A\u0435\u0440\u0430 \u2014 \u043D\u0435 URL.");
159
+ }
160
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new TrackerError("invalid_request", "\u0410\u0434\u0440\u0435\u0441 \u0442\u0440\u0435\u043A\u0435\u0440\u0430 \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C http \u0438\u043B\u0438 https.");
161
+ return raw;
162
+ }
163
+
164
+ // src/tracker/registry.lib.ts
165
+ var definitions = /* @__PURE__ */ new Map();
166
+ function registerTracker(definition) {
167
+ const kind = definition.descriptor.tracker;
168
+ if (definitions.has(kind)) throw new Error(`\u0422\u0440\u0435\u043A\u0435\u0440 ${kind} \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D.`);
169
+ definitions.set(kind, definition);
170
+ }
171
+ function findTracker(kind) {
172
+ return definitions.get(kind);
173
+ }
174
+ function listTrackers() {
175
+ return [...definitions.values()];
176
+ }
177
+
178
+ // src/tracker/github.lib.ts
179
+ var DEFAULT_API = "https://api.github.com";
180
+ var TARGET = /^[\w.-]+\/[\w.-]+$/;
181
+ var NUMBER = /^[1-9]\d{0,14}$/;
182
+ function githubTracker() {
183
+ return {
184
+ descriptor: {
185
+ tracker: "github",
186
+ title: "GitHub",
187
+ fields: [
188
+ { name: "target", required: true, secret: false },
189
+ { name: "token", required: true, secret: true },
190
+ { name: "url", required: false, secret: false }
191
+ ],
192
+ features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: true, statuses: ["open", "in_progress", "done", "declined"] }
193
+ },
194
+ create: (context) => createGithubAdapter(context)
195
+ };
196
+ }
197
+ function createGithubAdapter({ connection, fetch }) {
198
+ const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
199
+ if (!TARGET.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
200
+ const root = `${baseUrl(connection.url, DEFAULT_API)}/repos/${target}/issues`;
201
+ const headers = {
202
+ accept: "application/vnd.github+json",
203
+ "x-github-api-version": "2022-11-28",
204
+ ...connection.token === void 0 || connection.token === "" ? {} : { authorization: `Bearer ${connection.token}` }
205
+ };
206
+ const number = (key) => {
207
+ if (!NUMBER.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitHub \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
208
+ return key;
209
+ };
210
+ const rawOf = async (key) => {
211
+ const { body } = await requestJson(`${root}/${number(key)}`, { fetch, headers, missingIsIssue: true });
212
+ return isRecord(body) ? body : {};
213
+ };
214
+ return {
215
+ list: async (query) => {
216
+ const page = pageOf(query.cursor);
217
+ const params = new URLSearchParams({ state: stateFilter(query.status), per_page: String(query.limit), page: String(page) });
218
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
219
+ const items = (Array.isArray(body) ? body : []).filter((item) => isRecord(item) && item.pull_request === void 0).map(issueOf);
220
+ return {
221
+ items: query.status === void 0 ? items : items.filter((issue) => query.status?.includes(issue.status.category) === true),
222
+ .../rel="next"/.test(responseHeaders.get("link") ?? "") ? { nextCursor: String(page + 1) } : {}
223
+ };
224
+ },
225
+ get: async (key) => issueOf(await rawOf(key)),
226
+ create: async (input) => {
227
+ const labels = composeLabels(input.labels ?? [], input.priority, void 0);
228
+ const body = {
229
+ title: input.title,
230
+ ...input.description === void 0 ? {} : { body: input.description },
231
+ ...labels.length === 0 ? {} : { labels },
232
+ ...input.assignee === void 0 ? {} : { assignees: [input.assignee] }
233
+ };
234
+ return issueOf((await requestJson(root, { fetch, headers, method: "POST", body })).body);
235
+ },
236
+ update: async (key, patch) => {
237
+ const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : issueOf(await rawOf(key));
238
+ const response = await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body: patchBody(patch, current), missingIsIssue: true });
239
+ return issueOf(response.body);
240
+ },
241
+ comments: async (key, query) => {
242
+ const page = pageOf(query.cursor);
243
+ const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
244
+ const { body, headers: responseHeaders } = await requestJson(`${root}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
245
+ return { items: (Array.isArray(body) ? body : []).map(commentOf), .../rel="next"/.test(responseHeaders.get("link") ?? "") ? { nextCursor: String(page + 1) } : {} };
246
+ },
247
+ comment: async (key, body) => commentOf((await requestJson(`${root}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
248
+ };
249
+ }
250
+ function pageOf(cursor) {
251
+ const page = cursor === void 0 ? 1 : Number(cursor);
252
+ return Number.isInteger(page) && page >= 1 ? page : 1;
253
+ }
254
+ function stateFilter(status) {
255
+ if (status === void 0) return "all";
256
+ if (status.every((item) => item === "open" || item === "in_progress")) return "open";
257
+ if (status.every((item) => item === "done" || item === "declined")) return "closed";
258
+ return "all";
259
+ }
260
+ function composeLabels(plain, priority, status) {
261
+ const priorityLabel = priority === void 0 ? void 0 : priorityName(GITHUB_PRIORITY_LABELS, priority);
262
+ return [...plain, ...priorityLabel === void 0 ? [] : [priorityLabel], ...status === "in_progress" ? [GITHUB_IN_PROGRESS_LABEL] : []];
263
+ }
264
+ function patchBody(patch, current) {
265
+ const status = patch.status ?? current?.status.category;
266
+ const touchesLabels = patch.labels !== void 0 || patch.priority !== void 0 || patch.status !== void 0;
267
+ return {
268
+ ...patch.title === void 0 ? {} : { title: patch.title },
269
+ ...patch.description === void 0 ? {} : { body: patch.description },
270
+ ...patch.status === void 0 ? {} : { state: patch.status === "done" || patch.status === "declined" ? "closed" : "open", ...patch.status === "declined" ? { state_reason: "not_planned" } : patch.status === "done" ? { state_reason: "completed" } : { state_reason: "reopened" } },
271
+ ...touchesLabels ? { labels: composeLabels(patch.labels ?? current?.labels ?? [], patch.priority ?? current?.priority, status) } : {},
272
+ ...patch.assignee === void 0 ? {} : { assignees: patch.assignee === null ? [] : [patch.assignee] }
273
+ };
274
+ }
275
+ function issueOf(raw) {
276
+ const item = isRecord(raw) ? raw : {};
277
+ const names = Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : label)) : [];
278
+ const closed = item.state === "closed";
279
+ const category = closed ? lookup(GITHUB_CLOSE_REASONS, item.state_reason, "done") : names.includes(GITHUB_IN_PROGRESS_LABEL) ? "in_progress" : "open";
280
+ const priority = names.map((name) => lookup(GITHUB_PRIORITY_LABELS, name, void 0)).find((value) => value !== void 0);
281
+ const description = text(item.body);
282
+ const assignee = isRecord(item.assignee) ? text(item.assignee.login) : void 0;
283
+ const url = text(item.html_url);
284
+ const createdAt = toTimestamp(item.created_at);
285
+ const updatedAt = toTimestamp(item.updated_at);
286
+ const number = typeof item.number === "number" ? String(item.number) : "?";
287
+ return {
288
+ key: number,
289
+ title: text(item.title) ?? number,
290
+ ...description === void 0 ? {} : { description },
291
+ status: { category, name: closed ? `closed${typeof item.state_reason === "string" ? ` (${item.state_reason})` : ""}` : category === "in_progress" ? "in progress" : "open" },
292
+ ...priority === void 0 ? {} : { priority },
293
+ labels: names.filter((name) => name !== GITHUB_IN_PROGRESS_LABEL && !Object.hasOwn(GITHUB_PRIORITY_LABELS, name)),
294
+ ...assignee === void 0 ? {} : { assignee },
295
+ links: url === void 0 ? [] : [{ url, title: "GitHub" }],
296
+ ...createdAt === void 0 ? {} : { createdAt },
297
+ ...updatedAt === void 0 ? {} : { updatedAt }
298
+ };
299
+ }
300
+ function commentOf(raw) {
301
+ const item = isRecord(raw) ? raw : {};
302
+ const author = isRecord(item.user) ? text(item.user.login) : void 0;
303
+ const createdAt = toTimestamp(item.created_at);
304
+ return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
305
+ }
306
+
307
+ // src/tracker/gitlab.lib.ts
308
+ var DEFAULT_URL = "https://gitlab.com";
309
+ var TARGET2 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
310
+ var IID = /^[1-9]\d{0,14}$/;
311
+ function gitlabTracker() {
312
+ return {
313
+ descriptor: {
314
+ tracker: "gitlab",
315
+ title: "GitLab",
316
+ fields: [
317
+ { name: "target", required: true, secret: false },
318
+ { name: "token", required: true, secret: true },
319
+ { name: "url", required: false, secret: false }
320
+ ],
321
+ features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
322
+ },
323
+ create: (context) => createGitlabAdapter(context)
324
+ };
325
+ }
326
+ function createGitlabAdapter({ connection, fetch }) {
327
+ const target = requireTarget(connection.target, "\u043F\u0443\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 GitLab, group/project, \u0438\u043B\u0438 \u0435\u0433\u043E id");
328
+ if (!TARGET2.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
329
+ const root = `${baseUrl(connection.url, DEFAULT_URL)}/api/v4/projects/${encodeURIComponent(target)}/issues`;
330
+ const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
331
+ const iid = (key) => {
332
+ if (!IID.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
333
+ return key;
334
+ };
335
+ const currentOf = async (key) => issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missingIsIssue: true })).body);
336
+ return {
337
+ list: async (query) => {
338
+ const page = pageOf2(query.cursor);
339
+ const params = new URLSearchParams({ state: stateFilter2(query.status), per_page: String(query.limit), page: String(page), order_by: "created_at", sort: "desc" });
340
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
341
+ const items = (Array.isArray(body) ? body : []).map(issueOf2);
342
+ const next = responseHeaders.get("x-next-page");
343
+ return {
344
+ items: query.status === void 0 ? items : items.filter((issue) => query.status?.includes(issue.status.category) === true),
345
+ ...next === null || next === "" ? {} : { nextCursor: next }
346
+ };
347
+ },
348
+ get: currentOf,
349
+ create: async (input) => {
350
+ const labels = composeLabels2(input.labels ?? [], input.priority, void 0);
351
+ const body = { title: input.title, ...input.description === void 0 ? {} : { description: input.description }, ...labels.length === 0 ? {} : { labels: labels.join(",") } };
352
+ return issueOf2((await requestJson(root, { fetch, headers, method: "POST", body })).body);
353
+ },
354
+ update: async (key, patch) => {
355
+ const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : await currentOf(key);
356
+ return issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body: patchBody2(patch, current), missingIsIssue: true })).body);
357
+ },
358
+ comments: async (key, query) => {
359
+ const page = pageOf2(query.cursor);
360
+ const params = new URLSearchParams({ sort: "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
361
+ const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, headers, missingIsIssue: true });
362
+ const next = responseHeaders.get("x-next-page");
363
+ const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true).map(commentOf2);
364
+ return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
365
+ },
366
+ comment: async (key, body) => commentOf2((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
367
+ };
368
+ }
369
+ function pageOf2(cursor) {
370
+ const page = cursor === void 0 ? 1 : Number(cursor);
371
+ return Number.isInteger(page) && page >= 1 ? page : 1;
372
+ }
373
+ function stateFilter2(status) {
374
+ if (status === void 0) return "all";
375
+ if (status.every((item) => item === "open" || item === "in_progress")) return "opened";
376
+ if (status.every((item) => item === "done" || item === "declined")) return "closed";
377
+ return "all";
378
+ }
379
+ function composeLabels2(plain, priority, status) {
380
+ const priorityLabel = priority === void 0 ? void 0 : priorityName(GITLAB_PRIORITY_LABELS, priority);
381
+ return [...plain, ...priorityLabel === void 0 ? [] : [priorityLabel], ...status === "in_progress" ? [GITLAB_IN_PROGRESS_LABEL] : []];
382
+ }
383
+ function patchBody2(patch, current) {
384
+ const status = patch.status ?? current?.status.category;
385
+ const touchesLabels = patch.labels !== void 0 || patch.priority !== void 0 || patch.status !== void 0;
386
+ if (patch.status === "declined") throw new TrackerError("invalid_request", "\u0412 GitLab \u043D\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \xAB\u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0430\xBB.");
387
+ return {
388
+ ...patch.title === void 0 ? {} : { title: patch.title },
389
+ ...patch.description === void 0 ? {} : { description: patch.description },
390
+ ...patch.status === void 0 ? {} : { state_event: patch.status === "done" ? "close" : "reopen" },
391
+ ...touchesLabels ? { labels: composeLabels2(patch.labels ?? current?.labels ?? [], patch.priority ?? current?.priority, status).join(",") } : {}
392
+ };
393
+ }
394
+ function issueOf2(raw) {
395
+ const item = isRecord(raw) ? raw : {};
396
+ const names = stringList(item.labels);
397
+ const closed = item.state === "closed";
398
+ const category = closed ? "done" : names.includes(GITLAB_IN_PROGRESS_LABEL) ? "in_progress" : "open";
399
+ const priority = names.map((name) => lookup(GITLAB_PRIORITY_LABELS, name, void 0)).find((value) => value !== void 0);
400
+ const description = text(item.description);
401
+ const assignees = Array.isArray(item.assignees) ? item.assignees : [];
402
+ const first = assignees[0];
403
+ const assignee = isRecord(first) ? text(first.username) : void 0;
404
+ const url = text(item.web_url);
405
+ const createdAt = toTimestamp(item.created_at);
406
+ const updatedAt = toTimestamp(item.updated_at);
407
+ const key = typeof item.iid === "number" ? String(item.iid) : "?";
408
+ return {
409
+ key,
410
+ title: text(item.title) ?? key,
411
+ ...description === void 0 ? {} : { description },
412
+ status: { category, name: closed ? "closed" : category === "in_progress" ? "in progress" : "opened" },
413
+ ...priority === void 0 ? {} : { priority },
414
+ labels: names.filter((name) => name !== GITLAB_IN_PROGRESS_LABEL && !Object.hasOwn(GITLAB_PRIORITY_LABELS, name)),
415
+ ...assignee === void 0 ? {} : { assignee },
416
+ links: url === void 0 ? [] : [{ url, title: "GitLab" }],
417
+ ...createdAt === void 0 ? {} : { createdAt },
418
+ ...updatedAt === void 0 ? {} : { updatedAt }
419
+ };
420
+ }
421
+ function commentOf2(raw) {
422
+ const item = isRecord(raw) ? raw : {};
423
+ const author = isRecord(item.author) ? text(item.author.username) : void 0;
424
+ const createdAt = toTimestamp(item.created_at);
425
+ return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
426
+ }
427
+
428
+ // src/tracker/jira.lib.ts
429
+ var PROJECT_KEY = /^[A-Z][A-Z0-9_]{0,49}$/;
430
+ var ISSUE_KEY = /^[A-Z][A-Z0-9_]{0,49}-[1-9]\d{0,14}$/;
431
+ var FIELDS = "summary,description,status,priority,labels,assignee,created,updated";
432
+ var CATEGORY_JQL = { open: '"To Do"', in_progress: '"In Progress"', done: "Done" };
433
+ function jiraTracker() {
434
+ return {
435
+ descriptor: {
436
+ tracker: "jira",
437
+ title: "Jira",
438
+ fields: [
439
+ { name: "url", required: true, secret: false },
440
+ { name: "target", required: true, secret: false },
441
+ { name: "user", required: false, secret: false },
442
+ { name: "token", required: true, secret: true }
443
+ ],
444
+ features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
445
+ },
446
+ create: (context) => createJiraAdapter(context)
447
+ };
448
+ }
449
+ function createJiraAdapter({ connection, fetch }) {
450
+ if (connection.url === void 0) throw new TrackerError("invalid_request", "\u041D\u0435 \u0437\u0430\u0434\u0430\u043D \u0430\u0434\u0440\u0435\u0441 Jira.");
451
+ const project = requireTarget(connection.target, "\u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 Jira, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 PROJ");
452
+ if (!PROJECT_KEY.test(project)) throw new TrackerError("invalid_request", "target Jira \u2014 \u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u0435 \u0431\u0443\u043A\u0432\u044B \u0438 \u0446\u0438\u0444\u0440\u044B.");
453
+ const site = baseUrl(connection.url, connection.url);
454
+ const api = `${site}/rest/api/2`;
455
+ const token = connection.token ?? "";
456
+ const headers = { authorization: connection.user === void 0 ? `Bearer ${token}` : `Basic ${Buffer.from(`${connection.user}:${token}`).toString("base64")}` };
457
+ const key = (value) => {
458
+ if (!ISSUE_KEY.test(value)) throw new TrackerError("unknown_issue", `\u0412 Jira \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${value}.`);
459
+ return value;
460
+ };
461
+ const toIssue2 = (raw) => issueOf3(raw, site);
462
+ const load = async (value) => toIssue2((await requestJson(`${api}/issue/${key(value)}?fields=${FIELDS}`, { fetch, headers, missingIsIssue: true })).body);
463
+ return {
464
+ list: async (query) => {
465
+ const jql = listJql(project, query.status);
466
+ if (jql === void 0) return { items: [] };
467
+ if (query.cursor?.startsWith("s:") !== true) {
468
+ const params2 = new URLSearchParams({ jql, fields: FIELDS, maxResults: String(query.limit) });
469
+ if (query.cursor !== void 0) params2.set("nextPageToken", query.cursor);
470
+ try {
471
+ const { body: body2 } = await requestJson(`${api}/search/jql?${params2.toString()}`, { fetch, headers });
472
+ const record2 = isRecord(body2) ? body2 : {};
473
+ const next = text(record2.nextPageToken);
474
+ return { items: (Array.isArray(record2.issues) ? record2.issues : []).map(toIssue2), ...next === void 0 || record2.isLast === true ? {} : { nextCursor: next } };
475
+ } catch (error) {
476
+ if (!(error instanceof TrackerError) || !error.message.includes(" 404")) throw error;
477
+ }
478
+ }
479
+ const startAt = query.cursor?.startsWith("s:") === true ? Number(query.cursor.slice(2)) : 0;
480
+ const params = new URLSearchParams({ jql, fields: FIELDS, maxResults: String(query.limit), startAt: String(Number.isInteger(startAt) && startAt >= 0 ? startAt : 0) });
481
+ const { body } = await requestJson(`${api}/search?${params.toString()}`, { fetch, headers });
482
+ const record = isRecord(body) ? body : {};
483
+ const issues = Array.isArray(record.issues) ? record.issues : [];
484
+ const total = typeof record.total === "number" ? record.total : 0;
485
+ const at = typeof record.startAt === "number" ? record.startAt : 0;
486
+ return { items: issues.map(toIssue2), ...at + issues.length < total && issues.length > 0 ? { nextCursor: `s:${String(at + issues.length)}` } : {} };
487
+ },
488
+ get: load,
489
+ create: async (input) => {
490
+ const fields = {
491
+ project: { key: project },
492
+ summary: input.title,
493
+ issuetype: { name: JIRA_ISSUE_TYPE },
494
+ ...input.description === void 0 ? {} : { description: input.description },
495
+ ...input.priority === void 0 ? {} : { priority: { name: JIRA_PRIORITY_NAMES[input.priority] } },
496
+ ...input.labels === void 0 ? {} : { labels: input.labels.map((label) => label.replace(/\s+/g, "-")) }
497
+ };
498
+ const { body } = await requestJson(`${api}/issue`, { fetch, headers, method: "POST", body: { fields } });
499
+ const created = isRecord(body) ? text(body.key) : void 0;
500
+ if (created === void 0) throw new TrackerError("failed", "Jira \u0441\u043E\u0437\u0434\u0430\u043B\u0430 \u0437\u0430\u0434\u0430\u0447\u0443, \u043D\u043E \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 \u0435\u0451 \u043A\u043B\u044E\u0447.");
501
+ return load(created);
502
+ },
503
+ update: async (value, patch) => {
504
+ const fields = fieldsOf(patch);
505
+ if (Object.keys(fields).length > 0) await requestJson(`${api}/issue/${key(value)}`, { fetch, headers, method: "PUT", body: { fields }, missingIsIssue: true });
506
+ if (patch.status !== void 0) await transition(api, key(value), patch.status, fetch, headers);
507
+ return load(value);
508
+ },
509
+ comments: async (value, query) => {
510
+ const startAt = query.cursor === void 0 ? 0 : Number(query.cursor);
511
+ const params = new URLSearchParams({ startAt: String(Number.isInteger(startAt) && startAt >= 0 ? startAt : 0), maxResults: String(query.limit), orderBy: "created" });
512
+ const { body } = await requestJson(`${api}/issue/${key(value)}/comment?${params.toString()}`, { fetch, headers, missingIsIssue: true });
513
+ const record = isRecord(body) ? body : {};
514
+ const items = Array.isArray(record.comments) ? record.comments : [];
515
+ const total = typeof record.total === "number" ? record.total : 0;
516
+ const at = typeof record.startAt === "number" ? record.startAt : 0;
517
+ return { items: items.map(commentOf3), ...at + items.length < total && items.length > 0 ? { nextCursor: String(at + items.length) } : {} };
518
+ },
519
+ comment: async (value, body) => commentOf3((await requestJson(`${api}/issue/${key(value)}/comment`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
520
+ };
521
+ }
522
+ function listJql(project, status) {
523
+ const base = `project = "${project}"`;
524
+ if (status === void 0) return `${base} ORDER BY created DESC`;
525
+ const names = status.flatMap((item) => CATEGORY_JQL[item] ?? []);
526
+ return names.length === 0 ? void 0 : `${base} AND statusCategory in (${names.join(", ")}) ORDER BY created DESC`;
527
+ }
528
+ function fieldsOf(patch) {
529
+ return {
530
+ ...patch.title === void 0 ? {} : { summary: patch.title },
531
+ ...patch.description === void 0 ? {} : { description: patch.description },
532
+ ...patch.priority === void 0 ? {} : { priority: { name: JIRA_PRIORITY_NAMES[patch.priority] } },
533
+ ...patch.labels === void 0 ? {} : { labels: patch.labels.map((label) => label.replace(/\s+/g, "-")) }
534
+ };
535
+ }
536
+ async function transition(api, key, category, fetch, headers) {
537
+ const { body } = await requestJson(`${api}/issue/${key}/transitions`, { fetch, headers, missingIsIssue: true });
538
+ const list = isRecord(body) && Array.isArray(body.transitions) ? body.transitions : [];
539
+ const match = list.find((item) => {
540
+ if (!isRecord(item) || !isRecord(item.to) || !isRecord(item.to.statusCategory)) return false;
541
+ return lookup(JIRA_STATUS_CATEGORIES, item.to.statusCategory.key, void 0) === category;
542
+ });
543
+ const id = isRecord(match) ? text(match.id) : void 0;
544
+ if (id === void 0) throw new TrackerError("invalid_request", `\u0418\u0437 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u0441\u0442\u0430\u0442\u0443\u0441\u0430 Jira \u043D\u0435\u0442 \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0430 \u0432 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044E ${category}.`);
545
+ await requestJson(`${api}/issue/${key}/transitions`, { fetch, headers, method: "POST", body: { transition: { id } }, missingIsIssue: true });
546
+ }
547
+ function issueOf3(raw, site) {
548
+ const item = isRecord(raw) ? raw : {};
549
+ const fields = isRecord(item.fields) ? item.fields : {};
550
+ const status = isRecord(fields.status) ? fields.status : {};
551
+ const category = isRecord(status.statusCategory) ? status.statusCategory.key : void 0;
552
+ const priorityName2 = isRecord(fields.priority) && typeof fields.priority.name === "string" ? fields.priority.name.toLowerCase() : void 0;
553
+ const priority = lookup(JIRA_PRIORITIES, priorityName2, void 0);
554
+ const assignee = isRecord(fields.assignee) ? text(fields.assignee.displayName) ?? text(fields.assignee.name) : void 0;
555
+ const description = text(fields.description);
556
+ const createdAt = toTimestamp(fields.created);
557
+ const updatedAt = toTimestamp(fields.updated);
558
+ const key = text(item.key) ?? "?";
559
+ return {
560
+ key,
561
+ title: text(fields.summary) ?? key,
562
+ ...description === void 0 ? {} : { description },
563
+ status: { category: lookup(JIRA_STATUS_CATEGORIES, category, "open"), name: text(status.name) ?? "To Do" },
564
+ ...priority === void 0 ? {} : { priority },
565
+ labels: stringList(fields.labels),
566
+ ...assignee === void 0 ? {} : { assignee },
567
+ links: [{ url: `${site}/browse/${key}`, title: "Jira" }],
568
+ ...createdAt === void 0 ? {} : { createdAt },
569
+ ...updatedAt === void 0 ? {} : { updatedAt }
570
+ };
571
+ }
572
+ function commentOf3(raw) {
573
+ const item = isRecord(raw) ? raw : {};
574
+ const author = isRecord(item.author) ? text(item.author.displayName) : void 0;
575
+ const createdAt = toTimestamp(item.created);
576
+ return { id: text(item.id) ?? "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
577
+ }
578
+
579
+ // src/tracker/local.lib.ts
580
+ var STATUS_NAMES = { open: "Open", in_progress: "In progress", done: "Done", declined: "Declined" };
581
+ function localTracker() {
582
+ return {
583
+ descriptor: {
584
+ tracker: "local",
585
+ title: "Local",
586
+ fields: [],
587
+ features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: true, statuses: [...ALL_STATUSES] }
588
+ },
589
+ create: (context) => createLocalAdapter(context)
590
+ };
591
+ }
592
+ function createLocalAdapter({ store, projectId, author }) {
593
+ const number = (key) => {
594
+ if (!/^[1-9]\d{0,14}$/.test(key)) throw new TrackerError("unknown_issue", `\u0412 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u043C \u0442\u0440\u0435\u043A\u0435\u0440\u0435 \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
595
+ return Number(key);
596
+ };
597
+ const mustGet = (key) => {
598
+ const row = store.getLocalIssue(projectId, number(key));
599
+ if (row === void 0) throw new TrackerError("unknown_issue", `\u0412 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u043C \u0442\u0440\u0435\u043A\u0435\u0440\u0435 \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
600
+ return row;
601
+ };
602
+ return {
603
+ list: (query) => {
604
+ const page = store.listLocalIssues(projectId, { limit: query.limit, ...query.cursor === void 0 ? {} : { cursor: query.cursor }, ...query.status === void 0 ? {} : { status: query.status } });
605
+ return Promise.resolve({ items: page.items.map(toIssue), ...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor } });
606
+ },
607
+ get: (key) => Promise.resolve(toIssue(mustGet(key))),
608
+ create: (input) => Promise.resolve(toIssue(store.createLocalIssue(projectId, input))),
609
+ update: (key, patch) => {
610
+ const row = store.updateLocalIssue(projectId, mustGet(key).number, patch);
611
+ if (row === void 0) throw new TrackerError("unknown_issue", `\u0412 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u043C \u0442\u0440\u0435\u043A\u0435\u0440\u0435 \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
612
+ return Promise.resolve(toIssue(row));
613
+ },
614
+ comments: (key, query) => {
615
+ const page = store.listLocalComments(projectId, mustGet(key).number, { limit: query.limit, ...query.cursor === void 0 ? {} : { cursor: query.cursor } });
616
+ return Promise.resolve({ items: page.items.map(toComment), ...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor } });
617
+ },
618
+ comment: (key, body) => Promise.resolve(toComment(store.addLocalComment(projectId, mustGet(key).number, body, author)))
619
+ };
620
+ }
621
+ function toIssue(row) {
622
+ return {
623
+ key: String(row.number),
624
+ title: row.title,
625
+ ...row.description === void 0 ? {} : { description: row.description },
626
+ status: { category: row.status, name: STATUS_NAMES[row.status] },
627
+ ...row.priority === void 0 ? {} : { priority: row.priority },
628
+ labels: row.labels,
629
+ ...row.assignee === void 0 ? {} : { assignee: row.assignee },
630
+ links: [],
631
+ createdAt: row.createdAt,
632
+ updatedAt: row.updatedAt
633
+ };
634
+ }
635
+ function toComment(row) {
636
+ return { id: String(row.id), ...row.author === void 0 ? {} : { author: row.author }, body: row.body, createdAt: row.createdAt };
637
+ }
638
+
639
+ // src/tracker/sourcecraft.lib.ts
640
+ var DEFAULT_API2 = "https://api.sourcecraft.tech";
641
+ var DEFAULT_WEB = "https://sourcecraft.dev";
642
+ var TARGET3 = /^[\w.-]+\/[\w.-]+$/;
643
+ var SLUG = /^[\w-]{1,100}$/;
644
+ function sourcecraftTracker() {
645
+ return {
646
+ descriptor: {
647
+ tracker: "sourcecraft",
648
+ title: "SourceCraft",
649
+ fields: [
650
+ { name: "target", required: true, secret: false },
651
+ { name: "token", required: true, secret: true },
652
+ { name: "url", required: false, secret: false }
653
+ ],
654
+ features: { create: true, update: true, comment: true, priority: true, labels: false, assignee: false, statuses: [...ALL_STATUSES] }
655
+ },
656
+ create: (context) => createSourceCraftAdapter(context)
657
+ };
658
+ }
659
+ function createSourceCraftAdapter({ connection, fetch }) {
660
+ const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
661
+ if (!TARGET3.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
662
+ const api = baseUrl(connection.url, DEFAULT_API2);
663
+ const root = `${api}/repos/${target}/issues`;
664
+ const headers = { authorization: `Bearer ${connection.token ?? ""}` };
665
+ const web = connection.url === void 0 ? `${DEFAULT_WEB}/${target}/issues` : void 0;
666
+ const slug = (key) => {
667
+ if (!SLUG.test(key)) throw new TrackerError("unknown_issue", `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
668
+ return key;
669
+ };
670
+ const toIssue2 = (raw) => issueOf4(raw, web);
671
+ return {
672
+ list: async (query) => {
673
+ const params = new URLSearchParams({ page_size: String(query.limit) });
674
+ if (query.cursor !== void 0) params.set("page_token", query.cursor);
675
+ const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
676
+ const record = isRecord(body) ? body : {};
677
+ const issues = (Array.isArray(record.issues) ? record.issues : []).map(toIssue2);
678
+ return { items: query.status === void 0 ? issues : issues.filter((issue) => query.status?.includes(issue.status.category) === true), ...nextOf(record.next_page_token) };
679
+ },
680
+ get: async (key) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missingIsIssue: true })).body),
681
+ create: async (input) => {
682
+ const body = { title: input.title, ...input.description === void 0 ? {} : { description: input.description }, ...input.priority === void 0 ? {} : { priority: input.priority } };
683
+ return toIssue2((await requestJson(root, { fetch, headers, method: "POST", body })).body);
684
+ },
685
+ update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, method: "PATCH", body: patchBody3(patch), missingIsIssue: true })).body),
686
+ comments: async (key, query) => {
687
+ const params = new URLSearchParams({ page_size: String(query.limit) });
688
+ if (query.cursor !== void 0) params.set("page_token", query.cursor);
689
+ const { body } = await requestJson(`${root}/${slug(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
690
+ const record = isRecord(body) ? body : {};
691
+ return { items: (Array.isArray(record.issue_comments) ? record.issue_comments : []).map(commentOf4), ...nextOf(record.next_page_token) };
692
+ },
693
+ comment: async (key, body) => commentOf4((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
694
+ };
695
+ }
696
+ function patchBody3(patch) {
697
+ return {
698
+ ...patch.title === void 0 ? {} : { title: patch.title },
699
+ ...patch.description === void 0 ? {} : { description: patch.description },
700
+ ...patch.status === void 0 ? {} : { status_slug: SOURCECRAFT_STATUS_SLUGS[patch.status] },
701
+ ...patch.priority === void 0 ? {} : { priority: patch.priority }
702
+ };
703
+ }
704
+ function nextOf(token) {
705
+ const value = text(token);
706
+ return value === void 0 ? {} : { nextCursor: value };
707
+ }
708
+ function issueOf4(raw, web) {
709
+ const item = isRecord(raw) ? raw : {};
710
+ const status = isRecord(item.status) ? item.status : {};
711
+ const slug = text(item.slug) ?? "?";
712
+ const statusSlug = text(status.slug) ?? "open";
713
+ const priority = lookup(SOURCECRAFT_PRIORITIES, item.priority, void 0);
714
+ const assignee = isRecord(item.assignee) ? text(item.assignee.slug) : void 0;
715
+ const labels = Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : void 0)) : [];
716
+ const description = text(item.description);
717
+ const createdAt = toTimestamp(item.created_at);
718
+ const updatedAt = toTimestamp(item.updated_at);
719
+ return {
720
+ key: slug,
721
+ title: text(item.title) ?? slug,
722
+ ...description === void 0 ? {} : { description },
723
+ status: { category: lookup(SOURCECRAFT_STATUSES, statusSlug, "open"), name: text(status.name) ?? statusSlug },
724
+ ...priority === void 0 ? {} : { priority },
725
+ labels,
726
+ ...assignee === void 0 ? {} : { assignee },
727
+ links: web === void 0 ? [] : [{ url: `${web}/${slug}`, title: "SourceCraft" }],
728
+ ...createdAt === void 0 ? {} : { createdAt },
729
+ ...updatedAt === void 0 ? {} : { updatedAt }
730
+ };
731
+ }
732
+ function commentOf4(raw) {
733
+ const item = isRecord(raw) ? raw : {};
734
+ const author = isRecord(item.author) ? text(item.author.slug) : void 0;
735
+ const createdAt = toTimestamp(item.created_at);
736
+ return { id: text(item.id) ?? "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
737
+ }
738
+
739
+ // src/tracker/builtin.lib.ts
740
+ var registered = false;
741
+ function registerBuiltinTrackers() {
742
+ if (registered) return;
743
+ registered = true;
744
+ for (const definition of [localTracker(), sourcecraftTracker(), githubTracker(), gitlabTracker(), jiraTracker()]) registerTracker(definition);
745
+ }
746
+
747
+ // src/tracker/secrets.lib.ts
748
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
749
+ import { join } from "path";
750
+ var queue = Promise.resolve();
751
+ function exclusive(task) {
752
+ const run = queue.then(task, task);
753
+ queue = run.catch(() => void 0);
754
+ return run;
755
+ }
756
+ async function readAll(dataDir) {
757
+ let raw;
758
+ try {
759
+ raw = await readFile(join(dataDir, TRACKER_SECRETS_FILE), "utf8");
760
+ } catch (error) {
761
+ if (error.code === "ENOENT") return {};
762
+ throw error;
763
+ }
764
+ const parsed = JSON.parse(raw);
765
+ if (!isRecord(parsed)) return {};
766
+ return Object.fromEntries(Object.entries(parsed).filter((entry) => typeof entry[1] === "string"));
767
+ }
768
+ async function writeAll(dataDir, secrets) {
769
+ await mkdir(dataDir, { recursive: true, mode: 448 });
770
+ const target = join(dataDir, TRACKER_SECRETS_FILE);
771
+ const temp = `${target}.${String(process.pid)}.tmp`;
772
+ await writeFile(temp, JSON.stringify(secrets), { mode: 384 });
773
+ await rename(temp, target);
774
+ }
775
+ async function readTrackerToken(dataDir, projectId, sink = defaultSecretSink) {
776
+ const token = (await readAll(dataDir))[projectId];
777
+ if (token !== void 0) sink.register(token);
778
+ return token;
779
+ }
780
+ function hasTrackerToken(dataDir, projectId) {
781
+ return readAll(dataDir).then((secrets) => secrets[projectId] !== void 0);
782
+ }
783
+ function writeTrackerToken(dataDir, projectId, token, sink = defaultSecretSink) {
784
+ if (token !== "") sink.register(token);
785
+ return exclusive(async () => {
786
+ const secrets = await readAll(dataDir);
787
+ if (token === "") delete secrets[projectId];
788
+ else secrets[projectId] = token;
789
+ await writeAll(dataDir, secrets);
790
+ });
791
+ }
792
+ function deleteTrackerToken(dataDir, projectId, sink = defaultSecretSink) {
793
+ return writeTrackerToken(dataDir, projectId, "", sink);
794
+ }
795
+ export {
796
+ ALL_STATUSES,
797
+ GITHUB_CLOSE_REASONS,
798
+ GITHUB_IN_PROGRESS_LABEL,
799
+ GITHUB_PRIORITY_LABELS,
800
+ GITLAB_IN_PROGRESS_LABEL,
801
+ GITLAB_PRIORITY_LABELS,
802
+ JIRA_ISSUE_TYPE,
803
+ JIRA_PRIORITIES,
804
+ JIRA_PRIORITY_NAMES,
805
+ JIRA_STATUS_CATEGORIES,
806
+ SOURCECRAFT_PRIORITIES,
807
+ SOURCECRAFT_STATUSES,
808
+ SOURCECRAFT_STATUS_SLUGS,
809
+ TRACKER_ERROR_BODY_MAX,
810
+ TRACKER_LOCAL_AUTHOR,
811
+ TRACKER_PAGE_LIMIT_DEFAULT,
812
+ TRACKER_SECRETS_FILE,
813
+ TRACKER_TIMEOUT_MS,
814
+ TrackerError,
815
+ baseUrl,
816
+ clipText,
817
+ deleteTrackerToken,
818
+ findTracker,
819
+ githubTracker,
820
+ gitlabTracker,
821
+ hasTrackerToken,
822
+ isRecord,
823
+ jiraTracker,
824
+ listTrackers,
825
+ localTracker,
826
+ lookup,
827
+ nameOf,
828
+ priorityName,
829
+ readTrackerToken,
830
+ registerBuiltinTrackers,
831
+ registerTracker,
832
+ requestJson,
833
+ requireTarget,
834
+ sourcecraftTracker,
835
+ stringList,
836
+ text,
837
+ toTimestamp,
838
+ writeTrackerToken
839
+ };
840
+ //# sourceMappingURL=index.js.map