@rashidee/co2 1.3.10 → 1.3.12

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 (74) hide show
  1. package/dist/.co2-dat/app.db +0 -0
  2. package/dist/.co2-dat/app.db-shm +0 -0
  3. package/dist/.co2-dat/app.db-wal +0 -0
  4. package/dist/index.js +408 -73
  5. package/package.json +41 -41
  6. package/plugin/skills/mockgen-shadcn/SKILL.md +11 -7
  7. package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +14 -3
  8. package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +98 -40
  9. package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +98 -40
  10. package/plugin/skills/util-gencicdscript/SKILL.md +34 -8
  11. package/plugin/skills/util-gencicdscript/references/cicd-app-template.md +798 -689
  12. package/plugin/skills/util-plancicd/SKILL.md +63 -2
  13. package/static/assets/{abnfDiagram-VRR7QNED-ChoWkBm-.js → abnfDiagram-VRR7QNED-Qm0Wk_H_.js} +1 -1
  14. package/static/assets/{arc-BOiutWhq.js → arc-Cfk6Kt0D.js} +1 -1
  15. package/static/assets/{architectureDiagram-ZJ3FMSHR-Dn4YKYfM.js → architectureDiagram-ZJ3FMSHR-DQTlZaUK.js} +1 -1
  16. package/static/assets/{blockDiagram-677ZJIJ3-BrXEfs29.js → blockDiagram-677ZJIJ3-D-4ibdoX.js} +1 -1
  17. package/static/assets/{c4Diagram-LMCZKHZV-ChaIzrlx.js → c4Diagram-LMCZKHZV-CHuqcdJm.js} +1 -1
  18. package/static/assets/channel-H9DRrduO.js +1 -0
  19. package/static/assets/{chunk-2Q5K7J3B-BOlnmyI2.js → chunk-2Q5K7J3B-MB9pWQe1.js} +1 -1
  20. package/static/assets/{chunk-32BRIVSS-BYtO1gsq.js → chunk-32BRIVSS-BttYlwU1.js} +1 -1
  21. package/static/assets/{chunk-5VM5RSS4-Cmx4Morj.js → chunk-5VM5RSS4-oyt0Tagd.js} +1 -1
  22. package/static/assets/{chunk-EX3LRPZG-BcgUofmA.js → chunk-EX3LRPZG-DaTxdTWl.js} +1 -1
  23. package/static/assets/{chunk-JWPE2WC7-CnNRXdvN.js → chunk-JWPE2WC7-CNgDxPAD.js} +1 -1
  24. package/static/assets/{chunk-MOJQB5TN-DGG6Q3Wn.js → chunk-MOJQB5TN-CoAudO3B.js} +1 -1
  25. package/static/assets/{chunk-RYQCIY6F-C8erKHls.js → chunk-RYQCIY6F-BrkDEEha.js} +1 -1
  26. package/static/assets/{chunk-V7JOEXUC-BNkIJqzu.js → chunk-V7JOEXUC-D14rGvBO.js} +1 -1
  27. package/static/assets/{chunk-VR4S4FIN-CGq-RseS.js → chunk-VR4S4FIN-CAtas2vF.js} +1 -1
  28. package/static/assets/{chunk-XXDRQBXY-DOVfEC7j.js → chunk-XXDRQBXY-DcVAnNUQ.js} +1 -1
  29. package/static/assets/classDiagram-OUVF2IWQ-DRNPM383.js +1 -0
  30. package/static/assets/classDiagram-v2-EOCWNBFH-DRNPM383.js +1 -0
  31. package/static/assets/{cose-bilkent-JH36ORCC-BrK3gCzK.js → cose-bilkent-JH36ORCC-B7y4MI6Z.js} +1 -1
  32. package/static/assets/{cynefin-VYW2F7L2-BTMYzKLg.js → cynefin-VYW2F7L2-D2mnk-iJ.js} +1 -1
  33. package/static/assets/{cynefinDiagram-TSTJHNR4-Df4k0_Hb.js → cynefinDiagram-TSTJHNR4-DNzQJhG7.js} +1 -1
  34. package/static/assets/{dagre-VKFMJZFB-0LnYnEqq.js → dagre-VKFMJZFB-NMSyTo8N.js} +1 -1
  35. package/static/assets/{diagram-FQU43EPY-B5nIVD5g.js → diagram-FQU43EPY-CdqS5Ike.js} +1 -1
  36. package/static/assets/{diagram-G47NLZAW-B2YLBzdb.js → diagram-G47NLZAW-Bsb4T5JI.js} +1 -1
  37. package/static/assets/{diagram-NH7WQ7WH-DPVumXIF.js → diagram-NH7WQ7WH-Z-PyzBSC.js} +1 -1
  38. package/static/assets/{diagram-OA4YK3LP-Da20vcLJ.js → diagram-OA4YK3LP-BZBTYbxg.js} +1 -1
  39. package/static/assets/{diagram-WEI45ONY-DQbTpuRZ.js → diagram-WEI45ONY-BL2LA5H_.js} +1 -1
  40. package/static/assets/{ebnfDiagram-CCIWWBDH-BEn1vEyb.js → ebnfDiagram-CCIWWBDH-BGtukMeo.js} +1 -1
  41. package/static/assets/{erDiagram-Q63AITRT-Ccg0DVBG.js → erDiagram-Q63AITRT-B6OJ4YCl.js} +1 -1
  42. package/static/assets/{flowDiagram-23GEKE2U-B2dMjEpa.js → flowDiagram-23GEKE2U-BBkMgJ1i.js} +1 -1
  43. package/static/assets/{ganttDiagram-NO4QXBWP-Kp1UsG59.js → ganttDiagram-NO4QXBWP-Cv8KHFBD.js} +1 -1
  44. package/static/assets/{gitGraphDiagram-IHSO6WYX-D6RB8sPf.js → gitGraphDiagram-IHSO6WYX-hYxDNjWx.js} +1 -1
  45. package/static/assets/{index-CM4GfOLV.css → index-DKI3BgkF.css} +1 -1
  46. package/static/assets/{index-GEUFVo96.js → index-DnYGyYET.js} +118 -120
  47. package/static/assets/{infoDiagram-FWYZ7A6U-C4ILACOF.js → infoDiagram-FWYZ7A6U-WuQIiedv.js} +1 -1
  48. package/static/assets/{ishikawaDiagram-FXEZZL3T-Q2Ef9fW7.js → ishikawaDiagram-FXEZZL3T-BJGOD-IY.js} +1 -1
  49. package/static/assets/{journeyDiagram-5HDEW3XC-B7ZV1i0L.js → journeyDiagram-5HDEW3XC-Ba_NXseS.js} +1 -1
  50. package/static/assets/{kanban-definition-HUTT4EX6-CjaPV_vw.js → kanban-definition-HUTT4EX6-C5RMm9uF.js} +1 -1
  51. package/static/assets/{linear-BELpVz92.js → linear-CLKppNoj.js} +1 -1
  52. package/static/assets/{mindmap-definition-LN4V7U3C-COhl-GrE.js → mindmap-definition-LN4V7U3C-ClSG_qmQ.js} +1 -1
  53. package/static/assets/{pegDiagram-2B236MQR-3FlTtWva.js → pegDiagram-2B236MQR-C_JEZqk3.js} +1 -1
  54. package/static/assets/{pieDiagram-ENE6RG2P-CLeOh_sh.js → pieDiagram-ENE6RG2P-D0lx7wDi.js} +1 -1
  55. package/static/assets/{quadrantDiagram-ABIIQ3AL-D2MnobFS.js → quadrantDiagram-ABIIQ3AL-BBKy0PVs.js} +1 -1
  56. package/static/assets/{railroadDiagram-RFXS5EU6-FjtL4fEj.js → railroadDiagram-RFXS5EU6-C1Badu5q.js} +1 -1
  57. package/static/assets/{requirementDiagram-TGXJPOKE-C5Y2uWRV.js → requirementDiagram-TGXJPOKE-1j59jBQH.js} +1 -1
  58. package/static/assets/{sankeyDiagram-HTMAVEWB-ITj9bISn.js → sankeyDiagram-HTMAVEWB-BLR5S8aZ.js} +1 -1
  59. package/static/assets/{sequenceDiagram-DBY2YBRQ-Ct2xTSbI.js → sequenceDiagram-DBY2YBRQ-BlqdK4qm.js} +1 -1
  60. package/static/assets/{sizeCapture-X5ZJPWSS-w9hGRpov.js → sizeCapture-X5ZJPWSS-C27ndGIT.js} +1 -1
  61. package/static/assets/{stateDiagram-2N3HPSRC-BlxDF1qr.js → stateDiagram-2N3HPSRC-DOu4536_.js} +1 -1
  62. package/static/assets/stateDiagram-v2-6OUMAXLB-DvP1ZCp8.js +1 -0
  63. package/static/assets/{swimlanes-5IMT3BWC-DFYm_SCW.js → swimlanes-5IMT3BWC-BpGitR9A.js} +2 -2
  64. package/static/assets/swimlanesDiagram-G3AALYLV-B5cf18Ey.js +8 -0
  65. package/static/assets/{timeline-definition-FHXFAJF6-johIMUxt.js → timeline-definition-FHXFAJF6-JIvdjH7z.js} +1 -1
  66. package/static/assets/{vennDiagram-L72KCM5P-BFk2-H5y.js → vennDiagram-L72KCM5P-DulnqLWm.js} +1 -1
  67. package/static/assets/{wardleyDiagram-EHGQE667-T7509MWq.js → wardleyDiagram-EHGQE667--MhZNL8M.js} +1 -1
  68. package/static/assets/{xychartDiagram-FW5EYKEG-DskgQ7uq.js → xychartDiagram-FW5EYKEG-CjnNgnMQ.js} +1 -1
  69. package/static/index.html +2 -2
  70. package/static/assets/channel-CKovdojM.js +0 -1
  71. package/static/assets/classDiagram-OUVF2IWQ-BRxqfb0_.js +0 -1
  72. package/static/assets/classDiagram-v2-EOCWNBFH-BRxqfb0_.js +0 -1
  73. package/static/assets/stateDiagram-v2-6OUMAXLB-CQVgnOlz.js +0 -1
  74. package/static/assets/swimlanesDiagram-G3AALYLV-DubMPSZe.js +0 -8
package/dist/index.js CHANGED
@@ -6809,7 +6809,7 @@ var {
6809
6809
  // package.json
6810
6810
  var package_default = {
6811
6811
  name: "@rashidee/co2",
6812
- version: "1.3.10",
6812
+ version: "1.3.12",
6813
6813
  description: "Compound Context Studio \u2014 self-hosted team context authoring for CO2 projects",
6814
6814
  type: "module",
6815
6815
  license: "MIT",
@@ -14785,6 +14785,15 @@ function resolveSession(db) {
14785
14785
  await next();
14786
14786
  };
14787
14787
  }
14788
+ function findSessionUser(db, rawToken) {
14789
+ if (!rawToken) return null;
14790
+ const session = db.select().from(sessions).where(
14791
+ and(eq(sessions.tokenHash, hashToken(rawToken)), gt(sessions.expiresAt, (/* @__PURE__ */ new Date()).toISOString()))
14792
+ ).limit(1).get();
14793
+ if (!session) return null;
14794
+ const user = db.select().from(users).where(eq(users.id, session.userId)).limit(1).get();
14795
+ return user && user.isDeleted === 0 ? user : null;
14796
+ }
14788
14797
 
14789
14798
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
14790
14799
  var external_exports = {};
@@ -19151,6 +19160,14 @@ var gitResolveRequestSchema = external_exports.object({
19151
19160
  message: "Manual resolution requires the merged content",
19152
19161
  path: ["content"]
19153
19162
  });
19163
+ var gitPullResponseSchema = external_exports.object({
19164
+ status: external_exports.enum(["up_to_date", "merged", "conflicts"]),
19165
+ branch: external_exports.string(),
19166
+ /** New short HEAD hash — present only for `merged`. */
19167
+ commitHash: external_exports.string().optional(),
19168
+ /** Git's own summary (e.g. the conflict list) — display verbatim. */
19169
+ message: external_exports.string().optional()
19170
+ });
19154
19171
 
19155
19172
  // ../core/src/schemas/import.ts
19156
19173
  var importCandidateSchema = external_exports.object({
@@ -19905,6 +19922,64 @@ async function startMockupHub(db, workspacePath, projectId, requestHost, port) {
19905
19922
  }
19906
19923
  return toStatus({ location, running: false, activePort: targetPort }, requestHost, projectId);
19907
19924
  }
19925
+ var STRIP_HEADERS = /* @__PURE__ */ new Set([
19926
+ "connection",
19927
+ "keep-alive",
19928
+ "proxy-authenticate",
19929
+ "proxy-authorization",
19930
+ "te",
19931
+ "trailer",
19932
+ "transfer-encoding",
19933
+ "upgrade",
19934
+ "host",
19935
+ "content-length",
19936
+ "content-encoding",
19937
+ "accept-encoding"
19938
+ ]);
19939
+ async function proxyHubRequest(db, workspacePath, projectId, prefix, subPath, req) {
19940
+ const location = resolveHub(db, workspacePath, projectId);
19941
+ if (!location.present) throw svcError("MOCKUP_HUB_MISSING", 404);
19942
+ if (!location.configPort) throw svcError("MOCKUP_HUB_DOWN", 502);
19943
+ const url = new URL(req.url);
19944
+ const target = `http://127.0.0.1:${location.configPort}${subPath}${url.search}`;
19945
+ const headers = new Headers();
19946
+ req.headers.forEach((value, name) => {
19947
+ if (!STRIP_HEADERS.has(name.toLowerCase())) headers.set(name, value);
19948
+ });
19949
+ headers.set("x-forwarded-prefix", prefix);
19950
+ headers.set("x-forwarded-host", url.host);
19951
+ headers.set("x-forwarded-proto", url.protocol.replace(/:$/, ""));
19952
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
19953
+ let upstream;
19954
+ try {
19955
+ upstream = await fetch(target, {
19956
+ method: req.method,
19957
+ headers,
19958
+ body: hasBody ? await req.arrayBuffer() : void 0,
19959
+ redirect: "manual"
19960
+ });
19961
+ } catch {
19962
+ throw svcError("MOCKUP_HUB_DOWN", 502);
19963
+ }
19964
+ const responseHeaders = new Headers();
19965
+ upstream.headers.forEach((value, name) => {
19966
+ if (!STRIP_HEADERS.has(name.toLowerCase())) responseHeaders.set(name, value);
19967
+ });
19968
+ const redirectTo = responseHeaders.get("location");
19969
+ if (redirectTo?.startsWith("/") && redirectTo !== prefix && !redirectTo.startsWith(`${prefix}/`)) {
19970
+ responseHeaders.set("location", `${prefix}${redirectTo}`);
19971
+ }
19972
+ const prefixAware = upstream.headers.get("x-hub-prefix") !== null;
19973
+ responseHeaders.delete("x-hub-prefix");
19974
+ if (!prefixAware && responseHeaders.get("content-type")?.includes("text/html")) {
19975
+ const html = (await upstream.text()).replace(
19976
+ /(href|src|action|hx-get|hx-post|hx-put|hx-patch|hx-delete)="(\/[^"]*)"/g,
19977
+ (match2, attr, url2) => url2 === prefix || url2.startsWith(`${prefix}/`) ? match2 : `${attr}="${prefix}${url2}"`
19978
+ );
19979
+ return new Response(html, { status: upstream.status, headers: responseHeaders });
19980
+ }
19981
+ return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
19982
+ }
19908
19983
  function stopMockupHub(projectId) {
19909
19984
  const child = spawnedHubs.get(projectId);
19910
19985
  if (!child) return false;
@@ -20201,6 +20276,24 @@ function artifactRoutes(db, workspacePath) {
20201
20276
  return errorResponse(c, e);
20202
20277
  }
20203
20278
  });
20279
+ app.all(
20280
+ "/mockup-proxy/:projectId",
20281
+ (c) => (
20282
+ // Normalize to the trailing-slash form so the prefix always maps to the hub root.
20283
+ c.redirect(`${c.req.path}/`, 301)
20284
+ )
20285
+ );
20286
+ app.all("/mockup-proxy/:projectId/*", async (c) => {
20287
+ const projectId = Number(c.req.param("projectId"));
20288
+ if (!Number.isInteger(projectId) || projectId <= 0) return c.json({ code: "INVALID_ID" }, 400);
20289
+ const prefix = `/api/artifacts/mockup-proxy/${projectId}`;
20290
+ const subPath = c.req.path.slice(prefix.length) || "/";
20291
+ try {
20292
+ return await proxyHubRequest(db, workspacePath, projectId, prefix, subPath, c.req.raw);
20293
+ } catch (e) {
20294
+ return errorResponse(c, e);
20295
+ }
20296
+ });
20204
20297
  app.get("/:id/content", (c) => {
20205
20298
  const id = Number(c.req.param("id"));
20206
20299
  if (Number.isNaN(id)) return c.json({ code: "INVALID_ID" }, 400);
@@ -21770,16 +21863,19 @@ function brainstormRoutes(db, workspacePath) {
21770
21863
  import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
21771
21864
  import { join as join14 } from "path";
21772
21865
 
21866
+ // ../server/src/features/cicd/app-proxy.ts
21867
+ import { connect as connect2 } from "net";
21868
+
21773
21869
  // ../server/src/features/cicd/app-runner.ts
21774
21870
  import { spawn as spawn2 } from "child_process";
21775
21871
  import { randomBytes as randomBytes2 } from "crypto";
21776
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
21872
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
21777
21873
  import { request as httpRequest2 } from "http";
21778
21874
  import { join as join9 } from "path";
21779
21875
  function svcError6(code, status) {
21780
21876
  return Object.assign(new Error(code), { code, status });
21781
21877
  }
21782
- var DEFAULT_CICD_APP_PORT = 4100;
21878
+ var DEFAULT_CICD_APP_PORT = 5e3;
21783
21879
  var spawnedApps = /* @__PURE__ */ new Map();
21784
21880
  var guardTokens = /* @__PURE__ */ new Map();
21785
21881
  function killSpawnedApps() {
@@ -21830,55 +21926,81 @@ function probeApp(port, timeoutMs = 1e3) {
21830
21926
  req.end();
21831
21927
  });
21832
21928
  }
21833
- function toStatus2(location, running, requestHost) {
21834
- const hostname = requestHost.replace(/:\d+$/, "") || "localhost";
21929
+ function toStatus2(location, running, projectId) {
21835
21930
  return {
21836
21931
  present: location.present,
21837
21932
  running,
21838
21933
  port: location.present ? location.port : null,
21839
- url: location.present ? `http://${hostname}:${location.port}/` : null
21934
+ // Same-origin proxy path (app-proxy.ts routes) — works locally AND behind HTTPS/reverse
21935
+ // proxies; the iframe and "Open in new tab" both load through it.
21936
+ url: location.present ? `/api/cicd/${projectId}/app-proxy/` : null
21840
21937
  };
21841
21938
  }
21842
- async function getCicdAppStatus(db, workspacePath, projectId, requestHost) {
21939
+ async function getCicdAppStatus(db, workspacePath, projectId) {
21843
21940
  const location = resolveCicdApp(db, workspacePath, projectId);
21844
21941
  const running = location.present ? await probeApp(location.port) : false;
21845
- return toStatus2(location, running, requestHost);
21942
+ return toStatus2(location, running, projectId);
21846
21943
  }
21847
- async function startCicdApp(db, workspacePath, projectId, requestHost, studioPort) {
21944
+ async function startCicdApp(db, workspacePath, projectId, studioPort, port) {
21848
21945
  const location = resolveCicdApp(db, workspacePath, projectId);
21849
21946
  if (!location.present) throw svcError6("CICD_APP_MISSING", 404);
21850
- if (await probeApp(location.port)) return toStatus2(location, true, requestHost);
21947
+ const targetPort = port ?? location.port;
21948
+ if (targetPort !== location.port) {
21949
+ const configPath = join9(location.appDir, "cicd.config.json");
21950
+ let config = {};
21951
+ try {
21952
+ config = JSON.parse(readFileSync6(configPath, "utf8"));
21953
+ } catch {
21954
+ }
21955
+ config.port = targetPort;
21956
+ try {
21957
+ writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
21958
+ `);
21959
+ } catch {
21960
+ }
21961
+ location.port = targetPort;
21962
+ }
21963
+ if (await probeApp(targetPort)) {
21964
+ location.port = targetPort;
21965
+ return toStatus2(location, true, projectId);
21966
+ }
21851
21967
  const existing = spawnedApps.get(projectId);
21852
- if (!existing || existing.exitCode !== null) {
21853
- const token = randomBytes2(24).toString("hex");
21854
- guardTokens.set(projectId, token);
21855
- const child = spawn2(process.execPath, ["server.js"], {
21856
- cwd: location.appDir,
21857
- stdio: "ignore",
21858
- env: {
21859
- ...process.env,
21860
- PORT: String(location.port),
21861
- CO2_STUDIO_URL: `http://127.0.0.1:${studioPort}`,
21862
- CO2_PROJECT_ID: String(projectId),
21863
- CO2_GUARD_TOKEN: token
21864
- }
21865
- });
21866
- child.on("error", () => {
21867
- spawnedApps.delete(projectId);
21868
- guardTokens.delete(projectId);
21869
- });
21870
- child.on("exit", () => {
21871
- spawnedApps.delete(projectId);
21872
- guardTokens.delete(projectId);
21873
- });
21874
- spawnedApps.set(projectId, child);
21875
- db.update(cicdSetups).set({ appPort: location.port, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(cicdSetups.projectId, projectId)).run();
21968
+ if (existing && existing.exitCode === null) {
21969
+ try {
21970
+ existing.kill();
21971
+ } catch {
21972
+ }
21973
+ spawnedApps.delete(projectId);
21974
+ guardTokens.delete(projectId);
21876
21975
  }
21976
+ const token = randomBytes2(24).toString("hex");
21977
+ guardTokens.set(projectId, token);
21978
+ const child = spawn2(process.execPath, ["server.js"], {
21979
+ cwd: location.appDir,
21980
+ stdio: "ignore",
21981
+ env: {
21982
+ ...process.env,
21983
+ PORT: String(location.port),
21984
+ CO2_STUDIO_URL: `http://127.0.0.1:${studioPort}`,
21985
+ CO2_PROJECT_ID: String(projectId),
21986
+ CO2_GUARD_TOKEN: token
21987
+ }
21988
+ });
21989
+ child.on("error", () => {
21990
+ spawnedApps.delete(projectId);
21991
+ guardTokens.delete(projectId);
21992
+ });
21993
+ child.on("exit", () => {
21994
+ spawnedApps.delete(projectId);
21995
+ guardTokens.delete(projectId);
21996
+ });
21997
+ spawnedApps.set(projectId, child);
21998
+ db.update(cicdSetups).set({ appPort: location.port, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(cicdSetups.projectId, projectId)).run();
21877
21999
  for (let attempt = 0; attempt < 20; attempt++) {
21878
- if (await probeApp(location.port, 500)) return toStatus2(location, true, requestHost);
22000
+ if (await probeApp(location.port, 500)) return toStatus2(location, true, projectId);
21879
22001
  await new Promise((r) => setTimeout(r, 250));
21880
22002
  }
21881
- return toStatus2(location, false, requestHost);
22003
+ return toStatus2(location, false, projectId);
21882
22004
  }
21883
22005
  function stopCicdApp(projectId) {
21884
22006
  const child = spawnedApps.get(projectId);
@@ -21892,13 +22014,103 @@ function stopCicdApp(projectId) {
21892
22014
  return true;
21893
22015
  }
21894
22016
 
22017
+ // ../server/src/features/cicd/app-proxy.ts
22018
+ async function proxyCicdAppRequest(db, workspacePath, projectId, prefix, subPath, req) {
22019
+ const location = resolveCicdApp(db, workspacePath, projectId);
22020
+ if (!location.present) throw svcError6("CICD_APP_MISSING", 404);
22021
+ const url = new URL(req.url);
22022
+ const target = `http://127.0.0.1:${location.port}${subPath}${url.search}`;
22023
+ const headers = new Headers();
22024
+ req.headers.forEach((value, name) => {
22025
+ if (!STRIP_HEADERS.has(name.toLowerCase())) headers.set(name, value);
22026
+ });
22027
+ headers.set("x-forwarded-prefix", prefix);
22028
+ headers.set("x-forwarded-host", url.host);
22029
+ headers.set("x-forwarded-proto", url.protocol.replace(/:$/, ""));
22030
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
22031
+ let upstream;
22032
+ try {
22033
+ upstream = await fetch(target, {
22034
+ method: req.method,
22035
+ headers,
22036
+ body: hasBody ? await req.arrayBuffer() : void 0,
22037
+ redirect: "manual"
22038
+ });
22039
+ } catch {
22040
+ throw svcError6("CICD_APP_DOWN", 502);
22041
+ }
22042
+ const responseHeaders = new Headers();
22043
+ upstream.headers.forEach((value, name) => {
22044
+ if (!STRIP_HEADERS.has(name.toLowerCase())) responseHeaders.set(name, value);
22045
+ });
22046
+ const redirectTo = responseHeaders.get("location");
22047
+ if (redirectTo?.startsWith("/") && redirectTo !== prefix && !redirectTo.startsWith(`${prefix}/`)) {
22048
+ responseHeaders.set("location", `${prefix}${redirectTo}`);
22049
+ }
22050
+ return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
22051
+ }
22052
+ var CICD_PROXY_UPGRADE_RE = /^\/api\/cicd\/(\d+)\/app-proxy(\/.*)$/;
22053
+ function sessionTokenFromCookieHeader(cookieHeader) {
22054
+ if (!cookieHeader) return void 0;
22055
+ for (const part of cookieHeader.split(";")) {
22056
+ const [name, ...rest] = part.trim().split("=");
22057
+ if (name === SESSION_COOKIE) return rest.join("=");
22058
+ }
22059
+ return void 0;
22060
+ }
22061
+ function handleCicdProxyUpgrade(db, workspacePath, req, socket, head) {
22062
+ const url = new URL(req.url ?? "", "http://localhost");
22063
+ const match2 = url.pathname.match(CICD_PROXY_UPGRADE_RE);
22064
+ if (!match2) {
22065
+ socket.destroy();
22066
+ return;
22067
+ }
22068
+ const user = findSessionUser(db, sessionTokenFromCookieHeader(req.headers.cookie));
22069
+ if (!user || user.mustChangePassword) {
22070
+ socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
22071
+ socket.destroy();
22072
+ return;
22073
+ }
22074
+ let port;
22075
+ try {
22076
+ const location = resolveCicdApp(db, workspacePath, Number(match2[1]));
22077
+ if (!location.present) {
22078
+ socket.destroy();
22079
+ return;
22080
+ }
22081
+ port = location.port;
22082
+ } catch {
22083
+ socket.destroy();
22084
+ return;
22085
+ }
22086
+ const upstream = connect2(port, "127.0.0.1", () => {
22087
+ let raw2 = `${req.method} ${match2[2]}${url.search} HTTP/1.1\r
22088
+ `;
22089
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
22090
+ const name = req.rawHeaders[i];
22091
+ const value = name.toLowerCase() === "host" ? `127.0.0.1:${port}` : req.rawHeaders[i + 1];
22092
+ raw2 += `${name}: ${value}\r
22093
+ `;
22094
+ }
22095
+ upstream.write(`${raw2}\r
22096
+ `);
22097
+ if (head.length > 0) upstream.write(head);
22098
+ socket.pipe(upstream);
22099
+ upstream.pipe(socket);
22100
+ });
22101
+ upstream.on("error", () => socket.destroy());
22102
+ socket.on("error", () => upstream.destroy());
22103
+ upstream.on("close", () => socket.destroy());
22104
+ socket.on("close", () => upstream.destroy());
22105
+ }
22106
+
21895
22107
  // ../server/src/features/cicd/guard.ts
21896
22108
  import { execFileSync as execFileSync2 } from "child_process";
21897
22109
  import { existsSync as existsSync10 } from "fs";
21898
22110
  import { join as join11 } from "path";
21899
22111
 
21900
22112
  // ../server/src/features/prd-wizard/service.ts
21901
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
22113
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
21902
22114
  import { join as join10 } from "path";
21903
22115
  function svcError7(code, status) {
21904
22116
  return Object.assign(new Error(code), { code, status });
@@ -21976,7 +22188,7 @@ function generatePrdWizardMarkdown(db, workspacePath, requestId, targetModule) {
21976
22188
  `execution/${filename}`,
21977
22189
  targetModule
21978
22190
  );
21979
- writeFileSync5(filePath, markdown, "utf8");
22191
+ writeFileSync6(filePath, markdown, "utf8");
21980
22192
  db.update(prdWizardRequests).set({
21981
22193
  generatedMarkdownPath: filePath,
21982
22194
  status: "generated",
@@ -22213,7 +22425,7 @@ function evaluateBuildGuard(db, workspacePath, projectId, versionId) {
22213
22425
  }
22214
22426
 
22215
22427
  // ../server/src/features/cicd/service.ts
22216
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
22428
+ import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
22217
22429
  import { join as join13 } from "path";
22218
22430
 
22219
22431
  // ../server/src/features/batch-job/constants.ts
@@ -22356,7 +22568,7 @@ function requestPlan(db, workspacePath, projectId, userId) {
22356
22568
  const filename = `cicd-plan-${setup.id}-${Date.now()}.md`;
22357
22569
  const filePath = join13(executionDir, filename);
22358
22570
  const relative3 = `execution/${filename}`;
22359
- writeFileSync6(
22571
+ writeFileSync7(
22360
22572
  filePath,
22361
22573
  buildPlanRequestMarkdown(
22362
22574
  project.name,
@@ -22516,9 +22728,12 @@ function parseProjectId(c) {
22516
22728
  const id = Number(c.req.param("projectId"));
22517
22729
  return Number.isNaN(id) ? null : id;
22518
22730
  }
22731
+ var startBodySchema = external_exports.object({
22732
+ port: external_exports.coerce.number().int().min(1024).max(65535).optional()
22733
+ });
22519
22734
  function cicdRoutes(db, workspacePath, studioPort) {
22520
22735
  const app = new Hono2();
22521
- const toDto8 = async (c, setup) => {
22736
+ const toDto8 = async (setup) => {
22522
22737
  const planFile = setup.planRequestPath?.split(/[\\/]/).pop();
22523
22738
  return {
22524
22739
  id: setup.id,
@@ -22530,7 +22745,7 @@ function cicdRoutes(db, workspacePath, studioPort) {
22530
22745
  approvedAt: setup.approvedAt,
22531
22746
  generatedAt: setup.generatedAt,
22532
22747
  stale: isStale(db, setup),
22533
- app: await getCicdAppStatus(db, workspacePath, setup.projectId, c.req.header("host") ?? ""),
22748
+ app: await getCicdAppStatus(db, workspacePath, setup.projectId),
22534
22749
  createdAt: setup.createdAt,
22535
22750
  updatedAt: setup.updatedAt
22536
22751
  };
@@ -22548,7 +22763,7 @@ function cicdRoutes(db, workspacePath, studioPort) {
22548
22763
  startCicdWatcher(db, workspacePath, setup.id, "generation", user.id);
22549
22764
  }
22550
22765
  const fresh = getOrCreateSetup(db, projectId, user.id);
22551
- return c.json(await toDto8(c, fresh));
22766
+ return c.json(await toDto8(fresh));
22552
22767
  } catch (e) {
22553
22768
  return errorResponse3(c, e);
22554
22769
  }
@@ -22571,7 +22786,7 @@ function cicdRoutes(db, workspacePath, studioPort) {
22571
22786
  if (fresh.status === "generating" && !hasCicdWatcher(fresh.id, "generation")) {
22572
22787
  startCicdWatcher(db, workspacePath, fresh.id, "generation", user.id);
22573
22788
  }
22574
- return c.json(await toDto8(c, fresh));
22789
+ return c.json(await toDto8(fresh));
22575
22790
  } catch (e) {
22576
22791
  return errorResponse3(c, e);
22577
22792
  }
@@ -22625,10 +22840,11 @@ function cicdRoutes(db, workspacePath, studioPort) {
22625
22840
  app.post("/:projectId/app/start", async (c) => {
22626
22841
  const projectId = parseProjectId(c);
22627
22842
  if (projectId === null) return c.json({ code: "INVALID_ID" }, 400);
22843
+ const parsed = startBodySchema.safeParse(await c.req.json().catch(() => ({})));
22844
+ if (!parsed.success) return c.json({ code: "INVALID_PORT" }, 400);
22845
+ if (parsed.data.port === studioPort) return c.json({ code: "CICD_PORT_CONFLICT" }, 400);
22628
22846
  try {
22629
- return c.json(
22630
- await startCicdApp(db, workspacePath, projectId, c.req.header("host") ?? "", studioPort)
22631
- );
22847
+ return c.json(await startCicdApp(db, workspacePath, projectId, studioPort, parsed.data.port));
22632
22848
  } catch (e) {
22633
22849
  return errorResponse3(c, e);
22634
22850
  }
@@ -22638,9 +22854,25 @@ function cicdRoutes(db, workspacePath, studioPort) {
22638
22854
  if (projectId === null) return c.json({ code: "INVALID_ID" }, 400);
22639
22855
  try {
22640
22856
  stopCicdApp(projectId);
22641
- return c.json(
22642
- await getCicdAppStatus(db, workspacePath, projectId, c.req.header("host") ?? "")
22643
- );
22857
+ return c.json(await getCicdAppStatus(db, workspacePath, projectId));
22858
+ } catch (e) {
22859
+ return errorResponse3(c, e);
22860
+ }
22861
+ });
22862
+ app.all(
22863
+ "/:projectId/app-proxy",
22864
+ (c) => (
22865
+ // Normalize to the trailing-slash form so relative URLs in the page resolve under the prefix.
22866
+ c.redirect(`${c.req.path}/`, 301)
22867
+ )
22868
+ );
22869
+ app.all("/:projectId/app-proxy/*", async (c) => {
22870
+ const projectId = parseProjectId(c);
22871
+ if (projectId === null) return c.json({ code: "INVALID_ID" }, 400);
22872
+ const prefix = `/api/cicd/${projectId}/app-proxy`;
22873
+ const subPath = c.req.path.slice(prefix.length) || "/";
22874
+ try {
22875
+ return await proxyCicdAppRequest(db, workspacePath, projectId, prefix, subPath, c.req.raw);
22644
22876
  } catch (e) {
22645
22877
  return errorResponse3(c, e);
22646
22878
  }
@@ -22666,11 +22898,11 @@ function cicdGuardRoutes(db, workspacePath) {
22666
22898
  }
22667
22899
 
22668
22900
  // ../server/src/features/project/service.ts
22669
- import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
22901
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
22670
22902
  import { join as join16 } from "path";
22671
22903
 
22672
22904
  // ../server/src/features/project-config/service.ts
22673
- import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
22905
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
22674
22906
  import { join as join15 } from "path";
22675
22907
  function svcError9(code, status) {
22676
22908
  return Object.assign(new Error(code), { code, status });
@@ -22694,7 +22926,7 @@ function resolveImportedByName(db, importedBy) {
22694
22926
  function writeProjectFile(workspacePath, folder, fileName, content) {
22695
22927
  const dir = join15(workspacePath, folder);
22696
22928
  mkdirSync5(dir, { recursive: true });
22697
- writeFileSync7(join15(dir, fileName), content, "utf8");
22929
+ writeFileSync8(join15(dir, fileName), content, "utf8");
22698
22930
  }
22699
22931
  function toDto5(db, row, field) {
22700
22932
  return {
@@ -22861,8 +23093,8 @@ function scaffoldProjectFolder(workspacePath, folderName, name, baseIdea) {
22861
23093
  "# Feedback",
22862
23094
  "<!-- Human answers appear here -->"
22863
23095
  ].join("\n");
22864
- writeFileSync8(join16(projectDir2, `${folderName}.brainstorm.md`), template, "utf8");
22865
- writeFileSync8(
23096
+ writeFileSync9(join16(projectDir2, `${folderName}.brainstorm.md`), template, "utf8");
23097
+ writeFileSync9(
22866
23098
  join16(projectDir2, "context", "references", "brainstorm-protocol.md"),
22867
23099
  "# Brainstorm Protocol\n\n[Installed by the co2 plugin]\n",
22868
23100
  "utf8"
@@ -22900,7 +23132,7 @@ function updateBrainstormMd(db, workspacePath, projectId, content) {
22900
23132
  project.workspaceFolderName,
22901
23133
  `${project.workspaceFolderName}.brainstorm.md`
22902
23134
  );
22903
- writeFileSync8(filePath, content, "utf8");
23135
+ writeFileSync9(filePath, content, "utf8");
22904
23136
  }
22905
23137
  function listProjects(db, archived) {
22906
23138
  return db.select({
@@ -23023,7 +23255,7 @@ function regenerateCO2Files(db, workspacePath, projectId) {
23023
23255
  const projectDir2 = join16(workspacePath, project.workspaceFolderName);
23024
23256
  mkdirSync6(projectDir2, { recursive: true });
23025
23257
  const apps = db.select().from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
23026
- writeFileSync8(
23258
+ writeFileSync9(
23027
23259
  join16(projectDir2, "CLAUDE.md"),
23028
23260
  project.rawClaudeMd ?? buildClaudeMd(project.name, apps),
23029
23261
  "utf8"
@@ -23040,7 +23272,7 @@ function regenerateCO2Files(db, workspacePath, projectId) {
23040
23272
  const appRel = resolveAppFolderName(projectDir2, app.name);
23041
23273
  const appDir = join16(projectDir2, appRel, "context");
23042
23274
  mkdirSync6(appDir, { recursive: true });
23043
- writeFileSync8(
23275
+ writeFileSync9(
23044
23276
  join16(appDir, "PRD.md"),
23045
23277
  prdSnapshot[String(app.id)] ?? buildPrdMd(app.name, mods, loadPrdSections(db, app.id)),
23046
23278
  "utf8"
@@ -23433,7 +23665,7 @@ function dashboardRoutes(db) {
23433
23665
  }
23434
23666
 
23435
23667
  // ../server/src/features/defect/service.ts
23436
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
23668
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync10 } from "fs";
23437
23669
  import { join as join17 } from "path";
23438
23670
 
23439
23671
  // ../server/src/features/project-import/bug-md.ts
@@ -23865,7 +24097,7 @@ function writeBugMdFile(db, workspacePath, app, projectFolderName) {
23865
24097
  const appRel = resolveAppFolderName(projectDir2, app.name);
23866
24098
  const appContextDir = join17(projectDir2, appRel, "context");
23867
24099
  mkdirSync7(appContextDir, { recursive: true });
23868
- writeFileSync9(join17(appContextDir, "BUG.md"), content, "utf8");
24100
+ writeFileSync10(join17(appContextDir, "BUG.md"), content, "utf8");
23869
24101
  return { content, bugCount: bugs.length };
23870
24102
  }
23871
24103
  function syncBugStatusesFromMaster(db, trackingFilePath, applicationId) {
@@ -23909,7 +24141,7 @@ function syncBugMdToDisk(db, workspacePath, applicationId, bugId, mode = "replac
23909
24141
  ).all().find((m) => m.name.toLowerCase() === bug.module?.toLowerCase())?.category : void 0) ?? null;
23910
24142
  const merged = mergeBugIntoBugMd(readFileSync11(file, "utf8"), bug, semver, category, mode);
23911
24143
  if (merged !== null) {
23912
- writeFileSync9(file, merged, "utf8");
24144
+ writeFileSync10(file, merged, "utf8");
23913
24145
  return;
23914
24146
  }
23915
24147
  }
@@ -24043,7 +24275,7 @@ function defectRoutes(db, workspacePath) {
24043
24275
  }
24044
24276
 
24045
24277
  // ../server/src/features/document/service.ts
24046
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
24278
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "fs";
24047
24279
  import { join as join18 } from "path";
24048
24280
  function svcError12(code, status) {
24049
24281
  return Object.assign(new Error(code), { code, status });
@@ -24056,7 +24288,7 @@ function uploadDocument(db, dataDir, projectId, file) {
24056
24288
  mkdirSync8(dir, { recursive: true });
24057
24289
  const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, "_");
24058
24290
  const stamped = `${Date.now()}_${safeName}`;
24059
- writeFileSync10(join18(dir, stamped), file.bytes);
24291
+ writeFileSync11(join18(dir, stamped), file.bytes);
24060
24292
  const relativePath = `${projectId}/${stamped}`;
24061
24293
  const now = (/* @__PURE__ */ new Date()).toISOString();
24062
24294
  const inserted = db.insert(documents).values({
@@ -24143,7 +24375,7 @@ function documentRoutes(db, dataDir) {
24143
24375
 
24144
24376
  // ../server/src/features/git/service.ts
24145
24377
  import { execFileSync as execFileSync3 } from "child_process";
24146
- import { existsSync as existsSync18, readFileSync as readFileSync13, rmSync, writeFileSync as writeFileSync11 } from "fs";
24378
+ import { existsSync as existsSync18, readFileSync as readFileSync13, rmSync, writeFileSync as writeFileSync12 } from "fs";
24147
24379
  import { join as join19 } from "path";
24148
24380
  var GitServiceError = class extends Error {
24149
24381
  code;
@@ -24394,7 +24626,7 @@ function resolveConflict(db, workspacePath, projectId, input) {
24394
24626
  if (input.content === void 0) {
24395
24627
  throw new GitServiceError("CONTENT_REQUIRED", 400, "Manual resolution requires content");
24396
24628
  }
24397
- writeFileSync11(join19(dir, input.path), input.content, "utf8");
24629
+ writeFileSync12(join19(dir, input.path), input.content, "utf8");
24398
24630
  } else {
24399
24631
  git3(dir, ["checkout", `--${input.resolution}`, "--", input.path]);
24400
24632
  }
@@ -24404,6 +24636,67 @@ function resolveConflict(db, workspacePath, projectId, input) {
24404
24636
  throw new GitServiceError("GIT_RESOLVE_FAILED", 409, detail(e));
24405
24637
  }
24406
24638
  }
24639
+ function pullChanges(db, workspacePath, projectId, userId) {
24640
+ const dir = projectDir(db, workspacePath, projectId);
24641
+ if (!existsSync18(dir) || !isGitRepo(dir)) {
24642
+ throw new GitServiceError("NO_GIT_REPO", 404, "No git repository for this project");
24643
+ }
24644
+ const status = getGitStatus(db, workspacePath, projectId);
24645
+ if (!status.clean) {
24646
+ throw new GitServiceError(
24647
+ "UNCOMMITTED_CHANGES",
24648
+ 409,
24649
+ "Commit or discard your local changes before pulling"
24650
+ );
24651
+ }
24652
+ const branch = safe(() => git3(dir, ["branch", "--show-current"])) ?? "";
24653
+ if (branch === "") {
24654
+ throw new GitServiceError(
24655
+ "DETACHED_HEAD",
24656
+ 409,
24657
+ "The repository is in detached HEAD state \u2014 check out a branch before pulling"
24658
+ );
24659
+ }
24660
+ const ctx = resolveGitPushContext(db, projectId, userId);
24661
+ if (ctx.remoteUrl) ensureOrigin(dir, ctx.remoteUrl);
24662
+ const remotes = safe(() => git3(dir, ["remote"])) ?? "";
24663
+ if (!remotes.split("\n").includes("origin")) {
24664
+ throw new GitServiceError("NO_REMOTE", 400, "No git remote configured for this project");
24665
+ }
24666
+ const headBefore = safe(() => git3(dir, ["rev-parse", "--short", "HEAD"])) ?? "";
24667
+ const authB64 = ctx.auth ? Buffer.from(`${ctx.auth.username}:${ctx.auth.token}`).toString("base64") : null;
24668
+ try {
24669
+ execFileSync3(
24670
+ "git",
24671
+ [
24672
+ ...buildPushAuthArgs(ctx.auth),
24673
+ ...identityArgs(db, userId),
24674
+ "pull",
24675
+ "--no-rebase",
24676
+ "--no-edit",
24677
+ "origin",
24678
+ branch
24679
+ ],
24680
+ {
24681
+ cwd: dir,
24682
+ stdio: "pipe",
24683
+ timeout: 12e4,
24684
+ maxBuffer: 64 * 1024 * 1024,
24685
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
24686
+ }
24687
+ );
24688
+ } catch (e) {
24689
+ if (authB64) redactAuthHeaderFromError(e, authB64);
24690
+ const after = getGitStatus(db, workspacePath, projectId);
24691
+ if (after.hasConflicts) {
24692
+ return { status: "conflicts", branch, message: gitErrorDetail(e) };
24693
+ }
24694
+ throw new GitServiceError("GIT_PULL_FAILED", 409, withIdentityHint(gitErrorDetail(e)));
24695
+ }
24696
+ const headAfter = safe(() => git3(dir, ["rev-parse", "--short", "HEAD"])) ?? "";
24697
+ if (headAfter === headBefore) return { status: "up_to_date", branch };
24698
+ return { status: "merged", branch, commitHash: headAfter };
24699
+ }
24407
24700
  function detail(e) {
24408
24701
  const stderr = e.stderr?.toString().trim();
24409
24702
  return stderr || e.message || String(e);
@@ -24475,6 +24768,15 @@ function gitRoutes(db, workspacePath) {
24475
24768
  return errorResponse7(c, e);
24476
24769
  }
24477
24770
  });
24771
+ app.post("/pull/:projectId", (c) => {
24772
+ const projectId = Number(c.req.param("projectId"));
24773
+ if (Number.isNaN(projectId)) return c.json({ code: "INVALID_ID" }, 400);
24774
+ try {
24775
+ return c.json(pullChanges(db, workspacePath, projectId, c.get("user")?.id));
24776
+ } catch (e) {
24777
+ return errorResponse7(c, e);
24778
+ }
24779
+ });
24478
24780
  return app;
24479
24781
  }
24480
24782
 
@@ -25096,7 +25398,7 @@ function scanProject(db, workspacePath, folderName) {
25096
25398
  import { join as join22, relative as relative2 } from "path";
25097
25399
 
25098
25400
  // ../server/src/features/workflow/service.ts
25099
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
25401
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
25100
25402
  import { join as join21, relative, resolve as resolve2, sep } from "path";
25101
25403
  var ACTIVE_RUN_STATUSES = ["pending", "in_progress"];
25102
25404
  function svcError14(code, status) {
@@ -25323,7 +25625,7 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
25323
25625
  relPath
25324
25626
  );
25325
25627
  mkdirSync9(join21(projectDir2, "workflow", creatingUser.username), { recursive: true });
25326
- writeFileSync12(join21(projectDir2, relPath), content, "utf8");
25628
+ writeFileSync13(join21(projectDir2, relPath), content, "utf8");
25327
25629
  const now = (/* @__PURE__ */ new Date()).toISOString();
25328
25630
  const wfm = db.insert(workflowMarkdowns).values({
25329
25631
  projectId: input.projectId,
@@ -26167,7 +26469,7 @@ function projectRoutes(db, workspacePath) {
26167
26469
  }
26168
26470
 
26169
26471
  // ../server/src/features/shared-context/service.ts
26170
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync16, readdirSync as readdirSync4, statSync as statSync6, writeFileSync as writeFileSync13 } from "fs";
26472
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync16, readdirSync as readdirSync4, statSync as statSync6, writeFileSync as writeFileSync14 } from "fs";
26171
26473
  import { join as join23, resolve as resolve3, sep as sep2 } from "path";
26172
26474
  function svcError16(code, status) {
26173
26475
  return Object.assign(new Error(code), { code, status });
@@ -26258,7 +26560,7 @@ function uploadFile(db, workspacePath, projectId, folder, file) {
26258
26560
  const dir = folder ? safeResolve(root, folder) : root;
26259
26561
  if (folder && !existsSync21(dir)) throw svcError16("FOLDER_NOT_FOUND", 404);
26260
26562
  const safeName = sanitizeSegment(file.name);
26261
- writeFileSync13(join23(dir, safeName), file.bytes);
26563
+ writeFileSync14(join23(dir, safeName), file.bytes);
26262
26564
  return { path: folder ? `${folder}/${safeName}` : safeName };
26263
26565
  }
26264
26566
  function getFileContent(db, workspacePath, projectId, relPath) {
@@ -26273,7 +26575,7 @@ function saveMarkdown(db, workspacePath, projectId, relPath, content) {
26273
26575
  const root = resolveSharedRoot(db, workspacePath, projectId);
26274
26576
  const full = safeResolve(root, relPath);
26275
26577
  if (!existsSync21(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
26276
- writeFileSync13(full, content, "utf8");
26578
+ writeFileSync14(full, content, "utf8");
26277
26579
  }
26278
26580
 
26279
26581
  // ../server/src/features/shared-context/routes.ts
@@ -26987,6 +27289,26 @@ function appendScrollback(session, data) {
26987
27289
  session.bufferBytes -= session.buffer.shift().length;
26988
27290
  }
26989
27291
  }
27292
+ var HEARTBEAT_INTERVAL_MS = 3e4;
27293
+ function attachHeartbeat(ws, intervalMs = HEARTBEAT_INTERVAL_MS) {
27294
+ let alive = true;
27295
+ ws.on("pong", () => {
27296
+ alive = true;
27297
+ });
27298
+ const timer = setInterval(() => {
27299
+ if (!alive) {
27300
+ clearInterval(timer);
27301
+ ws.terminate();
27302
+ return;
27303
+ }
27304
+ alive = false;
27305
+ try {
27306
+ ws.ping();
27307
+ } catch {
27308
+ }
27309
+ }, intervalMs);
27310
+ ws.on("close", () => clearInterval(timer));
27311
+ }
26990
27312
  var sessions2 = /* @__PURE__ */ new Map();
26991
27313
  function closeTerminalSessions(projectId, reason) {
26992
27314
  const session = sessions2.get(projectId);
@@ -27018,8 +27340,21 @@ function resolveProjectFolder(db, projectId) {
27018
27340
  return row?.folder ?? null;
27019
27341
  }
27020
27342
  function createWsServer(httpServer, db, workspacePath) {
27021
- const wss = new import_websocket_server.default({ server: httpServer, path: "/ws/terminal" });
27343
+ const wss = new import_websocket_server.default({ noServer: true });
27344
+ httpServer.on("upgrade", (req, socket, head) => {
27345
+ const pathname = new URL(req.url ?? "", "http://localhost").pathname;
27346
+ if (pathname === "/ws/terminal") {
27347
+ wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
27348
+ return;
27349
+ }
27350
+ if (pathname.startsWith("/api/cicd/")) {
27351
+ handleCicdProxyUpgrade(db, workspacePath, req, socket, head);
27352
+ return;
27353
+ }
27354
+ socket.destroy();
27355
+ });
27022
27356
  wss.on("connection", async (ws, req) => {
27357
+ attachHeartbeat(ws);
27023
27358
  const url = new URL(req.url ?? "", "http://localhost");
27024
27359
  const projectId = Number(url.searchParams.get("projectId"));
27025
27360
  const userId = Number(url.searchParams.get("userId"));
@@ -27263,7 +27598,7 @@ function workspaceRoutes(_db, config, port, host, workspacePath) {
27263
27598
  }
27264
27599
 
27265
27600
  // ../server/src/lib/config.ts
27266
- import { existsSync as existsSync23, readFileSync as readFileSync18, writeFileSync as writeFileSync14 } from "fs";
27601
+ import { existsSync as existsSync23, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
27267
27602
  import { join as join25 } from "path";
27268
27603
  var configSchema = external_exports.object({
27269
27604
  port: external_exports.number().int().min(1).max(65535).optional(),
@@ -27281,7 +27616,7 @@ function loadConfig(dataDir) {
27281
27616
  }
27282
27617
  }
27283
27618
  function saveConfig(dataDir, config) {
27284
- writeFileSync14(join25(dataDir, "config.json"), JSON.stringify(config, null, 2));
27619
+ writeFileSync15(join25(dataDir, "config.json"), JSON.stringify(config, null, 2));
27285
27620
  }
27286
27621
 
27287
27622
  // ../server/src/lib/logger.ts