@veolab/discoverylab 1.4.1 → 1.4.2

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.
@@ -8707,11 +8707,11 @@ var require_mime_types = __commonJS({
8707
8707
  import { Hono } from "hono";
8708
8708
  import { cors } from "hono/cors";
8709
8709
  import { serve } from "@hono/node-server";
8710
- import { readFileSync as readFileSync2, existsSync as existsSync5, statSync as statSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, copyFileSync, cpSync, mkdtempSync, rmSync as rmSync2 } from "fs";
8710
+ import { readFileSync as readFileSync2, existsSync as existsSync5, statSync as statSync3, readdirSync as readdirSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, copyFileSync, cpSync, mkdtempSync, rmSync as rmSync2 } from "fs";
8711
8711
  import { exec, execSync as execSync2, spawn } from "child_process";
8712
8712
  import { createConnection } from "net";
8713
8713
  import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
8714
- import { join as join5, dirname as dirname2, basename as basename3 } from "path";
8714
+ import { join as join6, dirname as dirname2, basename as basename3 } from "path";
8715
8715
  import { fileURLToPath } from "url";
8716
8716
  import { eq, desc, and, inArray } from "drizzle-orm";
8717
8717
 
@@ -9318,7 +9318,7 @@ function attachPlaywrightNetworkCapture(page, options) {
9318
9318
 
9319
9319
  // src/core/export/pipeline.ts
9320
9320
  var import_mime_types = __toESM(require_mime_types(), 1);
9321
- import { existsSync as existsSync3 } from "fs";
9321
+ import { existsSync as existsSync3, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
9322
9322
  import { join as join3, basename as basename2, extname as extname2 } from "path";
9323
9323
  var adapters = /* @__PURE__ */ new Map();
9324
9324
  function registerAdapter(adapter) {
@@ -9380,16 +9380,15 @@ async function prepareAsset(asset, ctx) {
9380
9380
  }
9381
9381
  function resolveVideoFile(videoPath) {
9382
9382
  if (!existsSync3(videoPath)) return null;
9383
- const { statSync: statSync3, readdirSync: readdirSync3 } = __require("fs");
9384
- const stat = statSync3(videoPath);
9383
+ const stat = statSync2(videoPath);
9385
9384
  if (stat.isFile()) return videoPath;
9386
9385
  const videoExts = [".mp4", ".mov", ".webm", ".avi", ".mkv"];
9387
9386
  const searchDirs = [join3(videoPath, "video"), videoPath];
9388
9387
  for (const dir of searchDirs) {
9389
9388
  if (!existsSync3(dir)) continue;
9390
- const dirStat = statSync3(dir);
9389
+ const dirStat = statSync2(dir);
9391
9390
  if (!dirStat.isDirectory()) continue;
9392
- const files = readdirSync3(dir);
9391
+ const files = readdirSync2(dir);
9393
9392
  for (const file of files) {
9394
9393
  if (videoExts.some((ext) => file.toLowerCase().endsWith(ext))) {
9395
9394
  return join3(dir, file);
@@ -9493,6 +9492,8 @@ async function executeBatchExport(manifest, dataProvider, onProgress) {
9493
9492
  }
9494
9493
 
9495
9494
  // src/core/export/adapters/notion.ts
9495
+ import { readdirSync as readdirSync3 } from "fs";
9496
+ import { join as join4 } from "path";
9496
9497
  var SUPPORTED_TYPES = [
9497
9498
  "frames",
9498
9499
  // Images upload directly
@@ -9561,11 +9562,9 @@ var notionAdapter = {
9561
9562
  if (!asset.filePath) continue;
9562
9563
  switch (asset.type) {
9563
9564
  case "frames": {
9564
- const { readdirSync: readdirSync3 } = __require("fs");
9565
- const { join: join6 } = __require("path");
9566
9565
  try {
9567
9566
  const files = readdirSync3(asset.filePath);
9568
- const imageFiles = files.filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f)).sort().map((f) => join6(asset.filePath, f));
9567
+ const imageFiles = files.filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f)).sort().map((f) => join4(asset.filePath, f));
9569
9568
  const maxFrames = asset.metadata?.maxFrames || 20;
9570
9569
  screenshots.push(...imageFiles.slice(0, maxFrames));
9571
9570
  } catch {
@@ -9630,7 +9629,7 @@ var notionAdapter = {
9630
9629
  // src/core/testing/playwrightRecorder.ts
9631
9630
  import { chromium } from "playwright";
9632
9631
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
9633
- import { join as join4 } from "path";
9632
+ import { join as join5 } from "path";
9634
9633
  import { homedir as homedir2 } from "os";
9635
9634
  import { EventEmitter } from "events";
9636
9635
  var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
@@ -9673,8 +9672,8 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
9673
9672
  throw new Error("Recording already in progress");
9674
9673
  }
9675
9674
  const sessionId = `rec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
9676
- const baseDir = join4(homedir2(), ".discoverylab", "recordings", sessionId);
9677
- const screenshotsDir = join4(baseDir, "screenshots");
9675
+ const baseDir = join5(homedir2(), ".discoverylab", "recordings", sessionId);
9676
+ const screenshotsDir = join5(baseDir, "screenshots");
9678
9677
  mkdirSync3(screenshotsDir, { recursive: true });
9679
9678
  this.session = {
9680
9679
  id: sessionId,
@@ -9706,7 +9705,7 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
9706
9705
  this.session.viewportMode = viewportMode;
9707
9706
  const contextOptions = {
9708
9707
  recordVideo: {
9709
- dir: join4(baseDir, "video"),
9708
+ dir: join5(baseDir, "video"),
9710
9709
  size: captureResolution
9711
9710
  }
9712
9711
  };
@@ -9994,7 +9993,7 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
9994
9993
  this.actionCounter++;
9995
9994
  const actionId = `action_${this.actionCounter.toString().padStart(3, "0")}`;
9996
9995
  const screenshotName = `${actionId}_${actionData.type}.png`;
9997
- const screenshotPath = join4(this.session.screenshotsDir, screenshotName);
9996
+ const screenshotPath = join5(this.session.screenshotsDir, screenshotName);
9998
9997
  try {
9999
9998
  await this.page.screenshot({
10000
9999
  path: screenshotPath,
@@ -10069,10 +10068,10 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
10069
10068
  this.session.status = "stopped";
10070
10069
  this.session.endedAt = Date.now();
10071
10070
  const specCode = this.generateSpecCode();
10072
- const specPath = join4(this.session.screenshotsDir, "..", "test.spec.ts");
10071
+ const specPath = join5(this.session.screenshotsDir, "..", "test.spec.ts");
10073
10072
  writeFileSync3(specPath, specCode);
10074
10073
  this.session.specPath = specPath;
10075
- const metadataPath = join4(this.session.screenshotsDir, "..", "session.json");
10074
+ const metadataPath = join5(this.session.screenshotsDir, "..", "session.json");
10076
10075
  writeFileSync3(metadataPath, JSON.stringify(this.session, null, 2));
10077
10076
  try {
10078
10077
  await this.context?.close();
@@ -10159,7 +10158,7 @@ var PlaywrightRecorder = class _PlaywrightRecorder extends EventEmitter {
10159
10158
  if (!this.page || !this.session) return null;
10160
10159
  this.actionCounter++;
10161
10160
  const screenshotName = name || `manual_${this.actionCounter.toString().padStart(3, "0")}.png`;
10162
- const screenshotPath = join4(this.session.screenshotsDir, screenshotName);
10161
+ const screenshotPath = join5(this.session.screenshotsDir, screenshotName);
10163
10162
  await this.page.screenshot({ path: screenshotPath });
10164
10163
  this.emit("screenshot", screenshotPath, `manual_${this.actionCounter}`);
10165
10164
  return screenshotPath;
@@ -10283,10 +10282,10 @@ var TEST_VAR_KEY_REGEX = /^[A-Z][A-Z0-9_]{0,63}$/;
10283
10282
  var SCRIPT_PLACEHOLDER_REGEX = /\$\{([A-Z][A-Z0-9_]*)\}/g;
10284
10283
  var mobileReplayRuns = /* @__PURE__ */ new Map();
10285
10284
  function getMobileRecordingDir(recordingId) {
10286
- return join5(PROJECTS_DIR, "maestro-recordings", recordingId);
10285
+ return join6(PROJECTS_DIR, "maestro-recordings", recordingId);
10287
10286
  }
10288
10287
  function getMobileRecordingSessionPath(recordingId) {
10289
- return join5(getMobileRecordingDir(recordingId), "session.json");
10288
+ return join6(getMobileRecordingDir(recordingId), "session.json");
10290
10289
  }
10291
10290
  async function readMobileRecordingSessionData(recordingId) {
10292
10291
  const sessionPath = getMobileRecordingSessionPath(recordingId);
@@ -10314,9 +10313,9 @@ function getFirstRecordingScreenshotPath(session) {
10314
10313
  if (typeof session?.screenshotsDir !== "string" || !existsSync5(session.screenshotsDir)) {
10315
10314
  return void 0;
10316
10315
  }
10317
- const files = readdirSync2(session.screenshotsDir).filter((file) => file.toLowerCase().endsWith(".png")).sort();
10316
+ const files = readdirSync4(session.screenshotsDir).filter((file) => file.toLowerCase().endsWith(".png")).sort();
10318
10317
  if (files.length === 0) return void 0;
10319
- return join5(session.screenshotsDir, files[0]);
10318
+ return join6(session.screenshotsDir, files[0]);
10320
10319
  }
10321
10320
  function buildDefaultESVPNetworkProfile(session) {
10322
10321
  return buildAppLabNetworkProfile(
@@ -11382,15 +11381,15 @@ app.get("/setup", async (c) => {
11382
11381
  app.get("/", (c) => {
11383
11382
  const cwd = process.cwd();
11384
11383
  const possiblePaths = [
11385
- join5(__dirname, "index.html"),
11384
+ join6(__dirname, "index.html"),
11386
11385
  // Production: bundled (dist/index.html alongside server)
11387
- join5(__dirname, "..", "web", "index.html"),
11386
+ join6(__dirname, "..", "web", "index.html"),
11388
11387
  // Parent/web
11389
- join5(__dirname, "..", "..", "src", "web", "index.html"),
11388
+ join6(__dirname, "..", "..", "src", "web", "index.html"),
11390
11389
  // Two levels up/src/web
11391
- join5(cwd, "src", "web", "index.html"),
11390
+ join6(cwd, "src", "web", "index.html"),
11392
11391
  // Development: running from project root
11393
- join5(cwd, "dist", "index.html")
11392
+ join6(cwd, "dist", "index.html")
11394
11393
  // Production: running from project root
11395
11394
  ];
11396
11395
  for (const path of possiblePaths) {
@@ -11439,14 +11438,14 @@ function resolveVideoPath(videoPath) {
11439
11438
  if (!videoPath) return null;
11440
11439
  try {
11441
11440
  if (!existsSync5(videoPath)) return videoPath;
11442
- if (!statSync2(videoPath).isDirectory()) return videoPath;
11443
- const videoDir = join5(videoPath, "video");
11444
- if (existsSync5(videoDir) && statSync2(videoDir).isDirectory()) {
11445
- const videoFiles = readdirSync2(videoDir).filter((f) => /\.(mp4|mov|webm)$/i.test(f));
11446
- if (videoFiles.length > 0) return join5(videoDir, videoFiles[0]);
11447
- }
11448
- const directFiles = readdirSync2(videoPath).filter((f) => /\.(mp4|mov|webm)$/i.test(f));
11449
- if (directFiles.length > 0) return join5(videoPath, directFiles[0]);
11441
+ if (!statSync3(videoPath).isDirectory()) return videoPath;
11442
+ const videoDir = join6(videoPath, "video");
11443
+ if (existsSync5(videoDir) && statSync3(videoDir).isDirectory()) {
11444
+ const videoFiles = readdirSync4(videoDir).filter((f) => /\.(mp4|mov|webm)$/i.test(f));
11445
+ if (videoFiles.length > 0) return join6(videoDir, videoFiles[0]);
11446
+ }
11447
+ const directFiles = readdirSync4(videoPath).filter((f) => /\.(mp4|mov|webm)$/i.test(f));
11448
+ if (directFiles.length > 0) return join6(videoPath, directFiles[0]);
11450
11449
  return videoPath;
11451
11450
  } catch {
11452
11451
  return videoPath;
@@ -11455,7 +11454,7 @@ function resolveVideoPath(videoPath) {
11455
11454
  function resolveRecordingBaseDir(videoPath) {
11456
11455
  if (!videoPath || !existsSync5(videoPath)) return null;
11457
11456
  try {
11458
- if (statSync2(videoPath).isDirectory()) {
11457
+ if (statSync3(videoPath).isDirectory()) {
11459
11458
  return videoPath;
11460
11459
  }
11461
11460
  const parentDir = dirname2(videoPath);
@@ -11502,7 +11501,7 @@ async function maybeRepairMobileProjectThumbnail(project) {
11502
11501
  if (!recordingBaseDir || !existsSync5(recordingBaseDir)) {
11503
11502
  return project;
11504
11503
  }
11505
- const sessionPath = join5(recordingBaseDir, "session.json");
11504
+ const sessionPath = join6(recordingBaseDir, "session.json");
11506
11505
  if (!existsSync5(sessionPath)) {
11507
11506
  return project;
11508
11507
  }
@@ -11610,7 +11609,7 @@ app.get("/api/projects/:id", async (c) => {
11610
11609
  let networkCapture = null;
11611
11610
  let esvp = null;
11612
11611
  if (recordingBaseDir && existsSync6(recordingBaseDir)) {
11613
- const sessionPath = join5(recordingBaseDir, "session.json");
11612
+ const sessionPath = join6(recordingBaseDir, "session.json");
11614
11613
  if (existsSync6(sessionPath)) {
11615
11614
  try {
11616
11615
  const sessionData = JSON.parse(readFileSync3(sessionPath, "utf8"));
@@ -11675,13 +11674,13 @@ app.post("/api/projects", async (c) => {
11675
11674
  app.post("/api/projects/sync-orphans", async (c) => {
11676
11675
  try {
11677
11676
  const db = getDatabase();
11678
- const { readdirSync: readdirSync3, statSync: statSync3, existsSync: existsSync6 } = await import("fs");
11677
+ const { readdirSync: readdirSync5, statSync: statSync4, existsSync: existsSync6 } = await import("fs");
11679
11678
  if (!existsSync6(PROJECTS_DIR)) {
11680
11679
  return c.json({ synced: 0, projects: [], message: "Projects directory does not exist" });
11681
11680
  }
11682
- const dirs = readdirSync3(PROJECTS_DIR).filter((d) => {
11683
- const p = join5(PROJECTS_DIR, d);
11684
- return statSync3(p).isDirectory() && !d.startsWith(".");
11681
+ const dirs = readdirSync5(PROJECTS_DIR).filter((d) => {
11682
+ const p = join6(PROJECTS_DIR, d);
11683
+ return statSync4(p).isDirectory() && !d.startsWith(".");
11685
11684
  });
11686
11685
  const existing = await db.select({ id: projects.id }).from(projects);
11687
11686
  const existingIds = new Set(existing.map((p) => p.id));
@@ -11689,7 +11688,7 @@ app.post("/api/projects/sync-orphans", async (c) => {
11689
11688
  const orphans = dirs.filter((d) => !existingIds.has(d) && !specialDirs.has(d));
11690
11689
  const created = [];
11691
11690
  for (const id of orphans) {
11692
- const dirPath = join5(PROJECTS_DIR, id);
11691
+ const dirPath = join6(PROJECTS_DIR, id);
11693
11692
  const platform = id.includes("_web_") ? "web" : "mobile";
11694
11693
  const now = /* @__PURE__ */ new Date();
11695
11694
  await db.insert(projects).values({
@@ -12269,15 +12268,15 @@ app.delete("/api/projects/:id", async (c) => {
12269
12268
  const db = getDatabase();
12270
12269
  const id = c.req.param("id");
12271
12270
  const { rmSync: rmSync3 } = await import("fs");
12272
- const { join: join6 } = await import("path");
12271
+ const { join: join7 } = await import("path");
12273
12272
  await db.delete(frames).where(eq(frames.projectId, id));
12274
12273
  await db.delete(projects).where(eq(projects.id, id));
12275
12274
  await deleteTestVariablesForOwner("project", id);
12276
12275
  await deleteTestVariablesForOwner("mobile-recording", id);
12277
12276
  await deleteTestVariablesForOwner("web-recording", id);
12278
- const mobileRecordingDir = join6(PROJECTS_DIR, "maestro-recordings", id);
12279
- const webRecordingDir = join6(PROJECTS_DIR, "web-recordings", id);
12280
- const projectDir = join6(PROJECTS_DIR, id);
12277
+ const mobileRecordingDir = join7(PROJECTS_DIR, "maestro-recordings", id);
12278
+ const webRecordingDir = join7(PROJECTS_DIR, "web-recordings", id);
12279
+ const projectDir = join7(PROJECTS_DIR, id);
12281
12280
  if (existsSync5(mobileRecordingDir)) {
12282
12281
  rmSync3(mobileRecordingDir, { recursive: true, force: true });
12283
12282
  console.log(`[Delete] Removed mobile recording: ${id}`);
@@ -12291,12 +12290,12 @@ app.delete("/api/projects/:id", async (c) => {
12291
12290
  console.log(`[Delete] Removed project directory: ${id}`);
12292
12291
  }
12293
12292
  const { EXPORTS_DIR: exportsDir, FRAMES_DIR: framesDir } = await import("./db-745LC5YC.js");
12294
- const projectExportsDir = join6(exportsDir, id);
12293
+ const projectExportsDir = join7(exportsDir, id);
12295
12294
  if (existsSync5(projectExportsDir)) {
12296
12295
  rmSync3(projectExportsDir, { recursive: true, force: true });
12297
12296
  console.log(`[Delete] Removed exports directory: ${id}`);
12298
12297
  }
12299
- const projectFramesDir = join6(framesDir, id);
12298
+ const projectFramesDir = join7(framesDir, id);
12300
12299
  if (existsSync5(projectFramesDir)) {
12301
12300
  rmSync3(projectFramesDir, { recursive: true, force: true });
12302
12301
  console.log(`[Delete] Removed frames directory: ${id}`);
@@ -12315,19 +12314,19 @@ app.post("/api/upload", async (c) => {
12315
12314
  return c.json({ error: "No file provided" }, 400);
12316
12315
  }
12317
12316
  const db = getDatabase();
12318
- const { mkdirSync: mkdirSync5, writeFileSync: writeFileSync5, existsSync: fsExists, readdirSync: readdirSync3 } = await import("fs");
12317
+ const { mkdirSync: mkdirSync5, writeFileSync: writeFileSync5, existsSync: fsExists, readdirSync: readdirSync5 } = await import("fs");
12319
12318
  const { exec: exec2 } = await import("child_process");
12320
12319
  const { promisify } = await import("util");
12321
12320
  const execAsync = promisify(exec2);
12322
12321
  const { PROJECTS_DIR: PROJECTS_DIR2, FRAMES_DIR } = await import("./db-745LC5YC.js");
12323
12322
  const id = crypto.randomUUID();
12324
12323
  const now = /* @__PURE__ */ new Date();
12325
- const projectDir = join5(PROJECTS_DIR2, id);
12324
+ const projectDir = join6(PROJECTS_DIR2, id);
12326
12325
  if (!fsExists(projectDir)) {
12327
12326
  mkdirSync5(projectDir, { recursive: true });
12328
12327
  }
12329
12328
  const fileName = file.name;
12330
- const filePath = join5(projectDir, fileName);
12329
+ const filePath = join6(projectDir, fileName);
12331
12330
  const arrayBuffer = await file.arrayBuffer();
12332
12331
  writeFileSync5(filePath, Buffer.from(arrayBuffer));
12333
12332
  let platform = null;
@@ -12348,17 +12347,17 @@ app.post("/api/upload", async (c) => {
12348
12347
  let framePaths = [];
12349
12348
  try {
12350
12349
  if (isVideo) {
12351
- const projectFramesDir = join5(FRAMES_DIR, id);
12350
+ const projectFramesDir = join6(FRAMES_DIR, id);
12352
12351
  if (!fsExists(projectFramesDir)) {
12353
12352
  mkdirSync5(projectFramesDir, { recursive: true });
12354
12353
  }
12355
- const framePattern = join5(projectFramesDir, "frame_%04d.png");
12354
+ const framePattern = join6(projectFramesDir, "frame_%04d.png");
12356
12355
  try {
12357
12356
  await execAsync(`ffmpeg -i "${filePath}" -vf "fps=1" -frames:v 15 "${framePattern}" -y 2>/dev/null`);
12358
12357
  } catch {
12359
12358
  }
12360
12359
  if (fsExists(projectFramesDir)) {
12361
- framePaths = readdirSync3(projectFramesDir).filter((f) => f.endsWith(".png")).sort().map((f) => join5(projectFramesDir, f));
12360
+ framePaths = readdirSync5(projectFramesDir).filter((f) => f.endsWith(".png")).sort().map((f) => join6(projectFramesDir, f));
12362
12361
  }
12363
12362
  if (framePaths.length > 0) {
12364
12363
  const { bestFrame, analyses } = await selectBestFrame(framePaths);
@@ -12417,11 +12416,11 @@ app.post("/api/capture/start", async (c) => {
12417
12416
  const db = getDatabase();
12418
12417
  const id = crypto.randomUUID();
12419
12418
  const now = /* @__PURE__ */ new Date();
12420
- const projectDir = join5(PROJECTS_DIR2, id);
12419
+ const projectDir = join6(PROJECTS_DIR2, id);
12421
12420
  if (!fsExists(projectDir)) {
12422
12421
  mkdirSync5(projectDir, { recursive: true });
12423
12422
  }
12424
- const screenshotPath = join5(projectDir, "capture.png");
12423
+ const screenshotPath = join6(projectDir, "capture.png");
12425
12424
  const captureType = body.sourceType || body.type || "screen";
12426
12425
  const sourceId = typeof body.sourceId === "string" ? body.sourceId.trim() : "";
12427
12426
  const osPlatform = process.platform === "darwin" ? "macos" : process.platform;
@@ -12585,10 +12584,10 @@ app.post("/api/capture/mobile/start", async (c) => {
12585
12584
  const { mkdirSync: mkdirSync5 } = await import("fs");
12586
12585
  const execAsync = promisify(exec2);
12587
12586
  const projectId = `capture_${Date.now()}`;
12588
- const projectDir = join5(PROJECTS_DIR, projectId);
12589
- const screenshotsDir = join5(projectDir, "screenshots");
12587
+ const projectDir = join6(PROJECTS_DIR, projectId);
12588
+ const screenshotsDir = join6(projectDir, "screenshots");
12590
12589
  mkdirSync5(screenshotsDir, { recursive: true });
12591
- const videoPath = join5(projectDir, "recording.mp4");
12590
+ const videoPath = join6(projectDir, "recording.mp4");
12592
12591
  const initialAppId = getForegroundAppIdForPlatform(platform, targetDeviceId);
12593
12592
  let recordProcess = null;
12594
12593
  if (platform === "ios") {
@@ -12705,8 +12704,8 @@ app.post("/api/capture/web/start", async (c) => {
12705
12704
  const vpRes = CAPTURE_RESOLUTIONS[vpResKey || captureResKey || "1080"] || captureRes;
12706
12705
  const { mkdirSync: mkdirSync5 } = await import("fs");
12707
12706
  const projectId = `capture_web_${Date.now()}`;
12708
- const projectDir = join5(PROJECTS_DIR, projectId);
12709
- const screenshotsDir = join5(projectDir, "screenshots");
12707
+ const projectDir = join6(PROJECTS_DIR, projectId);
12708
+ const screenshotsDir = join6(projectDir, "screenshots");
12710
12709
  mkdirSync5(screenshotsDir, { recursive: true });
12711
12710
  let browser = null;
12712
12711
  let page = null;
@@ -12802,7 +12801,7 @@ app.post("/api/capture/web/stop", async (c) => {
12802
12801
  const projectId = session.projectId;
12803
12802
  const endedAt = Date.now();
12804
12803
  const { writeFileSync: writeFileSync5 } = await import("fs");
12805
- const projectDir = join5(PROJECTS_DIR, projectId);
12804
+ const projectDir = join6(PROJECTS_DIR, projectId);
12806
12805
  let finalPageUrl = session.url || null;
12807
12806
  let faviconCandidates = [];
12808
12807
  if (session.page) {
@@ -12833,7 +12832,7 @@ app.post("/api/capture/web/stop", async (c) => {
12833
12832
  await new Promise((resolve) => setTimeout(resolve, 1e3));
12834
12833
  session.networkDetach?.();
12835
12834
  if (projectId) {
12836
- const sessionPath = join5(projectDir, "session.json");
12835
+ const sessionPath = join6(projectDir, "session.json");
12837
12836
  writeFileSync5(sessionPath, JSON.stringify({
12838
12837
  id: projectId,
12839
12838
  name: `Web Capture - ${new Date(session.startTime).toISOString()}`,
@@ -12901,8 +12900,8 @@ async function analyzeProjectInBackground(projectId) {
12901
12900
  });
12902
12901
  return;
12903
12902
  }
12904
- const { readdirSync: readdirSync3 } = await import("fs");
12905
- const files = readdirSync3(projectDir);
12903
+ const { readdirSync: readdirSync5 } = await import("fs");
12904
+ const files = readdirSync5(projectDir);
12906
12905
  const videoFile = files.find((f) => f.endsWith(".mp4") || f.endsWith(".webm") || f.endsWith(".mov"));
12907
12906
  if (!videoFile) {
12908
12907
  broadcastProgress("save", "done", "Capture saved without video analysis");
@@ -12922,25 +12921,25 @@ async function analyzeProjectInBackground(projectId) {
12922
12921
  });
12923
12922
  return;
12924
12923
  }
12925
- const videoPath = join5(projectDir, videoFile);
12924
+ const videoPath = join6(projectDir, videoFile);
12926
12925
  const { exec: exec2 } = await import("child_process");
12927
12926
  const { promisify } = await import("util");
12928
12927
  const { mkdirSync: mkdirSync5 } = await import("fs");
12929
12928
  const execAsync = promisify(exec2);
12930
- const framesDir = join5(projectDir, "frames");
12929
+ const framesDir = join6(projectDir, "frames");
12931
12930
  mkdirSync5(framesDir, { recursive: true });
12932
12931
  broadcastProgress("extract", "running", "Extracting frames from video...");
12933
12932
  await execAsync(`ffmpeg -i "${videoPath}" -vf "fps=1" -q:v 2 "${framesDir}/frame_%04d.jpg" -y`);
12934
12933
  broadcastProgress("extract", "done", "Frames extracted");
12935
12934
  const { isBlankFrame } = await import("./frames-RCNLSDD6.js");
12936
12935
  const { unlinkSync } = await import("fs");
12937
- const allFrameFiles = readdirSync3(framesDir).filter((f) => f.endsWith(".jpg")).sort();
12936
+ const allFrameFiles = readdirSync5(framesDir).filter((f) => f.endsWith(".jpg")).sort();
12938
12937
  const frameFiles = [];
12939
12938
  for (const f of allFrameFiles) {
12940
- const { isBlank } = isBlankFrame(join5(framesDir, f));
12939
+ const { isBlank } = isBlankFrame(join6(framesDir, f));
12941
12940
  if (isBlank) {
12942
12941
  try {
12943
- unlinkSync(join5(framesDir, f));
12942
+ unlinkSync(join6(framesDir, f));
12944
12943
  } catch {
12945
12944
  }
12946
12945
  } else {
@@ -12977,7 +12976,7 @@ async function analyzeProjectInBackground(projectId) {
12977
12976
  const ocrFrames = frameFiles.slice(0, 10);
12978
12977
  broadcastProgress("ocr", "running", `Processing 0/${ocrFrames.length} frames...`, void 0, 0, ocrFrames.length);
12979
12978
  for (const [index, frameFile] of ocrFrames.entries()) {
12980
- const framePath = join5(framesDir, frameFile);
12979
+ const framePath = join6(framesDir, frameFile);
12981
12980
  const ocrResult = await recognizeText(framePath);
12982
12981
  if (ocrResult.success && ocrResult.text) {
12983
12982
  allOcrText += ocrResult.text + "\n\n";
@@ -13042,7 +13041,7 @@ async function analyzeProjectInBackground(projectId) {
13042
13041
  } else {
13043
13042
  broadcastProgress("summary", "skipped", "Skipped \u2014 no text to analyze");
13044
13043
  }
13045
- const extractedThumbnailPath = join5(framesDir, bestFrame);
13044
+ const extractedThumbnailPath = join6(framesDir, bestFrame);
13046
13045
  const resolvedThumbnailPath = project.thumbnailPath && existsSync5(project.thumbnailPath) ? project.thumbnailPath : existsSync5(extractedThumbnailPath) ? extractedThumbnailPath : null;
13047
13046
  const ocrEngine = ocrEngines.has("vision") ? "vision" : ocrEngines.has("tesseract") ? "tesseract" : null;
13048
13047
  const ocrConfidence = ocrConfidences.length > 0 ? ocrConfidences.reduce((sum, value) => sum + value, 0) / ocrConfidences.length : null;
@@ -13113,14 +13112,14 @@ app.post("/api/analyze/:id", async (c) => {
13113
13112
  try {
13114
13113
  const { exec: exec2 } = await import("child_process");
13115
13114
  const { promisify } = await import("util");
13116
- const { mkdirSync: mkdirSync5, readdirSync: readdirSync3 } = await import("fs");
13115
+ const { mkdirSync: mkdirSync5, readdirSync: readdirSync5 } = await import("fs");
13117
13116
  const execAsync = promisify(exec2);
13118
13117
  const { FRAMES_DIR } = await import("./db-745LC5YC.js");
13119
- const projectFramesDir = join5(FRAMES_DIR, id);
13118
+ const projectFramesDir = join6(FRAMES_DIR, id);
13120
13119
  if (!existsSync5(projectFramesDir)) {
13121
13120
  mkdirSync5(projectFramesDir, { recursive: true });
13122
13121
  }
13123
- const framePattern = join5(projectFramesDir, "frame_%04d.png");
13122
+ const framePattern = join6(projectFramesDir, "frame_%04d.png");
13124
13123
  try {
13125
13124
  const { stderr } = await execAsync(`ffmpeg -i "${filePath}" -vf "fps=1" -frames:v 10 "${framePattern}" -y 2>&1`);
13126
13125
  if (stderr) console.log("[FFmpeg] Output:", stderr.slice(0, 500));
@@ -13133,7 +13132,7 @@ app.post("/api/analyze/:id", async (c) => {
13133
13132
  console.error("[FFmpeg] Keyframe fallback also failed:", fallbackError);
13134
13133
  }
13135
13134
  }
13136
- const frameFiles = readdirSync3(projectFramesDir).filter((f) => f.endsWith(".png")).sort().map((f) => join5(projectFramesDir, f));
13135
+ const frameFiles = readdirSync5(projectFramesDir).filter((f) => f.endsWith(".png")).sort().map((f) => join6(projectFramesDir, f));
13137
13136
  frameCount = frameFiles.length;
13138
13137
  console.log(`[FFmpeg] Extracted ${frameCount} frames from video`);
13139
13138
  if (frameFiles.length > 0) {
@@ -13244,9 +13243,9 @@ app.get("/api/grid/backgrounds", async (c) => {
13244
13243
  try {
13245
13244
  const { getAvailableBackgrounds, PRESET_GRADIENTS, PRESET_SOLID_COLORS } = await import("./gridCompositor-VUWBZXYL.js");
13246
13245
  const possibleBgDirs = [
13247
- join5(__dirname, "..", "assets", "backgrounds"),
13248
- join5(__dirname, "..", "..", "assets", "backgrounds"),
13249
- join5(process.cwd(), "assets", "backgrounds")
13246
+ join6(__dirname, "..", "assets", "backgrounds"),
13247
+ join6(__dirname, "..", "..", "assets", "backgrounds"),
13248
+ join6(process.cwd(), "assets", "backgrounds")
13250
13249
  ];
13251
13250
  const backgroundsDir = possibleBgDirs.find((d) => existsSync5(d)) || possibleBgDirs[0];
13252
13251
  const imageBackgrounds = getAvailableBackgrounds(backgroundsDir);
@@ -13273,21 +13272,21 @@ app.post("/api/grid/preview", async (c) => {
13273
13272
  const { composeGrid, recommendLayout } = await import("./gridCompositor-VUWBZXYL.js");
13274
13273
  const { EXPORTS_DIR: EXPORTS_DIR2 } = await import("./db-745LC5YC.js");
13275
13274
  const { mkdirSync: mkdirSync5 } = await import("fs");
13276
- const previewDir = join5(EXPORTS_DIR2, "grid-previews");
13275
+ const previewDir = join6(EXPORTS_DIR2, "grid-previews");
13277
13276
  if (!existsSync5(previewDir)) {
13278
13277
  mkdirSync5(previewDir, { recursive: true });
13279
13278
  }
13280
13279
  const previewId = crypto.randomUUID();
13281
- const outputPath = join5(previewDir, `preview-${previewId}.png`);
13280
+ const outputPath = join6(previewDir, `preview-${previewId}.png`);
13282
13281
  const gridConfig = {
13283
13282
  ...config,
13284
13283
  layout: config?.layout || recommendLayout(images.length, config?.aspectRatio || "9:16")
13285
13284
  };
13286
13285
  if (gridConfig.background?.type === "image" && gridConfig.background?.imageId) {
13287
13286
  const possibleBgDirs = [
13288
- join5(__dirname, "..", "assets", "backgrounds"),
13289
- join5(__dirname, "..", "..", "assets", "backgrounds"),
13290
- join5(process.cwd(), "assets", "backgrounds")
13287
+ join6(__dirname, "..", "assets", "backgrounds"),
13288
+ join6(__dirname, "..", "..", "assets", "backgrounds"),
13289
+ join6(process.cwd(), "assets", "backgrounds")
13291
13290
  ];
13292
13291
  const backgroundsDir = possibleBgDirs.find((d) => existsSync5(d)) || possibleBgDirs[0];
13293
13292
  const { getAvailableBackgrounds } = await import("./gridCompositor-VUWBZXYL.js");
@@ -13430,13 +13429,13 @@ app.post("/api/grid/export", async (c) => {
13430
13429
  const { composeGrid, recommendLayout } = await import("./gridCompositor-VUWBZXYL.js");
13431
13430
  const { EXPORTS_DIR: EXPORTS_DIR2 } = await import("./db-745LC5YC.js");
13432
13431
  const { mkdirSync: mkdirSync5 } = await import("fs");
13433
- const exportDir = projectId ? join5(EXPORTS_DIR2, projectId) : join5(EXPORTS_DIR2, "grids");
13432
+ const exportDir = projectId ? join6(EXPORTS_DIR2, projectId) : join6(EXPORTS_DIR2, "grids");
13434
13433
  if (!existsSync5(exportDir)) {
13435
13434
  mkdirSync5(exportDir, { recursive: true });
13436
13435
  }
13437
13436
  const timestamp = Date.now();
13438
13437
  const exportFilename = filename || `grid-${timestamp}.png`;
13439
- const outputPath = join5(exportDir, exportFilename);
13438
+ const outputPath = join6(exportDir, exportFilename);
13440
13439
  const gridConfig = {
13441
13440
  ...config,
13442
13441
  layout: config?.layout || recommendLayout(images.length, config?.aspectRatio || "9:16"),
@@ -13445,9 +13444,9 @@ app.post("/api/grid/export", async (c) => {
13445
13444
  };
13446
13445
  if (gridConfig.background?.type === "image" && gridConfig.background?.imageId) {
13447
13446
  const possibleBgDirs = [
13448
- join5(__dirname, "..", "assets", "backgrounds"),
13449
- join5(__dirname, "..", "..", "assets", "backgrounds"),
13450
- join5(process.cwd(), "assets", "backgrounds")
13447
+ join6(__dirname, "..", "assets", "backgrounds"),
13448
+ join6(__dirname, "..", "..", "assets", "backgrounds"),
13449
+ join6(process.cwd(), "assets", "backgrounds")
13451
13450
  ];
13452
13451
  const backgroundsDir = possibleBgDirs.find((d) => existsSync5(d)) || possibleBgDirs[0];
13453
13452
  const { getAvailableBackgrounds } = await import("./gridCompositor-VUWBZXYL.js");
@@ -13514,11 +13513,11 @@ app.post("/api/grid/infographic", async (c) => {
13514
13513
  }
13515
13514
  const { composeInfographic } = await import("./gridCompositor-VUWBZXYL.js");
13516
13515
  const { EXPORTS_DIR: EXPORTS_DIR2 } = await import("./db-745LC5YC.js");
13517
- const exportDir = projectId ? join5(EXPORTS_DIR2, projectId) : join5(EXPORTS_DIR2, "grids");
13516
+ const exportDir = projectId ? join6(EXPORTS_DIR2, projectId) : join6(EXPORTS_DIR2, "grids");
13518
13517
  if (!existsSync5(exportDir)) {
13519
13518
  mkdirSync4(exportDir, { recursive: true });
13520
13519
  }
13521
- const outputPath = join5(exportDir, `infographic-${Date.now()}.png`);
13520
+ const outputPath = join6(exportDir, `infographic-${Date.now()}.png`);
13522
13521
  const result = await composeInfographic(
13523
13522
  images.map((img, i) => ({
13524
13523
  imagePath: img.path || img.imagePath,
@@ -13620,14 +13619,14 @@ app.get("/api/grid/project-frames/:id", async (c) => {
13620
13619
  }
13621
13620
  const project = result[0];
13622
13621
  const { FRAMES_DIR } = await import("./db-745LC5YC.js");
13623
- const { readdirSync: readdirSync3 } = await import("fs");
13622
+ const { readdirSync: readdirSync5 } = await import("fs");
13624
13623
  const availableFrames = [];
13625
13624
  const { isBlankFrame } = await import("./frames-RCNLSDD6.js");
13626
- const projectFramesDir = join5(FRAMES_DIR, id);
13625
+ const projectFramesDir = join6(FRAMES_DIR, id);
13627
13626
  if (existsSync5(projectFramesDir)) {
13628
- const frameFiles = readdirSync3(projectFramesDir).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
13627
+ const frameFiles = readdirSync5(projectFramesDir).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
13629
13628
  for (const f of frameFiles) {
13630
- const framePath = join5(projectFramesDir, f);
13629
+ const framePath = join6(projectFramesDir, f);
13631
13630
  if (isBlankFrame(framePath).isBlank) continue;
13632
13631
  availableFrames.push({
13633
13632
  path: framePath,
@@ -13636,11 +13635,11 @@ app.get("/api/grid/project-frames/:id", async (c) => {
13636
13635
  });
13637
13636
  }
13638
13637
  }
13639
- const projectFramesDir2 = join5(PROJECTS_DIR, id, "frames");
13638
+ const projectFramesDir2 = join6(PROJECTS_DIR, id, "frames");
13640
13639
  if (existsSync5(projectFramesDir2)) {
13641
- const frameFiles = readdirSync3(projectFramesDir2).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
13640
+ const frameFiles = readdirSync5(projectFramesDir2).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
13642
13641
  for (const f of frameFiles) {
13643
- const framePath = join5(projectFramesDir2, f);
13642
+ const framePath = join6(projectFramesDir2, f);
13644
13643
  if (!availableFrames.some((frame) => frame.path === framePath)) {
13645
13644
  availableFrames.push({
13646
13645
  path: framePath,
@@ -13668,14 +13667,14 @@ app.get("/api/grid/project-frames/:id", async (c) => {
13668
13667
  });
13669
13668
  }
13670
13669
  if (project.videoPath && existsSync5(project.videoPath)) {
13671
- const { statSync: statSync3 } = await import("fs");
13672
- const stats = statSync3(project.videoPath);
13670
+ const { statSync: statSync4 } = await import("fs");
13671
+ const stats = statSync4(project.videoPath);
13673
13672
  if (stats.isDirectory()) {
13674
- const screenshotsDir = join5(project.videoPath, "screenshots");
13673
+ const screenshotsDir = join6(project.videoPath, "screenshots");
13675
13674
  if (existsSync5(screenshotsDir)) {
13676
- const screenshotFiles = readdirSync3(screenshotsDir).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
13675
+ const screenshotFiles = readdirSync5(screenshotsDir).filter((f) => /\.(png|jpg|jpeg)$/i.test(f)).sort();
13677
13676
  for (const f of screenshotFiles) {
13678
- const screenshotPath = join5(screenshotsDir, f);
13677
+ const screenshotPath = join6(screenshotsDir, f);
13679
13678
  if (!availableFrames.some((frame) => frame.path === screenshotPath)) {
13680
13679
  availableFrames.push({
13681
13680
  path: screenshotPath,
@@ -13715,7 +13714,7 @@ function writeExportText(filePath, text) {
13715
13714
  function copyPathIntoExportBundle(sourcePath, destinationPath) {
13716
13715
  if (!sourcePath || !existsSync5(sourcePath)) return false;
13717
13716
  ensureExportParentDir(destinationPath);
13718
- if (statSync2(sourcePath).isDirectory()) {
13717
+ if (statSync3(sourcePath).isDirectory()) {
13719
13718
  cpSync(sourcePath, destinationPath, { recursive: true });
13720
13719
  } else {
13721
13720
  copyFileSync(sourcePath, destinationPath);
@@ -13725,16 +13724,16 @@ function copyPathIntoExportBundle(sourcePath, destinationPath) {
13725
13724
  function looksLikeRecordingDirectory(dirPath) {
13726
13725
  if (!dirPath || !existsSync5(dirPath)) return false;
13727
13726
  try {
13728
- if (!statSync2(dirPath).isDirectory()) return false;
13727
+ if (!statSync3(dirPath).isDirectory()) return false;
13729
13728
  } catch {
13730
13729
  return false;
13731
13730
  }
13732
13731
  return [
13733
- join5(dirPath, "session.json"),
13734
- join5(dirPath, "screenshots"),
13735
- join5(dirPath, "video"),
13736
- join5(dirPath, "test.yaml"),
13737
- join5(dirPath, "test.spec.ts")
13732
+ join6(dirPath, "session.json"),
13733
+ join6(dirPath, "screenshots"),
13734
+ join6(dirPath, "video"),
13735
+ join6(dirPath, "test.yaml"),
13736
+ join6(dirPath, "test.spec.ts")
13738
13737
  ].some((candidate) => existsSync5(candidate));
13739
13738
  }
13740
13739
  function resolveProjectBundleRecordingDir(originalVideoPath, resolvedVideoPath) {
@@ -13751,13 +13750,13 @@ function resolveProjectBundleRecordingDir(originalVideoPath, resolvedVideoPath)
13751
13750
  return null;
13752
13751
  }
13753
13752
  function copyProjectExportArtifacts(sourceDir, destinationDir) {
13754
- if (!existsSync5(sourceDir) || !statSync2(sourceDir).isDirectory()) {
13753
+ if (!existsSync5(sourceDir) || !statSync3(sourceDir).isDirectory()) {
13755
13754
  return 0;
13756
13755
  }
13757
13756
  let copiedCount = 0;
13758
- for (const entry of readdirSync2(sourceDir)) {
13757
+ for (const entry of readdirSync4(sourceDir)) {
13759
13758
  if (/\.(applab|esvp)$/i.test(entry)) continue;
13760
- if (copyPathIntoExportBundle(join5(sourceDir, entry), join5(destinationDir, entry))) {
13759
+ if (copyPathIntoExportBundle(join6(sourceDir, entry), join6(destinationDir, entry))) {
13761
13760
  copiedCount += 1;
13762
13761
  }
13763
13762
  }
@@ -13881,7 +13880,7 @@ app.post("/api/export", async (c) => {
13881
13880
  }
13882
13881
  const { promisify } = await import("util");
13883
13882
  const execAsync = promisify(exec);
13884
- const exportDir = join5(EXPORTS_DIR, projectId);
13883
+ const exportDir = join6(EXPORTS_DIR, projectId);
13885
13884
  if (!existsSync5(exportDir)) {
13886
13885
  mkdirSync4(exportDir, { recursive: true });
13887
13886
  }
@@ -13889,13 +13888,13 @@ app.post("/api/export", async (c) => {
13889
13888
  let outputPath = "";
13890
13889
  let mimeType = "image/png";
13891
13890
  const resolvedVideoPath = resolveVideoPath(rawProject.videoPath);
13892
- const isVideoPathDirectory = !!rawProject.videoPath && existsSync5(rawProject.videoPath) && statSync2(rawProject.videoPath).isDirectory();
13891
+ const isVideoPathDirectory = !!rawProject.videoPath && existsSync5(rawProject.videoPath) && statSync3(rawProject.videoPath).isDirectory();
13893
13892
  if (format === "applab" || format === "esvp") {
13894
13893
  const projectFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber);
13895
13894
  const exportRecords = await db.select().from(projectExports).where(eq(projectExports.projectId, projectId)).orderBy(desc(projectExports.createdAt));
13896
13895
  const recordingBaseDir = resolveProjectBundleRecordingDir(rawProject.videoPath, resolvedVideoPath);
13897
- const exportArtifactsDir = join5(EXPORTS_DIR, projectId);
13898
- const sessionPath = recordingBaseDir ? join5(recordingBaseDir, "session.json") : null;
13896
+ const exportArtifactsDir = join6(EXPORTS_DIR, projectId);
13897
+ const sessionPath = recordingBaseDir ? join6(recordingBaseDir, "session.json") : null;
13899
13898
  let sessionData = null;
13900
13899
  let networkEntries = [];
13901
13900
  let networkCapture = null;
@@ -13911,9 +13910,9 @@ app.post("/api/export", async (c) => {
13911
13910
  sessionData = null;
13912
13911
  }
13913
13912
  }
13914
- const stagingRoot = mkdtempSync(join5(tmpdir2(), `${format}-export-`));
13913
+ const stagingRoot = mkdtempSync(join6(tmpdir2(), `${format}-export-`));
13915
13914
  const bundleFolderName = `${sanitizeProjectExportSlug(rawProject.name)}-${new Date(timestamp).toISOString().slice(0, 10)}`;
13916
- const bundleRoot = join5(stagingRoot, bundleFolderName);
13915
+ const bundleRoot = join6(stagingRoot, bundleFolderName);
13917
13916
  mkdirSync4(bundleRoot, { recursive: true });
13918
13917
  try {
13919
13918
  const summaryPath = rawProject.aiSummary ? "analysis/app-intelligence.md" : null;
@@ -13924,25 +13923,25 @@ app.post("/api/export", async (c) => {
13924
13923
  const extensionMatch = basename3(frame.imagePath).match(/(\.[^.]+)$/);
13925
13924
  const extension = extensionMatch ? extensionMatch[1] : ".png";
13926
13925
  const relativeImagePath = `frames/frame-${String(frame.frameNumber).padStart(4, "0")}${extension}`;
13927
- copyPathIntoExportBundle(frame.imagePath, join5(bundleRoot, relativeImagePath));
13926
+ copyPathIntoExportBundle(frame.imagePath, join6(bundleRoot, relativeImagePath));
13928
13927
  return {
13929
13928
  ...frame,
13930
13929
  imagePath: relativeImagePath
13931
13930
  };
13932
13931
  });
13933
13932
  const mediaFiles = [];
13934
- if (resolvedVideoPath && existsSync5(resolvedVideoPath) && !statSync2(resolvedVideoPath).isDirectory()) {
13933
+ if (resolvedVideoPath && existsSync5(resolvedVideoPath) && !statSync3(resolvedVideoPath).isDirectory()) {
13935
13934
  const relativePath = `media/${resolvedMediaName}`;
13936
- copyPathIntoExportBundle(resolvedVideoPath, join5(bundleRoot, relativePath));
13935
+ copyPathIntoExportBundle(resolvedVideoPath, join6(bundleRoot, relativePath));
13937
13936
  mediaFiles.push({ role: "primary-media", path: relativePath });
13938
13937
  }
13939
13938
  if (rawProject.thumbnailPath && existsSync5(rawProject.thumbnailPath) && thumbnailName) {
13940
13939
  const relativePath = `media/${thumbnailName}`;
13941
- copyPathIntoExportBundle(rawProject.thumbnailPath, join5(bundleRoot, relativePath));
13940
+ copyPathIntoExportBundle(rawProject.thumbnailPath, join6(bundleRoot, relativePath));
13942
13941
  mediaFiles.push({ role: "thumbnail", path: relativePath });
13943
13942
  }
13944
- const recordingIncluded = recordingBaseDir ? copyPathIntoExportBundle(recordingBaseDir, join5(bundleRoot, "recording")) : false;
13945
- const exportArtifactCount = copyProjectExportArtifacts(exportArtifactsDir, join5(bundleRoot, "exports"));
13943
+ const recordingIncluded = recordingBaseDir ? copyPathIntoExportBundle(recordingBaseDir, join6(bundleRoot, "recording")) : false;
13944
+ const exportArtifactCount = copyProjectExportArtifacts(exportArtifactsDir, join6(bundleRoot, "exports"));
13946
13945
  const esvpSessionId = resolveProjectESVPSessionId(esvp);
13947
13946
  const esvpServerUrl = resolveProjectESVPServerUrl(esvp);
13948
13947
  let esvpSnapshot = null;
@@ -13968,7 +13967,7 @@ app.post("/api/export", async (c) => {
13968
13967
  thumbnailPath: thumbnailName ? `media/${thumbnailName}` : normalizedProject.thumbnailPath,
13969
13968
  frames: bundledFrames
13970
13969
  };
13971
- writeExportJson(join5(bundleRoot, "manifest.json"), {
13970
+ writeExportJson(join6(bundleRoot, "manifest.json"), {
13972
13971
  bundleVersion: 1,
13973
13972
  packageFormat: format,
13974
13973
  exportedAt: new Date(timestamp).toISOString(),
@@ -13996,42 +13995,42 @@ app.post("/api/export", async (c) => {
13996
13995
  esvpSessionId: esvpSessionId || null
13997
13996
  }
13998
13997
  });
13999
- writeExportJson(join5(bundleRoot, "metadata", "project.json"), packagedProject);
14000
- writeExportJson(join5(bundleRoot, "metadata", "exports.json"), exportRecords);
13998
+ writeExportJson(join6(bundleRoot, "metadata", "project.json"), packagedProject);
13999
+ writeExportJson(join6(bundleRoot, "metadata", "exports.json"), exportRecords);
14001
14000
  if (sessionData) {
14002
- writeExportJson(join5(bundleRoot, "metadata", "session.json"), sessionData);
14001
+ writeExportJson(join6(bundleRoot, "metadata", "session.json"), sessionData);
14003
14002
  }
14004
14003
  if (rawProject.taskHubLinks) {
14005
- writeExportJson(join5(bundleRoot, "taskhub", "links.json"), normalizedProject.taskHubLinks);
14004
+ writeExportJson(join6(bundleRoot, "taskhub", "links.json"), normalizedProject.taskHubLinks);
14006
14005
  }
14007
14006
  if (rawProject.taskRequirements) {
14008
- writeExportJson(join5(bundleRoot, "taskhub", "requirements.json"), normalizedProject.taskRequirements);
14007
+ writeExportJson(join6(bundleRoot, "taskhub", "requirements.json"), normalizedProject.taskRequirements);
14009
14008
  }
14010
14009
  if (rawProject.taskTestMap) {
14011
- writeExportJson(join5(bundleRoot, "taskhub", "test-map.json"), normalizedProject.taskTestMap);
14010
+ writeExportJson(join6(bundleRoot, "taskhub", "test-map.json"), normalizedProject.taskTestMap);
14012
14011
  }
14013
14012
  if (summaryPath) {
14014
- writeExportText(join5(bundleRoot, summaryPath), rawProject.aiSummary || "");
14013
+ writeExportText(join6(bundleRoot, summaryPath), rawProject.aiSummary || "");
14015
14014
  }
14016
14015
  if (ocrPath) {
14017
- writeExportText(join5(bundleRoot, ocrPath), rawProject.ocrText || "");
14016
+ writeExportText(join6(bundleRoot, ocrPath), rawProject.ocrText || "");
14018
14017
  }
14019
14018
  if (bundledFrames.length > 0) {
14020
- writeExportJson(join5(bundleRoot, "analysis", "frames.json"), bundledFrames);
14019
+ writeExportJson(join6(bundleRoot, "analysis", "frames.json"), bundledFrames);
14021
14020
  }
14022
14021
  if (networkEntries.length > 0) {
14023
- writeExportJson(join5(bundleRoot, "network", "entries.json"), networkEntries);
14022
+ writeExportJson(join6(bundleRoot, "network", "entries.json"), networkEntries);
14024
14023
  }
14025
14024
  if (networkCapture) {
14026
- writeExportJson(join5(bundleRoot, "network", "capture.json"), networkCapture);
14025
+ writeExportJson(join6(bundleRoot, "network", "capture.json"), networkCapture);
14027
14026
  }
14028
14027
  if (esvp) {
14029
- writeExportJson(join5(bundleRoot, "network", "esvp.json"), esvp);
14028
+ writeExportJson(join6(bundleRoot, "network", "esvp.json"), esvp);
14030
14029
  }
14031
14030
  if (esvpSnapshot) {
14032
- writeExportJson(join5(bundleRoot, "esvp", "snapshot.json"), esvpSnapshot);
14031
+ writeExportJson(join6(bundleRoot, "esvp", "snapshot.json"), esvpSnapshot);
14033
14032
  }
14034
- writeExportText(join5(bundleRoot, "README.txt"), [
14033
+ writeExportText(join6(bundleRoot, "README.txt"), [
14035
14034
  `${rawProject.name}`,
14036
14035
  `Exported from DiscoveryLab ${APP_VERSION} on ${new Date(timestamp).toISOString()}.`,
14037
14036
  "",
@@ -14045,24 +14044,24 @@ app.post("/api/export", async (c) => {
14045
14044
  "- previously generated export assets such as grids and renders",
14046
14045
  "- Task Hub links, requirements, and test map"
14047
14046
  ].join("\n"));
14048
- outputPath = join5(exportDir, `export-${timestamp}.${format}`);
14047
+ outputPath = join6(exportDir, `export-${timestamp}.${format}`);
14049
14048
  mimeType = "application/zip";
14050
14049
  await createProjectArchive(bundleRoot, outputPath);
14051
14050
  } finally {
14052
14051
  rmSync2(stagingRoot, { recursive: true, force: true });
14053
14052
  }
14054
14053
  } else if (isVideoPathDirectory && format === "gif") {
14055
- outputPath = join5(exportDir, `export-${timestamp}.gif`);
14054
+ outputPath = join6(exportDir, `export-${timestamp}.gif`);
14056
14055
  mimeType = "image/gif";
14057
- const screenshotsDir = join5(rawProject.videoPath, "screenshots");
14058
- const screenshotsDirExists = existsSync5(screenshotsDir) && statSync2(screenshotsDir).isDirectory();
14056
+ const screenshotsDir = join6(rawProject.videoPath, "screenshots");
14057
+ const screenshotsDirExists = existsSync5(screenshotsDir) && statSync3(screenshotsDir).isDirectory();
14059
14058
  const sourceDir = screenshotsDirExists ? screenshotsDir : rawProject.videoPath;
14060
- const { readdirSync: readdirSync3 } = await import("fs");
14061
- const pngFiles = readdirSync3(sourceDir).filter((f) => f.endsWith(".png")).sort().map((f) => join5(sourceDir, f));
14059
+ const { readdirSync: readdirSync5 } = await import("fs");
14060
+ const pngFiles = readdirSync5(sourceDir).filter((f) => f.endsWith(".png")).sort().map((f) => join6(sourceDir, f));
14062
14061
  if (pngFiles.length === 0) {
14063
14062
  return c.json({ error: "No screenshots found in recording" }, 400);
14064
14063
  }
14065
- const concatPath = join5(exportDir, `concat-${timestamp}.txt`);
14064
+ const concatPath = join6(exportDir, `concat-${timestamp}.txt`);
14066
14065
  const concatContent = pngFiles.map((f) => `file '${f}'
14067
14066
  duration 0.5`).join("\n");
14068
14067
  writeFileSync4(concatPath, concatContent);
@@ -14079,17 +14078,17 @@ duration 0.5`).join("\n");
14079
14078
  return c.json({ error: "GIF creation failed - FFmpeg required. Install with: brew install ffmpeg" }, 400);
14080
14079
  }
14081
14080
  } else if (isVideoPathDirectory && format === "mp4") {
14082
- outputPath = join5(exportDir, `export-${timestamp}.mp4`);
14081
+ outputPath = join6(exportDir, `export-${timestamp}.mp4`);
14083
14082
  mimeType = "video/mp4";
14084
- const screenshotsDir = join5(rawProject.videoPath, "screenshots");
14085
- const screenshotsDirExists = existsSync5(screenshotsDir) && statSync2(screenshotsDir).isDirectory();
14083
+ const screenshotsDir = join6(rawProject.videoPath, "screenshots");
14084
+ const screenshotsDirExists = existsSync5(screenshotsDir) && statSync3(screenshotsDir).isDirectory();
14086
14085
  const sourceDir = screenshotsDirExists ? screenshotsDir : rawProject.videoPath;
14087
- const { readdirSync: readdirSync3 } = await import("fs");
14088
- const pngFiles = readdirSync3(sourceDir).filter((f) => f.endsWith(".png")).sort().map((f) => join5(sourceDir, f));
14086
+ const { readdirSync: readdirSync5 } = await import("fs");
14087
+ const pngFiles = readdirSync5(sourceDir).filter((f) => f.endsWith(".png")).sort().map((f) => join6(sourceDir, f));
14089
14088
  if (pngFiles.length === 0) {
14090
14089
  return c.json({ error: "No screenshots found in recording" }, 400);
14091
14090
  }
14092
- const concatPath = join5(exportDir, `concat-${timestamp}.txt`);
14091
+ const concatPath = join6(exportDir, `concat-${timestamp}.txt`);
14093
14092
  const concatContent = pngFiles.map((f) => `file '${f}'
14094
14093
  duration 0.5`).join("\n");
14095
14094
  writeFileSync4(concatPath, concatContent);
@@ -14106,12 +14105,12 @@ duration 0.5`).join("\n");
14106
14105
  return c.json({ error: "Video creation failed - FFmpeg required. Install with: brew install ffmpeg" }, 400);
14107
14106
  }
14108
14107
  } else if (isVideoPathDirectory) {
14109
- outputPath = join5(exportDir, `export-${timestamp}`);
14108
+ outputPath = join6(exportDir, `export-${timestamp}`);
14110
14109
  cpSync(rawProject.videoPath, outputPath, { recursive: true });
14111
14110
  mimeType = "application/octet-stream";
14112
14111
  } else if (format === "png" || format === "jpg" || format === "jpeg") {
14113
14112
  const ext = format === "jpg" ? "jpeg" : format;
14114
- outputPath = join5(exportDir, `export-${timestamp}.${format}`);
14113
+ outputPath = join6(exportDir, `export-${timestamp}.${format}`);
14115
14114
  mimeType = `image/${ext}`;
14116
14115
  if (resolvedVideoPath?.endsWith(`.${format}`)) {
14117
14116
  copyFileSync(resolvedVideoPath, outputPath);
@@ -14120,11 +14119,11 @@ duration 0.5`).join("\n");
14120
14119
  await execAsync(`sips -s format ${format} "${resolvedVideoPath}" --out "${outputPath}"`);
14121
14120
  } catch {
14122
14121
  copyFileSync(resolvedVideoPath, outputPath);
14123
- outputPath = join5(exportDir, `export-${timestamp}${resolvedVideoPath?.substring(resolvedVideoPath.lastIndexOf("."))}`);
14122
+ outputPath = join6(exportDir, `export-${timestamp}${resolvedVideoPath?.substring(resolvedVideoPath.lastIndexOf("."))}`);
14124
14123
  }
14125
14124
  }
14126
14125
  } else if (format === "gif") {
14127
- outputPath = join5(exportDir, `export-${timestamp}.gif`);
14126
+ outputPath = join6(exportDir, `export-${timestamp}.gif`);
14128
14127
  mimeType = "image/gif";
14129
14128
  try {
14130
14129
  await execAsync(`ffmpeg -i "${resolvedVideoPath}" -vf "fps=10,scale=320:-1:flags=lanczos" "${outputPath}" -y`);
@@ -14134,7 +14133,7 @@ duration 0.5`).join("\n");
14134
14133
  mimeType = "image/png";
14135
14134
  }
14136
14135
  } else if (format === "mp4") {
14137
- outputPath = join5(exportDir, `export-${timestamp}.mp4`);
14136
+ outputPath = join6(exportDir, `export-${timestamp}.mp4`);
14138
14137
  mimeType = "video/mp4";
14139
14138
  if (resolvedVideoPath?.endsWith(".mp4")) {
14140
14139
  copyFileSync(resolvedVideoPath, outputPath);
@@ -14147,7 +14146,7 @@ duration 0.5`).join("\n");
14147
14146
  }
14148
14147
  } else {
14149
14148
  const ext = resolvedVideoPath?.substring(resolvedVideoPath.lastIndexOf(".")) || "";
14150
- outputPath = join5(exportDir, `export-${timestamp}${ext}`);
14149
+ outputPath = join6(exportDir, `export-${timestamp}${ext}`);
14151
14150
  if (resolvedVideoPath) {
14152
14151
  copyFileSync(resolvedVideoPath, outputPath);
14153
14152
  }
@@ -14170,7 +14169,7 @@ ${rawProject.aiSummary}
14170
14169
  ${rawProject.ocrText}
14171
14170
  `;
14172
14171
  }
14173
- const textPath = join5(exportDir, `export-${timestamp}.txt`);
14172
+ const textPath = join6(exportDir, `export-${timestamp}.txt`);
14174
14173
  writeFileSync4(textPath, textContent);
14175
14174
  }
14176
14175
  if (destination === "local" || destination === "clipboard") {
@@ -14205,7 +14204,7 @@ app.get("/api/visualization/:projectId/:templateId", async (c) => {
14205
14204
  const [project] = await db.select().from(projects).where(eq(projects.id, projectId)).limit(1);
14206
14205
  if (!project) return c.json({ error: "Project not found" }, 404);
14207
14206
  const { FRAMES_DIR } = await import("./db-745LC5YC.js");
14208
- const projectFramesDir = join5(FRAMES_DIR, projectId);
14207
+ const projectFramesDir = join6(FRAMES_DIR, projectId);
14209
14208
  const dbFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(10);
14210
14209
  let frameImages = [];
14211
14210
  if (dbFrames.length > 0) {
@@ -14216,20 +14215,20 @@ app.get("/api/visualization/:projectId/:templateId", async (c) => {
14216
14215
  }));
14217
14216
  } else {
14218
14217
  const screenshotsDirs = [
14219
- join5(projectFramesDir),
14218
+ join6(projectFramesDir),
14220
14219
  ...project.videoPath ? [
14221
- join5(project.videoPath, "screenshots"),
14220
+ join6(project.videoPath, "screenshots"),
14222
14221
  project.videoPath
14223
14222
  ] : [],
14224
- join5(PROJECTS_DIR, "maestro-recordings", projectId, "screenshots"),
14225
- join5(PROJECTS_DIR, "web-recordings", projectId, "screenshots")
14223
+ join6(PROJECTS_DIR, "maestro-recordings", projectId, "screenshots"),
14224
+ join6(PROJECTS_DIR, "web-recordings", projectId, "screenshots")
14226
14225
  ];
14227
14226
  for (const dir of screenshotsDirs) {
14228
- if (!existsSync5(dir) || !statSync2(dir).isDirectory()) continue;
14229
- const files = readdirSync2(dir).filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f)).sort().slice(0, 10);
14227
+ if (!existsSync5(dir) || !statSync3(dir).isDirectory()) continue;
14228
+ const files = readdirSync4(dir).filter((f) => /\.(png|jpg|jpeg|gif|webp)$/i.test(f)).sort().slice(0, 10);
14230
14229
  if (files.length > 0) {
14231
14230
  frameImages = files.map((f, i) => ({
14232
- imageUrl: `/api/file?path=${encodeURIComponent(join5(dir, f))}`,
14231
+ imageUrl: `/api/file?path=${encodeURIComponent(join6(dir, f))}`,
14233
14232
  label: `Screen ${i + 1}`,
14234
14233
  number: i + 1
14235
14234
  }));
@@ -14290,7 +14289,7 @@ app.get("/api/visualization/:projectId/:templateId", async (c) => {
14290
14289
  case "app-flow-map": {
14291
14290
  let phases = [];
14292
14291
  const { EXPORTS_DIR: expDir } = await import("./db-745LC5YC.js");
14293
- const cachePath = join5(expDir, projectId, "flowmap-cache.json");
14292
+ const cachePath = join6(expDir, projectId, "flowmap-cache.json");
14294
14293
  let cachedFlowMap = null;
14295
14294
  try {
14296
14295
  if (existsSync5(cachePath)) {
@@ -14379,7 +14378,7 @@ Rules:
14379
14378
  }
14380
14379
  }
14381
14380
  try {
14382
- const cacheDir = join5(expDir, projectId);
14381
+ const cacheDir = join6(expDir, projectId);
14383
14382
  if (!existsSync5(cacheDir)) mkdirSync4(cacheDir, { recursive: true });
14384
14383
  const cacheData = {
14385
14384
  title: vizData.title,
@@ -14417,11 +14416,11 @@ Rules:
14417
14416
  default:
14418
14417
  return c.json({ error: `Unknown template: ${templateId}` }, 400);
14419
14418
  }
14420
- const templatePath = join5(__dirname, "..", "core", "visualizations", "templates", `${templateId}.html`);
14419
+ const templatePath = join6(__dirname, "..", "core", "visualizations", "templates", `${templateId}.html`);
14421
14420
  const possiblePaths = [
14422
14421
  templatePath,
14423
- join5(__dirname, "..", "..", "src", "core", "visualizations", "templates", `${templateId}.html`),
14424
- join5(process.cwd(), "src", "core", "visualizations", "templates", `${templateId}.html`)
14422
+ join6(__dirname, "..", "..", "src", "core", "visualizations", "templates", `${templateId}.html`),
14423
+ join6(process.cwd(), "src", "core", "visualizations", "templates", `${templateId}.html`)
14425
14424
  ];
14426
14425
  let htmlContent = "";
14427
14426
  for (const p of possiblePaths) {
@@ -14459,12 +14458,12 @@ app.post("/api/visualization/screenshot", async (c) => {
14459
14458
  return c.json({ error: "projectId and templateId required" }, 400);
14460
14459
  }
14461
14460
  const { EXPORTS_DIR: EXPORTS_DIR2 } = await import("./db-745LC5YC.js");
14462
- const exportDir = join5(EXPORTS_DIR2, projectId);
14461
+ const exportDir = join6(EXPORTS_DIR2, projectId);
14463
14462
  if (!existsSync5(exportDir)) {
14464
14463
  mkdirSync4(exportDir, { recursive: true });
14465
14464
  }
14466
14465
  const ext = format === "gif" ? "gif" : "png";
14467
- const outputPath = join5(exportDir, `viz-${templateId}-${Date.now()}.${ext}`);
14466
+ const outputPath = join6(exportDir, `viz-${templateId}-${Date.now()}.${ext}`);
14468
14467
  const serverPort = process.env.PORT || "3847";
14469
14468
  const vizUrl = `http://localhost:${serverPort}/api/visualization/${projectId}/${templateId}`;
14470
14469
  const { chromium: chromium2 } = await import("playwright");
@@ -14473,12 +14472,12 @@ app.post("/api/visualization/screenshot", async (c) => {
14473
14472
  await page.goto(vizUrl, { waitUntil: "networkidle" });
14474
14473
  await page.waitForTimeout(2500);
14475
14474
  if (format === "gif") {
14476
- const framesDir = join5(exportDir, `viz-gif-frames-${Date.now()}`);
14475
+ const framesDir = join6(exportDir, `viz-gif-frames-${Date.now()}`);
14477
14476
  mkdirSync4(framesDir, { recursive: true });
14478
14477
  const frameCount = 20;
14479
14478
  const intervalMs = 200;
14480
14479
  for (let i = 0; i < frameCount; i++) {
14481
- await page.screenshot({ path: join5(framesDir, `frame-${String(i).padStart(3, "0")}.png`) });
14480
+ await page.screenshot({ path: join6(framesDir, `frame-${String(i).padStart(3, "0")}.png`) });
14482
14481
  await page.waitForTimeout(intervalMs);
14483
14482
  }
14484
14483
  await browser.close();
@@ -14486,13 +14485,13 @@ app.post("/api/visualization/screenshot", async (c) => {
14486
14485
  const execPromise = promisify(exec);
14487
14486
  try {
14488
14487
  await execPromise(
14489
- `ffmpeg -framerate 5 -i "${join5(framesDir, "frame-%03d.png")}" -vf "scale=600:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 "${outputPath}" -y`,
14488
+ `ffmpeg -framerate 5 -i "${join6(framesDir, "frame-%03d.png")}" -vf "scale=600:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 "${outputPath}" -y`,
14490
14489
  { timeout: 3e4 }
14491
14490
  );
14492
14491
  } catch (ffmpegError) {
14493
14492
  const { copyFileSync: copyFileSync2 } = await import("fs");
14494
14493
  const fallbackPath = outputPath.replace(".gif", ".png");
14495
- copyFileSync2(join5(framesDir, "frame-000.png"), fallbackPath);
14494
+ copyFileSync2(join6(framesDir, "frame-000.png"), fallbackPath);
14496
14495
  const { rmSync: rmSync4 } = await import("fs");
14497
14496
  rmSync4(framesDir, { recursive: true, force: true });
14498
14497
  return c.json({
@@ -14531,16 +14530,16 @@ app.get("/api/export/document/:projectId", async (c) => {
14531
14530
  if (frameData.length === 0 && project.videoPath) {
14532
14531
  const { FRAMES_DIR } = await import("./db-745LC5YC.js");
14533
14532
  const dirs = [
14534
- join5(FRAMES_DIR, projectId),
14535
- join5(project.videoPath, "screenshots"),
14536
- join5(PROJECTS_DIR, "maestro-recordings", projectId, "screenshots"),
14537
- join5(PROJECTS_DIR, "web-recordings", projectId, "screenshots")
14533
+ join6(FRAMES_DIR, projectId),
14534
+ join6(project.videoPath, "screenshots"),
14535
+ join6(PROJECTS_DIR, "maestro-recordings", projectId, "screenshots"),
14536
+ join6(PROJECTS_DIR, "web-recordings", projectId, "screenshots")
14538
14537
  ];
14539
14538
  for (const dir of dirs) {
14540
- if (existsSync5(dir) && statSync2(dir).isDirectory()) {
14541
- const files = readdirSync2(dir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f)).sort().slice(0, 20);
14539
+ if (existsSync5(dir) && statSync3(dir).isDirectory()) {
14540
+ const files = readdirSync4(dir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f)).sort().slice(0, 20);
14542
14541
  if (files.length > 0) {
14543
- frameData = files.map((f, i) => ({ imagePath: join5(dir, f), ocrText: null }));
14542
+ frameData = files.map((f, i) => ({ imagePath: join6(dir, f), ocrText: null }));
14544
14543
  break;
14545
14544
  }
14546
14545
  }
@@ -14576,7 +14575,7 @@ app.post("/api/export/notion-page", async (c) => {
14576
14575
  if (!doc || !parentPageId) {
14577
14576
  return c.json({ error: "document and parentPageId required" }, 400);
14578
14577
  }
14579
- const notionSettingsPath = join5(DATA_DIR, "notion-settings.json");
14578
+ const notionSettingsPath = join6(DATA_DIR, "notion-settings.json");
14580
14579
  let apiToken = "";
14581
14580
  if (existsSync5(notionSettingsPath)) {
14582
14581
  const settings = JSON.parse(readFileSync2(notionSettingsPath, "utf-8"));
@@ -14616,7 +14615,7 @@ app.post("/api/export/batch", async (c) => {
14616
14615
  return p || null;
14617
14616
  },
14618
14617
  getFramesDir(projectId) {
14619
- return join5(FRAMES_DIR, projectId);
14618
+ return join6(FRAMES_DIR, projectId);
14620
14619
  }
14621
14620
  };
14622
14621
  const result = await executeBatchExport(manifest, dataProvider, (progress) => {
@@ -14653,13 +14652,13 @@ app.get("/assets/*", async (c) => {
14653
14652
  const assetPath = c.req.path.replace("/assets/", "");
14654
14653
  const cwd = process.cwd();
14655
14654
  const possiblePaths = [
14656
- join5(__dirname, "..", "assets", assetPath),
14655
+ join6(__dirname, "..", "assets", assetPath),
14657
14656
  // Production: dist/../assets
14658
- join5(__dirname, "..", "..", "assets", assetPath),
14657
+ join6(__dirname, "..", "..", "assets", assetPath),
14659
14658
  // Alternative structure
14660
- join5(cwd, "assets", assetPath),
14659
+ join6(cwd, "assets", assetPath),
14661
14660
  // Development: running from project root
14662
- join5(cwd, "src", "assets", assetPath)
14661
+ join6(cwd, "src", "assets", assetPath)
14663
14662
  // Development: src/assets
14664
14663
  ];
14665
14664
  for (const path of possiblePaths) {
@@ -14701,9 +14700,9 @@ app.get("/api/file", async (c) => {
14701
14700
  if (!existsSync5(resolvedPath)) {
14702
14701
  return c.json({ error: "File not found" }, 404);
14703
14702
  }
14704
- const { readFileSync: readFileSync3, statSync: statSync3 } = await import("fs");
14703
+ const { readFileSync: readFileSync3, statSync: statSync4 } = await import("fs");
14705
14704
  const { basename: basename4, extname: extname3 } = await import("path");
14706
- const stat = statSync3(resolvedPath);
14705
+ const stat = statSync4(resolvedPath);
14707
14706
  if (stat.isDirectory()) {
14708
14707
  return c.json({ error: "Path resolves to a directory" }, 400);
14709
14708
  }
@@ -14746,10 +14745,10 @@ app.get("/api/files", async (c) => {
14746
14745
  if (!existsSync5(decodedPath)) {
14747
14746
  return c.json({ error: "Directory not found" }, 404);
14748
14747
  }
14749
- const { readdirSync: readdirSync3, statSync: statSync3 } = await import("fs");
14750
- const files = readdirSync3(decodedPath).filter((name) => {
14748
+ const { readdirSync: readdirSync5, statSync: statSync4 } = await import("fs");
14749
+ const files = readdirSync5(decodedPath).filter((name) => {
14751
14750
  try {
14752
- return statSync3(join5(decodedPath, name)).isFile();
14751
+ return statSync4(join6(decodedPath, name)).isFile();
14753
14752
  } catch {
14754
14753
  return false;
14755
14754
  }
@@ -14865,7 +14864,7 @@ app.get("/api/integrations/jira-mcp/status", async (c) => {
14865
14864
  const { execSync: execSync3 } = await import("child_process");
14866
14865
  const { existsSync: existsSync6, readFileSync: readFileSync3 } = await import("fs");
14867
14866
  const { homedir: homedir4 } = await import("os");
14868
- const { join: join6 } = await import("path");
14867
+ const { join: join7 } = await import("path");
14869
14868
  const claudeCliAvailable = isClaudeCliAvailable();
14870
14869
  let configured = false;
14871
14870
  let available = false;
@@ -14914,8 +14913,8 @@ app.get("/api/integrations/jira-mcp/status", async (c) => {
14914
14913
  }
14915
14914
  if (!configured) {
14916
14915
  const settingsPaths = [
14917
- join6(homedir4(), ".claude", "settings.json"),
14918
- join6(homedir4(), ".claude", "settings.local.json")
14916
+ join7(homedir4(), ".claude", "settings.json"),
14917
+ join7(homedir4(), ".claude", "settings.local.json")
14919
14918
  ];
14920
14919
  for (const filePath of settingsPaths) {
14921
14920
  if (!existsSync6(filePath)) continue;
@@ -15472,12 +15471,12 @@ app.post("/api/testing/mobile/device/tap", async (c) => {
15472
15471
  await lock.acquired;
15473
15472
  try {
15474
15473
  const { mkdir, writeFile, rm } = await import("fs/promises");
15475
- const { join: join6 } = await import("path");
15476
- const tempBase = join6(tmpdir2(), "discoverylab-maestro-live");
15477
- const flowPath = join6(tempBase, `tap-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.yaml`);
15478
- const outputDir = join6(tempBase, `out-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
15474
+ const { join: join7 } = await import("path");
15475
+ const tempBase = join7(tmpdir2(), "discoverylab-maestro-live");
15476
+ const flowPath = join7(tempBase, `tap-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.yaml`);
15477
+ const outputDir = join7(tempBase, `out-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
15479
15478
  await mkdir(tempBase, { recursive: true });
15480
- const maestroLogDir = join6(homedir3(), "Library", "Logs", "maestro");
15479
+ const maestroLogDir = join7(homedir3(), "Library", "Logs", "maestro");
15481
15480
  await mkdir(maestroLogDir, { recursive: true });
15482
15481
  const flowLines = [];
15483
15482
  let resolvedAppId = appId || session?.appId;
@@ -15581,8 +15580,8 @@ app.post("/api/testing/mobile/record/stop", async (c) => {
15581
15580
  }
15582
15581
  const session = await recorder.stopRecording();
15583
15582
  const projectId = session.id;
15584
- const { dirname: dirname3, join: join6 } = await import("path");
15585
- const { readdirSync: readdirSync3, writeFileSync: writeFileSync5, existsSync: existsSync6, readFileSync: readFileSync3 } = await import("fs");
15583
+ const { dirname: dirname3, join: join7 } = await import("path");
15584
+ const { readdirSync: readdirSync5, writeFileSync: writeFileSync5, existsSync: existsSync6, readFileSync: readFileSync3 } = await import("fs");
15586
15585
  const outputDir = dirname3(session.flowPath || session.screenshotsDir);
15587
15586
  if (!session.appId && session.deviceId && session.platform) {
15588
15587
  session.appId = getForegroundAppIdForPlatform(session.platform, session.deviceId) || void 0;
@@ -15591,10 +15590,10 @@ app.post("/api/testing/mobile/record/stop", async (c) => {
15591
15590
  let screenshotCount = 0;
15592
15591
  let screenshotFiles = [];
15593
15592
  try {
15594
- screenshotFiles = readdirSync3(session.screenshotsDir).filter((f) => f.endsWith(".png")).sort();
15593
+ screenshotFiles = readdirSync5(session.screenshotsDir).filter((f) => f.endsWith(".png")).sort();
15595
15594
  screenshotCount = screenshotFiles.length;
15596
15595
  if (screenshotFiles.length > 0) {
15597
- thumbnailPath = join6(session.screenshotsDir, screenshotFiles[0]);
15596
+ thumbnailPath = join7(session.screenshotsDir, screenshotFiles[0]);
15598
15597
  }
15599
15598
  } catch {
15600
15599
  }
@@ -15614,7 +15613,7 @@ app.post("/api/testing/mobile/record/stop", async (c) => {
15614
15613
  const actionsCount = actions.length;
15615
15614
  const deviceShort = session.deviceName?.split(" ")[0] || session.platform?.toUpperCase() || "Mobile";
15616
15615
  const projectName = actionsCount > 0 ? `${deviceShort} Test - ${actionsCount} actions - ${dateStr}` : `${deviceShort} Recording - ${dateStr}`;
15617
- const flowPath = join6(outputDir, "test.yaml");
15616
+ const flowPath = join7(outputDir, "test.yaml");
15618
15617
  let flowContent = "";
15619
15618
  if (existsSync6(flowPath)) {
15620
15619
  try {
@@ -15804,9 +15803,9 @@ app.post("/api/projects/:id/retry-analysis", async (c) => {
15804
15803
  const candidateScreenshotDirs = [];
15805
15804
  if (projectPath && existsSync5(projectPath)) {
15806
15805
  try {
15807
- const projectStats = statSync2(projectPath);
15806
+ const projectStats = statSync3(projectPath);
15808
15807
  if (projectStats.isDirectory()) {
15809
- candidateScreenshotDirs.push(join5(projectPath, "screenshots"));
15808
+ candidateScreenshotDirs.push(join6(projectPath, "screenshots"));
15810
15809
  candidateScreenshotDirs.push(projectPath);
15811
15810
  }
15812
15811
  } catch (err) {
@@ -15818,7 +15817,7 @@ app.post("/api/projects/:id/retry-analysis", async (c) => {
15818
15817
  for (const candidateDir of candidateScreenshotDirs) {
15819
15818
  if (!existsSync5(candidateDir)) continue;
15820
15819
  try {
15821
- const pngs = readdirSync2(candidateDir).filter((file) => file.toLowerCase().endsWith(".png")).sort();
15820
+ const pngs = readdirSync4(candidateDir).filter((file) => file.toLowerCase().endsWith(".png")).sort();
15822
15821
  if (pngs.length > 0) {
15823
15822
  screenshotsDir = candidateDir;
15824
15823
  screenshotFiles = pngs;
@@ -15850,7 +15849,7 @@ app.post("/api/projects/:id/retry-analysis", async (c) => {
15850
15849
  }
15851
15850
  if (projectPath && existsSync5(projectPath)) {
15852
15851
  try {
15853
- const projectStats = statSync2(projectPath);
15852
+ const projectStats = statSync3(projectPath);
15854
15853
  if (projectStats.isDirectory()) {
15855
15854
  console.log(`[RetryAnalysis] Restarting directory analysis for ${projectId}`);
15856
15855
  runProjectAnalysisInBackgroundWithWatchdog(projectId, "RetryAnalysis(directory)");
@@ -15880,14 +15879,14 @@ app.post("/api/projects/:id/retry-analysis", async (c) => {
15880
15879
  app.post("/api/testing/mobile/recordings/:id/analyze", async (c) => {
15881
15880
  try {
15882
15881
  const recordingId = c.req.param("id");
15883
- const { readdirSync: readdirSync3, writeFileSync: writeFileSync5, existsSync: fsExistsSync } = await import("fs");
15884
- const { join: join6 } = await import("path");
15885
- const recordingDir = join6(PROJECTS_DIR, "maestro-recordings", recordingId);
15886
- const screenshotsDir = join6(recordingDir, "screenshots");
15882
+ const { readdirSync: readdirSync5, writeFileSync: writeFileSync5, existsSync: fsExistsSync } = await import("fs");
15883
+ const { join: join7 } = await import("path");
15884
+ const recordingDir = join7(PROJECTS_DIR, "maestro-recordings", recordingId);
15885
+ const screenshotsDir = join7(recordingDir, "screenshots");
15887
15886
  if (!fsExistsSync(screenshotsDir)) {
15888
15887
  return c.json({ error: "Screenshots directory not found" }, 404);
15889
15888
  }
15890
- const screenshotFiles = readdirSync3(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
15889
+ const screenshotFiles = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
15891
15890
  if (screenshotFiles.length < 2) {
15892
15891
  return c.json({ error: "Not enough screenshots for analysis (need at least 2)" }, 400);
15893
15892
  }
@@ -15912,7 +15911,7 @@ app.post("/api/testing/mobile/recordings/:id/analyze", async (c) => {
15912
15911
  analysisResult.appName,
15913
15912
  analysisResult.actionDetectionProvider
15914
15913
  );
15915
- const flowPath = join6(recordingDir, "test.yaml");
15914
+ const flowPath = join7(recordingDir, "test.yaml");
15916
15915
  writeFileSync5(flowPath, maestroYaml, "utf-8");
15917
15916
  console.log(`[MobileRecording] AI detected ${analysisResult.actions.length} actions, YAML saved to ${flowPath}`);
15918
15917
  return c.json({
@@ -15943,16 +15942,16 @@ app.post("/api/testing/playwright/codegen", async (c) => {
15943
15942
  });
15944
15943
  app.get("/api/testing/mobile/recordings", async (c) => {
15945
15944
  try {
15946
- const { readdirSync: readdirSync3, readFileSync: readFileSync3, statSync: statSync3 } = await import("fs");
15947
- const { join: join6 } = await import("path");
15948
- const recordingsDir = join6(PROJECTS_DIR, "maestro-recordings");
15945
+ const { readdirSync: readdirSync5, readFileSync: readFileSync3, statSync: statSync4 } = await import("fs");
15946
+ const { join: join7 } = await import("path");
15947
+ const recordingsDir = join7(PROJECTS_DIR, "maestro-recordings");
15949
15948
  if (!existsSync5(recordingsDir)) {
15950
15949
  return c.json({ recordings: [] });
15951
15950
  }
15952
15951
  const recordings = [];
15953
- const dirs = readdirSync3(recordingsDir);
15952
+ const dirs = readdirSync5(recordingsDir);
15954
15953
  for (const dir of dirs) {
15955
- const sessionPath = join6(recordingsDir, dir, "session.json");
15954
+ const sessionPath = join7(recordingsDir, dir, "session.json");
15956
15955
  if (existsSync5(sessionPath)) {
15957
15956
  try {
15958
15957
  const session = JSON.parse(readFileSync3(sessionPath, "utf-8"));
@@ -15984,9 +15983,9 @@ app.get("/api/testing/mobile/recordings/:id", async (c) => {
15984
15983
  try {
15985
15984
  const { id } = c.req.param();
15986
15985
  const { readFileSync: readFileSync3 } = await import("fs");
15987
- const { join: join6 } = await import("path");
15988
- const sessionPath = join6(PROJECTS_DIR, "maestro-recordings", id, "session.json");
15989
- const flowPath = join6(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
15986
+ const { join: join7 } = await import("path");
15987
+ const sessionPath = join7(PROJECTS_DIR, "maestro-recordings", id, "session.json");
15988
+ const flowPath = join7(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
15990
15989
  if (!existsSync5(sessionPath)) {
15991
15990
  return c.json({ error: "Recording not found" }, 404);
15992
15991
  }
@@ -16663,8 +16662,8 @@ app.put("/api/testing/mobile/recordings/:id/flow", async (c) => {
16663
16662
  const body = await c.req.json();
16664
16663
  const { flowCode } = body;
16665
16664
  const { writeFileSync: writeFileSync5 } = await import("fs");
16666
- const { join: join6 } = await import("path");
16667
- const flowPath = join6(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
16665
+ const { join: join7 } = await import("path");
16666
+ const flowPath = join7(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
16668
16667
  writeFileSync5(flowPath, flowCode, "utf-8");
16669
16668
  return c.json({ success: true });
16670
16669
  } catch (error) {
@@ -16676,8 +16675,8 @@ app.delete("/api/testing/mobile/recordings/:id", async (c) => {
16676
16675
  try {
16677
16676
  const { id } = c.req.param();
16678
16677
  const { rmSync: rmSync3 } = await import("fs");
16679
- const { join: join6 } = await import("path");
16680
- const recordingDir = join6(PROJECTS_DIR, "maestro-recordings", id);
16678
+ const { join: join7 } = await import("path");
16679
+ const recordingDir = join7(PROJECTS_DIR, "maestro-recordings", id);
16681
16680
  if (!existsSync5(recordingDir)) {
16682
16681
  return c.json({ error: "Recording not found" }, 404);
16683
16682
  }
@@ -16752,8 +16751,8 @@ app.post("/api/testing/mobile/recordings/:id/replay", async (c) => {
16752
16751
  const requestedDevicePlatform = body?.devicePlatform === "ios" || body?.devicePlatform === "android" ? body.devicePlatform : void 0;
16753
16752
  const skipDeviceValidation = body?.skipDeviceValidation === true;
16754
16753
  const { readFileSync: readFileSync3 } = await import("fs");
16755
- const { join: join6 } = await import("path");
16756
- const flowPath = join6(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
16754
+ const { join: join7 } = await import("path");
16755
+ const flowPath = join7(PROJECTS_DIR, "maestro-recordings", id, "test.yaml");
16757
16756
  if (!existsSync5(flowPath)) {
16758
16757
  return c.json({ error: "Flow file not found" }, 404);
16759
16758
  }
@@ -16891,7 +16890,7 @@ var autoCaptureScreenshotCount = 0;
16891
16890
  app.post("/api/testing/mobile/auto-capture/start", async (c) => {
16892
16891
  try {
16893
16892
  const { recordingId, platform } = await c.req.json();
16894
- const { join: join6 } = await import("path");
16893
+ const { join: join7 } = await import("path");
16895
16894
  const { mkdirSync: mkdirSync5, existsSync: fsExistsSync, writeFileSync: writeFileSync5 } = await import("fs");
16896
16895
  if (!recordingId) {
16897
16896
  return c.json({ error: "recordingId required" }, 400);
@@ -16902,7 +16901,7 @@ app.post("/api/testing/mobile/auto-capture/start", async (c) => {
16902
16901
  }
16903
16902
  autoCaptureRecordingId = recordingId;
16904
16903
  autoCaptureScreenshotCount = 0;
16905
- const screenshotsDir = join6(PROJECTS_DIR, "maestro-recordings", recordingId, "screenshots");
16904
+ const screenshotsDir = join7(PROJECTS_DIR, "maestro-recordings", recordingId, "screenshots");
16906
16905
  if (!fsExistsSync(screenshotsDir)) {
16907
16906
  mkdirSync5(screenshotsDir, { recursive: true });
16908
16907
  }
@@ -16910,7 +16909,7 @@ app.post("/api/testing/mobile/auto-capture/start", async (c) => {
16910
16909
  const execCaptureAsync = promisify(exec);
16911
16910
  autoCaptureInterval = setInterval(async () => {
16912
16911
  try {
16913
- const screenshotPath = join6(screenshotsDir, `auto_${Date.now()}_${autoCaptureScreenshotCount}.png`);
16912
+ const screenshotPath = join7(screenshotsDir, `auto_${Date.now()}_${autoCaptureScreenshotCount}.png`);
16914
16913
  autoCaptureScreenshotCount++;
16915
16914
  if (platform === "ios") {
16916
16915
  await execCaptureAsync(`xcrun simctl io booted screenshot "${screenshotPath}"`, { timeout: 5e3 });
@@ -16944,8 +16943,8 @@ app.post("/api/testing/mobile/auto-capture/start", async (c) => {
16944
16943
  app.post("/api/testing/mobile/auto-capture/stop", async (c) => {
16945
16944
  try {
16946
16945
  const { recordingId } = await c.req.json();
16947
- const { join: join6 } = await import("path");
16948
- const { readdirSync: readdirSync3, existsSync: fsExistsSync } = await import("fs");
16946
+ const { join: join7 } = await import("path");
16947
+ const { readdirSync: readdirSync5, existsSync: fsExistsSync } = await import("fs");
16949
16948
  if (autoCaptureInterval) {
16950
16949
  clearInterval(autoCaptureInterval);
16951
16950
  autoCaptureInterval = null;
@@ -16956,9 +16955,9 @@ app.post("/api/testing/mobile/auto-capture/stop", async (c) => {
16956
16955
  autoCaptureScreenshotCount = 0;
16957
16956
  let savedScreenshots = 0;
16958
16957
  if (stoppedId) {
16959
- const screenshotsDir = join6(PROJECTS_DIR, "maestro-recordings", stoppedId, "screenshots");
16958
+ const screenshotsDir = join7(PROJECTS_DIR, "maestro-recordings", stoppedId, "screenshots");
16960
16959
  if (fsExistsSync(screenshotsDir)) {
16961
- savedScreenshots = readdirSync3(screenshotsDir).filter((f) => f.endsWith(".png")).length;
16960
+ savedScreenshots = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).length;
16962
16961
  }
16963
16962
  }
16964
16963
  console.log(`[AutoCapture] Stopped for ${stoppedId || recordingId}, ${savedScreenshots} screenshots saved`);
@@ -17001,11 +17000,11 @@ function shellQuote(value) {
17001
17000
  async function runClaudeCliWithArgs(prompt, args, timeoutMs) {
17002
17001
  const { mkdtemp, writeFile, readFile, rm } = await import("fs/promises");
17003
17002
  const { tmpdir: tmpdir3 } = await import("os");
17004
- const { join: join6 } = await import("path");
17005
- const tempDir = await mkdtemp(join6(tmpdir3(), "claude-cli-"));
17006
- const promptPath = join6(tempDir, "prompt.txt");
17007
- const stdoutPath = join6(tempDir, "stdout.txt");
17008
- const stderrPath = join6(tempDir, "stderr.txt");
17003
+ const { join: join7 } = await import("path");
17004
+ const tempDir = await mkdtemp(join7(tmpdir3(), "claude-cli-"));
17005
+ const promptPath = join7(tempDir, "prompt.txt");
17006
+ const stdoutPath = join7(tempDir, "stdout.txt");
17007
+ const stderrPath = join7(tempDir, "stderr.txt");
17009
17008
  await writeFile(promptPath, prompt, "utf8");
17010
17009
  const quotedArgs = args.map((arg) => shellQuote(arg)).join(" ");
17011
17010
  const shellScript = [
@@ -17557,9 +17556,9 @@ async function runOCRInBackground(projectId, screenshotsDir, screenshotFiles) {
17557
17556
  };
17558
17557
  try {
17559
17558
  const { recognizeTextBatch } = await import("./ocr-FXRLEP66.js");
17560
- const { join: join6 } = await import("path");
17559
+ const { join: join7 } = await import("path");
17561
17560
  broadcastProgress("ocr", "running", `Processing 0/${screenshotFiles.length} screenshots...`, void 0, 0, screenshotFiles.length);
17562
- const fullPaths = screenshotFiles.map((f) => join6(screenshotsDir, f));
17561
+ const fullPaths = screenshotFiles.map((f) => join7(screenshotsDir, f));
17563
17562
  const ocrResult = await recognizeTextBatch(
17564
17563
  fullPaths,
17565
17564
  { recognitionLevel: "accurate" },
@@ -17642,8 +17641,8 @@ async function runOCRInBackground(projectId, screenshotsDir, screenshotFiles) {
17642
17641
  analysisResult.actionDetectionProvider
17643
17642
  );
17644
17643
  const { writeFileSync: writeFileSync5 } = await import("fs");
17645
- const recordingDir2 = join6(screenshotsDir, "..");
17646
- const flowPath = join6(recordingDir2, "test.yaml");
17644
+ const recordingDir2 = join7(screenshotsDir, "..");
17645
+ const flowPath = join7(recordingDir2, "test.yaml");
17647
17646
  writeFileSync5(flowPath, maestroYaml, "utf-8");
17648
17647
  detectedActionsCount = analysisResult.actions.length;
17649
17648
  console.log(`[BackgroundOCR] AI detected ${detectedActionsCount} actions, YAML saved to ${flowPath}`);
@@ -17664,10 +17663,10 @@ async function runOCRInBackground(projectId, screenshotsDir, screenshotFiles) {
17664
17663
  } else {
17665
17664
  broadcastProgress("actions", "skipped", "Not enough screenshots for action detection");
17666
17665
  }
17667
- const recordingDir = join6(screenshotsDir, "..");
17666
+ const recordingDir = join7(screenshotsDir, "..");
17668
17667
  let iconCoverPath = null;
17669
17668
  try {
17670
- const sessionPath = join6(recordingDir, "session.json");
17669
+ const sessionPath = join7(recordingDir, "session.json");
17671
17670
  if (existsSync5(sessionPath)) {
17672
17671
  const sessionData = JSON.parse(readFileSync2(sessionPath, "utf8"));
17673
17672
  if ((sessionData?.platform === "ios" || sessionData?.platform === "android") && typeof sessionData?.deviceId === "string") {
@@ -18363,8 +18362,8 @@ User: ${truncateForPrompt(message, 800)}`;
18363
18362
  executionStarted = true;
18364
18363
  const flowYaml = generateChatMaestroYaml(commands, appIdForFlow);
18365
18364
  const { writeFile, rm } = await import("fs/promises");
18366
- const { join: join6 } = await import("path");
18367
- const flowPath = join6(
18365
+ const { join: join7 } = await import("path");
18366
+ const flowPath = join7(
18368
18367
  PROJECTS_DIR,
18369
18368
  `temp-chat-flow-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.yaml`
18370
18369
  );
@@ -18514,7 +18513,7 @@ function sanitizeAppLabNetworkProxySettings(value) {
18514
18513
  };
18515
18514
  }
18516
18515
  function getAppLabNetworkProxySettingsPath() {
18517
- return join5(DATA_DIR, "network-proxy-settings.json");
18516
+ return join6(DATA_DIR, "network-proxy-settings.json");
18518
18517
  }
18519
18518
  function resolveAppLabNetworkProxyTimeoutMs() {
18520
18519
  const raw = typeof process.env.DISCOVERYLAB_NETWORK_PROXY_MAX_DURATION_MS === "string" ? process.env.DISCOVERYLAB_NETWORK_PROXY_MAX_DURATION_MS.trim() : "";
@@ -18589,8 +18588,8 @@ var llmSettings = {};
18589
18588
  (async () => {
18590
18589
  try {
18591
18590
  const { readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
18592
- const { join: join6 } = await import("path");
18593
- const settingsPath = join6(DATA_DIR, "llm-settings.json");
18591
+ const { join: join7 } = await import("path");
18592
+ const settingsPath = join7(DATA_DIR, "llm-settings.json");
18594
18593
  if (existsSync6(settingsPath)) {
18595
18594
  llmSettings = JSON.parse(readFileSync3(settingsPath, "utf8"));
18596
18595
  if (llmSettings.anthropicApiKey) process.env.ANTHROPIC_API_KEY = llmSettings.anthropicApiKey;
@@ -18618,7 +18617,7 @@ app.get("/api/settings/llm", async (c) => {
18618
18617
  app.put("/api/settings/llm", async (c) => {
18619
18618
  try {
18620
18619
  const { writeFileSync: writeFileSync5 } = await import("fs");
18621
- const { join: join6 } = await import("path");
18620
+ const { join: join7 } = await import("path");
18622
18621
  const body = await c.req.json();
18623
18622
  if (body.anthropicApiKey && !body.anthropicApiKey.startsWith("\u2022\u2022")) {
18624
18623
  llmSettings.anthropicApiKey = body.anthropicApiKey;
@@ -18652,7 +18651,7 @@ app.put("/api/settings/llm", async (c) => {
18652
18651
  if (body.preferredProvider !== void 0) {
18653
18652
  llmSettings.preferredProvider = body.preferredProvider;
18654
18653
  }
18655
- const settingsPath = join6(DATA_DIR, "llm-settings.json");
18654
+ const settingsPath = join7(DATA_DIR, "llm-settings.json");
18656
18655
  writeFileSync5(settingsPath, JSON.stringify(llmSettings, null, 2));
18657
18656
  return c.json({ success: true, message: "LLM settings saved" });
18658
18657
  } catch (error) {
@@ -18664,8 +18663,8 @@ var jiraSettings = {};
18664
18663
  (async () => {
18665
18664
  try {
18666
18665
  const { readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
18667
- const { join: join6 } = await import("path");
18668
- const settingsPath = join6(DATA_DIR, "jira-settings.json");
18666
+ const { join: join7 } = await import("path");
18667
+ const settingsPath = join7(DATA_DIR, "jira-settings.json");
18669
18668
  if (existsSync6(settingsPath)) {
18670
18669
  jiraSettings = JSON.parse(readFileSync3(settingsPath, "utf8"));
18671
18670
  if (jiraSettings.baseUrl) process.env.JIRA_BASE_URL = jiraSettings.baseUrl;
@@ -18688,7 +18687,7 @@ app.get("/api/settings/jira", async (c) => {
18688
18687
  app.put("/api/settings/jira", async (c) => {
18689
18688
  try {
18690
18689
  const { writeFileSync: writeFileSync5 } = await import("fs");
18691
- const { join: join6 } = await import("path");
18690
+ const { join: join7 } = await import("path");
18692
18691
  const body = await c.req.json();
18693
18692
  if (body.baseUrl !== void 0) {
18694
18693
  jiraSettings.baseUrl = body.baseUrl;
@@ -18702,7 +18701,7 @@ app.put("/api/settings/jira", async (c) => {
18702
18701
  jiraSettings.apiToken = body.apiToken;
18703
18702
  process.env.JIRA_API_TOKEN = body.apiToken;
18704
18703
  }
18705
- const settingsPath = join6(DATA_DIR, "jira-settings.json");
18704
+ const settingsPath = join7(DATA_DIR, "jira-settings.json");
18706
18705
  writeFileSync5(settingsPath, JSON.stringify(jiraSettings, null, 2));
18707
18706
  return c.json({ success: true, message: "Jira settings saved" });
18708
18707
  } catch (error) {
@@ -18713,7 +18712,7 @@ app.put("/api/settings/jira", async (c) => {
18713
18712
  var notionSettings = {};
18714
18713
  (async () => {
18715
18714
  try {
18716
- const settingsPath = join5(DATA_DIR, "notion-settings.json");
18715
+ const settingsPath = join6(DATA_DIR, "notion-settings.json");
18717
18716
  if (existsSync5(settingsPath)) {
18718
18717
  notionSettings = JSON.parse(readFileSync2(settingsPath, "utf8"));
18719
18718
  console.log("[Notion Settings] Loaded from file");
@@ -18740,7 +18739,7 @@ app.put("/api/settings/notion", async (c) => {
18740
18739
  notionSettings.lastParentPageId = body.lastParentPageId;
18741
18740
  notionSettings.lastParentPageTitle = body.lastParentPageTitle || "";
18742
18741
  }
18743
- const settingsPath = join5(DATA_DIR, "notion-settings.json");
18742
+ const settingsPath = join6(DATA_DIR, "notion-settings.json");
18744
18743
  writeFileSync4(settingsPath, JSON.stringify(notionSettings, null, 2));
18745
18744
  return c.json({ success: true });
18746
18745
  } catch (error) {
@@ -19038,13 +19037,13 @@ app.post("/api/recorder/stop", async (c) => {
19038
19037
  let projectId = null;
19039
19038
  let ocrInProgress = false;
19040
19039
  try {
19041
- const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync3, copyFileSync: copyFileSync2, mkdirSync: mkdirSync5 } = await import("fs");
19042
- const { join: join6 } = await import("path");
19040
+ const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync5, copyFileSync: copyFileSync2, mkdirSync: mkdirSync5 } = await import("fs");
19041
+ const { join: join7 } = await import("path");
19043
19042
  const { homedir: homedir4 } = await import("os");
19044
19043
  const recordingId = session?.id;
19045
19044
  if (recordingId) {
19046
- const recordingDir = join6(homedir4(), ".discoverylab", "recordings", recordingId);
19047
- const sessionPath = join6(recordingDir, "session.json");
19045
+ const recordingDir = join7(homedir4(), ".discoverylab", "recordings", recordingId);
19046
+ const sessionPath = join7(recordingDir, "session.json");
19048
19047
  if (existsSync6(sessionPath)) {
19049
19048
  const sessionData = JSON.parse(readFileSync3(sessionPath, "utf8"));
19050
19049
  const sessionName = sessionData?.name || sessionData?.session?.name || `Recording ${recordingId}`;
@@ -19060,19 +19059,19 @@ app.post("/api/recorder/stop", async (c) => {
19060
19059
  INSERT INTO projects (id, name, video_path, platform, status, created_at, updated_at)
19061
19060
  VALUES (?, ?, ?, ?, ?, ?, ?)
19062
19061
  `).run(projectId, sessionName, recordingDir, "web", "ready", now, now);
19063
- const framesDir = join6(DATA_DIR, "projects", projectId, "frames");
19062
+ const framesDir = join7(DATA_DIR, "projects", projectId, "frames");
19064
19063
  mkdirSync5(framesDir, { recursive: true });
19065
- const screenshotsDir = join6(recordingDir, "screenshots");
19064
+ const screenshotsDir = join7(recordingDir, "screenshots");
19066
19065
  let thumbnailPath = null;
19067
19066
  let frameCount = 0;
19068
19067
  const screenshotFiles = [];
19069
19068
  if (existsSync6(screenshotsDir)) {
19070
- const screenshots = readdirSync3(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
19069
+ const screenshots = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
19071
19070
  frameCount = screenshots.length;
19072
19071
  screenshotFiles.push(...screenshots);
19073
19072
  screenshots.forEach((file, index) => {
19074
- const src = join6(screenshotsDir, file);
19075
- const dest = join6(framesDir, `frame_${(index + 1).toString().padStart(4, "0")}.png`);
19073
+ const src = join7(screenshotsDir, file);
19074
+ const dest = join7(framesDir, `frame_${(index + 1).toString().padStart(4, "0")}.png`);
19076
19075
  copyFileSync2(src, dest);
19077
19076
  if (index === 0) {
19078
19077
  thumbnailPath = dest;
@@ -19164,15 +19163,15 @@ app.post("/api/recorder/screenshot", async (c) => {
19164
19163
  });
19165
19164
  app.get("/api/recorder/recordings", async (c) => {
19166
19165
  try {
19167
- const { readdirSync: readdirSync3, readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
19168
- const { join: join6 } = await import("path");
19166
+ const { readdirSync: readdirSync5, readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
19167
+ const { join: join7 } = await import("path");
19169
19168
  const { homedir: homedir4 } = await import("os");
19170
- const recordingsDir = join6(homedir4(), ".discoverylab", "recordings");
19169
+ const recordingsDir = join7(homedir4(), ".discoverylab", "recordings");
19171
19170
  if (!existsSync6(recordingsDir)) {
19172
19171
  return c.json({ recordings: [] });
19173
19172
  }
19174
- const recordings = readdirSync3(recordingsDir).filter((dir) => dir.startsWith("rec_")).map((dir) => {
19175
- const sessionPath = join6(recordingsDir, dir, "session.json");
19173
+ const recordings = readdirSync5(recordingsDir).filter((dir) => dir.startsWith("rec_")).map((dir) => {
19174
+ const sessionPath = join7(recordingsDir, dir, "session.json");
19176
19175
  if (existsSync6(sessionPath)) {
19177
19176
  try {
19178
19177
  const session = JSON.parse(readFileSync3(sessionPath, "utf8"));
@@ -19192,18 +19191,18 @@ app.get("/api/recorder/recordings", async (c) => {
19192
19191
  app.get("/api/recorder/recordings/:id", async (c) => {
19193
19192
  try {
19194
19193
  const { id } = c.req.param();
19195
- const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync3 } = await import("fs");
19196
- const { join: join6 } = await import("path");
19194
+ const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync5 } = await import("fs");
19195
+ const { join: join7 } = await import("path");
19197
19196
  const { homedir: homedir4 } = await import("os");
19198
- const recordingDir = join6(homedir4(), ".discoverylab", "recordings", id);
19199
- const sessionPath = join6(recordingDir, "session.json");
19197
+ const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
19198
+ const sessionPath = join7(recordingDir, "session.json");
19200
19199
  if (!existsSync6(sessionPath)) {
19201
19200
  return c.json({ error: "Recording not found" }, 404);
19202
19201
  }
19203
19202
  const session = JSON.parse(readFileSync3(sessionPath, "utf8"));
19204
- const screenshotsDir = join6(recordingDir, "screenshots");
19205
- const screenshots = existsSync6(screenshotsDir) ? readdirSync3(screenshotsDir).filter((f) => f.endsWith(".png")) : [];
19206
- const specPath = join6(recordingDir, "test.spec.ts");
19203
+ const screenshotsDir = join7(recordingDir, "screenshots");
19204
+ const screenshots = existsSync6(screenshotsDir) ? readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")) : [];
19205
+ const specPath = join7(recordingDir, "test.spec.ts");
19207
19206
  const specContent = existsSync6(specPath) ? readFileSync3(specPath, "utf8") : null;
19208
19207
  return c.json({
19209
19208
  session,
@@ -19221,13 +19220,13 @@ app.put("/api/recorder/recordings/:id/spec", async (c) => {
19221
19220
  const body = await c.req.json();
19222
19221
  const specCode = typeof body?.specCode === "string" ? body.specCode : "";
19223
19222
  const { writeFileSync: writeFileSync5, existsSync: fsExistsSync, mkdirSync: mkdirSync5 } = await import("fs");
19224
- const { join: join6 } = await import("path");
19223
+ const { join: join7 } = await import("path");
19225
19224
  const { homedir: homedir4 } = await import("os");
19226
- const recordingDir = join6(homedir4(), ".discoverylab", "recordings", id);
19225
+ const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
19227
19226
  if (!fsExistsSync(recordingDir)) {
19228
19227
  return c.json({ error: "Recording not found" }, 404);
19229
19228
  }
19230
- const specPath = join6(recordingDir, "test.spec.ts");
19229
+ const specPath = join7(recordingDir, "test.spec.ts");
19231
19230
  mkdirSync5(recordingDir, { recursive: true });
19232
19231
  writeFileSync5(specPath, specCode, "utf8");
19233
19232
  const placeholders = parseScriptPlaceholders(specCode);
@@ -19245,10 +19244,10 @@ app.post("/api/recorder/recordings/:id/run", async (c) => {
19245
19244
  const timeout = Number.isFinite(Number(body?.timeout)) ? Number(body.timeout) : void 0;
19246
19245
  const browser = body?.browser === "firefox" || body?.browser === "webkit" || body?.browser === "chromium" ? body.browser : void 0;
19247
19246
  const { readFileSync: readFileSync3, existsSync: fsExistsSync, mkdirSync: mkdirSync5, writeFileSync: writeFileSync5 } = await import("fs");
19248
- const { join: join6 } = await import("path");
19247
+ const { join: join7 } = await import("path");
19249
19248
  const { homedir: homedir4 } = await import("os");
19250
- const recordingDir = join6(homedir4(), ".discoverylab", "recordings", id);
19251
- const specPath = join6(recordingDir, "test.spec.ts");
19249
+ const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
19250
+ const specPath = join7(recordingDir, "test.spec.ts");
19252
19251
  if (!fsExistsSync(recordingDir) || !fsExistsSync(specPath)) {
19253
19252
  return c.json({ error: "Recording spec not found" }, 404);
19254
19253
  }
@@ -19269,11 +19268,11 @@ app.post("/api/recorder/recordings/:id/run", async (c) => {
19269
19268
  envTest: resolved.envTestText
19270
19269
  }, 400);
19271
19270
  }
19272
- const runtimeDir = join6(recordingDir, ".runtime");
19271
+ const runtimeDir = join7(recordingDir, ".runtime");
19273
19272
  mkdirSync5(runtimeDir, { recursive: true });
19274
19273
  const runStamp = Date.now();
19275
- const runtimeSpecPath = join6(runtimeDir, `test.runtime.${runStamp}.spec.ts`);
19276
- const runtimeEnvPath = join6(runtimeDir, `.env.test.${runStamp}`);
19274
+ const runtimeSpecPath = join7(runtimeDir, `test.runtime.${runStamp}.spec.ts`);
19275
+ const runtimeEnvPath = join7(runtimeDir, `.env.test.${runStamp}`);
19277
19276
  writeFileSync5(runtimeSpecPath, applied.code, "utf8");
19278
19277
  writeFileSync5(runtimeEnvPath, resolved.envTestText || "", "utf8");
19279
19278
  const result = await runPlaywrightTest({
@@ -19289,7 +19288,7 @@ app.post("/api/recorder/recordings/:id/run", async (c) => {
19289
19288
  screenshot: body?.screenshot === "on" || body?.screenshot === "only-on-failure" ? body.screenshot : "only-on-failure",
19290
19289
  trace: body?.trace === "on" || body?.trace === "retain-on-failure" ? body.trace : "retain-on-failure"
19291
19290
  },
19292
- outputDir: join6(recordingDir, "playwright-runs", String(runStamp)),
19291
+ outputDir: join7(recordingDir, "playwright-runs", String(runStamp)),
19293
19292
  reporter: "json"
19294
19293
  });
19295
19294
  return c.json({
@@ -19310,9 +19309,9 @@ app.delete("/api/recorder/recordings/:id", async (c) => {
19310
19309
  try {
19311
19310
  const { id } = c.req.param();
19312
19311
  const { rmSync: rmSync3, existsSync: fsExistsSync } = await import("fs");
19313
- const { join: join6 } = await import("path");
19312
+ const { join: join7 } = await import("path");
19314
19313
  const { homedir: homedir4 } = await import("os");
19315
- const recordingDir = join6(homedir4(), ".discoverylab", "recordings", id);
19314
+ const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
19316
19315
  if (!fsExistsSync(recordingDir)) {
19317
19316
  return c.json({ error: "Recording not found" }, 404);
19318
19317
  }
@@ -19322,7 +19321,7 @@ app.delete("/api/recorder/recordings/:id", async (c) => {
19322
19321
  await db.delete(projects).where(eq(projects.id, id));
19323
19322
  await deleteTestVariablesForOwner("web-recording", id);
19324
19323
  await deleteTestVariablesForOwner("project", id);
19325
- const projectDir = join6(PROJECTS_DIR, id);
19324
+ const projectDir = join7(PROJECTS_DIR, id);
19326
19325
  if (fsExistsSync(projectDir)) {
19327
19326
  rmSync3(projectDir, { recursive: true, force: true });
19328
19327
  }
@@ -19335,23 +19334,23 @@ app.delete("/api/recorder/recordings/:id", async (c) => {
19335
19334
  });
19336
19335
  app.delete("/api/recorder/recordings", async (c) => {
19337
19336
  try {
19338
- const { rmSync: rmSync3, existsSync: fsExistsSync, readdirSync: readdirSync3 } = await import("fs");
19339
- const { join: join6 } = await import("path");
19337
+ const { rmSync: rmSync3, existsSync: fsExistsSync, readdirSync: readdirSync5 } = await import("fs");
19338
+ const { join: join7 } = await import("path");
19340
19339
  const { homedir: homedir4 } = await import("os");
19341
19340
  const deletedIds = [];
19342
- const recordingsDir = join6(homedir4(), ".discoverylab", "recordings");
19341
+ const recordingsDir = join7(homedir4(), ".discoverylab", "recordings");
19343
19342
  if (fsExistsSync(recordingsDir)) {
19344
- const dirs = readdirSync3(recordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
19343
+ const dirs = readdirSync5(recordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
19345
19344
  for (const dir of dirs) {
19346
- rmSync3(join6(recordingsDir, dir), { recursive: true, force: true });
19345
+ rmSync3(join7(recordingsDir, dir), { recursive: true, force: true });
19347
19346
  deletedIds.push(dir);
19348
19347
  }
19349
19348
  }
19350
- const mobileRecordingsDir = join6(PROJECTS_DIR, "maestro-recordings");
19349
+ const mobileRecordingsDir = join7(PROJECTS_DIR, "maestro-recordings");
19351
19350
  if (fsExistsSync(mobileRecordingsDir)) {
19352
- const dirs = readdirSync3(mobileRecordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
19351
+ const dirs = readdirSync5(mobileRecordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
19353
19352
  for (const dir of dirs) {
19354
- rmSync3(join6(mobileRecordingsDir, dir), { recursive: true, force: true });
19353
+ rmSync3(join7(mobileRecordingsDir, dir), { recursive: true, force: true });
19355
19354
  deletedIds.push(dir);
19356
19355
  }
19357
19356
  }
@@ -19363,7 +19362,7 @@ app.delete("/api/recorder/recordings", async (c) => {
19363
19362
  await deleteTestVariablesForOwner("web-recording", id);
19364
19363
  await deleteTestVariablesForOwner("mobile-recording", id);
19365
19364
  await deleteTestVariablesForOwner("project", id);
19366
- const projectDir = join6(PROJECTS_DIR, id);
19365
+ const projectDir = join7(PROJECTS_DIR, id);
19367
19366
  if (fsExistsSync(projectDir)) {
19368
19367
  rmSync3(projectDir, { recursive: true, force: true });
19369
19368
  }
@@ -19380,9 +19379,9 @@ app.get("/api/recorder/screenshots/:sessionId/:filename", async (c) => {
19380
19379
  try {
19381
19380
  const { sessionId, filename } = c.req.param();
19382
19381
  const { readFileSync: readFileSync3, existsSync: existsSync6 } = await import("fs");
19383
- const { join: join6 } = await import("path");
19382
+ const { join: join7 } = await import("path");
19384
19383
  const { homedir: homedir4 } = await import("os");
19385
- const filePath = join6(homedir4(), ".discoverylab", "recordings", sessionId, "screenshots", filename);
19384
+ const filePath = join7(homedir4(), ".discoverylab", "recordings", sessionId, "screenshots", filename);
19386
19385
  if (!existsSync6(filePath)) {
19387
19386
  return c.json({ error: "Screenshot not found" }, 404);
19388
19387
  }
@@ -19398,16 +19397,16 @@ app.get("/api/recorder/screenshots/:sessionId/:filename", async (c) => {
19398
19397
  app.get("/api/recorder/screenshot/:filename", async (c) => {
19399
19398
  try {
19400
19399
  const { filename } = c.req.param();
19401
- const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync3 } = await import("fs");
19402
- const { join: join6 } = await import("path");
19400
+ const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync5 } = await import("fs");
19401
+ const { join: join7 } = await import("path");
19403
19402
  const { homedir: homedir4 } = await import("os");
19404
- const recordingsDir = join6(homedir4(), ".discoverylab", "recordings");
19403
+ const recordingsDir = join7(homedir4(), ".discoverylab", "recordings");
19405
19404
  if (!existsSync6(recordingsDir)) {
19406
19405
  return c.json({ error: "No recordings directory" }, 404);
19407
19406
  }
19408
- const sessions = readdirSync3(recordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
19407
+ const sessions = readdirSync5(recordingsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
19409
19408
  for (const sessionId of sessions) {
19410
- const filePath = join6(recordingsDir, sessionId, "screenshots", filename);
19409
+ const filePath = join7(recordingsDir, sessionId, "screenshots", filename);
19411
19410
  if (existsSync6(filePath)) {
19412
19411
  const buffer = readFileSync3(filePath);
19413
19412
  return new Response(buffer, {
@@ -19427,11 +19426,11 @@ app.get("/api/recorder/screenshot/:filename", async (c) => {
19427
19426
  app.post("/api/recorder/recordings/:id/create-project", async (c) => {
19428
19427
  try {
19429
19428
  const { id } = c.req.param();
19430
- const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync3, copyFileSync: copyFileSync2, mkdirSync: mkdirSync5 } = await import("fs");
19431
- const { join: join6, basename: basename4 } = await import("path");
19429
+ const { readFileSync: readFileSync3, existsSync: existsSync6, readdirSync: readdirSync5, copyFileSync: copyFileSync2, mkdirSync: mkdirSync5 } = await import("fs");
19430
+ const { join: join7, basename: basename4 } = await import("path");
19432
19431
  const { homedir: homedir4 } = await import("os");
19433
- const recordingDir = join6(homedir4(), ".discoverylab", "recordings", id);
19434
- const sessionPath = join6(recordingDir, "session.json");
19432
+ const recordingDir = join7(homedir4(), ".discoverylab", "recordings", id);
19433
+ const sessionPath = join7(recordingDir, "session.json");
19435
19434
  if (!existsSync6(sessionPath)) {
19436
19435
  return c.json({ error: "Recording not found" }, 404);
19437
19436
  }
@@ -19457,17 +19456,17 @@ app.post("/api/recorder/recordings/:id/create-project", async (c) => {
19457
19456
  now,
19458
19457
  now
19459
19458
  );
19460
- const framesDir = join6(DATA_DIR, "projects", projectId, "frames");
19459
+ const framesDir = join7(DATA_DIR, "projects", projectId, "frames");
19461
19460
  mkdirSync5(framesDir, { recursive: true });
19462
- const screenshotsDir = join6(recordingDir, "screenshots");
19461
+ const screenshotsDir = join7(recordingDir, "screenshots");
19463
19462
  let thumbnailPath = null;
19464
19463
  let frameCount = 0;
19465
19464
  if (existsSync6(screenshotsDir)) {
19466
- const screenshots = readdirSync3(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
19465
+ const screenshots = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
19467
19466
  frameCount = screenshots.length;
19468
19467
  screenshots.forEach((file, index) => {
19469
- const src = join6(screenshotsDir, file);
19470
- const dest = join6(framesDir, `frame_${(index + 1).toString().padStart(4, "0")}.png`);
19468
+ const src = join7(screenshotsDir, file);
19469
+ const dest = join7(framesDir, `frame_${(index + 1).toString().padStart(4, "0")}.png`);
19471
19470
  copyFileSync2(src, dest);
19472
19471
  if (index === 0) {
19473
19472
  thumbnailPath = dest;
@@ -19499,7 +19498,7 @@ app.post("/api/recorder/recordings/:id/create-project", async (c) => {
19499
19498
  }
19500
19499
  let ocrInProgress = false;
19501
19500
  if (frameCount > 0 && existsSync6(screenshotsDir)) {
19502
- const screenshotFiles = readdirSync3(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
19501
+ const screenshotFiles = readdirSync5(screenshotsDir).filter((f) => f.endsWith(".png")).sort();
19503
19502
  if (screenshotFiles.length > 0) {
19504
19503
  sqlite.prepare(`UPDATE projects SET status = ?, updated_at = ? WHERE id = ?`).run("analyzing", Date.now(), projectId);
19505
19504
  ocrInProgress = true;
@@ -19858,11 +19857,11 @@ app.put("/api/projects/:id/template-content", async (c) => {
19858
19857
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
19859
19858
  };
19860
19859
  const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync5 } = await import("fs");
19861
- const projectDir = join5(PROJECTS_DIR, id);
19860
+ const projectDir = join6(PROJECTS_DIR, id);
19862
19861
  if (!existsSync5(projectDir)) {
19863
19862
  mkdirSync5(projectDir, { recursive: true });
19864
19863
  }
19865
- const contentPath = join5(projectDir, "template-content.json");
19864
+ const contentPath = join6(projectDir, "template-content.json");
19866
19865
  writeFileSync5(contentPath, JSON.stringify(savedContent));
19867
19866
  return c.json({ success: true, message: "Template content saved", content: savedContent });
19868
19867
  } catch (error) {
@@ -19940,7 +19939,7 @@ app.get("/api/templates/player/*", async (c) => {
19940
19939
  return c.json({ error: "Templates not installed" }, 404);
19941
19940
  }
19942
19941
  const requestedPath = c.req.path.replace("/api/templates/player/", "");
19943
- const filePath = join5(bundlePath, requestedPath || "index.html");
19942
+ const filePath = join6(bundlePath, requestedPath || "index.html");
19944
19943
  if (!existsSync5(filePath)) {
19945
19944
  return c.json({ error: "File not found" }, 404);
19946
19945
  }
@@ -20044,7 +20043,6 @@ var TEMPLATE_MAX_DURATION_SECONDS = 60;
20044
20043
  var DEFAULT_ANDROID_DEVICE_MOCKUP = "mockup-android-galaxy.png";
20045
20044
  var ANDROID_DEVICE_MOCKUP_FALLBACKS = [
20046
20045
  DEFAULT_ANDROID_DEVICE_MOCKUP,
20047
- "mockup-android.png",
20048
20046
  "mockup-android-google-pixel-9-pro.png"
20049
20047
  ];
20050
20048
  function getTemplateProjectState(project) {
@@ -20076,7 +20074,7 @@ function resolveTemplateVideoPath(project) {
20076
20074
  return null;
20077
20075
  }
20078
20076
  try {
20079
- if (statSync2(resolvedVideoPath).isDirectory()) {
20077
+ if (statSync3(resolvedVideoPath).isDirectory()) {
20080
20078
  return null;
20081
20079
  }
20082
20080
  } catch {
@@ -20194,11 +20192,11 @@ function listAndroidDeviceMockupIds() {
20194
20192
  if (!bundlePath) {
20195
20193
  return [...ANDROID_DEVICE_MOCKUP_FALLBACKS];
20196
20194
  }
20197
- const publicDir = join5(bundlePath, "public");
20195
+ const publicDir = join6(bundlePath, "public");
20198
20196
  if (!existsSync5(publicDir)) {
20199
20197
  return [...ANDROID_DEVICE_MOCKUP_FALLBACKS];
20200
20198
  }
20201
- const files = readdirSync2(publicDir).filter((file) => /^mockup-android.*\.png$/i.test(file));
20199
+ const files = readdirSync4(publicDir).filter((file) => /^mockup-android.*\.png$/i.test(file));
20202
20200
  const unique = new Set(files.length > 0 ? files : ANDROID_DEVICE_MOCKUP_FALLBACKS);
20203
20201
  return [...unique].sort((left, right) => {
20204
20202
  const leftIndex = ANDROID_DEVICE_MOCKUP_FALLBACKS.indexOf(left);
@@ -20358,7 +20356,7 @@ function groupNetworkIntoTabs(entries) {
20358
20356
  });
20359
20357
  }
20360
20358
  function loadEditedTemplateContent(projectId) {
20361
- const contentPath = join5(PROJECTS_DIR, projectId, "template-content.json");
20359
+ const contentPath = join6(PROJECTS_DIR, projectId, "template-content.json");
20362
20360
  if (!existsSync5(contentPath)) return null;
20363
20361
  try {
20364
20362
  const raw = readFileSync2(contentPath, "utf-8");
@@ -20377,7 +20375,7 @@ function loadEditedTemplateContent(projectId) {
20377
20375
  function loadProjectNetworkData(project) {
20378
20376
  const recordingDir = resolveRecordingBaseDir(project.videoPath);
20379
20377
  if (recordingDir) {
20380
- const sessionPath = join5(recordingDir, "session.json");
20378
+ const sessionPath = join6(recordingDir, "session.json");
20381
20379
  if (existsSync5(sessionPath)) {
20382
20380
  try {
20383
20381
  const session = JSON.parse(readFileSync2(sessionPath, "utf-8"));