hyperframes 0.4.11-alpha.1 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ var VERSION;
54
54
  var init_version = __esm({
55
55
  "src/version.ts"() {
56
56
  "use strict";
57
- VERSION = true ? "0.4.11-alpha.1" : "0.0.0-dev";
57
+ VERSION = true ? "0.4.12" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -5199,7 +5199,9 @@ function readConfig() {
5199
5199
  telemetryNoticeShown: parsed.telemetryNoticeShown ?? DEFAULT_CONFIG.telemetryNoticeShown,
5200
5200
  commandCount: parsed.commandCount ?? DEFAULT_CONFIG.commandCount,
5201
5201
  lastUpdateCheck: parsed.lastUpdateCheck,
5202
- latestVersion: parsed.latestVersion
5202
+ latestVersion: parsed.latestVersion,
5203
+ pendingUpdate: parsed.pendingUpdate,
5204
+ completedUpdate: parsed.completedUpdate
5203
5205
  };
5204
5206
  cachedConfig = config;
5205
5207
  return { ...config };
@@ -5429,8 +5431,8 @@ function flushSync() {
5429
5431
  eventQueue = [];
5430
5432
  const payload = JSON.stringify({ api_key: POSTHOG_API_KEY, batch });
5431
5433
  try {
5432
- const { spawn: spawn12 } = __require("child_process");
5433
- const child = spawn12(
5434
+ const { spawn: spawn13 } = __require("child_process");
5435
+ const child = spawn13(
5434
5436
  process.execPath,
5435
5437
  [
5436
5438
  "-e",
@@ -20492,7 +20494,7 @@ var init_config2 = __esm({
20492
20494
  ffmpegStreamingTimeout: 6e5,
20493
20495
  hdr: false,
20494
20496
  hdrAutoDetect: true,
20495
- audioGain: 1.35,
20497
+ audioGain: 1,
20496
20498
  frameDataUriCacheLimit: 256,
20497
20499
  playerReadyTimeout: 45e3,
20498
20500
  renderReadyTimeout: 15e3,
@@ -22729,6 +22731,37 @@ async function convertSdrToHdr(inputPath, outputPath, signal, config) {
22729
22731
  );
22730
22732
  }
22731
22733
  }
22734
+ async function convertVfrToCfr(inputPath, outputPath, targetFps, startTime, duration, signal, config) {
22735
+ const timeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
22736
+ const args = [
22737
+ "-ss",
22738
+ String(startTime),
22739
+ "-i",
22740
+ inputPath,
22741
+ "-t",
22742
+ String(duration),
22743
+ "-fps_mode",
22744
+ "cfr",
22745
+ "-r",
22746
+ String(targetFps),
22747
+ "-c:v",
22748
+ "libx264",
22749
+ "-preset",
22750
+ "fast",
22751
+ "-crf",
22752
+ "18",
22753
+ "-c:a",
22754
+ "copy",
22755
+ "-y",
22756
+ outputPath
22757
+ ];
22758
+ const result = await runFfmpeg(args, { signal, timeout });
22759
+ if (!result.success) {
22760
+ throw new Error(
22761
+ `VFR\u2192CFR conversion failed (exit ${result.exitCode}): ${result.stderr.slice(-300)}`
22762
+ );
22763
+ }
22764
+ }
22732
22765
  async function extractAllVideoFrames(videos, baseDir, options, signal, config, compiledDir) {
22733
22766
  const startTime = Date.now();
22734
22767
  const extracted = [];
@@ -22786,6 +22819,39 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
22786
22819
  }
22787
22820
  }
22788
22821
  }
22822
+ const vfrNormDir = join21(options.outputDir, "_vfr_normalized");
22823
+ for (let i2 = 0; i2 < resolvedVideos.length; i2++) {
22824
+ if (signal?.aborted) break;
22825
+ const entry = resolvedVideos[i2];
22826
+ if (!entry) continue;
22827
+ const metadata = await extractVideoMetadata(entry.videoPath);
22828
+ if (!metadata.isVFR) continue;
22829
+ let segDuration = entry.video.end - entry.video.start;
22830
+ if (!Number.isFinite(segDuration) || segDuration <= 0) {
22831
+ const sourceRemaining = metadata.durationSeconds - entry.video.mediaStart;
22832
+ segDuration = sourceRemaining > 0 ? sourceRemaining : metadata.durationSeconds;
22833
+ }
22834
+ mkdirSync12(vfrNormDir, { recursive: true });
22835
+ const normalizedPath = join21(vfrNormDir, `${entry.video.id}_cfr.mp4`);
22836
+ try {
22837
+ await convertVfrToCfr(
22838
+ entry.videoPath,
22839
+ normalizedPath,
22840
+ options.fps,
22841
+ entry.video.mediaStart,
22842
+ segDuration,
22843
+ signal,
22844
+ config
22845
+ );
22846
+ entry.videoPath = normalizedPath;
22847
+ entry.video = { ...entry.video, mediaStart: 0 };
22848
+ } catch (err) {
22849
+ errors.push({
22850
+ videoId: entry.video.id,
22851
+ error: err instanceof Error ? err.message : String(err)
22852
+ });
22853
+ }
22854
+ }
22789
22855
  const results = await Promise.all(
22790
22856
  resolvedVideos.map(async ({ video, videoPath }) => {
22791
22857
  if (signal?.aborted) {
@@ -23546,7 +23612,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
23546
23612
  end: element.end,
23547
23613
  mediaStart: element.mediaStart,
23548
23614
  duration: element.end - element.start,
23549
- volume: element.volume || 1
23615
+ volume: element.volume ?? 1
23550
23616
  });
23551
23617
  } catch (err) {
23552
23618
  errors.push(`Error: ${element.id} \u2014 ${err instanceof Error ? err.message : String(err)}`);
@@ -27658,8 +27724,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
27658
27724
  );
27659
27725
  }
27660
27726
  if (metadata.isVFR) {
27661
- console.warn(
27662
- `[Compiler] WARNING: Video "${video.id}" is variable frame rate (VFR). Screen recordings and phone videos are often VFR, which causes stuttering and frame skipping in renders. Re-encode with: ${reencode}`
27727
+ console.info(
27728
+ `[Compiler] Video "${video.id}" is variable frame rate (VFR); the engine will normalize it to CFR before frame extraction. If rendering feels slow on this video, pre-encode once with: ${reencode}`
27663
27729
  );
27664
27730
  }
27665
27731
  }).catch(() => {
@@ -27857,11 +27923,11 @@ import { join as join31, dirname as dirname10, resolve as resolve15 } from "path
27857
27923
  import { randomUUID as randomUUID2 } from "crypto";
27858
27924
  import { freemem as freemem2 } from "os";
27859
27925
  import { fileURLToPath as fileURLToPath3 } from "url";
27860
- async function safeCleanup(label2, fn, log = defaultLogger) {
27926
+ async function safeCleanup(label2, fn, log2 = defaultLogger) {
27861
27927
  try {
27862
27928
  await fn();
27863
27929
  } catch (err) {
27864
- log.debug(`Cleanup failed (${label2})`, {
27930
+ log2.debug(`Cleanup failed (${label2})`, {
27865
27931
  error: err instanceof Error ? err.message : String(err)
27866
27932
  });
27867
27933
  }
@@ -27889,7 +27955,7 @@ function updateJobStatus(job, status, stage, progress, onProgress) {
27889
27955
  if (status === "failed" || status === "complete") job.completedAt = /* @__PURE__ */ new Date();
27890
27956
  if (onProgress) onProgress(job, stage);
27891
27957
  }
27892
- function installDebugLogger(logPath, log = defaultLogger) {
27958
+ function installDebugLogger(logPath, log2 = defaultLogger) {
27893
27959
  const origLog = console.log;
27894
27960
  const origError = console.error;
27895
27961
  const origWarn = console.warn;
@@ -27900,7 +27966,7 @@ function installDebugLogger(logPath, log = defaultLogger) {
27900
27966
  try {
27901
27967
  appendFileSync(logPath, line);
27902
27968
  } catch (err) {
27903
- log.debug("Debug log write failed", {
27969
+ log2.debug("Debug log write failed", {
27904
27970
  logPath,
27905
27971
  error: err instanceof Error ? err.message : String(err)
27906
27972
  });
@@ -27967,15 +28033,15 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
27967
28033
  writeFileSync10(join31(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
27968
28034
  }
27969
28035
  }
27970
- function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
28036
+ function applyRenderModeHints(cfg, compiled, log2 = defaultLogger) {
27971
28037
  if (cfg.forceScreenshot || !compiled.renderModeHints.recommendScreenshot) return;
27972
28038
  cfg.forceScreenshot = true;
27973
- log.warn("Auto-selected screenshot capture mode for render compatibility", {
28039
+ log2.warn("Auto-selected screenshot capture mode for render compatibility", {
27974
28040
  reasonCodes: compiled.renderModeHints.reasons.map((reason) => reason.code),
27975
28041
  reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
27976
28042
  });
27977
28043
  }
27978
- function blitHdrVideoLayer(canvas, el, time, fps, hdrFrameDirs, hdrStartTimes, width, height, log, sourceTransfer, targetTransfer) {
28044
+ function blitHdrVideoLayer(canvas, el, time, fps, hdrFrameDirs, hdrStartTimes, width, height, log2, sourceTransfer, targetTransfer) {
27979
28045
  const frameDir = hdrFrameDirs.get(el.id);
27980
28046
  const startTime = hdrStartTimes.get(el.id);
27981
28047
  if (!frameDir || startTime === void 0) {
@@ -28025,14 +28091,14 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrFrameDirs, hdrStartTimes, w
28025
28091
  );
28026
28092
  }
28027
28093
  } catch (err) {
28028
- if (log) {
28029
- log.debug(`HDR blit failed for ${el.id}`, {
28094
+ if (log2) {
28095
+ log2.debug(`HDR blit failed for ${el.id}`, {
28030
28096
  error: err instanceof Error ? err.message : String(err)
28031
28097
  });
28032
28098
  }
28033
28099
  }
28034
28100
  }
28035
- function blitHdrImageLayer(canvas, el, hdrImageBuffers, width, height, log, sourceTransfer, targetTransfer) {
28101
+ function blitHdrImageLayer(canvas, el, hdrImageBuffers, width, height, log2, sourceTransfer, targetTransfer) {
28036
28102
  const buf = hdrImageBuffers.get(el.id);
28037
28103
  if (!buf) {
28038
28104
  return;
@@ -28074,8 +28140,8 @@ function blitHdrImageLayer(canvas, el, hdrImageBuffers, width, height, log, sour
28074
28140
  );
28075
28141
  }
28076
28142
  } catch (err) {
28077
- if (log) {
28078
- log.debug(`HDR image blit failed for ${el.id}`, {
28143
+ if (log2) {
28144
+ log2.debug(`HDR image blit failed for ${el.id}`, {
28079
28145
  error: err instanceof Error ? err.message : String(err)
28080
28146
  });
28081
28147
  }
@@ -28126,7 +28192,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28126
28192
  const debugDir = join31(producerRoot, ".debug");
28127
28193
  const workDir = job.config.debug ? join31(debugDir, job.id) : join31(dirname10(outputPath), `work-${job.id}`);
28128
28194
  const pipelineStart = Date.now();
28129
- const log = job.config.logger ?? defaultLogger;
28195
+ const log2 = job.config.logger ?? defaultLogger;
28130
28196
  let fileServer = null;
28131
28197
  let probeSession = null;
28132
28198
  let lastBrowserConsole = [];
@@ -28159,7 +28225,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28159
28225
  if (!existsSync28(workDir)) mkdirSync17(workDir, { recursive: true });
28160
28226
  if (job.config.debug) {
28161
28227
  const logPath = join31(workDir, "render.log");
28162
- restoreLogger = installDebugLogger(logPath, log);
28228
+ restoreLogger = installDebugLogger(logPath, log2);
28163
28229
  }
28164
28230
  const entryFile = job.config.entryFile || "index.html";
28165
28231
  let htmlPath = join31(projectDir, entryFile);
@@ -28187,7 +28253,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28187
28253
  }
28188
28254
  writeFileSync10(wrapperPath, standaloneHtml, "utf-8");
28189
28255
  htmlPath = wrapperPath;
28190
- log.info("Extracted standalone entry from index.html host context", {
28256
+ log2.info("Extracted standalone entry from index.html host context", {
28191
28257
  entryFile
28192
28258
  });
28193
28259
  }
@@ -28197,9 +28263,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28197
28263
  let compiled = await compileForRender(projectDir, htmlPath, join31(workDir, "downloads"));
28198
28264
  assertNotAborted();
28199
28265
  perfStages.compileOnlyMs = Date.now() - compileStart;
28200
- applyRenderModeHints(cfg, compiled, log);
28266
+ applyRenderModeHints(cfg, compiled, log2);
28201
28267
  writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
28202
- log.info("Compiled composition metadata", {
28268
+ log2.info("Compiled composition metadata", {
28203
28269
  entryFile,
28204
28270
  staticDuration: compiled.staticDuration,
28205
28271
  width: compiled.width,
@@ -28251,13 +28317,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28251
28317
  if (composition.duration <= 0) {
28252
28318
  const discoveredDuration = await getCompositionDuration(probeSession);
28253
28319
  assertNotAborted();
28254
- log.info("Probed composition duration from browser", {
28320
+ log2.info("Probed composition duration from browser", {
28255
28321
  discoveredDuration,
28256
28322
  staticDuration: compiled.staticDuration
28257
28323
  });
28258
28324
  composition.duration = discoveredDuration;
28259
28325
  } else {
28260
- log.info("Using static duration from data-duration attribute", {
28326
+ log2.info("Using static duration from data-duration attribute", {
28261
28327
  duration: composition.duration
28262
28328
  });
28263
28329
  }
@@ -28387,7 +28453,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28387
28453
  }
28388
28454
  }
28389
28455
  } catch (err) {
28390
- log.warn("Failed to gather browser diagnostics for zero-duration composition", {
28456
+ log2.warn("Failed to gather browser diagnostics for zero-duration composition", {
28391
28457
  error: err instanceof Error ? err.message : String(err)
28392
28458
  });
28393
28459
  diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
@@ -28400,7 +28466,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28400
28466
  (line) => /404|ERR_NAME_NOT_RESOLVED|ERR_CONNECTION_REFUSED|net::ERR_/i.test(line)
28401
28467
  );
28402
28468
  if (failedRequests.length > 0) {
28403
- log.warn("Browser encountered network failures during page load:", {
28469
+ log2.warn("Browser encountered network failures during page load:", {
28404
28470
  failures: failedRequests.slice(0, 10)
28405
28471
  });
28406
28472
  for (const line of failedRequests.slice(0, 5)) {
@@ -28507,13 +28573,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28507
28573
  }
28508
28574
  }
28509
28575
  if (effectiveHdr && outputFormat !== "mp4") {
28510
- log.warn(
28576
+ log2.warn(
28511
28577
  `[Render] HDR source detected but format is ${outputFormat} \u2014 falling back to SDR. Use --format mp4 for HDR10 output.`
28512
28578
  );
28513
28579
  effectiveHdr = void 0;
28514
28580
  }
28515
28581
  if (effectiveHdr) {
28516
- log.info(
28582
+ log2.info(
28517
28583
  `[Render] HDR source detected \u2014 output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
28518
28584
  );
28519
28585
  }
@@ -28568,7 +28634,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28568
28634
  const preset = getEncoderPreset(job.config.quality, outputFormat, encoderHdr);
28569
28635
  job.framesRendered = 0;
28570
28636
  if (hasHdrContent) {
28571
- log.info("[Render] HDR layered composite: z-ordered DOM + native HLG video layers");
28637
+ log2.info("[Render] HDR layered composite: z-ordered DOM + native HLG video layers");
28572
28638
  cfg.forceScreenshot = true;
28573
28639
  const hdrVideoIds = composition.videos.filter((v) => nativeHdrVideoIds.has(v.id)).map((v) => v.id);
28574
28640
  const hdrVideoSrcPaths = /* @__PURE__ */ new Map();
@@ -28611,7 +28677,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28611
28677
  endFrame: Math.ceil((t3.time + t3.duration) * job.config.fps)
28612
28678
  }));
28613
28679
  if (transitionRanges.length > 0) {
28614
- log.info("[Render] Detected shader transitions for HDR compositing", {
28680
+ log2.info("[Render] Detected shader transitions for HDR compositing", {
28615
28681
  count: transitionRanges.length,
28616
28682
  transitions: transitionRanges.map((t3) => ({
28617
28683
  shader: t3.shader,
@@ -28704,7 +28770,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28704
28770
  const result = await runFfmpeg(ffmpegArgs, { signal: abortSignal });
28705
28771
  if (!result.success) {
28706
28772
  hdrDiagnostics.videoExtractionFailures += 1;
28707
- log.error("HDR frame pre-extraction failed; aborting render", {
28773
+ log2.error("HDR frame pre-extraction failed; aborting render", {
28708
28774
  videoId,
28709
28775
  srcPath,
28710
28776
  stderr: result.stderr.slice(-400)
@@ -28746,7 +28812,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28746
28812
  }
28747
28813
  } catch (err) {
28748
28814
  hdrDiagnostics.imageDecodeFailures += 1;
28749
- log.error("HDR image decode failed; aborting render", {
28815
+ log2.error("HDR image decode failed; aborting render", {
28750
28816
  imageId,
28751
28817
  srcPath,
28752
28818
  error: err instanceof Error ? err.message : String(err)
@@ -28790,7 +28856,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28790
28856
  const layers = groupIntoLayers(filteredStacking);
28791
28857
  const shouldLog = debugDumpEnabled && debugFrameIndex >= 0;
28792
28858
  if (shouldLog) {
28793
- log.info("[diag] compositeToBuffer plan", {
28859
+ log2.info("[diag] compositeToBuffer plan", {
28794
28860
  frame: debugFrameIndex,
28795
28861
  time: time.toFixed(3),
28796
28862
  filterSize: elementFilter?.size,
@@ -28821,7 +28887,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28821
28887
  hdrImageBuffers,
28822
28888
  width,
28823
28889
  height,
28824
- log,
28890
+ log2,
28825
28891
  imageTransfers.get(layer.element.id),
28826
28892
  effectiveHdr?.transfer
28827
28893
  );
@@ -28835,7 +28901,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28835
28901
  hdrVideoStartTimes,
28836
28902
  width,
28837
28903
  height,
28838
- log,
28904
+ log2,
28839
28905
  videoTransfers.get(layer.element.id),
28840
28906
  effectiveHdr?.transfer
28841
28907
  );
@@ -28844,7 +28910,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28844
28910
  const after2 = countNonZeroRgb482(canvas);
28845
28911
  if (isHdrImage) {
28846
28912
  const buf = hdrImageBuffers.get(layer.element.id);
28847
- log.info("[diag] hdr layer blit", {
28913
+ log2.info("[diag] hdr layer blit", {
28848
28914
  frame: debugFrameIndex,
28849
28915
  layerIdx,
28850
28916
  id: layer.element.id,
@@ -28860,7 +28926,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28860
28926
  const localTime = time - startTime;
28861
28927
  const frameNum = Math.floor(localTime * job.config.fps) + 1;
28862
28928
  const expectedFrame = frameDir ? join31(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
28863
- log.info("[diag] hdr layer blit", {
28929
+ log2.info("[diag] hdr layer blit", {
28864
28930
  frame: debugFrameIndex,
28865
28931
  layerIdx,
28866
28932
  id: layer.element.id,
@@ -28903,7 +28969,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28903
28969
  const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
28904
28970
  const dumpPath = join31(debugDumpDir, dumpName);
28905
28971
  writeFileSync10(dumpPath, domPng);
28906
- log.info("[diag] dom layer blit", {
28972
+ log2.info("[diag] dom layer blit", {
28907
28973
  frame: debugFrameIndex,
28908
28974
  layerIdx,
28909
28975
  layerIds: layer.elementIds,
@@ -28916,7 +28982,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28916
28982
  });
28917
28983
  }
28918
28984
  } catch (err) {
28919
- log.warn("DOM layer decode/blit failed; skipping overlay", {
28985
+ log2.warn("DOM layer decode/blit failed; skipping overlay", {
28920
28986
  layerIds: layer.elementIds,
28921
28987
  error: err instanceof Error ? err.message : String(err)
28922
28988
  });
@@ -28925,7 +28991,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28925
28991
  }
28926
28992
  if (shouldLog && debugDumpDir) {
28927
28993
  const finalNonZero = countNonZeroRgb482(canvas);
28928
- log.info("[diag] compositeToBuffer end", {
28994
+ log2.info("[diag] compositeToBuffer end", {
28929
28995
  frame: debugFrameIndex,
28930
28996
  finalNonZeroPixels: finalNonZero,
28931
28997
  totalPixels: width * height,
@@ -28954,7 +29020,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28954
29020
  );
28955
29021
  if (i2 % 30 === 0) {
28956
29022
  const hdrEl = stackingInfo.find((e2) => e2.isHdr);
28957
- log.debug("[Render] HDR layer composite frame", {
29023
+ log2.debug("[Render] HDR layer composite frame", {
28958
29024
  frame: i2,
28959
29025
  time: time.toFixed(2),
28960
29026
  hdrElement: hdrEl ? { z: hdrEl.zIndex, visible: hdrEl.visible, width: hdrEl.width } : null,
@@ -28987,7 +29053,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
28987
29053
  hdrImageBuffers,
28988
29054
  width,
28989
29055
  height,
28990
- log,
29056
+ log2,
28991
29057
  imageTransfers.get(el.id),
28992
29058
  effectiveHdr?.transfer
28993
29059
  );
@@ -29001,7 +29067,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29001
29067
  hdrVideoStartTimes,
29002
29068
  width,
29003
29069
  height,
29004
- log,
29070
+ log2,
29005
29071
  videoTransfers.get(el.id),
29006
29072
  effectiveHdr?.transfer
29007
29073
  );
@@ -29027,7 +29093,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29027
29093
  effectiveHdr.transfer
29028
29094
  );
29029
29095
  } catch (err) {
29030
- log.warn("DOM layer decode/blit failed; skipping overlay for transition scene", {
29096
+ log2.warn("DOM layer decode/blit failed; skipping overlay for transition scene", {
29031
29097
  frameIndex: i2,
29032
29098
  sceneIds: Array.from(sceneIds),
29033
29099
  error: err instanceof Error ? err.message : String(err)
@@ -29059,7 +29125,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29059
29125
  try {
29060
29126
  rmSync6(frameDir, { recursive: true, force: true });
29061
29127
  } catch (err) {
29062
- log.warn("Failed to clean up HDR frame directory", {
29128
+ log2.warn("Failed to clean up HDR frame directory", {
29063
29129
  videoId,
29064
29130
  frameDir,
29065
29131
  error: err instanceof Error ? err.message : String(err)
@@ -29367,7 +29433,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29367
29433
  try {
29368
29434
  writeFileSync10(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
29369
29435
  } catch (err) {
29370
- log.debug("Failed to write perf summary", {
29436
+ log2.debug("Failed to write perf summary", {
29371
29437
  perfOutputPath,
29372
29438
  error: err instanceof Error ? err.message : String(err)
29373
29439
  });
@@ -29379,14 +29445,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29379
29445
  copyFileSync2(outputPath, debugOutput);
29380
29446
  }
29381
29447
  } else if (process.env.KEEP_TEMP === "1") {
29382
- log.info("KEEP_TEMP=1 \u2014 leaving workDir on disk for inspection", { workDir });
29448
+ log2.info("KEEP_TEMP=1 \u2014 leaving workDir on disk for inspection", { workDir });
29383
29449
  } else {
29384
29450
  await safeCleanup(
29385
29451
  "remove workDir",
29386
29452
  () => {
29387
29453
  rmSync6(workDir, { recursive: true, force: true });
29388
29454
  },
29389
- log
29455
+ log2
29390
29456
  );
29391
29457
  }
29392
29458
  if (restoreLogger) restoreLogger();
@@ -29401,12 +29467,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29401
29467
  () => {
29402
29468
  fs4.close();
29403
29469
  },
29404
- log
29470
+ log2
29405
29471
  );
29406
29472
  }
29407
29473
  if (probeSession) {
29408
29474
  const session = probeSession;
29409
- await safeCleanup("close probe session (cancel)", () => closeCaptureSession(session), log);
29475
+ await safeCleanup("close probe session (cancel)", () => closeCaptureSession(session), log2);
29410
29476
  }
29411
29477
  if (!job.config.debug) {
29412
29478
  await safeCleanup(
@@ -29414,7 +29480,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29414
29480
  () => {
29415
29481
  rmSync6(workDir, { recursive: true, force: true });
29416
29482
  },
29417
- log
29483
+ log2
29418
29484
  );
29419
29485
  }
29420
29486
  if (restoreLogger) restoreLogger();
@@ -29425,7 +29491,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29425
29491
  const isTimeoutError = errorMessage.includes("Waiting failed") || errorMessage.includes("timeout exceeded") || errorMessage.includes("Navigation timeout");
29426
29492
  const wasParallel = job.config.workers !== 1;
29427
29493
  if (isTimeoutError && wasParallel) {
29428
- log.warn(
29494
+ log2.warn(
29429
29495
  `Parallel capture timed out with ${job.config.workers ?? "auto"} workers. Video-heavy compositions often need sequential capture. Retry with --workers 1`
29430
29496
  );
29431
29497
  }
@@ -29450,12 +29516,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29450
29516
  () => {
29451
29517
  fs4.close();
29452
29518
  },
29453
- log
29519
+ log2
29454
29520
  );
29455
29521
  }
29456
29522
  if (probeSession) {
29457
29523
  const session = probeSession;
29458
- await safeCleanup("close probe session (error)", () => closeCaptureSession(session), log);
29524
+ await safeCleanup("close probe session (error)", () => closeCaptureSession(session), log2);
29459
29525
  }
29460
29526
  if (!job.config.debug) {
29461
29527
  await safeCleanup(
@@ -29463,7 +29529,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
29463
29529
  () => {
29464
29530
  if (existsSync28(workDir)) rmSync6(workDir, { recursive: true, force: true });
29465
29531
  },
29466
- log
29532
+ log2
29467
29533
  );
29468
29534
  }
29469
29535
  if (restoreLogger) restoreLogger();
@@ -29727,12 +29793,12 @@ async function prepareRenderBody(body) {
29727
29793
  }
29728
29794
  };
29729
29795
  }
29730
- function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
29796
+ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log2) {
29731
29797
  try {
29732
29798
  return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
29733
29799
  } catch (error) {
29734
29800
  const fallbackPath = resolve17(rendersDir, `producer-fallback-${Date.now()}.mp4`);
29735
- log.warn("Failed to resolve output path, using fallback", {
29801
+ log2.warn("Failed to resolve output path, using fallback", {
29736
29802
  fallback: fallbackPath,
29737
29803
  error: error instanceof Error ? error.message : String(error)
29738
29804
  });
@@ -29764,19 +29830,19 @@ function createArtifactStore(ttlMs) {
29764
29830
  }
29765
29831
  };
29766
29832
  }
29767
- function cleanupTempDir(dir, log) {
29833
+ function cleanupTempDir(dir, log2) {
29768
29834
  if (!dir) return;
29769
29835
  try {
29770
29836
  rmSync7(dir, { recursive: true, force: true });
29771
29837
  } catch (error) {
29772
- log.warn("Failed to cleanup temp project dir", {
29838
+ log2.warn("Failed to cleanup temp project dir", {
29773
29839
  cleanupProjectDir: dir,
29774
29840
  error: error instanceof Error ? error.message : String(error)
29775
29841
  });
29776
29842
  }
29777
29843
  }
29778
29844
  function createRenderHandlers(options = {}) {
29779
- const log = options.logger ?? defaultLogger;
29845
+ const log2 = options.logger ?? defaultLogger;
29780
29846
  const getRequestId = options.getRequestId ?? ((c2) => c2.req.header("x-request-id") || crypto2.randomUUID());
29781
29847
  const outputUrlPrefix = options.outputUrlPrefix ?? "/outputs";
29782
29848
  const rendersDir = options.rendersDir ?? process.env.PRODUCER_RENDERS_DIR ?? "/tmp";
@@ -29803,7 +29869,7 @@ function createRenderHandlers(options = {}) {
29803
29869
  return c2.json({ success: false, requestId, error: preparedResult.error }, 400);
29804
29870
  }
29805
29871
  const result = runHyperframeLint(preparedResult.prepared);
29806
- log.info("lint completed", {
29872
+ log2.info("lint completed", {
29807
29873
  requestId,
29808
29874
  entryFile: preparedResult.prepared.entryFile,
29809
29875
  source: preparedResult.prepared.source,
@@ -29836,12 +29902,12 @@ function createRenderHandlers(options = {}) {
29836
29902
  input.projectDir,
29837
29903
  input.outputPath,
29838
29904
  rendersDir,
29839
- log
29905
+ log2
29840
29906
  );
29841
29907
  const outputDir = dirname11(absoluteOutputPath);
29842
29908
  if (!existsSync30(outputDir)) mkdirSync18(outputDir, { recursive: true });
29843
29909
  const release2 = await renderSemaphore.acquire();
29844
- log.info("render started", {
29910
+ log2.info("render started", {
29845
29911
  requestId,
29846
29912
  projectDir: input.projectDir,
29847
29913
  fps: input.fps,
@@ -29855,7 +29921,7 @@ function createRenderHandlers(options = {}) {
29855
29921
  useGpu: input.useGpu,
29856
29922
  debug: input.debug,
29857
29923
  entryFile: input.entryFile,
29858
- logger: log
29924
+ logger: log2
29859
29925
  });
29860
29926
  let lastLoggedPct = -10;
29861
29927
  try {
@@ -29863,14 +29929,14 @@ function createRenderHandlers(options = {}) {
29863
29929
  const pct = Math.floor(j2.progress * 100);
29864
29930
  if (pct >= lastLoggedPct + 10) {
29865
29931
  lastLoggedPct = pct;
29866
- log.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
29932
+ log2.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
29867
29933
  }
29868
29934
  });
29869
29935
  const fileSize = existsSync30(absoluteOutputPath) ? statSync9(absoluteOutputPath).size : 0;
29870
29936
  const durationMs = Date.now() - t0;
29871
29937
  const outputToken = store.register(absoluteOutputPath);
29872
29938
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
29873
- log.info("render completed", {
29939
+ log2.info("render completed", {
29874
29940
  requestId,
29875
29941
  durationMs,
29876
29942
  fileSize,
@@ -29890,7 +29956,7 @@ function createRenderHandlers(options = {}) {
29890
29956
  } catch (error) {
29891
29957
  const durationMs = Date.now() - t0;
29892
29958
  const errorMsg = error instanceof Error ? error.message : String(error);
29893
- log.error("render failed", {
29959
+ log2.error("render failed", {
29894
29960
  requestId,
29895
29961
  durationMs,
29896
29962
  error: errorMsg,
@@ -29909,7 +29975,7 @@ function createRenderHandlers(options = {}) {
29909
29975
  );
29910
29976
  } finally {
29911
29977
  release2();
29912
- cleanupTempDir(cleanupProjectDir, log);
29978
+ cleanupTempDir(cleanupProjectDir, log2);
29913
29979
  }
29914
29980
  };
29915
29981
  const renderStream = (c2) => {
@@ -29947,11 +30013,11 @@ function createRenderHandlers(options = {}) {
29947
30013
  input.projectDir,
29948
30014
  input.outputPath,
29949
30015
  rendersDir,
29950
- log
30016
+ log2
29951
30017
  );
29952
30018
  const outputDir = dirname11(absoluteOutputPath);
29953
30019
  if (!existsSync30(outputDir)) mkdirSync18(outputDir, { recursive: true });
29954
- log.info("render-stream started", { requestId, projectDir: input.projectDir });
30020
+ log2.info("render-stream started", { requestId, projectDir: input.projectDir });
29955
30021
  const job = createRenderJob({
29956
30022
  fps: input.fps,
29957
30023
  quality: input.quality,
@@ -29960,7 +30026,7 @@ function createRenderHandlers(options = {}) {
29960
30026
  useGpu: input.useGpu,
29961
30027
  debug: input.debug,
29962
30028
  entryFile: input.entryFile,
29963
- logger: log
30029
+ logger: log2
29964
30030
  });
29965
30031
  const abortController = new AbortController();
29966
30032
  const onRequestAbort = () => abortController.abort(new RenderCancelledError("request_aborted"));
@@ -29998,7 +30064,7 @@ function createRenderHandlers(options = {}) {
29998
30064
  const fileSize = existsSync30(absoluteOutputPath) ? statSync9(absoluteOutputPath).size : 0;
29999
30065
  const outputToken = store.register(absoluteOutputPath);
30000
30066
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
30001
- log.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
30067
+ log2.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
30002
30068
  await stream.writeSSE({
30003
30069
  data: JSON.stringify({
30004
30070
  type: "complete",
@@ -30025,7 +30091,7 @@ function createRenderHandlers(options = {}) {
30025
30091
  }
30026
30092
  const errorMsg = error instanceof Error ? error.message : String(error);
30027
30093
  const elapsedMs = Date.now() - t0;
30028
- log.error("render-stream failed", {
30094
+ log2.error("render-stream failed", {
30029
30095
  requestId,
30030
30096
  elapsedMs,
30031
30097
  error: errorMsg,
@@ -30044,7 +30110,7 @@ function createRenderHandlers(options = {}) {
30044
30110
  } finally {
30045
30111
  release2();
30046
30112
  c2.req.raw.signal.removeEventListener("abort", onRequestAbort);
30047
- cleanupTempDir(cleanupProjectDir, log);
30113
+ cleanupTempDir(cleanupProjectDir, log2);
30048
30114
  }
30049
30115
  });
30050
30116
  };
@@ -30087,22 +30153,22 @@ function createProducerApp(options = {}) {
30087
30153
  }
30088
30154
  function startServer(options = {}) {
30089
30155
  const port = options.port ?? parseInt(process.env.PRODUCER_PORT ?? "9847", 10);
30090
- const log = options.logger ?? defaultLogger;
30156
+ const log2 = options.logger ?? defaultLogger;
30091
30157
  const app = createProducerApp(options);
30092
30158
  const server = serve3({ fetch: app.fetch, port }, () => {
30093
- log.info(`Listening on http://localhost:${port}`);
30159
+ log2.info(`Listening on http://localhost:${port}`);
30094
30160
  });
30095
30161
  server.setTimeout(0);
30096
30162
  server.requestTimeout = 0;
30097
30163
  server.keepAliveTimeout = 0;
30098
30164
  function shutdown(signal) {
30099
- log.info(`Received ${signal}, shutting down`);
30165
+ log2.info(`Received ${signal}, shutting down`);
30100
30166
  server.close(() => {
30101
- log.info("Server closed");
30167
+ log2.info("Server closed");
30102
30168
  process.exit(0);
30103
30169
  });
30104
30170
  setTimeout(() => {
30105
- log.warn("Forced exit after 30s timeout");
30171
+ log2.warn("Forced exit after 30s timeout");
30106
30172
  process.exit(1);
30107
30173
  }, 3e4).unref();
30108
30174
  }
@@ -37824,7 +37890,7 @@ var require_node = __commonJS({
37824
37890
  var tty = __require("tty");
37825
37891
  var util = __require("util");
37826
37892
  exports.init = init;
37827
- exports.log = log;
37893
+ exports.log = log2;
37828
37894
  exports.formatArgs = formatArgs;
37829
37895
  exports.save = save;
37830
37896
  exports.load = load;
@@ -37959,7 +38025,7 @@ var require_node = __commonJS({
37959
38025
  }
37960
38026
  return (/* @__PURE__ */ new Date()).toISOString() + " ";
37961
38027
  }
37962
- function log(...args) {
38028
+ function log2(...args) {
37963
38029
  return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + "\n");
37964
38030
  }
37965
38031
  function save(namespaces) {
@@ -47424,7 +47490,7 @@ var require_logging_utils = __commonJS({
47424
47490
  exports.getDebugBackend = getDebugBackend;
47425
47491
  exports.getStructuredBackend = getStructuredBackend;
47426
47492
  exports.setBackend = setBackend;
47427
- exports.log = log;
47493
+ exports.log = log2;
47428
47494
  var events_1 = __require("events");
47429
47495
  var process2 = __importStar(__require("process"));
47430
47496
  var util = __importStar(__require("util"));
@@ -47456,7 +47522,7 @@ var require_logging_utils = __commonJS({
47456
47522
  this.func.info = (...args) => this.invokeSeverity(LogSeverity.INFO, ...args);
47457
47523
  this.func.warn = (...args) => this.invokeSeverity(LogSeverity.WARNING, ...args);
47458
47524
  this.func.error = (...args) => this.invokeSeverity(LogSeverity.ERROR, ...args);
47459
- this.func.sublog = (namespace2) => log(namespace2, this.func);
47525
+ this.func.sublog = (namespace2) => log2(namespace2, this.func);
47460
47526
  }
47461
47527
  invoke(fields, ...args) {
47462
47528
  if (this.upstream) {
@@ -47623,7 +47689,7 @@ var require_logging_utils = __commonJS({
47623
47689
  cachedBackend = backend;
47624
47690
  loggerCache.clear();
47625
47691
  }
47626
- function log(namespace, parent) {
47692
+ function log2(namespace, parent) {
47627
47693
  if (!cachedBackend) {
47628
47694
  const enablesFlag = process2.env[exports.env.nodeEnables];
47629
47695
  if (!enablesFlag) {
@@ -47756,7 +47822,7 @@ var require_src5 = __commonJS({
47756
47822
  exports.HEADER_NAME = "Metadata-Flavor";
47757
47823
  exports.HEADER_VALUE = "Google";
47758
47824
  exports.HEADERS = Object.freeze({ [exports.HEADER_NAME]: exports.HEADER_VALUE });
47759
- var log = logger.log("gcp-metadata");
47825
+ var log2 = logger.log("gcp-metadata");
47760
47826
  exports.METADATA_SERVER_DETECTION = Object.freeze({
47761
47827
  "assume-present": "don't try to ping the metadata server, but assume it's present",
47762
47828
  none: "don't try to ping the metadata server, but don't try to use it either",
@@ -47819,9 +47885,9 @@ var require_src5 = __commonJS({
47819
47885
  responseType: "text",
47820
47886
  timeout: requestTimeout()
47821
47887
  };
47822
- log.info("instance request %j", req);
47888
+ log2.info("instance request %j", req);
47823
47889
  const res = await requestMethod(req);
47824
- log.info("instance metadata is %s", res.data);
47890
+ log2.info("instance metadata is %s", res.data);
47825
47891
  const metadataFlavor = res.headers.get(exports.HEADER_NAME);
47826
47892
  if (metadataFlavor !== exports.HEADER_VALUE) {
47827
47893
  throw new RangeError(`Invalid response from metadata service: incorrect ${exports.HEADER_NAME} header. Expected '${exports.HEADER_VALUE}', got ${metadataFlavor ? `'${metadataFlavor}'` : "no header"}`);
@@ -78025,8 +78091,8 @@ var init_capture2 = __esm({
78025
78091
  } catch (err) {
78026
78092
  const errMsg = err instanceof Error ? err.message : String(err);
78027
78093
  try {
78028
- const { mkdirSync: mkdirSync29, writeFileSync: writeFileSync23 } = await import("fs");
78029
- mkdirSync29(outputDir, { recursive: true });
78094
+ const { mkdirSync: mkdirSync30, writeFileSync: writeFileSync23 } = await import("fs");
78095
+ mkdirSync30(outputDir, { recursive: true });
78030
78096
  const isTimeout = /timeout|timed out/i.test(errMsg);
78031
78097
  const reason = isTimeout ? "Page navigation timed out \u2014 the site may be blocking headless browsers or requires authentication." : `Capture failed: ${errMsg}`;
78032
78098
  writeFileSync23(
@@ -78093,6 +78159,256 @@ var init_telemetry2 = __esm({
78093
78159
  }
78094
78160
  });
78095
78161
 
78162
+ // src/utils/installerDetection.ts
78163
+ import { realpathSync } from "fs";
78164
+ import { posix as posix2 } from "path";
78165
+ function resolveEntry() {
78166
+ const entry = process.argv[1];
78167
+ if (!entry) return null;
78168
+ try {
78169
+ return realpathSync(entry);
78170
+ } catch {
78171
+ return entry;
78172
+ }
78173
+ }
78174
+ function normalizePath(path2) {
78175
+ return path2.replaceAll("\\", "/");
78176
+ }
78177
+ function isWorkspaceLink(realEntry) {
78178
+ const normalized = normalizePath(realEntry);
78179
+ return normalized.includes("/packages/cli/");
78180
+ }
78181
+ function isEphemeralExec(realEntry) {
78182
+ const normalized = normalizePath(realEntry);
78183
+ return normalized.includes("/_npx/") || normalized.includes("/.npm/_npx/") || posix2.basename(posix2.dirname(normalized)).startsWith("bunx-");
78184
+ }
78185
+ function isHomebrewInstall(realEntry) {
78186
+ return normalizePath(realEntry).includes("/Cellar/hyperframes/");
78187
+ }
78188
+ function detectInstaller() {
78189
+ const realEntry = resolveEntry();
78190
+ if (!realEntry) {
78191
+ return {
78192
+ kind: "skip",
78193
+ installCommand: () => null,
78194
+ reason: "Could not resolve process entry path"
78195
+ };
78196
+ }
78197
+ const normalizedEntry = normalizePath(realEntry);
78198
+ if (isWorkspaceLink(realEntry)) {
78199
+ return {
78200
+ kind: "skip",
78201
+ installCommand: () => null,
78202
+ reason: "Running from a workspace link (monorepo dev)"
78203
+ };
78204
+ }
78205
+ if (isEphemeralExec(realEntry)) {
78206
+ return {
78207
+ kind: "skip",
78208
+ installCommand: () => null,
78209
+ reason: "Running via ephemeral exec (npx / bunx)"
78210
+ };
78211
+ }
78212
+ if (isHomebrewInstall(realEntry)) {
78213
+ return {
78214
+ kind: "brew",
78215
+ // Updating a brew formula isn't a straight `install`; the formula needs
78216
+ // to have been published. Defer to `brew upgrade` which is a no-op if
78217
+ // the tap hasn't caught up.
78218
+ installCommand: () => "brew upgrade hyperframes",
78219
+ reason: `Homebrew install detected at ${realEntry}`
78220
+ };
78221
+ }
78222
+ if (normalizedEntry.includes("/.bun/")) {
78223
+ return {
78224
+ kind: "bun",
78225
+ installCommand: (version) => `bun add -g hyperframes@${version}`,
78226
+ reason: `bun global install detected at ${realEntry}`
78227
+ };
78228
+ }
78229
+ if (normalizedEntry.includes("/pnpm/global/")) {
78230
+ return {
78231
+ kind: "pnpm",
78232
+ installCommand: (version) => `pnpm add -g hyperframes@${version}`,
78233
+ reason: `pnpm global install detected at ${realEntry}`
78234
+ };
78235
+ }
78236
+ if (normalizedEntry.includes("/lib/node_modules/hyperframes/") || normalizedEntry.includes("/npm/node_modules/hyperframes/")) {
78237
+ return {
78238
+ kind: "npm",
78239
+ installCommand: (version) => `npm install -g hyperframes@${version}`,
78240
+ reason: `npm global install detected at ${realEntry}`
78241
+ };
78242
+ }
78243
+ return {
78244
+ kind: "skip",
78245
+ installCommand: () => null,
78246
+ reason: `Unknown install layout at ${realEntry}`
78247
+ };
78248
+ }
78249
+ var init_installerDetection = __esm({
78250
+ "src/utils/installerDetection.ts"() {
78251
+ "use strict";
78252
+ }
78253
+ });
78254
+
78255
+ // src/utils/autoUpdate.ts
78256
+ var autoUpdate_exports = {};
78257
+ __export(autoUpdate_exports, {
78258
+ reportCompletedUpdate: () => reportCompletedUpdate,
78259
+ scheduleBackgroundInstall: () => scheduleBackgroundInstall
78260
+ });
78261
+ import { spawn as spawn12 } from "child_process";
78262
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync29, openSync } from "fs";
78263
+ import { homedir as homedir10 } from "os";
78264
+ import { join as join53 } from "path";
78265
+ import { compareVersions as compareVersions2 } from "compare-versions";
78266
+ function isAutoInstallDisabled() {
78267
+ if (isDevMode()) return true;
78268
+ if (process.env["CI"] === "true" || process.env["CI"] === "1") return true;
78269
+ if (process.env["HYPERFRAMES_NO_UPDATE_CHECK"] === "1") return true;
78270
+ if (process.env["HYPERFRAMES_NO_AUTO_INSTALL"] === "1") return true;
78271
+ return false;
78272
+ }
78273
+ function majorOf(version) {
78274
+ const match = /^(\d+)\./.exec(version);
78275
+ return match?.[1] ? Number.parseInt(match[1], 10) : Number.NaN;
78276
+ }
78277
+ function log(line) {
78278
+ try {
78279
+ mkdirSync29(CONFIG_DIR2, { recursive: true, mode: 448 });
78280
+ appendFileSync2(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
78281
+ `, { mode: 384 });
78282
+ } catch {
78283
+ }
78284
+ }
78285
+ function launchDetachedInstall(installCommand, version) {
78286
+ mkdirSync29(CONFIG_DIR2, { recursive: true, mode: 448 });
78287
+ const configFile = join53(CONFIG_DIR2, "config.json");
78288
+ const nodeScript = `
78289
+ const { exec } = require("node:child_process");
78290
+ const { readFileSync, renameSync, writeFileSync } = require("node:fs");
78291
+ const CFG = ${JSON.stringify(configFile)};
78292
+ const TMP = \`\${CFG}.tmp\`;
78293
+ const VERSION = ${JSON.stringify(version)};
78294
+ const CMD = ${JSON.stringify(installCommand)};
78295
+ exec(CMD, { windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (err, _stdout, stderr) => {
78296
+ let cfg = {};
78297
+ try { cfg = JSON.parse(readFileSync(CFG, "utf-8")); } catch (e) {}
78298
+ cfg.completedUpdate = {
78299
+ version: VERSION,
78300
+ ok: !err,
78301
+ finishedAt: new Date().toISOString(),
78302
+ ...(err ? { error: String(stderr || err.message || "install failed").slice(-400) } : {}),
78303
+ };
78304
+ delete cfg.pendingUpdate;
78305
+ try {
78306
+ writeFileSync(TMP, JSON.stringify(cfg, null, 2) + "\\n", { mode: 0o600 });
78307
+ renameSync(TMP, CFG);
78308
+ } catch (e) {}
78309
+ });
78310
+ `;
78311
+ const out = openSync(LOG_FILE, "a", 384);
78312
+ const child = spawn12(process.execPath, ["-e", nodeScript], {
78313
+ detached: true,
78314
+ stdio: ["ignore", out, out],
78315
+ windowsHide: true,
78316
+ env: { ...process.env, HYPERFRAMES_NO_UPDATE_CHECK: "1", HYPERFRAMES_NO_AUTO_INSTALL: "1" }
78317
+ });
78318
+ child.unref();
78319
+ log(`[launch] pid=${child.pid ?? "?"} cmd=${installCommand} version=${version}`);
78320
+ }
78321
+ function scheduleBackgroundInstall(latestVersion, currentVersion) {
78322
+ if (isAutoInstallDisabled()) return false;
78323
+ if (!latestVersion || !currentVersion) return false;
78324
+ let cmp;
78325
+ try {
78326
+ cmp = compareVersions2(latestVersion, currentVersion);
78327
+ } catch {
78328
+ return false;
78329
+ }
78330
+ if (cmp <= 0) return false;
78331
+ const latestMajor = majorOf(latestVersion);
78332
+ const currentMajor = majorOf(currentVersion);
78333
+ if (Number.isFinite(latestMajor) && Number.isFinite(currentMajor) && latestMajor > currentMajor) {
78334
+ log(`[skip] major-bump ${currentVersion} -> ${latestVersion}`);
78335
+ return false;
78336
+ }
78337
+ const installer = detectInstaller();
78338
+ if (installer.kind === "skip") {
78339
+ log(`[skip] ${installer.reason}`);
78340
+ return false;
78341
+ }
78342
+ const installCommand = installer.installCommand(latestVersion);
78343
+ if (!installCommand) return false;
78344
+ const config = readConfig();
78345
+ if (config.pendingUpdate) {
78346
+ const startedAt = Date.parse(config.pendingUpdate.startedAt);
78347
+ const age = Number.isFinite(startedAt) ? Date.now() - startedAt : Number.POSITIVE_INFINITY;
78348
+ if (age < PENDING_TIMEOUT_MS && config.pendingUpdate.version === latestVersion) {
78349
+ return false;
78350
+ }
78351
+ }
78352
+ if (config.completedUpdate && config.completedUpdate.version === latestVersion) {
78353
+ return false;
78354
+ }
78355
+ config.pendingUpdate = {
78356
+ version: latestVersion,
78357
+ command: installCommand,
78358
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
78359
+ };
78360
+ writeConfig(config);
78361
+ try {
78362
+ launchDetachedInstall(installCommand, latestVersion);
78363
+ return true;
78364
+ } catch (err) {
78365
+ log(`[error] spawn failed: ${String(err)}`);
78366
+ const rollback = readConfig();
78367
+ delete rollback.pendingUpdate;
78368
+ writeConfig(rollback);
78369
+ return false;
78370
+ }
78371
+ }
78372
+ function reportCompletedUpdate() {
78373
+ if (process.env["HYPERFRAMES_NO_UPDATE_CHECK"] === "1") return;
78374
+ const config = readConfig();
78375
+ const done = config.completedUpdate;
78376
+ if (!done) return;
78377
+ if (done.ok) {
78378
+ delete config.completedUpdate;
78379
+ writeConfig(config);
78380
+ } else if (!done.reported) {
78381
+ config.completedUpdate = { ...done, reported: true };
78382
+ writeConfig(config);
78383
+ } else {
78384
+ return;
78385
+ }
78386
+ if (!process.stderr.isTTY) return;
78387
+ if (done.ok) {
78388
+ process.stderr.write(` hyperframes auto-updated to v${done.version}
78389
+
78390
+ `);
78391
+ } else if (!done.reported) {
78392
+ process.stderr.write(
78393
+ ` hyperframes auto-update to v${done.version} failed. Run \`hyperframes upgrade\` to retry.
78394
+
78395
+ `
78396
+ );
78397
+ }
78398
+ }
78399
+ var CONFIG_DIR2, LOG_FILE, PENDING_TIMEOUT_MS;
78400
+ var init_autoUpdate = __esm({
78401
+ "src/utils/autoUpdate.ts"() {
78402
+ "use strict";
78403
+ init_config();
78404
+ init_env();
78405
+ init_installerDetection();
78406
+ CONFIG_DIR2 = join53(homedir10(), ".hyperframes");
78407
+ LOG_FILE = join53(CONFIG_DIR2, "auto-update.log");
78408
+ PENDING_TIMEOUT_MS = 10 * 60 * 1e3;
78409
+ }
78410
+ });
78411
+
78096
78412
  // import("./commands/**/*.js") in src/help.ts
78097
78413
  var globImport_commands_js;
78098
78414
  var init_ = __esm({
@@ -78294,10 +78610,15 @@ if (!isHelp && command !== "telemetry" && command !== "unknown") {
78294
78610
  });
78295
78611
  }
78296
78612
  if (!isHelp && !hasJsonFlag && command !== "upgrade") {
78297
- Promise.resolve().then(() => (init_updateCheck(), updateCheck_exports)).then((mod) => {
78613
+ Promise.resolve().then(() => (init_autoUpdate(), autoUpdate_exports)).then((mod) => mod.reportCompletedUpdate()).catch(() => {
78614
+ });
78615
+ Promise.resolve().then(() => (init_updateCheck(), updateCheck_exports)).then(async (mod) => {
78298
78616
  _printUpdateNotice = mod.printUpdateNotice;
78299
- mod.checkForUpdate().catch(() => {
78300
- });
78617
+ const result = await mod.checkForUpdate().catch(() => null);
78618
+ if (result?.updateAvailable) {
78619
+ const auto = await Promise.resolve().then(() => (init_autoUpdate(), autoUpdate_exports)).catch(() => null);
78620
+ auto?.scheduleBackgroundInstall(result.latest, result.current);
78621
+ }
78301
78622
  });
78302
78623
  }
78303
78624
  process.on("beforeExit", () => {