@r_masseater/ops-harbor 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -48,10 +48,317 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = import.meta.require;
49
49
 
50
50
  // ../../packages/ops-harbor-core/src/alerts.ts
51
+ function inferCheckState(checks) {
52
+ if (checks.length === 0)
53
+ return "unknown";
54
+ let hasNeutral = false;
55
+ for (const check of checks) {
56
+ if (check.status !== "completed") {
57
+ return "pending";
58
+ }
59
+ const conclusion = (check.conclusion ?? "").toLowerCase();
60
+ if (["failure", "timed_out", "action_required", "cancelled"].includes(conclusion)) {
61
+ return "failure";
62
+ }
63
+ if (["neutral", "skipped"].includes(conclusion)) {
64
+ hasNeutral = true;
65
+ }
66
+ }
67
+ return hasNeutral ? "neutral" : "success";
68
+ }
69
+ function inferReviewState(reviews, reviewDecision) {
70
+ const normalizedDecision = (reviewDecision ?? "").toUpperCase();
71
+ if (normalizedDecision === "CHANGES_REQUESTED")
72
+ return "changes_requested";
73
+ if (normalizedDecision === "APPROVED")
74
+ return "approved";
75
+ const latest = reviews.toSorted((a, b) => b.submittedAt.localeCompare(a.submittedAt))[0];
76
+ return latest?.state ?? "none";
77
+ }
78
+ function inferMergeState(item) {
79
+ const state = item.mergeStateStatus.toUpperCase();
80
+ if (item.mergeable === "conflicting" || ["DIRTY", "CONFLICTING"].includes(state)) {
81
+ return "conflicting";
82
+ }
83
+ if (state === "BEHIND")
84
+ return "behind";
85
+ if (["BLOCKED", "DRAFT", "UNSTABLE"].includes(state))
86
+ return "blocked";
87
+ if (state === "CLEAN" || item.mergeable === "mergeable")
88
+ return "clean";
89
+ return "unknown";
90
+ }
91
+ function summarizeStatus(checks, reviews, item) {
92
+ const checkState = inferCheckState(checks);
93
+ const reviewState = inferReviewState(reviews, item.reviewDecision);
94
+ const mergeState = inferMergeState(item);
95
+ const overall = checkState === "unknown" && reviewState === "none" && mergeState === "unknown" ? "unknown" : checkState === "failure" || reviewState === "changes_requested" || mergeState !== "clean" && mergeState !== "unknown" ? "needs_attention" : checkState === "pending" ? "running" : "healthy";
96
+ return { overall, checkState, reviewState, mergeState };
97
+ }
98
+ function createAlert(type, severity, title, message, createdAt, source) {
99
+ return { type, severity, title, message, createdAt, source };
100
+ }
101
+ function deriveAlerts(item, recentEvents = []) {
102
+ const alerts = [];
103
+ const now = item.updatedAt;
104
+ if (item.statusSummary.checkState === "failure") {
105
+ alerts.push(createAlert("ci_failed", "critical", "CI failed", "One or more required checks failed on the latest head commit.", now, "state"));
106
+ }
107
+ if (item.statusSummary.mergeState === "conflicting") {
108
+ alerts.push(createAlert("conflicted", "critical", "Merge conflicts detected", `Branch ${item.headBranch} conflicts with ${item.baseBranch}.`, now, "state"));
109
+ }
110
+ if (item.statusSummary.mergeState === "behind") {
111
+ alerts.push(createAlert("base_behind", "warning", "Base branch moved ahead", `${item.headBranch} should be rebased or merged with ${item.baseBranch}.`, now, "state"));
112
+ }
113
+ if (item.statusSummary.reviewState === "changes_requested") {
114
+ alerts.push(createAlert("review_changes_requested", "critical", "Changes requested", "A reviewer requested follow-up changes on this work item.", now, "state"));
115
+ }
116
+ const reviewCommentEvent = recentEvents.filter((event) => event.type === "review_commented").toSorted((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
117
+ if (reviewCommentEvent) {
118
+ alerts.push(createAlert("review_commented", "info", "Review comment received", reviewCommentEvent.message, reviewCommentEvent.createdAt, "event"));
119
+ }
120
+ const blockers = alerts.filter((alert) => alert.severity !== "info");
121
+ return { alerts, blockers };
122
+ }
51
123
  function hasBlockingAlerts(item) {
52
124
  return item.alerts.some((alert) => alert.severity !== "info");
53
125
  }
54
126
 
127
+ // ../../packages/ops-harbor-core/src/github.ts
128
+ import { createSign } from "crypto";
129
+ function toBase64Url(value) {
130
+ return Buffer.from(value).toString("base64").replaceAll("=", "").replaceAll("+", "-").replaceAll("/", "_");
131
+ }
132
+ function createAppJwt(appId, privateKeyPem) {
133
+ const now = Math.floor(Date.now() / 1000);
134
+ const header = { alg: "RS256", typ: "JWT" };
135
+ const payload = {
136
+ iat: now - 60,
137
+ exp: now + 9 * 60,
138
+ iss: appId
139
+ };
140
+ const encoded = `${toBase64Url(JSON.stringify(header))}.${toBase64Url(JSON.stringify(payload))}`;
141
+ const signer = createSign("RSA-SHA256");
142
+ signer.update(encoded);
143
+ const signature = signer.sign(privateKeyPem);
144
+ return `${encoded}.${toBase64Url(signature)}`;
145
+ }
146
+ async function createInstallationToken(config, installationId) {
147
+ const jwt = createAppJwt(config.githubAppId, config.githubPrivateKey);
148
+ const response = await fetch(`${config.githubApiUrl}/app/installations/${installationId}/access_tokens`, {
149
+ method: "POST",
150
+ headers: {
151
+ Accept: "application/vnd.github+json",
152
+ Authorization: `Bearer ${jwt}`,
153
+ "User-Agent": "ops-harbor"
154
+ }
155
+ });
156
+ if (!response.ok) {
157
+ throw new Error(`Failed to create installation token: ${response.status} ${response.statusText}`);
158
+ }
159
+ const data = await response.json();
160
+ return data.token;
161
+ }
162
+ async function graphqlRequest(config, installationId, query, variables) {
163
+ const token = await createInstallationToken(config, installationId);
164
+ const response = await fetch(`${config.githubApiUrl}/graphql`, {
165
+ method: "POST",
166
+ headers: {
167
+ Accept: "application/vnd.github+json",
168
+ Authorization: `Bearer ${token}`,
169
+ "Content-Type": "application/json",
170
+ "User-Agent": "ops-harbor"
171
+ },
172
+ body: JSON.stringify({ query, variables })
173
+ });
174
+ if (!response.ok) {
175
+ throw new Error(`GitHub GraphQL request failed: ${response.status} ${response.statusText}`);
176
+ }
177
+ const json = await response.json();
178
+ if (json.errors?.length) {
179
+ throw new Error(json.errors.map((error) => error.message).join("; "));
180
+ }
181
+ if (!json.data) {
182
+ throw new Error("GitHub GraphQL response did not contain data.");
183
+ }
184
+ return json.data;
185
+ }
186
+ function mapChecks(node) {
187
+ const contexts = node.commits.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? [];
188
+ return contexts.map((context) => {
189
+ if (context.__typename === "CheckRun") {
190
+ return {
191
+ name: context.name,
192
+ status: context.status,
193
+ conclusion: context.conclusion,
194
+ url: context.detailsUrl ?? null,
195
+ startedAt: context.startedAt ?? null,
196
+ completedAt: context.completedAt ?? null
197
+ };
198
+ }
199
+ return {
200
+ name: context.context,
201
+ status: context.state === "PENDING" ? "pending" : "completed",
202
+ conclusion: context.state.toLowerCase(),
203
+ url: context.targetUrl ?? null,
204
+ startedAt: context.createdAt ?? null,
205
+ completedAt: context.createdAt ?? null
206
+ };
207
+ });
208
+ }
209
+ function mapReviews(node) {
210
+ return node.latestReviews.nodes.map((review) => ({
211
+ id: review.id,
212
+ state: review.state.toLowerCase(),
213
+ author: review.author?.login ?? "unknown",
214
+ submittedAt: review.submittedAt,
215
+ ...review.body ? { bodySnippet: review.body.slice(0, 240) } : {},
216
+ ...review.url ? { url: review.url } : {}
217
+ }));
218
+ }
219
+ function mapPullRequestNode(node, installationId) {
220
+ const checks = mapChecks(node);
221
+ const reviews = mapReviews(node);
222
+ const statusSummary = summarizeStatus(checks, reviews, {
223
+ mergeable: node.mergeable === "CONFLICTING" ? "conflicting" : node.mergeable === "MERGEABLE" ? "mergeable" : "unknown",
224
+ mergeStateStatus: node.mergeStateStatus,
225
+ reviewDecision: node.reviewDecision
226
+ });
227
+ return {
228
+ id: node.id,
229
+ provider: "github",
230
+ kind: "pull_request",
231
+ repository: node.repository.nameWithOwner,
232
+ number: node.number,
233
+ title: node.title,
234
+ url: node.url,
235
+ state: node.state.toLowerCase(),
236
+ author: node.author?.login ?? "unknown",
237
+ isDraft: node.isDraft,
238
+ headBranch: node.headRefName,
239
+ headSha: node.headRefOid,
240
+ baseBranch: node.baseRefName,
241
+ mergeable: node.mergeable === "CONFLICTING" ? "conflicting" : node.mergeable === "MERGEABLE" ? "mergeable" : "unknown",
242
+ mergeStateStatus: node.mergeStateStatus,
243
+ reviewDecision: node.reviewDecision,
244
+ statusSummary,
245
+ checks,
246
+ reviews,
247
+ alerts: [],
248
+ lastActivityAt: node.updatedAt,
249
+ updatedAt: node.updatedAt,
250
+ providerPayload: {
251
+ installationId,
252
+ repositoryUrl: node.repository.url
253
+ }
254
+ };
255
+ }
256
+ async function listAppInstallations(config) {
257
+ const jwt = createAppJwt(config.githubAppId, config.githubPrivateKey);
258
+ const installations = [];
259
+ for (let page = 1;; page += 1) {
260
+ const response = await fetch(`${config.githubApiUrl}/app/installations?per_page=100&page=${page}`, {
261
+ method: "GET",
262
+ headers: {
263
+ Accept: "application/vnd.github+json",
264
+ Authorization: `Bearer ${jwt}`,
265
+ "User-Agent": "ops-harbor"
266
+ }
267
+ });
268
+ if (!response.ok) {
269
+ throw new Error(`Failed to list app installations: ${response.status} ${response.statusText}`);
270
+ }
271
+ const data = await response.json();
272
+ const pageInstallations = Array.isArray(data) ? data : data.installations ?? [];
273
+ installations.push(...pageInstallations);
274
+ if (pageInstallations.length < 100) {
275
+ break;
276
+ }
277
+ }
278
+ return installations;
279
+ }
280
+ async function fetchOpenPullRequestsForAuthor(config, installationId, author, limit = 100) {
281
+ const items = [];
282
+ let cursor = null;
283
+ while (items.length < limit) {
284
+ const data = await graphqlRequest(config, installationId, SEARCH_QUERY, {
285
+ query: `is:pr is:open author:${author} archived:false`,
286
+ first: Math.min(50, limit - items.length),
287
+ after: cursor
288
+ });
289
+ items.push(...data.search.nodes.map((node) => mapPullRequestNode(node, installationId)));
290
+ if (!data.search.pageInfo.hasNextPage)
291
+ break;
292
+ cursor = data.search.pageInfo.endCursor;
293
+ }
294
+ return items;
295
+ }
296
+ var SEARCH_QUERY = `
297
+ query SearchPullRequests($query: String!, $first: Int!, $after: String) {
298
+ search(query: $query, type: ISSUE, first: $first, after: $after) {
299
+ pageInfo { hasNextPage endCursor }
300
+ nodes {
301
+ ... on PullRequest {
302
+ id
303
+ number
304
+ title
305
+ url
306
+ state
307
+ isDraft
308
+ mergeable
309
+ mergeStateStatus
310
+ reviewDecision
311
+ updatedAt
312
+ repository { nameWithOwner url }
313
+ author { login }
314
+ baseRefName
315
+ headRefName
316
+ headRefOid
317
+ latestReviews(first: 20) {
318
+ nodes {
319
+ id
320
+ state
321
+ body
322
+ submittedAt
323
+ url
324
+ author { login }
325
+ }
326
+ }
327
+ commits(last: 1) {
328
+ nodes {
329
+ commit {
330
+ statusCheckRollup {
331
+ contexts(first: 50) {
332
+ nodes {
333
+ __typename
334
+ ... on CheckRun {
335
+ name
336
+ status
337
+ conclusion
338
+ detailsUrl
339
+ startedAt
340
+ completedAt
341
+ }
342
+ ... on StatusContext {
343
+ context
344
+ state
345
+ targetUrl
346
+ createdAt
347
+ description
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+ `;
360
+ var init_github = () => {};
361
+
55
362
  // ../../packages/ops-harbor-core/src/db-helpers.ts
56
363
  function mapWorkItemsToAlertSummaries(workItems) {
57
364
  return workItems.map((item) => ({
@@ -152,9 +459,9 @@ var init_port_finder = () => {};
152
459
 
153
460
  // ../../packages/ops-harbor-core/src/sqlite.ts
154
461
  import { createRequire } from "module";
155
- import { resolve } from "path";
156
462
  function openSqliteDatabase(filename) {
157
- const db = typeof Bun !== "undefined" ? new (requireFromModule("bun:sqlite")).Database(filename) : new (requireFromWorkingDirectory("better-sqlite3"))(filename);
463
+ const Ctor = overriddenCtor ?? createRequire(import.meta.url)("bun:sqlite").Database;
464
+ const db = new Ctor(filename);
158
465
  db.exec("PRAGMA journal_mode = WAL");
159
466
  return db;
160
467
  }
@@ -167,14 +474,12 @@ function parseJson(value, fallback) {
167
474
  return fallback;
168
475
  }
169
476
  }
170
- var requireFromModule, requireFromWorkingDirectory;
171
- var init_sqlite = __esm(() => {
172
- requireFromModule = createRequire(import.meta.url);
173
- requireFromWorkingDirectory = createRequire(resolve(process.cwd(), "package.json"));
174
- });
477
+ var overriddenCtor;
478
+ var init_sqlite = () => {};
175
479
 
176
480
  // ../../packages/ops-harbor-core/src/index.ts
177
481
  var init_src = __esm(() => {
482
+ init_github();
178
483
  init_filters();
179
484
  init_prompt();
180
485
  init_port_finder();
@@ -227,6 +532,15 @@ function ensureSchema(db) {
227
532
  CREATE INDEX IF NOT EXISTS idx_local_runs_lookup ON automation_runs(work_item_id, finished_at DESC);
228
533
  `);
229
534
  }
535
+ function upsertWorkItems(db, items) {
536
+ const tx = db.transaction(() => {
537
+ const stmt = db.prepare(`INSERT OR REPLACE INTO work_items (id, repository, state, updated_at, payload_json) VALUES (?, ?, ?, ?, ?)`);
538
+ for (const item of items) {
539
+ stmt.run(item.id, item.repository, item.state, item.updatedAt, JSON.stringify(item));
540
+ }
541
+ });
542
+ tx();
543
+ }
230
544
  function replaceSnapshot(db, workItems, events) {
231
545
  const tx = db.transaction(() => {
232
546
  db.exec(`DELETE FROM work_items; DELETE FROM activity_events;`);
@@ -277,10 +591,10 @@ function listActivity(db, options = {}) {
277
591
  function listAlerts(db) {
278
592
  return mapWorkItemsToAlertSummaries(listWorkItems(db, { hasBlocker: true }));
279
593
  }
280
- function recordAutomationRun(db, run) {
594
+ function recordAutomationRun(db, run2) {
281
595
  db.prepare(`INSERT OR REPLACE INTO automation_runs (
282
596
  id, work_item_id, repository, trigger_type, status, started_at, finished_at, summary
283
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(run.id, run.workItemId, run.repository, run.triggerType, run.status, run.startedAt, run.finishedAt, run.summary);
597
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(run2.id, run2.workItemId, run2.repository, run2.triggerType, run2.status, run2.startedAt, run2.finishedAt, run2.summary);
284
598
  }
285
599
  function listAutomationRuns(db, limit = 100) {
286
600
  return db.prepare(`SELECT
@@ -832,7 +1146,7 @@ var require_serve_static2 = __commonJS((exports, module) => {
832
1146
  });
833
1147
  module.exports = __toCommonJS(serve_static_exports);
834
1148
  var import_promises2 = __require("fs/promises");
835
- var import_node_path4 = __require("path");
1149
+ var import_node_path3 = __require("path");
836
1150
  var import_serve_static = require_serve_static();
837
1151
  var serveStatic2 = (options) => {
838
1152
  return async function serveStatic22(c, next) {
@@ -851,7 +1165,7 @@ var require_serve_static2 = __commonJS((exports, module) => {
851
1165
  return (0, import_serve_static.serveStatic)({
852
1166
  ...options,
853
1167
  getContent,
854
- join: import_node_path4.join,
1168
+ join: import_node_path3.join,
855
1169
  isDir
856
1170
  })(c, next);
857
1171
  };
@@ -1065,10 +1379,10 @@ var require_concurrent = __commonJS((exports, module) => {
1065
1379
  };
1066
1380
  }
1067
1381
  const pool = /* @__PURE__ */ new Set;
1068
- const run = async (fn, promise, resolve2) => {
1382
+ const run2 = async (fn, promise, resolve) => {
1069
1383
  if (pool.size >= concurrency) {
1070
- promise ||= new Promise((r) => resolve2 = r);
1071
- setTimeout(() => run(fn, promise, resolve2));
1384
+ promise ||= new Promise((r) => resolve = r);
1385
+ setTimeout(() => run2(fn, promise, resolve));
1072
1386
  return promise;
1073
1387
  }
1074
1388
  const marker = {};
@@ -1079,14 +1393,14 @@ var require_concurrent = __commonJS((exports, module) => {
1079
1393
  } else {
1080
1394
  pool.delete(marker);
1081
1395
  }
1082
- if (resolve2) {
1083
- resolve2(result);
1396
+ if (resolve) {
1397
+ resolve(result);
1084
1398
  return promise;
1085
1399
  } else {
1086
1400
  return result;
1087
1401
  }
1088
1402
  };
1089
- return { run };
1403
+ return { run: run2 };
1090
1404
  };
1091
1405
  });
1092
1406
 
@@ -1297,7 +1611,7 @@ var require_middleware = __commonJS((exports, module) => {
1297
1611
  ssgParams: () => ssgParams2
1298
1612
  });
1299
1613
  module.exports = __toCommonJS(middleware_exports);
1300
- var import_utils = require_utils2();
1614
+ var import_utils3 = require_utils2();
1301
1615
  var SSG_CONTEXT = "HONO_SSG_CONTEXT";
1302
1616
  var X_HONO_DISABLE_SSG_HEADER_KEY2 = "x-hono-disable-ssg";
1303
1617
  var SSG_DISABLED_RESPONSE = (() => {
@@ -1311,7 +1625,7 @@ var require_middleware = __commonJS((exports, module) => {
1311
1625
  }
1312
1626
  })();
1313
1627
  var ssgParams2 = (params) => async (c, next) => {
1314
- if ((0, import_utils.isDynamicRoute)(c.req.path)) {
1628
+ if ((0, import_utils3.isDynamicRoute)(c.req.path)) {
1315
1629
  c.req.raw.ssgParams = Array.isArray(params) ? params : await params(c);
1316
1630
  return c.notFound();
1317
1631
  }
@@ -1496,10 +1810,10 @@ var require_html2 = __commonJS((exports, module) => {
1496
1810
  var html_exports = {};
1497
1811
  __export2(html_exports, {
1498
1812
  html: () => html,
1499
- raw: () => import_html2.raw
1813
+ raw: () => import_html3.raw
1500
1814
  });
1501
1815
  module.exports = __toCommonJS(html_exports);
1502
- var import_html2 = require_html();
1816
+ var import_html3 = require_html();
1503
1817
  var html = (strings, ...values) => {
1504
1818
  const buffer = [""];
1505
1819
  for (let i = 0, len = strings.length - 1;i < len; i++) {
@@ -1508,7 +1822,7 @@ var require_html2 = __commonJS((exports, module) => {
1508
1822
  for (let i2 = 0, len2 = children.length;i2 < len2; i2++) {
1509
1823
  const child = children[i2];
1510
1824
  if (typeof child === "string") {
1511
- (0, import_html2.escapeToBuffer)(child, buffer);
1825
+ (0, import_html3.escapeToBuffer)(child, buffer);
1512
1826
  } else if (typeof child === "number") {
1513
1827
  buffer[0] += child;
1514
1828
  } else if (typeof child === "boolean" || child === null || child === undefined) {
@@ -1527,12 +1841,12 @@ var require_html2 = __commonJS((exports, module) => {
1527
1841
  } else if (child instanceof Promise) {
1528
1842
  buffer.unshift("", child);
1529
1843
  } else {
1530
- (0, import_html2.escapeToBuffer)(child.toString(), buffer);
1844
+ (0, import_html3.escapeToBuffer)(child.toString(), buffer);
1531
1845
  }
1532
1846
  }
1533
1847
  }
1534
1848
  buffer[0] += strings.at(-1);
1535
- return buffer.length === 1 ? "callbacks" in buffer ? (0, import_html2.raw)((0, import_html2.resolveCallbackSync)((0, import_html2.raw)(buffer[0], buffer.callbacks))) : (0, import_html2.raw)(buffer[0]) : (0, import_html2.stringBufferToString)(buffer, buffer.callbacks);
1849
+ return buffer.length === 1 ? "callbacks" in buffer ? (0, import_html3.raw)((0, import_html3.resolveCallbackSync)((0, import_html3.raw)(buffer[0], buffer.callbacks))) : (0, import_html3.raw)(buffer[0]) : (0, import_html3.stringBufferToString)(buffer, buffer.callbacks);
1536
1850
  };
1537
1851
  });
1538
1852
 
@@ -1561,7 +1875,7 @@ var require_plugins = __commonJS((exports, module) => {
1561
1875
  redirectPlugin: () => redirectPlugin2
1562
1876
  });
1563
1877
  module.exports = __toCommonJS(plugins_exports);
1564
- var import_html2 = require_html2();
1878
+ var import_html3 = require_html2();
1565
1879
  var defaultPlugin2 = () => {
1566
1880
  return {
1567
1881
  afterResponseHook: (res) => {
@@ -1574,7 +1888,7 @@ var require_plugins = __commonJS((exports, module) => {
1574
1888
  };
1575
1889
  var REDIRECT_STATUS_CODES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
1576
1890
  var generateRedirectHtml = (location) => {
1577
- const content = import_html2.html`<!DOCTYPE html>
1891
+ const content = import_html3.html`<!DOCTYPE html>
1578
1892
  <title>Redirecting to: ${location}</title>
1579
1893
  <meta http-equiv="refresh" content="0;url=${location}" />
1580
1894
  <meta name="robots" content="noindex" />
@@ -1636,27 +1950,27 @@ var require_ssg = __commonJS((exports, module) => {
1636
1950
  toSSG: () => toSSG2
1637
1951
  });
1638
1952
  module.exports = __toCommonJS(ssg_exports);
1639
- var import_utils = require_utils();
1953
+ var import_utils3 = require_utils();
1640
1954
  var import_concurrent = require_concurrent();
1641
1955
  var import_mime = require_mime();
1642
1956
  var import_middleware = require_middleware();
1643
1957
  var import_plugins = require_plugins();
1644
- var import_utils2 = require_utils2();
1958
+ var import_utils22 = require_utils2();
1645
1959
  var DEFAULT_CONCURRENCY = 2;
1646
1960
  var DEFAULT_CONTENT_TYPE = "text/plain";
1647
1961
  var DEFAULT_OUTPUT_DIR = "./static";
1648
1962
  var generateFilePath = (routePath, outDir, mimeType, extensionMap) => {
1649
1963
  const extension = determineExtension(mimeType, extensionMap);
1650
1964
  if (routePath.endsWith(`.${extension}`)) {
1651
- return (0, import_utils2.joinPaths)(outDir, routePath);
1965
+ return (0, import_utils22.joinPaths)(outDir, routePath);
1652
1966
  }
1653
1967
  if (routePath === "/") {
1654
- return (0, import_utils2.joinPaths)(outDir, `index.${extension}`);
1968
+ return (0, import_utils22.joinPaths)(outDir, `index.${extension}`);
1655
1969
  }
1656
1970
  if (routePath.endsWith("/")) {
1657
- return (0, import_utils2.joinPaths)(outDir, routePath, `index.${extension}`);
1971
+ return (0, import_utils22.joinPaths)(outDir, routePath, `index.${extension}`);
1658
1972
  }
1659
- return (0, import_utils2.joinPaths)(outDir, `${routePath}.${extension}`);
1973
+ return (0, import_utils22.joinPaths)(outDir, `${routePath}.${extension}`);
1660
1974
  };
1661
1975
  var parseResponseContent = async (response) => {
1662
1976
  const contentType = response.headers.get("Content-Type");
@@ -1732,7 +2046,7 @@ var require_ssg = __commonJS((exports, module) => {
1732
2046
  var fetchRoutesContent = function* (app, beforeRequestHook, afterResponseHook, concurrency) {
1733
2047
  const baseURL = "http://localhost";
1734
2048
  const pool = (0, import_concurrent.createPool)({ concurrency });
1735
- for (const route of (0, import_utils2.filterStaticGenerateRoutes)(app)) {
2049
+ for (const route of (0, import_utils22.filterStaticGenerateRoutes)(app)) {
1736
2050
  const thisRouteBaseURL = new URL(route.path, baseURL).toString();
1737
2051
  let forGetInfoURLRequest = new Request(thisRouteBaseURL);
1738
2052
  yield new Promise(async (resolveGetInfo, rejectGetInfo) => {
@@ -1747,7 +2061,7 @@ var require_ssg = __commonJS((exports, module) => {
1747
2061
  }
1748
2062
  await pool.run(() => app.fetch(forGetInfoURLRequest, { [import_middleware.SSG_CONTEXT]: true }));
1749
2063
  if (!forGetInfoURLRequest.ssgParams) {
1750
- if ((0, import_utils2.isDynamicRoute)(route.path)) {
2064
+ if ((0, import_utils22.isDynamicRoute)(route.path)) {
1751
2065
  resolveGetInfo(undefined);
1752
2066
  return;
1753
2067
  }
@@ -1761,7 +2075,7 @@ var require_ssg = __commonJS((exports, module) => {
1761
2075
  for (const param of forGetInfoURLRequest.ssgParams) {
1762
2076
  yield new Promise(async (resolveReq, rejectReq) => {
1763
2077
  try {
1764
- const replacedUrlParam = (0, import_utils.replaceUrlParam)(route.path, param);
2078
+ const replacedUrlParam = (0, import_utils3.replaceUrlParam)(route.path, param);
1765
2079
  let response = await pool.run(() => app.request(replacedUrlParam, requestInit, {
1766
2080
  [import_middleware.SSG_CONTEXT]: true
1767
2081
  }));
@@ -1804,7 +2118,7 @@ var require_ssg = __commonJS((exports, module) => {
1804
2118
  }
1805
2119
  const { routePath, content, mimeType } = awaitedData;
1806
2120
  const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap);
1807
- const dirPath = (0, import_utils2.dirname)(filePath);
2121
+ const dirPath = (0, import_utils22.dirname)(filePath);
1808
2122
  if (!createdDirs.has(dirPath)) {
1809
2123
  await fsModule.mkdir(dirPath, { recursive: true });
1810
2124
  createdDirs.add(dirPath);
@@ -8779,7 +9093,7 @@ class Protocol {
8779
9093
  return;
8780
9094
  }
8781
9095
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
8782
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
9096
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
8783
9097
  options?.signal?.throwIfAborted();
8784
9098
  }
8785
9099
  } catch (error2) {
@@ -8791,7 +9105,7 @@ class Protocol {
8791
9105
  }
8792
9106
  request(request, resultSchema, options) {
8793
9107
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
8794
- return new Promise((resolve3, reject) => {
9108
+ return new Promise((resolve2, reject) => {
8795
9109
  const earlyReject = (error2) => {
8796
9110
  reject(error2);
8797
9111
  };
@@ -8869,7 +9183,7 @@ class Protocol {
8869
9183
  if (!parseResult.success) {
8870
9184
  reject(parseResult.error);
8871
9185
  } else {
8872
- resolve3(parseResult.data);
9186
+ resolve2(parseResult.data);
8873
9187
  }
8874
9188
  } catch (error2) {
8875
9189
  reject(error2);
@@ -9060,12 +9374,12 @@ class Protocol {
9060
9374
  interval = task.pollInterval;
9061
9375
  }
9062
9376
  } catch {}
9063
- return new Promise((resolve3, reject) => {
9377
+ return new Promise((resolve2, reject) => {
9064
9378
  if (signal.aborted) {
9065
9379
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
9066
9380
  return;
9067
9381
  }
9068
- const timeoutId = setTimeout(resolve3, interval);
9382
+ const timeoutId = setTimeout(resolve2, interval);
9069
9383
  signal.addEventListener("abort", () => {
9070
9384
  clearTimeout(timeoutId);
9071
9385
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -12050,7 +12364,7 @@ var require_compile = __commonJS((exports) => {
12050
12364
  const schOrFunc = root.refs[ref];
12051
12365
  if (schOrFunc)
12052
12366
  return schOrFunc;
12053
- let _sch = resolve3.call(this, root, ref);
12367
+ let _sch = resolve2.call(this, root, ref);
12054
12368
  if (_sch === undefined) {
12055
12369
  const schema = (_a2 = root.localRefs) === null || _a2 === undefined ? undefined : _a2[ref];
12056
12370
  const { schemaId } = this.opts;
@@ -12077,7 +12391,7 @@ var require_compile = __commonJS((exports) => {
12077
12391
  function sameSchemaEnv(s1, s2) {
12078
12392
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
12079
12393
  }
12080
- function resolve3(root, ref) {
12394
+ function resolve2(root, ref) {
12081
12395
  let sch;
12082
12396
  while (typeof (sch = this.refs[ref]) == "string")
12083
12397
  ref = sch;
@@ -12607,7 +12921,7 @@ var require_fast_uri = __commonJS((exports, module) => {
12607
12921
  }
12608
12922
  return uri;
12609
12923
  }
12610
- function resolve3(baseURI, relativeURI, options) {
12924
+ function resolve2(baseURI, relativeURI, options) {
12611
12925
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
12612
12926
  const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
12613
12927
  schemelessOptions.skipEscape = true;
@@ -12835,7 +13149,7 @@ var require_fast_uri = __commonJS((exports, module) => {
12835
13149
  var fastUri = {
12836
13150
  SCHEMES,
12837
13151
  normalize,
12838
- resolve: resolve3,
13152
+ resolve: resolve2,
12839
13153
  resolveComponent,
12840
13154
  equal,
12841
13155
  serialize,
@@ -16218,12 +16532,12 @@ class StdioServerTransport {
16218
16532
  this.onclose?.();
16219
16533
  }
16220
16534
  send(message) {
16221
- return new Promise((resolve3) => {
16535
+ return new Promise((resolve2) => {
16222
16536
  const json = serializeMessage(message);
16223
16537
  if (this._stdout.write(json)) {
16224
- resolve3();
16538
+ resolve2();
16225
16539
  } else {
16226
- this._stdout.once("drain", resolve3);
16540
+ this._stdout.once("drain", resolve2);
16227
16541
  }
16228
16542
  });
16229
16543
  }
@@ -18011,8 +18325,150 @@ var cors = (options) => {
18011
18325
  };
18012
18326
  };
18013
18327
 
18328
+ // ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/utils/stream.js
18329
+ var StreamingApi = class {
18330
+ writer;
18331
+ encoder;
18332
+ writable;
18333
+ abortSubscribers = [];
18334
+ responseReadable;
18335
+ aborted = false;
18336
+ closed = false;
18337
+ constructor(writable, _readable) {
18338
+ this.writable = writable;
18339
+ this.writer = writable.getWriter();
18340
+ this.encoder = new TextEncoder;
18341
+ const reader = _readable.getReader();
18342
+ this.abortSubscribers.push(async () => {
18343
+ await reader.cancel();
18344
+ });
18345
+ this.responseReadable = new ReadableStream({
18346
+ async pull(controller) {
18347
+ const { done, value } = await reader.read();
18348
+ done ? controller.close() : controller.enqueue(value);
18349
+ },
18350
+ cancel: () => {
18351
+ this.abort();
18352
+ }
18353
+ });
18354
+ }
18355
+ async write(input) {
18356
+ try {
18357
+ if (typeof input === "string") {
18358
+ input = this.encoder.encode(input);
18359
+ }
18360
+ await this.writer.write(input);
18361
+ } catch {}
18362
+ return this;
18363
+ }
18364
+ async writeln(input) {
18365
+ await this.write(input + `
18366
+ `);
18367
+ return this;
18368
+ }
18369
+ sleep(ms) {
18370
+ return new Promise((res) => setTimeout(res, ms));
18371
+ }
18372
+ async close() {
18373
+ try {
18374
+ await this.writer.close();
18375
+ } catch {}
18376
+ this.closed = true;
18377
+ }
18378
+ async pipe(body) {
18379
+ this.writer.releaseLock();
18380
+ await body.pipeTo(this.writable, { preventClose: true });
18381
+ this.writer = this.writable.getWriter();
18382
+ }
18383
+ onAbort(listener) {
18384
+ this.abortSubscribers.push(listener);
18385
+ }
18386
+ abort() {
18387
+ if (!this.aborted) {
18388
+ this.aborted = true;
18389
+ this.abortSubscribers.forEach((subscriber) => subscriber());
18390
+ }
18391
+ }
18392
+ };
18393
+
18394
+ // ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/helper/streaming/utils.js
18395
+ var isOldBunVersion = () => {
18396
+ const version = typeof Bun !== "undefined" ? Bun.version : undefined;
18397
+ if (version === undefined) {
18398
+ return false;
18399
+ }
18400
+ const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
18401
+ isOldBunVersion = () => result;
18402
+ return result;
18403
+ };
18404
+
18405
+ // ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/helper/streaming/sse.js
18406
+ var SSEStreamingApi = class extends StreamingApi {
18407
+ constructor(writable, readable) {
18408
+ super(writable, readable);
18409
+ }
18410
+ async writeSSE(message) {
18411
+ const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
18412
+ const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
18413
+ return `data: ${line}`;
18414
+ }).join(`
18415
+ `);
18416
+ for (const key of ["event", "id", "retry"]) {
18417
+ if (message[key] && /[\r\n]/.test(message[key])) {
18418
+ throw new Error(`${key} must not contain "\\r" or "\\n"`);
18419
+ }
18420
+ }
18421
+ const sseData = [
18422
+ message.event && `event: ${message.event}`,
18423
+ dataLines,
18424
+ message.id && `id: ${message.id}`,
18425
+ message.retry && `retry: ${message.retry}`
18426
+ ].filter(Boolean).join(`
18427
+ `) + `
18428
+
18429
+ `;
18430
+ await this.write(sseData);
18431
+ }
18432
+ };
18433
+ var run = async (stream, cb, onError) => {
18434
+ try {
18435
+ await cb(stream);
18436
+ } catch (e) {
18437
+ if (e instanceof Error && onError) {
18438
+ await onError(e, stream);
18439
+ await stream.writeSSE({
18440
+ event: "error",
18441
+ data: e.message
18442
+ });
18443
+ } else {
18444
+ console.error(e);
18445
+ }
18446
+ } finally {
18447
+ stream.close();
18448
+ }
18449
+ };
18450
+ var contextStash = /* @__PURE__ */ new WeakMap;
18451
+ var streamSSE = (c, cb, onError) => {
18452
+ const { readable, writable } = new TransformStream;
18453
+ const stream = new SSEStreamingApi(writable, readable);
18454
+ if (isOldBunVersion()) {
18455
+ c.req.raw.signal.addEventListener("abort", () => {
18456
+ if (!stream.closed) {
18457
+ stream.abort();
18458
+ }
18459
+ });
18460
+ }
18461
+ contextStash.set(stream.responseReadable, c);
18462
+ c.header("Transfer-Encoding", "chunked");
18463
+ c.header("Content-Type", "text/event-stream");
18464
+ c.header("Cache-Control", "no-cache");
18465
+ c.header("Connection", "keep-alive");
18466
+ run(stream, cb, onError);
18467
+ return c.newResponse(stream.responseReadable);
18468
+ };
18469
+
18014
18470
  // src/server.ts
18015
- import { join as join3, resolve as resolve2 } from "path";
18471
+ import { join as join3, resolve } from "path";
18016
18472
 
18017
18473
  // src/lib/automation.ts
18018
18474
  init_src();
@@ -18596,7 +19052,7 @@ function isTriggerDisabled(config, repository, workItem, trigger) {
18596
19052
  }
18597
19053
  function runCommand(command, args, options = {}) {
18598
19054
  const timeoutMs = options.timeoutMs ?? 300000;
18599
- return new Promise((resolve2, reject) => {
19055
+ return new Promise((resolve, reject) => {
18600
19056
  const child = spawn(command, args, {
18601
19057
  cwd: options.cwd,
18602
19058
  env: {
@@ -18631,7 +19087,7 @@ function runCommand(command, args, options = {}) {
18631
19087
  });
18632
19088
  child.on("close", (exitCode) => {
18633
19089
  clearTimeout(timeout);
18634
- resolve2({
19090
+ resolve({
18635
19091
  exitCode: exitCode ?? 1,
18636
19092
  stdout: stdout.trim(),
18637
19093
  stderr: stderr.trim()
@@ -18787,7 +19243,7 @@ var cachedLogin = null;
18787
19243
  var cachedAt = 0;
18788
19244
  var inFlight = null;
18789
19245
  function runGhCommand(args) {
18790
- return new Promise((resolve2, reject) => {
19246
+ return new Promise((resolve, reject) => {
18791
19247
  const child = spawn2("gh", args, {
18792
19248
  stdio: ["ignore", "pipe", "pipe"]
18793
19249
  });
@@ -18801,7 +19257,7 @@ function runGhCommand(args) {
18801
19257
  });
18802
19258
  child.on("error", reject);
18803
19259
  child.on("close", (exitCode) => {
18804
- resolve2({
19260
+ resolve({
18805
19261
  exitCode: exitCode ?? 1,
18806
19262
  stdout: stdout.trim(),
18807
19263
  stderr: stderr.trim()
@@ -18840,6 +19296,9 @@ var ConfigSchema = object({
18840
19296
  controlPlaneUrl: pipe(string(), minLength(1)),
18841
19297
  authorLogin: optional(string()),
18842
19298
  internalApiToken: optional(string()),
19299
+ githubAppId: optional(pipe(string(), minLength(1))),
19300
+ githubPrivateKey: optional(pipe(string(), minLength(1))),
19301
+ githubApiUrl: optional(pipe(string(), minLength(1))),
18843
19302
  syncIntervalSeconds: number(),
18844
19303
  automationPollSeconds: number(),
18845
19304
  runner: optional(object({
@@ -18877,7 +19336,7 @@ var ConfigSchema = object({
18877
19336
  function defaultConfig() {
18878
19337
  return {
18879
19338
  controlPlaneUrl: "http://127.0.0.1:4130",
18880
- syncIntervalSeconds: 60,
19339
+ syncIntervalSeconds: 300,
18881
19340
  automationPollSeconds: 20,
18882
19341
  repositories: [],
18883
19342
  pullRequestOverrides: []
@@ -18905,6 +19364,9 @@ function normalizeConfig(config) {
18905
19364
  syncIntervalSeconds: config.syncIntervalSeconds,
18906
19365
  automationPollSeconds: config.automationPollSeconds,
18907
19366
  ...config.internalApiToken ? { internalApiToken: config.internalApiToken } : {},
19367
+ ...config.githubAppId ? { githubAppId: config.githubAppId } : {},
19368
+ ...config.githubPrivateKey ? { githubPrivateKey: config.githubPrivateKey } : {},
19369
+ ...config.githubApiUrl ? { githubApiUrl: config.githubApiUrl } : {},
18908
19370
  repositories: config.repositories.map((repository) => ({
18909
19371
  repository: repository.repository,
18910
19372
  path: repository.path,
@@ -18942,6 +19404,35 @@ function withDetectedAuthorLogin(config, detectedAuthorLogin) {
18942
19404
  // src/server.ts
18943
19405
  init_local_db();
18944
19406
 
19407
+ // src/lib/github-sync.ts
19408
+ init_src();
19409
+ init_local_db();
19410
+ function toGitHubAppConfig(config) {
19411
+ if (!config.githubAppId || !config.githubPrivateKey) {
19412
+ throw new Error("GitHub App is not configured. Set githubAppId and githubPrivateKey in config.");
19413
+ }
19414
+ return {
19415
+ githubAppId: config.githubAppId,
19416
+ githubPrivateKey: config.githubPrivateKey,
19417
+ githubApiUrl: config.githubApiUrl ?? "https://api.github.com"
19418
+ };
19419
+ }
19420
+ async function syncAuthorPrs(db, config) {
19421
+ const appConfig = toGitHubAppConfig(config);
19422
+ if (!config.authorLogin) {
19423
+ throw new Error("authorLogin is not configured.");
19424
+ }
19425
+ const installations = await listAppInstallations(appConfig);
19426
+ const allItems = await Promise.all(installations.map((inst) => fetchOpenPullRequestsForAuthor(appConfig, inst.id, config.authorLogin)));
19427
+ const items = allItems.flat();
19428
+ for (const item of items) {
19429
+ const { alerts } = deriveAlerts(item);
19430
+ item.alerts = alerts;
19431
+ }
19432
+ upsertWorkItems(db, items);
19433
+ return { synchronized: items.length };
19434
+ }
19435
+
18945
19436
  // src/lib/sync.ts
18946
19437
  init_local_db();
18947
19438
  async function syncFromControlPlane(db, config) {
@@ -18965,6 +19456,19 @@ async function syncFromControlPlane(db, config) {
18965
19456
  }
18966
19457
 
18967
19458
  // src/server.ts
19459
+ function createEventBus() {
19460
+ const listeners = new Set;
19461
+ return {
19462
+ subscribe(listener) {
19463
+ listeners.add(listener);
19464
+ return () => listeners.delete(listener);
19465
+ },
19466
+ broadcast(event, data) {
19467
+ for (const listener of listeners)
19468
+ listener(event, data);
19469
+ }
19470
+ };
19471
+ }
18968
19472
  function runExclusive(guard, fn) {
18969
19473
  if (guard.promise)
18970
19474
  return guard.promise;
@@ -18974,13 +19478,30 @@ function runExclusive(guard, fn) {
18974
19478
  return guard.promise;
18975
19479
  }
18976
19480
  function createLocalApp(db = openLocalDb()) {
19481
+ const bus = createEventBus();
18977
19482
  const state = {
18978
19483
  db,
19484
+ bus,
18979
19485
  syncGuard: { promise: null },
18980
19486
  workerGuard: { promise: null }
18981
19487
  };
18982
19488
  const app = new Hono2;
18983
19489
  app.use("/api/*", cors());
19490
+ app.get("/api/events", (c) => {
19491
+ return streamSSE(c, async (stream2) => {
19492
+ const unsubscribe = bus.subscribe((event, data) => {
19493
+ stream2.writeSSE({ event, data: JSON.stringify(data) });
19494
+ });
19495
+ const heartbeat = setInterval(() => {
19496
+ stream2.writeSSE({ event: "heartbeat", data: "{}" });
19497
+ }, 30000);
19498
+ stream2.onAbort(() => {
19499
+ clearInterval(heartbeat);
19500
+ unsubscribe();
19501
+ });
19502
+ await new Promise(() => {});
19503
+ });
19504
+ });
18984
19505
  app.get("/api/health", (_c) => {
18985
19506
  return Response.json({ ok: true });
18986
19507
  });
@@ -19001,6 +19522,18 @@ function createLocalApp(db = openLocalDb()) {
19001
19522
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
19002
19523
  }
19003
19524
  });
19525
+ app.post("/api/sync/author", async (c) => {
19526
+ if (state.syncGuard.promise) {
19527
+ return c.json({ error: "sync already in progress" }, 409);
19528
+ }
19529
+ try {
19530
+ const config = await loadConfig();
19531
+ const result = await runExclusive(state.syncGuard, () => syncAuthorPrs(state.db, config));
19532
+ return c.json(result);
19533
+ } catch (error) {
19534
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
19535
+ }
19536
+ });
19004
19537
  app.get("/api/work-items", (c) => c.json(listWorkItems(state.db, parseWorkItemFilterFromQuery((key) => c.req.query(key)))));
19005
19538
  app.get("/api/work-items/:id", (c) => {
19006
19539
  const item = getWorkItem(state.db, c.req.param("id"));
@@ -19023,19 +19556,69 @@ function createLocalApp(db = openLocalDb()) {
19023
19556
  return app;
19024
19557
  }
19025
19558
  async function runSync(state, config) {
19026
- return runExclusive(state.syncGuard, async () => {
19559
+ const result = await runExclusive(state.syncGuard, async () => {
19027
19560
  const currentConfig = config ?? await loadConfig();
19028
19561
  return syncFromControlPlane(state.db, currentConfig);
19029
19562
  });
19563
+ state.bus.broadcast("sync_completed", {});
19564
+ return result;
19030
19565
  }
19031
19566
  async function runWorker(state, config) {
19032
- return runExclusive(state.workerGuard, async () => {
19567
+ const result = await runExclusive(state.workerGuard, async () => {
19033
19568
  const currentConfig = config ?? await loadConfig();
19034
19569
  return processNextAutomationJob(state.db, currentConfig, "ops-harbor-local");
19035
19570
  });
19571
+ state.bus.broadcast("automation_completed", {});
19572
+ return result;
19036
19573
  }
19037
19574
  function delay(ms) {
19038
- return new Promise((resolve3) => setTimeout(resolve3, ms));
19575
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
19576
+ }
19577
+ function subscribeToControlPlane(state) {
19578
+ const connect = async () => {
19579
+ try {
19580
+ const config = await loadConfig();
19581
+ const url = `${config.controlPlaneUrl}/api/events`;
19582
+ const headers = { Accept: "text/event-stream" };
19583
+ if (config.internalApiToken) {
19584
+ headers["x-ops-harbor-token"] = config.internalApiToken;
19585
+ }
19586
+ const response = await fetch(url, { headers });
19587
+ if (!response.ok || !response.body) {
19588
+ throw new Error(`CP SSE connection failed: ${String(response.status)}`);
19589
+ }
19590
+ const reader = response.body.getReader();
19591
+ const decoder = new TextDecoder;
19592
+ let buffer = "";
19593
+ while (true) {
19594
+ const { done, value } = await reader.read();
19595
+ if (done)
19596
+ break;
19597
+ buffer += decoder.decode(value, { stream: true });
19598
+ const lines = buffer.split(`
19599
+ `);
19600
+ buffer = lines.pop() ?? "";
19601
+ let currentEvent = "";
19602
+ for (const line of lines) {
19603
+ if (line.startsWith("event: ")) {
19604
+ currentEvent = line.slice(7).trim();
19605
+ } else if (line.startsWith("data: ") && currentEvent) {
19606
+ if (currentEvent === "webhook_processed" || currentEvent === "sync_completed") {
19607
+ runSync(state);
19608
+ }
19609
+ currentEvent = "";
19610
+ } else if (line === "") {
19611
+ currentEvent = "";
19612
+ }
19613
+ }
19614
+ }
19615
+ } catch (error) {
19616
+ console.error("CP SSE error, reconnecting in 5s", error);
19617
+ }
19618
+ await delay(5000);
19619
+ subscribeToControlPlane(state);
19620
+ };
19621
+ connect();
19039
19622
  }
19040
19623
  async function startBackgroundLoops(state) {
19041
19624
  const runSyncLoop = async () => {
@@ -19064,10 +19647,11 @@ async function startBackgroundLoops(state) {
19064
19647
  };
19065
19648
  runSyncLoop();
19066
19649
  runWorkerLoop();
19650
+ subscribeToControlPlane(state);
19067
19651
  }
19068
19652
  function createServedApp() {
19069
19653
  const { serveStatic: serveStatic2 } = require_bun();
19070
- const distDir = import.meta.dir.endsWith("/src") ? resolve2(import.meta.dir, "../dist") : import.meta.dir;
19654
+ const distDir = import.meta.dir.endsWith("/src") ? resolve(import.meta.dir, "../dist") : import.meta.dir;
19071
19655
  const clientDir = join3(distDir, "client");
19072
19656
  const app = createLocalApp();
19073
19657
  app.use("/*", serveStatic2({ root: clientDir }));