hyperframes 0.7.93 → 0.7.96

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
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.7.93" : "0.0.0-dev";
53
+ VERSION = true ? "0.7.96" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -479,6 +479,7 @@ var init_dist = __esm({
479
479
  });
480
480
 
481
481
  // src/telemetry/runId.ts
482
+ import { randomUUID } from "crypto";
482
483
  function getRunId() {
483
484
  if (!resolved) {
484
485
  const value = process.env["HYPERFRAMES_RUN_ID"]?.trim().slice(0, 128);
@@ -487,7 +488,11 @@ function getRunId() {
487
488
  }
488
489
  return runId;
489
490
  }
490
- var resolved, runId;
491
+ function getInvocationId() {
492
+ invocationId ??= randomUUID();
493
+ return invocationId;
494
+ }
495
+ var resolved, runId, invocationId;
491
496
  var init_runId = __esm({
492
497
  "src/telemetry/runId.ts"() {
493
498
  "use strict";
@@ -62374,10 +62379,15 @@ function parseCanaryOverride(raw) {
62374
62379
  function canaryFeatureKey(name) {
62375
62380
  return `${CANARY_FEATURE_PREFIX}${name}`;
62376
62381
  }
62382
+ function canaryReasonKey(name) {
62383
+ return `canary_reason_${name.replace(/[^A-Za-z0-9]+/g, "_")}`;
62384
+ }
62377
62385
  function canaryFeatureProperties(entries2) {
62378
62386
  const props = {};
62379
62387
  for (const entry of entries2) {
62380
62388
  props[canaryFeatureKey(entry.name)] = entry.enabled ? "true" : "false";
62389
+ if (entry.reason !== void 0)
62390
+ props[canaryReasonKey(entry.name)] = entry.reason;
62381
62391
  }
62382
62392
  return props;
62383
62393
  }
@@ -62735,7 +62745,7 @@ var init_policy = __esm({
62735
62745
  import { existsSync as existsSync2, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
62736
62746
  import { join as join3 } from "path";
62737
62747
  import { homedir } from "os";
62738
- import { randomUUID } from "crypto";
62748
+ import { randomUUID as randomUUID2 } from "crypto";
62739
62749
  function salvageInstallState(parsed) {
62740
62750
  const markerAt = typeof parsed.markerAt === "string" ? parsed.markerAt : void 0;
62741
62751
  const bucketSeed = parseNonEmptyString(parsed.bucketSeed);
@@ -62765,9 +62775,10 @@ function warnSeedBackfillFailed(error) {
62765
62775
  }
62766
62776
  function backfillBucketSeed(config) {
62767
62777
  const recorded = readInstallState();
62768
- config.bucketSeed = (isInstallState(recorded) ? recorded.bucketSeed : void 0) ?? randomUUID();
62778
+ config.bucketSeed = (isInstallState(recorded) ? recorded.bucketSeed : void 0) ?? randomUUID2();
62769
62779
  const write = writeConfigWithResult(config);
62770
62780
  if (!write.ok) warnSeedBackfillFailed(write.error);
62781
+ return write;
62771
62782
  }
62772
62783
  function installStateLatchedFired() {
62773
62784
  if (latchedFiredSeen) return true;
@@ -62856,6 +62867,7 @@ function mintAndCacheConfig() {
62856
62867
  const config = mintConfig();
62857
62868
  const write = writeConfigWithResult(config);
62858
62869
  if (!write.ok) warnSeedBackfillFailed(write.error);
62870
+ classifyIdentity(write.ok ? "unknown" : "process_only", writeOutcomeOf(write));
62859
62871
  cachedConfig = { ...config };
62860
62872
  return { ...config };
62861
62873
  }
@@ -62864,7 +62876,7 @@ function mintConfig() {
62864
62876
  const state = isInstallState(read) ? read : null;
62865
62877
  return {
62866
62878
  ...DEFAULT_CONFIG,
62867
- anonymousId: randomUUID(),
62879
+ anonymousId: randomUUID2(),
62868
62880
  // A corrupt record still means this machine had an install — reporting it
62869
62881
  // as `false` would count a partial disk write as a brand-new machine and
62870
62882
  // understate recoverable churn, which is the one thing this field exists
@@ -62875,7 +62887,7 @@ function mintConfig() {
62875
62887
  // machine stays tripped for the new one, and the canary bucketing seed is
62876
62888
  // inherited so cohorts hold across a config.json re-mint.
62877
62889
  deParallelRouterTrialFired: state?.deParallelRouterTrialFired === true ? true : void 0,
62878
- bucketSeed: state?.bucketSeed ?? randomUUID()
62890
+ bucketSeed: state?.bucketSeed ?? randomUUID2()
62879
62891
  };
62880
62892
  }
62881
62893
  function recordRecentRender(id, ok) {
@@ -62884,6 +62896,23 @@ function recordRecentRender(id, ok) {
62884
62896
  config.recentRenders = ring.slice(-MAX_RECENT_RENDERS);
62885
62897
  writeConfig(config);
62886
62898
  }
62899
+ function classifyIdentity(persistence, outcome) {
62900
+ if (identityPersistence !== void 0) return;
62901
+ identityPersistence = persistence;
62902
+ identityWriteOutcome = outcome;
62903
+ }
62904
+ function writeOutcomeOf(write) {
62905
+ if (!write.ok) return "failed";
62906
+ return write.mirrored === false ? "ok_unmirrored" : "ok";
62907
+ }
62908
+ function getIdentityPersistence() {
62909
+ readConfig();
62910
+ return identityPersistence ?? "unknown";
62911
+ }
62912
+ function getIdentityWriteOutcome() {
62913
+ readConfig();
62914
+ return identityWriteOutcome;
62915
+ }
62887
62916
  function parseNonEmptyString(value) {
62888
62917
  return typeof value === "string" && value.length > 0 ? value : void 0;
62889
62918
  }
@@ -62933,7 +62962,7 @@ function materializeConfig(parsed) {
62933
62962
  return {
62934
62963
  ...passthroughFields(parsed),
62935
62964
  telemetryEnabled: parsed.telemetryEnabled ?? DEFAULT_CONFIG.telemetryEnabled,
62936
- anonymousId: parsed.anonymousId || randomUUID(),
62965
+ anonymousId: parsed.anonymousId || randomUUID2(),
62937
62966
  telemetryNoticeShown: parsed.telemetryNoticeShown ?? DEFAULT_CONFIG.telemetryNoticeShown,
62938
62967
  commandCount: parsed.commandCount ?? DEFAULT_CONFIG.commandCount,
62939
62968
  renderSuccessCount: parsed.renderSuccessCount ?? DEFAULT_CONFIG.renderSuccessCount,
@@ -62949,16 +62978,21 @@ function readConfig() {
62949
62978
  const raw = readFileSync(CONFIG_FILE, "utf-8");
62950
62979
  const parsed = JSON.parse(raw);
62951
62980
  const config = materializeConfig(parsed);
62981
+ const idFromDisk = parseNonEmptyString(parsed.anonymousId) !== void 0;
62952
62982
  if (config.bucketSeed === void 0) {
62953
- backfillBucketSeed(config);
62983
+ const write = backfillBucketSeed(config);
62984
+ if (idFromDisk) classifyIdentity("durable");
62985
+ else classifyIdentity(write.ok ? "unknown" : "process_only", writeOutcomeOf(write));
62954
62986
  cachedConfig = config;
62955
62987
  return { ...config };
62956
62988
  }
62989
+ classifyIdentity(idFromDisk ? "durable" : "process_only");
62957
62990
  cachedConfig = config;
62958
62991
  return { ...config };
62959
62992
  } catch {
62960
62993
  const config = { ...mintConfig(), telemetryEnabled: false };
62961
- writeConfig(config);
62994
+ const write = writeConfigWithResult(config);
62995
+ classifyIdentity(write.ok ? "unknown" : "process_only", writeOutcomeOf(write));
62962
62996
  return config;
62963
62997
  }
62964
62998
  }
@@ -62988,7 +63022,7 @@ function incrementCommandCount() {
62988
63022
  writeConfig(config);
62989
63023
  return config.commandCount;
62990
63024
  }
62991
- var CONFIG_DIR, CONFIG_FILE, STATE_FILE, LEGACY_STATE_FILE, seedBackfillWarned, latchedFiredSeen, stateMarkerSynced, stateFiredSynced, MAX_RECENT_RENDERS, DEFAULT_CONFIG, cachedConfig, CONFIG_PATH, STATE_PATH;
63025
+ var CONFIG_DIR, CONFIG_FILE, STATE_FILE, LEGACY_STATE_FILE, seedBackfillWarned, latchedFiredSeen, stateMarkerSynced, stateFiredSynced, MAX_RECENT_RENDERS, DEFAULT_CONFIG, cachedConfig, identityPersistence, identityWriteOutcome, CONFIG_PATH, STATE_PATH;
62992
63026
  var init_config = __esm({
62993
63027
  "src/telemetry/config.ts"() {
62994
63028
  "use strict";
@@ -70836,7 +70870,7 @@ import {
70836
70870
  statSync as statSync4,
70837
70871
  unlinkSync
70838
70872
  } from "fs";
70839
- import { createHash, randomUUID as randomUUID2 } from "crypto";
70873
+ import { createHash, randomUUID as randomUUID3 } from "crypto";
70840
70874
  import { BlockList, isIP } from "net";
70841
70875
  import { dirname as dirname6, extname as extname2, join as join9 } from "path";
70842
70876
  import { Readable, Transform } from "stream";
@@ -71004,7 +71038,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
71004
71038
  if (error.code !== "EEXIST") throw error;
71005
71039
  }
71006
71040
  if (createdLock) {
71007
- const owner = `${CACHE_LOCK_OWNER_PREFIX}${randomUUID2()}`;
71041
+ const owner = `${CACHE_LOCK_OWNER_PREFIX}${randomUUID3()}`;
71008
71042
  try {
71009
71043
  mkdirSync5(join9(lockPath, owner));
71010
71044
  const entries2 = readdirSync3(lockPath);
@@ -84955,7 +84989,7 @@ var init_htmlTemplate = __esm({
84955
84989
  });
84956
84990
 
84957
84991
  // ../engine/src/services/extractionCache.ts
84958
- import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
84992
+ import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
84959
84993
  import {
84960
84994
  existsSync as existsSync13,
84961
84995
  lstatSync as lstatSync2,
@@ -85003,7 +85037,7 @@ function lookupCacheEntry(rootDir, input2) {
85003
85037
  return { entry: { dir, keyHash }, hit: complete };
85004
85038
  }
85005
85039
  function partialCacheEntryDir(entry) {
85006
- return `${entry.dir}.partial-${process.pid}-${randomUUID3().slice(0, 8)}`;
85040
+ return `${entry.dir}.partial-${process.pid}-${randomUUID4().slice(0, 8)}`;
85007
85041
  }
85008
85042
  function isTargetExistsRenameError(err) {
85009
85043
  const code = err.code;
@@ -90728,7 +90762,10 @@ function canaryDecisionsForStudio() {
90728
90762
  }
90729
90763
  function canaryEventProperties() {
90730
90764
  return canaryFeatureProperties(
90731
- CANARIES.map((c3) => ({ name: c3.name, enabled: resolveCanary(c3.name).enabled }))
90765
+ CANARIES.map((c3) => {
90766
+ const { enabled, reason } = resolveCanary(c3.name);
90767
+ return { name: c3.name, enabled, reason };
90768
+ })
90732
90769
  );
90733
90770
  }
90734
90771
  var decisions, decisionsTelemetryPosture;
@@ -90746,10 +90783,10 @@ var init_canary2 = __esm({
90746
90783
 
90747
90784
  // src/telemetry/transport.ts
90748
90785
  import { spawn as spawn5 } from "child_process";
90749
- import { randomUUID as randomUUID4 } from "crypto";
90786
+ import { randomUUID as randomUUID5 } from "crypto";
90750
90787
  function enqueue(event, properties, distinctId) {
90751
90788
  eventQueue.push({
90752
- uuid: randomUUID4(),
90789
+ uuid: randomUUID5(),
90753
90790
  event,
90754
90791
  distinctId,
90755
90792
  properties,
@@ -90867,6 +90904,19 @@ function trackEvent(event, properties = {}, distinctId) {
90867
90904
  // could not read. Without it a partial disk write is indistinguishable
90868
90905
  // from a genuinely fresh install. Absent in the normal case.
90869
90906
  install_state_file_corrupt: readConfig().stateFileCorrupt,
90907
+ // Whether this process's anonymousId can be trusted to survive to the
90908
+ // next run: `durable` (loaded from a preexisting config), `unknown`
90909
+ // (minted+persisted this run — an ephemeral HOME is indistinguishable
90910
+ // from a genuine first run), `process_only` (persist failed). Install-
90911
+ // grain metrics should count only durable identities; the identity-
90912
+ // churn workloads (fresh id per run) are never durable.
90913
+ identity_persistence: getIdentityPersistence(),
90914
+ // Outcome of the identity-establishing config write; absent when the
90915
+ // identity came from disk and nothing needed writing.
90916
+ config_write_outcome: getIdentityWriteOutcome(),
90917
+ // Groups one invocation's events even when the install identity is
90918
+ // untrustworthy. Always present, unlike the orchestrator-set run_id.
90919
+ invocation_id: getInvocationId(),
90870
90920
  // Canary assignments as `$feature/canary-<name>` — PostHog's native flag
90871
90921
  // property shape, so breakdowns and experiment analysis work on a canary
90872
90922
  // with nothing configured server-side. On EVERY event, not just renders:
@@ -90901,6 +90951,7 @@ var init_client = __esm({
90901
90951
  "src/telemetry/client.ts"() {
90902
90952
  "use strict";
90903
90953
  init_config();
90954
+ init_runId();
90904
90955
  init_version();
90905
90956
  init_colors();
90906
90957
  init_diagnostics2();
@@ -94501,7 +94552,7 @@ import { execFileSync as execFileSync5 } from "child_process";
94501
94552
  import { existsSync as existsSync25, readFileSync as readFileSync14, mkdirSync as mkdirSync12, unlinkSync as unlinkSync3 } from "fs";
94502
94553
  import { join as join21, extname as extname5 } from "path";
94503
94554
  import { tmpdir as tmpdir3 } from "os";
94504
- import { randomUUID as randomUUID5 } from "crypto";
94555
+ import { randomUUID as randomUUID6 } from "crypto";
94505
94556
  function detectLanguage(whisperPath, modelPath2, wavPath) {
94506
94557
  try {
94507
94558
  const output = execFileSync5(whisperPath, ["--model", modelPath2, "--detect-language", wavPath], {
@@ -94643,7 +94694,7 @@ function isVideoFile(filePath) {
94643
94694
  return VIDEO_EXTENSIONS.has(extname5(filePath).toLowerCase());
94644
94695
  }
94645
94696
  function tempWavPath() {
94646
- return join21(tmpdir3(), `hyperframes-audio-${process.pid}-${randomUUID5()}.wav`);
94697
+ return join21(tmpdir3(), `hyperframes-audio-${process.pid}-${randomUUID6()}.wav`);
94647
94698
  }
94648
94699
  function extractAudio(videoPath) {
94649
94700
  const ffmpegPath = findFFmpeg();
@@ -99246,7 +99297,7 @@ var init_chunk_6H3V3WGJ = __esm({
99246
99297
 
99247
99298
  // ../studio-server/dist/chunk-ZPI6QXJH.js
99248
99299
  import { spawn as spawn8 } from "child_process";
99249
- import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
99300
+ import { createHash as createHash6, randomUUID as randomUUID7 } from "crypto";
99250
99301
  import {
99251
99302
  existsSync as existsSync210,
99252
99303
  mkdirSync as mkdirSync15,
@@ -99570,7 +99621,7 @@ async function transcodeToCache(absoluteSourcePath, cachePath2, variant) {
99570
99621
  if (existsSync210(cachePath2)) return cachePath2;
99571
99622
  const cacheDir = dirname14(cachePath2);
99572
99623
  mkdirSync15(cacheDir, { recursive: true });
99573
- const tempPath = join29(cacheDir, `.tmp-${randomUUID6()}-${basename4(cachePath2)}`);
99624
+ const tempPath = join29(cacheDir, `.tmp-${randomUUID7()}-${basename4(cachePath2)}`);
99574
99625
  try {
99575
99626
  await runFfmpeg2(absoluteSourcePath, tempPath, variant);
99576
99627
  renameSync6(tempPath, cachePath2);
@@ -106487,7 +106538,7 @@ import { basename as basename5, join as join42 } from "path";
106487
106538
  import { mkdirSync as mkdirSync22, readdirSync as readdirSync32, readFileSync as readFileSync32, unlinkSync as unlinkSync5, writeFileSync as writeFileSync32 } from "fs";
106488
106539
  import { Buffer as Buffer2 } from "buffer";
106489
106540
  import { join as join52, relative as relative22 } from "path";
106490
- import { createHash as createHash22, randomUUID as randomUUID7 } from "crypto";
106541
+ import { createHash as createHash22, randomUUID as randomUUID8 } from "crypto";
106491
106542
  import { existsSync as existsSync37, readFileSync as readFileSync42, realpathSync as realpathSync6 } from "fs";
106492
106543
  import { randomUUID as randomUUID22 } from "crypto";
106493
106544
  import { dirname as dirname15, relative as relative32, resolve as resolve24, sep as sep7 } from "path";
@@ -106511,9 +106562,19 @@ import { join as join92 } from "path";
106511
106562
  import { streamSSE } from "hono/streaming";
106512
106563
  import { existsSync as existsSync72, readFileSync as readFileSync102, mkdirSync as mkdirSync42, unlinkSync as unlinkSync32, readdirSync as readdirSync52, statSync as statSync32 } from "fs";
106513
106564
  import { join as join102 } from "path";
106514
- import { existsSync as existsSync82, readFileSync as readFileSync112, writeFileSync as writeFileSync62, mkdirSync as mkdirSync52, statSync as statSync42 } from "fs";
106565
+ import {
106566
+ existsSync as existsSync82,
106567
+ mkdirSync as mkdirSync52,
106568
+ readFileSync as readFileSync112,
106569
+ readdirSync as readdirSync62,
106570
+ renameSync as renameSync22,
106571
+ rmSync as rmSync32,
106572
+ statSync as statSync42,
106573
+ unlinkSync as unlinkSync42,
106574
+ writeFileSync as writeFileSync62
106575
+ } from "fs";
106515
106576
  import { join as join112 } from "path";
106516
- import { createHash as createHash42 } from "crypto";
106577
+ import { createHash as createHash42, randomUUID as randomUUID32 } from "crypto";
106517
106578
  import { existsSync as existsSync92, readFileSync as readFileSync122, writeFileSync as writeFileSync72, mkdirSync as mkdirSync62 } from "fs";
106518
106579
  import { join as join122 } from "path";
106519
106580
  import { closeSync as closeSync32, constants as constants22, fstatSync as fstatSync2, openSync as openSync32, readSync } from "fs";
@@ -106940,7 +107001,7 @@ function fileContentVersion(content) {
106940
107001
  }
106941
107002
  function createWriteToken(requestToken) {
106942
107003
  const token = requestToken?.trim();
106943
- return token && token.length <= 200 ? token : randomUUID7();
107004
+ return token && token.length <= 200 ? token : randomUUID8();
106944
107005
  }
106945
107006
  function recordFileWriteReceipt(absPath, receipt) {
106946
107007
  const now = Date.now();
@@ -110026,6 +110087,46 @@ function registerRenderRoutes(api, adapter2) {
110026
110087
  return c3.json({ renders: files });
110027
110088
  });
110028
110089
  }
110090
+ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
110091
+ if (!existsSync82(cacheDir)) return;
110092
+ const files = readdirSync62(cacheDir, { withFileTypes: true }).flatMap((entry) => {
110093
+ if (!entry.isFile()) return [];
110094
+ const path2 = join112(cacheDir, entry.name);
110095
+ try {
110096
+ const stats = statSync42(path2);
110097
+ return [{ path: path2, bytes: stats.size, mtimeMs: stats.mtimeMs }];
110098
+ } catch {
110099
+ return [];
110100
+ }
110101
+ });
110102
+ const retained = [];
110103
+ for (const file of files) {
110104
+ if (!protectedPaths.has(file.path) && now - file.mtimeMs > THUMBNAIL_CACHE_MAX_AGE_MS) {
110105
+ rmSync32(file.path, { force: true });
110106
+ } else {
110107
+ retained.push(file);
110108
+ }
110109
+ }
110110
+ let bytes = retained.reduce((total, file) => total + file.bytes, 0);
110111
+ for (const file of retained.sort((left, right) => left.mtimeMs - right.mtimeMs)) {
110112
+ if (bytes <= THUMBNAIL_CACHE_MAX_BYTES) break;
110113
+ if (protectedPaths.has(file.path)) continue;
110114
+ try {
110115
+ unlinkSync42(file.path);
110116
+ bytes -= file.bytes;
110117
+ } catch {
110118
+ }
110119
+ }
110120
+ }
110121
+ function writeThumbnailAtomically(path2, buffer) {
110122
+ const temporaryPath = `${path2}.${process.pid}.${randomUUID32()}.tmp`;
110123
+ try {
110124
+ writeFileSync62(temporaryPath, buffer, { flag: "wx" });
110125
+ renameSync22(temporaryPath, path2);
110126
+ } finally {
110127
+ rmSync32(temporaryPath, { force: true });
110128
+ }
110129
+ }
110029
110130
  function registerThumbnailRoutes(api, adapter2) {
110030
110131
  api.get("/projects/:id/thumbnail/*", async (c3) => {
110031
110132
  if (!adapter2.generateThumbnail) {
@@ -110046,6 +110147,8 @@ function registerThumbnailRoutes(api, adapter2) {
110046
110147
  const selector = url.searchParams.get("selector") || void 0;
110047
110148
  const format = url.searchParams.get("format") === "png" ? "png" : "jpeg";
110048
110149
  const contentType = format === "png" ? "image/png" : "image/jpeg";
110150
+ const requestedOutput = url.searchParams.get("output");
110151
+ const outputMode = requestedOutput === "source" || requestedOutput !== "preview" && format === "png" ? "source" : "preview";
110049
110152
  const rawSelectorIndex = Number.parseInt(url.searchParams.get("selectorIndex") || "0", 10);
110050
110153
  const selectorIndex = Number.isFinite(rawSelectorIndex) && rawSelectorIndex > 0 ? rawSelectorIndex : void 0;
110051
110154
  const urlVersion = url.searchParams.get("v") || "";
@@ -110083,37 +110186,62 @@ function registerThumbnailRoutes(api, adapter2) {
110083
110186
  const cacheDir = join112(project.dir, ".thumbnails");
110084
110187
  const selectorKey = selector ? `_${selector.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 80)}_${selectorIndex ?? 0}` : "";
110085
110188
  const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
110086
- const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`;
110189
+ const outputScale = outputMode === "source" ? 1 : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
110190
+ const outputWidth = Math.max(1, Math.round(compW * outputScale));
110191
+ const outputHeight = Math.max(1, Math.round(compH * outputScale));
110192
+ const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${outputMode}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${outputWidth}x${outputHeight}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`;
110087
110193
  const cachePath2 = join112(cacheDir, cacheKey);
110194
+ if (!prunedCacheDirs.has(cacheDir)) {
110195
+ prunedCacheDirs.add(cacheDir);
110196
+ pruneThumbnailCache(
110197
+ cacheDir,
110198
+ /* @__PURE__ */ new Set([...thumbnailGenerationCoordinator.protectedKeys(), cachePath2])
110199
+ );
110200
+ }
110088
110201
  if (existsSync82(cachePath2)) {
110089
110202
  return new Response(new Uint8Array(readFileSync112(cachePath2)), {
110090
110203
  headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }
110091
110204
  });
110092
110205
  }
110093
110206
  try {
110094
- const buffer = await adapter2.generateThumbnail({
110095
- project,
110096
- compPath,
110097
- seekTime,
110098
- width: compW,
110099
- height: compH,
110100
- previewUrl,
110101
- selector,
110102
- format,
110103
- selectorIndex
110104
- });
110207
+ const buffer = await thumbnailGenerationCoordinator.acquire(
110208
+ cachePath2,
110209
+ c3.req.raw.signal,
110210
+ async (signal) => {
110211
+ const generated = await adapter2.generateThumbnail({
110212
+ project,
110213
+ compPath,
110214
+ seekTime,
110215
+ width: compW,
110216
+ height: compH,
110217
+ outputWidth,
110218
+ outputHeight,
110219
+ previewUrl,
110220
+ selector,
110221
+ format,
110222
+ selectorIndex,
110223
+ signal
110224
+ });
110225
+ if (!generated) return null;
110226
+ if (!existsSync82(cacheDir)) mkdirSync52(cacheDir, { recursive: true });
110227
+ writeThumbnailAtomically(cachePath2, generated);
110228
+ return generated;
110229
+ }
110230
+ );
110105
110231
  if (!buffer) {
110106
110232
  return c3.json(
110107
110233
  { error: "Thumbnail generation failed \u2014 Chrome browser may not be available" },
110108
110234
  500
110109
110235
  );
110110
110236
  }
110111
- if (!existsSync82(cacheDir)) mkdirSync52(cacheDir, { recursive: true });
110112
- writeFileSync62(cachePath2, buffer);
110237
+ pruneThumbnailCache(cacheDir, thumbnailGenerationCoordinator.protectedKeys());
110113
110238
  return new Response(new Uint8Array(buffer), {
110114
110239
  headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }
110115
110240
  });
110116
110241
  } catch (err) {
110242
+ if (err instanceof DOMException && err.name === "AbortError") {
110243
+ return new Response(null, { status: 499 });
110244
+ }
110117
110245
  const msg = err instanceof Error ? err.message : String(err);
110118
110246
  return c3.json({ error: `Thumbnail generation failed: ${msg}` }, 500);
110119
110247
  }
@@ -110592,6 +110720,18 @@ function createStudioApi(adapter2) {
110592
110720
  registerGlobalAssetRoutes(api);
110593
110721
  return api;
110594
110722
  }
110723
+ function thumbnailDeviceScaleFactor({
110724
+ width,
110725
+ height,
110726
+ outputWidth,
110727
+ outputHeight
110728
+ }) {
110729
+ const dimensions = [width, height, outputWidth, outputHeight];
110730
+ if (dimensions.some((value) => !Number.isFinite(value) || value <= 0)) {
110731
+ throw new RangeError("Thumbnail dimensions must be positive finite numbers");
110732
+ }
110733
+ return Math.min(1, outputWidth / width, outputHeight / height);
110734
+ }
110595
110735
  function createBackgroundRemovalJob(opts, render3) {
110596
110736
  const state = {
110597
110737
  id: opts.jobId,
@@ -110644,7 +110784,7 @@ function updateBackgroundRemovalProgress(state, event) {
110644
110784
  state.framesProcessed = event.index;
110645
110785
  state.avgMsPerFrame = event.avgMsPerFrame;
110646
110786
  }
110647
- var IGNORE_DIRS, SIGNATURE_TEXT_EXTENSIONS, SIGNATURE_EXCLUDED_DIRS, MAX_SIGNATURE_TEXT_BYTES, STUDIO_SIGNATURE_MANIFEST_PATHS, projectSignatureCache, COMPOSITION_ID_RE, MIME_TYPES2, SAMPLE_RATE, PEAK_COUNT, WAVEFORM_CACHE_VERSION, VIDEO_EXT2, AUDIO_EXT2, DEFAULT_KEEP_PER_FILE, RECEIPT_TTL_MS, receipts, CompositionInsertionError, differential, behavioral, GSAP_MUTATION_CAPABILITIES, GSAP_WRITER_MIGRATION, atomicCutTail, HOLD_SYNC_MUTATION_TYPES, REGEXP_SPECIALS, NON_RENDERED_TAGS, VARIABLES_PAYLOAD_ERROR, PROJECT_SIGNATURE_META, GSAP_CDN_VERSION, GSAP_CDN_SCRIPT, GSAP_CUSTOM_EASE_CDN_SCRIPT, GSAP_MOTION_PATH_CDN_SCRIPT, GSAP_CDN_FALLBACK_SCRIPT, VALID_RESOLUTIONS, THUMBNAIL_CACHE_VERSION, MAX_FONT_RESULTS, GOOGLE_FONTS_METADATA_URL, GOOGLE_FONTS_FETCH_TIMEOUT_MS, cachedFonts, cachedGoogleFonts, GOOGLE_FONT_FALLBACKS, VIDEO_EXTENSIONS2, IMAGE_EXTENSIONS, VIDEO_OUTPUT_EXTENSIONS, QUALITIES, DEVICES;
110787
+ var IGNORE_DIRS, SIGNATURE_TEXT_EXTENSIONS, SIGNATURE_EXCLUDED_DIRS, MAX_SIGNATURE_TEXT_BYTES, STUDIO_SIGNATURE_MANIFEST_PATHS, projectSignatureCache, COMPOSITION_ID_RE, MIME_TYPES2, SAMPLE_RATE, PEAK_COUNT, WAVEFORM_CACHE_VERSION, VIDEO_EXT2, AUDIO_EXT2, DEFAULT_KEEP_PER_FILE, RECEIPT_TTL_MS, receipts, CompositionInsertionError, differential, behavioral, GSAP_MUTATION_CAPABILITIES, GSAP_WRITER_MIGRATION, atomicCutTail, HOLD_SYNC_MUTATION_TYPES, REGEXP_SPECIALS, NON_RENDERED_TAGS, VARIABLES_PAYLOAD_ERROR, PROJECT_SIGNATURE_META, GSAP_CDN_VERSION, GSAP_CDN_SCRIPT, GSAP_CUSTOM_EASE_CDN_SCRIPT, GSAP_MOTION_PATH_CDN_SCRIPT, GSAP_CDN_FALLBACK_SCRIPT, VALID_RESOLUTIONS, ThumbnailGenerationCoordinator, thumbnailGenerationCoordinator, THUMBNAIL_CACHE_VERSION, THUMBNAIL_MAX_OUTPUT_WIDTH, THUMBNAIL_MAX_OUTPUT_HEIGHT, THUMBNAIL_CACHE_MAX_BYTES, THUMBNAIL_CACHE_MAX_AGE_MS, prunedCacheDirs, MAX_FONT_RESULTS, GOOGLE_FONTS_METADATA_URL, GOOGLE_FONTS_FETCH_TIMEOUT_MS, cachedFonts, cachedGoogleFonts, GOOGLE_FONT_FALLBACKS, VIDEO_EXTENSIONS2, IMAGE_EXTENSIONS, VIDEO_OUTPUT_EXTENSIONS, QUALITIES, DEVICES;
110648
110788
  var init_dist9 = __esm({
110649
110789
  "../studio-server/dist/index.js"() {
110650
110790
  "use strict";
@@ -110885,7 +111025,113 @@ var init_dist9 = __esm({
110885
111025
  })();
110886
111026
  </script>`;
110887
111027
  VALID_RESOLUTIONS = new Set(VALID_CANVAS_RESOLUTIONS);
111028
+ ThumbnailGenerationCoordinator = class {
111029
+ constructor(concurrency = 1) {
111030
+ this.concurrency = concurrency;
111031
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
111032
+ throw new RangeError("Thumbnail concurrency must be a positive integer");
111033
+ }
111034
+ }
111035
+ concurrency;
111036
+ entries = /* @__PURE__ */ new Map();
111037
+ queue = [];
111038
+ activeEntries = /* @__PURE__ */ new Set();
111039
+ active = 0;
111040
+ acquire(key2, signal, work) {
111041
+ if (signal.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
111042
+ let entry = this.entries.get(key2);
111043
+ if (!entry) {
111044
+ let resolve510;
111045
+ let reject;
111046
+ const promise = new Promise((resolvePromise, rejectPromise) => {
111047
+ resolve510 = resolvePromise;
111048
+ reject = rejectPromise;
111049
+ });
111050
+ entry = {
111051
+ key: key2,
111052
+ controller: new AbortController(),
111053
+ leases: 0,
111054
+ state: "queued",
111055
+ work,
111056
+ promise,
111057
+ resolve: resolve510,
111058
+ reject
111059
+ };
111060
+ this.entries.set(key2, entry);
111061
+ this.queue.push(entry);
111062
+ }
111063
+ entry.leases++;
111064
+ this.pump();
111065
+ return this.lease(entry, signal);
111066
+ }
111067
+ protectedKeys() {
111068
+ return /* @__PURE__ */ new Set([...this.entries.keys(), ...[...this.activeEntries].map((entry) => entry.key)]);
111069
+ }
111070
+ lease(entry, signal) {
111071
+ return new Promise((resolve510, reject) => {
111072
+ let released = false;
111073
+ const release4 = () => {
111074
+ if (released) return;
111075
+ released = true;
111076
+ signal.removeEventListener("abort", onAbort);
111077
+ entry.leases--;
111078
+ if (entry.leases > 0 || !this.entries.has(entry.key)) return;
111079
+ entry.controller.abort();
111080
+ if (this.entries.get(entry.key) === entry) this.entries.delete(entry.key);
111081
+ if (entry.state === "queued") {
111082
+ const index = this.queue.indexOf(entry);
111083
+ if (index >= 0) this.queue.splice(index, 1);
111084
+ entry.reject(new DOMException("Aborted", "AbortError"));
111085
+ }
111086
+ };
111087
+ const onAbort = () => {
111088
+ release4();
111089
+ reject(new DOMException("Aborted", "AbortError"));
111090
+ };
111091
+ signal.addEventListener("abort", onAbort, { once: true });
111092
+ entry.promise.then(
111093
+ (value) => {
111094
+ release4();
111095
+ resolve510(value);
111096
+ },
111097
+ (reason) => {
111098
+ release4();
111099
+ reject(reason);
111100
+ }
111101
+ );
111102
+ });
111103
+ }
111104
+ pump() {
111105
+ while (this.active < this.concurrency) {
111106
+ const entry = this.queue.shift();
111107
+ if (!entry) return;
111108
+ if (entry.leases === 0) continue;
111109
+ entry.state = "active";
111110
+ this.activeEntries.add(entry);
111111
+ this.active++;
111112
+ void this.run(entry);
111113
+ }
111114
+ }
111115
+ async run(entry) {
111116
+ try {
111117
+ entry.resolve(await entry.work(entry.controller.signal));
111118
+ } catch (error) {
111119
+ entry.reject(error);
111120
+ } finally {
111121
+ this.active--;
111122
+ this.activeEntries.delete(entry);
111123
+ if (this.entries.get(entry.key) === entry) this.entries.delete(entry.key);
111124
+ this.pump();
111125
+ }
111126
+ }
111127
+ };
111128
+ thumbnailGenerationCoordinator = new ThumbnailGenerationCoordinator(1);
110888
111129
  THUMBNAIL_CACHE_VERSION = "v4";
111130
+ THUMBNAIL_MAX_OUTPUT_WIDTH = 240;
111131
+ THUMBNAIL_MAX_OUTPUT_HEIGHT = 135;
111132
+ THUMBNAIL_CACHE_MAX_BYTES = 512 * 1024 * 1024;
111133
+ THUMBNAIL_CACHE_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
111134
+ prunedCacheDirs = /* @__PURE__ */ new Set();
110889
111135
  MAX_FONT_RESULTS = 2e3;
110890
111136
  GOOGLE_FONTS_METADATA_URL = "https://fonts.google.com/metadata/fonts";
110891
111137
  GOOGLE_FONTS_FETCH_TIMEOUT_MS = 3e3;
@@ -124868,7 +125114,7 @@ import {
124868
125114
  import { tmpdir as tmpdir8 } from "os";
124869
125115
  import { join as join60, dirname as dirname25, resolve as resolve33 } from "path";
124870
125116
  import { totalmem as totalmem2 } from "os";
124871
- import { randomUUID as randomUUID8 } from "crypto";
125117
+ import { randomUUID as randomUUID9 } from "crypto";
124872
125118
  import { fileURLToPath as fileURLToPath5 } from "url";
124873
125119
  function sampleDirectoryBytes(dir) {
124874
125120
  let total = 0;
@@ -125227,7 +125473,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
125227
125473
  }
125228
125474
  function createRenderJob(config) {
125229
125475
  return {
125230
- id: randomUUID8(),
125476
+ id: randomUUID9(),
125231
125477
  config: {
125232
125478
  ...config,
125233
125479
  fps: toFps(config.fps),
@@ -132359,9 +132605,19 @@ function createStudioServer(options) {
132359
132605
  );
132360
132606
  }
132361
132607
  let page = null;
132608
+ const closePage = () => void page?.close().catch(() => {
132609
+ });
132610
+ opts.signal.addEventListener("abort", closePage, { once: true });
132362
132611
  try {
132363
132612
  page = await session.browser.newPage();
132364
- await page.setViewport({ width: opts.width || 1920, height: opts.height || 1080 });
132613
+ if (opts.signal.aborted) return null;
132614
+ const width = opts.width || 1920;
132615
+ const height = opts.height || 1080;
132616
+ await page.setViewport({
132617
+ width,
132618
+ height,
132619
+ deviceScaleFactor: thumbnailDeviceScaleFactor(opts)
132620
+ });
132365
132621
  await page.goto(opts.previewUrl, { waitUntil: "domcontentloaded", timeout: 1e4 });
132366
132622
  await page.waitForFunction(
132367
132623
  () => {
@@ -132404,12 +132660,15 @@ function createStudioServer(options) {
132404
132660
  );
132405
132661
  return screenshot;
132406
132662
  } catch (err) {
132407
- console.warn(
132408
- "[Studio] Thumbnail generation failed:",
132409
- err instanceof Error ? err.message : err
132410
- );
132663
+ if (!opts.signal.aborted) {
132664
+ console.warn(
132665
+ "[Studio] Thumbnail generation failed:",
132666
+ err instanceof Error ? err.message : err
132667
+ );
132668
+ }
132411
132669
  return null;
132412
132670
  } finally {
132671
+ opts.signal.removeEventListener("abort", closePage);
132413
132672
  await page?.close().catch(() => {
132414
132673
  });
132415
132674
  }
@@ -135057,6 +135316,7 @@ __export(studio_api_exports, {
135057
135316
  getElementScreenshotClip: () => getElementScreenshotClip,
135058
135317
  getMimeType: () => getMimeType,
135059
135318
  isSafePath: () => isSafePath,
135319
+ thumbnailDeviceScaleFactor: () => thumbnailDeviceScaleFactor,
135060
135320
  walkDir: () => walkDir
135061
135321
  });
135062
135322
  var init_studio_api = __esm({
@@ -135598,7 +135858,7 @@ var init_present = __esm({
135598
135858
  function isAuthError(err) {
135599
135859
  return err instanceof AuthError;
135600
135860
  }
135601
- var AuthError, ErrNotConfigured, ErrInvalidStore, ErrUnauthenticated, ErrApi, ErrOAuthNotConfigured, ErrRefreshFailed;
135861
+ var AuthError, ErrNotConfigured, ErrInvalidStore, ErrUnauthenticated, ErrApi, ErrOAuthNotConfigured, ErrRefreshFailed, ErrDeviceAuthFailed;
135602
135862
  var init_errors = __esm({
135603
135863
  "src/auth/errors.ts"() {
135604
135864
  "use strict";
@@ -135638,6 +135898,11 @@ var init_errors = __esm({
135638
135898
  detail ? `Failed to refresh OAuth tokens: ${detail}` : "Failed to refresh OAuth tokens",
135639
135899
  "Run `hyperframes auth login` to re-authenticate."
135640
135900
  );
135901
+ ErrDeviceAuthFailed = (detail) => new AuthError(
135902
+ "DEVICE_AUTH_FAILED",
135903
+ `Device authorization failed: ${detail}`,
135904
+ "Run `hyperframes auth login --device` to start a new code."
135905
+ );
135641
135906
  }
135642
135907
  });
135643
135908
 
@@ -136347,6 +136612,9 @@ function tokenEndpoint() {
136347
136612
  function revokeEndpoint() {
136348
136613
  return process.env["HYPERFRAMES_OAUTH_REVOKE_URL"] || DEFAULT_REVOKE_URL;
136349
136614
  }
136615
+ function deviceAuthorizationEndpoint() {
136616
+ return process.env["HYPERFRAMES_OAUTH_DEVICE_URL"] || DEFAULT_DEVICE_AUTHORIZATION_URL;
136617
+ }
136350
136618
  function resolveClientId() {
136351
136619
  const override = process.env["HYPERFRAMES_OAUTH_CLIENT_ID"];
136352
136620
  const id = override && override.length > 0 ? override : DEFAULT_CLIENT_ID;
@@ -136402,6 +136670,127 @@ async function startAuthorizationCodeFlow(opts = {}) {
136402
136670
  await persistOAuth(tokens, { preserveMissing: false });
136403
136671
  return { tokens };
136404
136672
  }
136673
+ async function startDeviceAuthorizationFlow(opts = {}) {
136674
+ const runtime = {
136675
+ clientId: resolveClientId(),
136676
+ fetchImpl: opts.fetchImpl ?? fetch,
136677
+ sleepImpl: opts.sleepImpl ?? ((ms) => new Promise((resolve77) => setTimeout(resolve77, ms))),
136678
+ now: opts.now ?? Date.now,
136679
+ requestTimeoutMs: opts.requestTimeoutMs ?? DEVICE_REQUEST_TIMEOUT_MS
136680
+ };
136681
+ const issuance = await requestDeviceAuthorization(runtime, opts.scope ?? DEFAULT_SCOPES);
136682
+ await opts.onChallenge?.({
136683
+ userCode: issuance.userCode,
136684
+ verificationUri: issuance.verificationUri,
136685
+ ...issuance.verificationUriComplete ? { verificationUriComplete: issuance.verificationUriComplete } : {}
136686
+ });
136687
+ return await pollDeviceToken(runtime, issuance);
136688
+ }
136689
+ async function requestDeviceAuthorization(runtime, scope) {
136690
+ return await withDeviceRequestTimeout(
136691
+ runtime,
136692
+ "could not reach the authorization server",
136693
+ async (signal) => {
136694
+ const response = await runtime.fetchImpl(deviceAuthorizationEndpoint(), {
136695
+ method: "POST",
136696
+ headers: {
136697
+ "content-type": "application/x-www-form-urlencoded",
136698
+ accept: "application/json"
136699
+ },
136700
+ body: new URLSearchParams({ client_id: runtime.clientId, scope }).toString(),
136701
+ signal
136702
+ });
136703
+ if (!response.ok) {
136704
+ throw ErrDeviceAuthFailed(`authorization server returned HTTP ${response.status}`);
136705
+ }
136706
+ return parseDeviceAuthorizationResponse(await readJsonOrDeviceError(response));
136707
+ }
136708
+ );
136709
+ }
136710
+ async function pollDeviceToken(runtime, issuance) {
136711
+ const deadline = runtime.now() + Math.min(issuance.expiresIn, MAX_DEVICE_FLOW_SECONDS) * 1e3;
136712
+ let intervalSeconds = issuance.interval;
136713
+ while (runtime.now() < deadline) {
136714
+ const remainingMs = deadline - runtime.now();
136715
+ if (remainingMs <= 0) break;
136716
+ await runtime.sleepImpl(Math.min(intervalSeconds * 1e3, remainingMs));
136717
+ const result = await requestDeviceToken(runtime, issuance.deviceCode);
136718
+ if (result.tokens) return result.tokens;
136719
+ if (result.slowDown) {
136720
+ intervalSeconds = Math.min(
136721
+ Math.max(intervalSeconds + 5, result.retryAfterSeconds ?? 0),
136722
+ MAX_DEVICE_POLL_SECONDS
136723
+ );
136724
+ }
136725
+ }
136726
+ throw ErrDeviceAuthFailed("the code expired");
136727
+ }
136728
+ async function requestDeviceToken(runtime, deviceCode) {
136729
+ return await withDeviceRequestTimeout(
136730
+ runtime,
136731
+ "lost contact with the authorization server",
136732
+ async (signal) => {
136733
+ const response = await runtime.fetchImpl(tokenEndpoint(), {
136734
+ method: "POST",
136735
+ headers: {
136736
+ "content-type": "application/x-www-form-urlencoded",
136737
+ accept: "application/json"
136738
+ },
136739
+ body: new URLSearchParams({
136740
+ grant_type: DEVICE_CODE_GRANT_TYPE,
136741
+ device_code: deviceCode,
136742
+ client_id: runtime.clientId
136743
+ }).toString(),
136744
+ signal
136745
+ });
136746
+ return await evaluateDevicePollResponse(response, runtime.now());
136747
+ }
136748
+ );
136749
+ }
136750
+ async function evaluateDevicePollResponse(response, nowMs) {
136751
+ if (response.ok) {
136752
+ return { tokens: parseTokenResponse(await readJsonOrDeviceError(response)) };
136753
+ }
136754
+ const error = await readDeviceOAuthError(response);
136755
+ switch (error) {
136756
+ case "authorization_pending":
136757
+ return {};
136758
+ case "slow_down":
136759
+ return { slowDown: true, retryAfterSeconds: retryAfterSeconds(response, nowMs) };
136760
+ case "access_denied":
136761
+ throw ErrDeviceAuthFailed("access was denied");
136762
+ case "expired_token":
136763
+ throw ErrDeviceAuthFailed("the code expired");
136764
+ default:
136765
+ if (response.status === 429) {
136766
+ return { slowDown: true, retryAfterSeconds: retryAfterSeconds(response, nowMs) };
136767
+ }
136768
+ throw ErrDeviceAuthFailed(`authorization server returned HTTP ${response.status}`);
136769
+ }
136770
+ }
136771
+ async function withDeviceRequestTimeout(runtime, networkError, operation) {
136772
+ const controller = new AbortController();
136773
+ const timer = setTimeout(() => controller.abort(), runtime.requestTimeoutMs);
136774
+ try {
136775
+ return await operation(controller.signal);
136776
+ } catch (err) {
136777
+ if (controller.signal.aborted) {
136778
+ throw ErrDeviceAuthFailed("authorization server request timed out");
136779
+ }
136780
+ if (isAuthError(err)) throw err;
136781
+ throw ErrDeviceAuthFailed(networkError);
136782
+ } finally {
136783
+ clearTimeout(timer);
136784
+ }
136785
+ }
136786
+ function retryAfterSeconds(response, nowMs) {
136787
+ const value = response.headers.get("retry-after")?.trim();
136788
+ if (!value) return void 0;
136789
+ if (/^\d+$/.test(value)) return Math.min(Number(value), MAX_DEVICE_POLL_SECONDS);
136790
+ const retryAt = Date.parse(value);
136791
+ if (!Number.isFinite(retryAt)) return void 0;
136792
+ return Math.min(Math.max(Math.ceil((retryAt - nowMs) / 1e3), 0), MAX_DEVICE_POLL_SECONDS);
136793
+ }
136405
136794
  async function refreshTokens(refresh_token, opts = {}) {
136406
136795
  const clientId = resolveClientId();
136407
136796
  const fetchImpl = opts.fetchImpl ?? fetch;
@@ -136562,6 +136951,146 @@ async function persistOAuth(tokens, opts) {
136562
136951
  const oauth = opts.preserveMissing ? { ...existing.oauth, ...tokens } : { ...tokens };
136563
136952
  await writeStore({ ...existing, oauth });
136564
136953
  }
136954
+ async function persistVerifiedOAuthSession(tokens, user) {
136955
+ let credentials = {};
136956
+ try {
136957
+ ({ credentials } = await readStore());
136958
+ } catch {
136959
+ credentials = {};
136960
+ }
136961
+ const next = {
136962
+ ...credentials,
136963
+ oauth: { ...tokens }
136964
+ };
136965
+ if (user.email || user.first_name || user.last_name || user.username) {
136966
+ next.user = {
136967
+ ...credentials.user,
136968
+ email: user.email,
136969
+ first_name: user.first_name,
136970
+ last_name: user.last_name,
136971
+ username: user.username
136972
+ };
136973
+ } else {
136974
+ delete next.user;
136975
+ }
136976
+ await writeStore(next);
136977
+ }
136978
+ function parseDeviceAuthorizationResponse(payload) {
136979
+ const data2 = requireDeviceAuthorizationRecord(payload);
136980
+ const deviceCode = stringField(data2, "device_code");
136981
+ const userCode = stringField(data2, "user_code");
136982
+ const verificationUri = requiredSafeVerificationUri(data2, "verification_uri");
136983
+ const verificationUriComplete = optionalSafeVerificationUri(data2, "verification_uri_complete");
136984
+ const expiresIn = strictNumericField(data2, "expires_in");
136985
+ const interval = strictNumericField(data2, "interval");
136986
+ requireSafeDeviceCode(deviceCode);
136987
+ requireSafeDeviceCode(userCode);
136988
+ const timing2 = normalizeDeviceAuthorizationTiming(data2, expiresIn, interval);
136989
+ return {
136990
+ deviceCode,
136991
+ userCode,
136992
+ verificationUri,
136993
+ ...verificationUriComplete ? { verificationUriComplete } : {},
136994
+ ...timing2
136995
+ };
136996
+ }
136997
+ function requireSafeDeviceCode(value) {
136998
+ if (!value || !isHeaderSafe(value)) {
136999
+ throw ErrDeviceAuthFailed("authorization server returned an invalid response");
137000
+ }
137001
+ }
137002
+ function normalizeDeviceAuthorizationTiming(data2, expiresIn, interval) {
137003
+ if (!isPositiveNumber(expiresIn) || data2["interval"] !== void 0 && !isPositiveNumber(interval)) {
137004
+ throw ErrDeviceAuthFailed("authorization server returned invalid timing values");
137005
+ }
137006
+ return {
137007
+ expiresIn,
137008
+ interval: Math.min(
137009
+ Math.max(Math.ceil(interval ?? MIN_DEVICE_POLL_SECONDS), MIN_DEVICE_POLL_SECONDS),
137010
+ MAX_DEVICE_POLL_SECONDS
137011
+ )
137012
+ };
137013
+ }
137014
+ function requiredSafeVerificationUri(data2, key2) {
137015
+ const value = normalizeSafeVerificationUri(stringField(data2, key2));
137016
+ if (!value) throw ErrDeviceAuthFailed("authorization server returned an unsafe verification URL");
137017
+ return value;
137018
+ }
137019
+ function optionalSafeVerificationUri(data2, key2) {
137020
+ if (data2[key2] === void 0) return void 0;
137021
+ return requiredSafeVerificationUri(data2, key2);
137022
+ }
137023
+ function requireDeviceAuthorizationRecord(payload) {
137024
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
137025
+ throw ErrDeviceAuthFailed("authorization server returned an invalid response");
137026
+ }
137027
+ return payload;
137028
+ }
137029
+ function isPositiveNumber(value) {
137030
+ return value !== void 0 && value > 0;
137031
+ }
137032
+ function normalizeSafeVerificationUri(value) {
137033
+ if (!value || !isHeaderSafe(value)) return void 0;
137034
+ try {
137035
+ const url = new URL(value);
137036
+ if (url.username || url.password) return void 0;
137037
+ const allowed = url.protocol === "https:" || url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname);
137038
+ return allowed ? url.href : void 0;
137039
+ } catch {
137040
+ return void 0;
137041
+ }
137042
+ }
137043
+ async function readJsonOrDeviceError(res) {
137044
+ return await readBoundedDeviceJson(res);
137045
+ }
137046
+ async function readDeviceOAuthError(res) {
137047
+ try {
137048
+ const payload = await readBoundedDeviceJson(res);
137049
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return void 0;
137050
+ const error = payload["error"];
137051
+ return typeof error === "string" ? error : void 0;
137052
+ } catch {
137053
+ return void 0;
137054
+ }
137055
+ }
137056
+ function strictNumericField(obj, key2) {
137057
+ const value = obj[key2];
137058
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
137059
+ if (typeof value !== "string" || value.trim() === "") return void 0;
137060
+ const parsed = Number(value);
137061
+ return Number.isFinite(parsed) ? parsed : void 0;
137062
+ }
137063
+ async function readBoundedDeviceJson(res) {
137064
+ if (!res.body) throw ErrDeviceAuthFailed("authorization server returned no data");
137065
+ const reader = res.body.getReader();
137066
+ const chunks = [];
137067
+ let total = 0;
137068
+ try {
137069
+ while (true) {
137070
+ const { done, value } = await reader.read();
137071
+ if (done) break;
137072
+ if (!value) continue;
137073
+ total += value.byteLength;
137074
+ if (total > MAX_DEVICE_RESPONSE_BYTES) {
137075
+ await reader.cancel();
137076
+ throw ErrDeviceAuthFailed("authorization server response was too large");
137077
+ }
137078
+ chunks.push(value);
137079
+ }
137080
+ const body = new Uint8Array(total);
137081
+ let offset2 = 0;
137082
+ for (const chunk of chunks) {
137083
+ body.set(chunk, offset2);
137084
+ offset2 += chunk.byteLength;
137085
+ }
137086
+ return JSON.parse(new TextDecoder().decode(body));
137087
+ } catch (err) {
137088
+ if (isAuthError(err)) throw err;
137089
+ throw ErrDeviceAuthFailed("authorization server returned non-JSON data");
137090
+ } finally {
137091
+ reader.releaseLock();
137092
+ }
137093
+ }
136565
137094
  async function readJsonOrThrow(res) {
136566
137095
  try {
136567
137096
  return await res.json();
@@ -136576,7 +137105,7 @@ async function safeText2(res) {
136576
137105
  return "";
136577
137106
  }
136578
137107
  }
136579
- var REVOKE_TIMEOUT_MS, MIN_EXPIRES_IN_SECONDS, DEFAULT_CLIENT_ID, DEFAULT_SCOPES, DEFAULT_AUTHORIZE_URL, DEFAULT_TOKEN_URL, DEFAULT_REVOKE_URL;
137108
+ var REVOKE_TIMEOUT_MS, MIN_EXPIRES_IN_SECONDS, DEFAULT_CLIENT_ID, DEFAULT_SCOPES, DEFAULT_AUTHORIZE_URL, DEFAULT_TOKEN_URL, DEFAULT_REVOKE_URL, DEFAULT_DEVICE_AUTHORIZATION_URL, DEVICE_CODE_GRANT_TYPE, MAX_DEVICE_FLOW_SECONDS, MIN_DEVICE_POLL_SECONDS, MAX_DEVICE_POLL_SECONDS, MAX_DEVICE_RESPONSE_BYTES, DEVICE_REQUEST_TIMEOUT_MS;
136580
137109
  var init_oauth = __esm({
136581
137110
  "src/auth/oauth.ts"() {
136582
137111
  "use strict";
@@ -136595,6 +137124,13 @@ var init_oauth = __esm({
136595
137124
  DEFAULT_AUTHORIZE_URL = "https://app.heygen.com/oauth/authorize";
136596
137125
  DEFAULT_TOKEN_URL = "https://api2.heygen.com/v1/oauth/token";
136597
137126
  DEFAULT_REVOKE_URL = "https://api2.heygen.com/v1/oauth/revoke";
137127
+ DEFAULT_DEVICE_AUTHORIZATION_URL = "https://api2.heygen.com/v1/oauth/device_authorization";
137128
+ DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
137129
+ MAX_DEVICE_FLOW_SECONDS = 30 * 60;
137130
+ MIN_DEVICE_POLL_SECONDS = 5;
137131
+ MAX_DEVICE_POLL_SECONDS = 60;
137132
+ MAX_DEVICE_RESPONSE_BYTES = 64 * 1024;
137133
+ DEVICE_REQUEST_TIMEOUT_MS = 15e3;
136598
137134
  }
136599
137135
  });
136600
137136
 
@@ -136613,7 +137149,7 @@ var init_auth = __esm({
136613
137149
  });
136614
137150
 
136615
137151
  // src/utils/projectLink.ts
136616
- import { randomUUID as randomUUID9 } from "crypto";
137152
+ import { randomUUID as randomUUID10 } from "crypto";
136617
137153
  import { existsSync as existsSync75, mkdirSync as mkdirSync39, readFileSync as readFileSync46, writeFileSync as writeFileSync28 } from "fs";
136618
137154
  import { homedir as homedir16 } from "os";
136619
137155
  import { join as join83, resolve as resolve47 } from "path";
@@ -136664,7 +137200,7 @@ function ensureProjectId(absDir) {
136664
137200
  const path2 = resolve47(absDir);
136665
137201
  const existing = links[path2];
136666
137202
  if (existing) return existing.projectId;
136667
- const projectId = randomUUID9();
137203
+ const projectId = randomUUID10();
136668
137204
  links[path2] = { projectId, url: "" };
136669
137205
  writeProjectLinks(links);
136670
137206
  return projectId;
@@ -150344,7 +150880,7 @@ __export(feedback_exports, {
150344
150880
  default: () => feedback_default,
150345
150881
  examples: () => examples26
150346
150882
  });
150347
- import { randomUUID as randomUUID10 } from "crypto";
150883
+ import { randomUUID as randomUUID11 } from "crypto";
150348
150884
  import { resolve as resolve66 } from "path";
150349
150885
  import open from "open";
150350
150886
  function normalizeComment(raw) {
@@ -150497,7 +151033,7 @@ var init_feedback2 = __esm({
150497
151033
  }
150498
151034
  const comment = normalizeComment(args.comment);
150499
151035
  const doctorSummary = await getDoctorSummary();
150500
- const feedbackId = randomUUID10();
151036
+ const feedbackId = randomUUID11();
150501
151037
  const config = readConfig();
150502
151038
  const joinKeys = buildTelemetryJoinKeys({
150503
151039
  feedbackId,
@@ -158802,7 +159338,7 @@ var require_gaxios = __commonJS({
158802
159338
  var retry_js_1 = require_retry3();
158803
159339
  var stream_1 = __require("stream");
158804
159340
  var interceptor_js_1 = require_interceptor();
158805
- var randomUUID11 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
159341
+ var randomUUID12 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
158806
159342
  var HTTP_STATUS_NO_CONTENT = 204;
158807
159343
  var Gaxios = class {
158808
159344
  agentCache = /* @__PURE__ */ new Map();
@@ -159075,7 +159611,7 @@ var require_gaxios = __commonJS({
159075
159611
  */
159076
159612
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
159077
159613
  if (opts.multipart?.length) {
159078
- const boundary = await randomUUID11();
159614
+ const boundary = await randomUUID12();
159079
159615
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
159080
159616
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
159081
159617
  } else if (shouldDirectlyPassData) {
@@ -192744,7 +193280,7 @@ __export(compare_exports, {
192744
193280
  parseCompareArgs: () => parseCompareArgs,
192745
193281
  prepareCompareVariantProjects: () => prepareCompareVariantProjects
192746
193282
  });
192747
- import { cpSync as cpSync6, existsSync as existsSync105, mkdirSync as mkdirSync51, mkdtempSync as mkdtempSync17, renameSync as renameSync15, rmSync as rmSync32, statSync as statSync36 } from "fs";
193283
+ import { cpSync as cpSync6, existsSync as existsSync105, mkdirSync as mkdirSync51, mkdtempSync as mkdtempSync17, renameSync as renameSync15, rmSync as rmSync33, statSync as statSync36 } from "fs";
192748
193284
  import { tmpdir as tmpdir17 } from "os";
192749
193285
  import { basename as basename28, dirname as dirname55, extname as extname23, join as join110 } from "path";
192750
193286
  function defaultLabelForPath(input2) {
@@ -192864,7 +193400,7 @@ function stageHtmlVariant(variant) {
192864
193400
  stagedDir
192865
193401
  };
192866
193402
  } catch (err) {
192867
- rmSync32(stagedDir, { recursive: true, force: true });
193403
+ rmSync33(stagedDir, { recursive: true, force: true });
192868
193404
  throw err;
192869
193405
  }
192870
193406
  }
@@ -192893,7 +193429,7 @@ function prepareCompareVariantProjects(variants) {
192893
193429
  } catch (err) {
192894
193430
  for (const variant of prepared) {
192895
193431
  if (variant.stagedDir) {
192896
- rmSync32(variant.stagedDir, { recursive: true, force: true });
193432
+ rmSync33(variant.stagedDir, { recursive: true, force: true });
192897
193433
  }
192898
193434
  }
192899
193435
  throw err;
@@ -192902,7 +193438,7 @@ function prepareCompareVariantProjects(variants) {
192902
193438
  function cleanupPreparedCompareVariants(variants) {
192903
193439
  for (const variant of variants) {
192904
193440
  if (variant.stagedDir) {
192905
- rmSync32(variant.stagedDir, { recursive: true, force: true });
193441
+ rmSync33(variant.stagedDir, { recursive: true, force: true });
192906
193442
  }
192907
193443
  }
192908
193444
  }
@@ -192974,7 +193510,7 @@ async function renderCompareSheet(parsed) {
192974
193510
  return buildCompareSuccessPayload(parsed.outPath, variants, capResult);
192975
193511
  } finally {
192976
193512
  cleanupPreparedCompareVariants(prepared);
192977
- rmSync32(frameDir, { recursive: true, force: true });
193513
+ rmSync33(frameDir, { recursive: true, force: true });
192978
193514
  }
192979
193515
  }
192980
193516
  function printJson2(payload) {
@@ -197690,7 +198226,7 @@ __export(state_exports, {
197690
198226
  stateFilePath: () => stateFilePath,
197691
198227
  writeStackOutputs: () => writeStackOutputs
197692
198228
  });
197693
- import { existsSync as existsSync113, mkdirSync as mkdirSync58, readdirSync as readdirSync38, readFileSync as readFileSync77, rmSync as rmSync33, writeFileSync as writeFileSync53 } from "fs";
198229
+ import { existsSync as existsSync113, mkdirSync as mkdirSync58, readdirSync as readdirSync38, readFileSync as readFileSync77, rmSync as rmSync34, writeFileSync as writeFileSync53 } from "fs";
197694
198230
  import { dirname as dirname56, join as join121 } from "path";
197695
198231
  function stateFilePath(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
197696
198232
  return join121(cwd, STATE_DIR_NAME, `${STATE_FILE_PREFIX}${stackName}.json`);
@@ -197712,7 +198248,7 @@ function readStackOutputs(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
197712
198248
  }
197713
198249
  function deleteStackOutputs(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
197714
198250
  const path2 = stateFilePath(stackName, cwd);
197715
- if (existsSync113(path2)) rmSync33(path2);
198251
+ if (existsSync113(path2)) rmSync34(path2);
197716
198252
  }
197717
198253
  function listStackNames(cwd = process.cwd()) {
197718
198254
  const dir = join121(cwd, STATE_DIR_NAME);
@@ -201752,6 +202288,98 @@ __export(login_exports, {
201752
202288
  default: () => login_default
201753
202289
  });
201754
202290
  import { stdin as input } from "process";
202291
+ function isRemoteOrHeadless() {
202292
+ const remoteEnvironment = [
202293
+ "CODESPACES",
202294
+ "GITHUB_CODESPACES",
202295
+ "REMOTE_CONTAINERS",
202296
+ "GITPOD_WORKSPACE_ID",
202297
+ "container"
202298
+ ].some(envFlagEnabled);
202299
+ return Boolean(
202300
+ process.env["SSH_CONNECTION"] || process.env["SSH_CLIENT"] || process.env["SSH_TTY"] || process.env["BROWSER"] === "none" || process.env["HF_NO_BROWSER"] === "1" || remoteEnvironment || process.stdout.isTTY !== true
202301
+ );
202302
+ }
202303
+ function envFlagEnabled(name) {
202304
+ const value = process.env[name]?.trim().toLowerCase();
202305
+ return Boolean(value && value !== "0" && value !== "false" && value !== "no");
202306
+ }
202307
+ function assertAttendedDeviceFlow() {
202308
+ if (envFlagEnabled("CI") || process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
202309
+ console.error(
202310
+ c.error(
202311
+ "`--device` requires an attended terminal and is disabled in CI. Use an API key or workload credential for automation."
202312
+ )
202313
+ );
202314
+ failUsage();
202315
+ }
202316
+ }
202317
+ async function runDeviceLogin() {
202318
+ assertAttendedDeviceFlow();
202319
+ assertOAuthConfiguredOrExit();
202320
+ const { trackAuthLoginStarted: trackAuthLoginStarted2, trackAuthLoginCompleted: trackAuthLoginCompleted2, trackAuthLoginFailed: trackAuthLoginFailed2, identifyUser: identifyUser2 } = await Promise.resolve().then(() => (init_telemetry2(), telemetry_exports2));
202321
+ trackAuthLoginStarted2("device");
202322
+ let tokens;
202323
+ try {
202324
+ tokens = await startDeviceAuthorizationFlow({
202325
+ onChallenge: ({ verificationUri, verificationUriComplete, userCode }) => {
202326
+ console.log(`Open ${c.accent(verificationUriComplete ?? verificationUri)} in a browser.`);
202327
+ if (!verificationUriComplete) console.log(`Enter code ${c.bold(userCode)}.`);
202328
+ console.log(c.dim("Waiting for approval\u2026"));
202329
+ }
202330
+ });
202331
+ } catch (err) {
202332
+ const message = err.message || "Device authorization failed.";
202333
+ trackAuthLoginFailed2("device", /expired/i.test(message) ? "flow_timeout" : "flow_error");
202334
+ console.error(c.error(message));
202335
+ failCommand();
202336
+ }
202337
+ const credential = {
202338
+ type: "oauth",
202339
+ access_token: tokens.access_token,
202340
+ ...tokens.refresh_token ? { refresh_token: tokens.refresh_token } : {},
202341
+ source: "file_json",
202342
+ refreshable: false
202343
+ };
202344
+ let user;
202345
+ try {
202346
+ user = await new AuthClient().getCurrentUser(credential);
202347
+ } catch (err) {
202348
+ await revokeDeviceTokens(tokens);
202349
+ trackAuthLoginFailed2("device", "rejected");
202350
+ console.error(
202351
+ c.error(
202352
+ `HeyGen could not verify the approved device session; no credential was saved. ${err.message}`
202353
+ )
202354
+ );
202355
+ failCommand();
202356
+ }
202357
+ try {
202358
+ await persistVerifiedOAuthSession(tokens, toStoredUserInfo(user));
202359
+ } catch (err) {
202360
+ await revokeDeviceTokens(tokens);
202361
+ trackAuthLoginFailed2("device", "flow_error");
202362
+ console.error(
202363
+ c.error(
202364
+ `Could not save the verified device session; it was revoked. ${err.message}`
202365
+ )
202366
+ );
202367
+ failCommand();
202368
+ }
202369
+ const id = identityKey(user);
202370
+ if (id) identifyUser2(id);
202371
+ trackAuthLoginCompleted2("device", id);
202372
+ const identity = userDisplayName(toStoredUserInfo(user)) ?? "(unknown user)";
202373
+ console.log(c.success(`\u2713 Signed in as ${identity}.`));
202374
+ }
202375
+ async function revokeDeviceTokens(tokens) {
202376
+ await revokeTokens(tokens.access_token, { token_type_hint: "access_token" });
202377
+ if (tokens.refresh_token) {
202378
+ await revokeTokens(tokens.refresh_token, {
202379
+ token_type_hint: "refresh_token"
202380
+ });
202381
+ }
202382
+ }
201755
202383
  async function runOAuthLogin() {
201756
202384
  assertOAuthConfiguredOrExit();
201757
202385
  const { trackAuthLoginStarted: trackAuthLoginStarted2, trackAuthLoginFailed: trackAuthLoginFailed2 } = await Promise.resolve().then(() => (init_telemetry2(), telemetry_exports2));
@@ -201886,7 +202514,11 @@ async function rollback(previous) {
201886
202514
  async function verifyAndReport(key2) {
201887
202515
  const client = new AuthClient();
201888
202516
  try {
201889
- const user = await client.getCurrentUser({ type: "api_key", key: key2, source: "file_json" });
202517
+ const user = await client.getCurrentUser({
202518
+ type: "api_key",
202519
+ key: key2,
202520
+ source: "file_json"
202521
+ });
201890
202522
  await persistUserInfo(user);
201891
202523
  const identity = userDisplayName(toStoredUserInfo(user)) ?? "(unknown user)";
201892
202524
  console.log(c.success(`\u2713 API key saved. Authenticated as ${identity}.`));
@@ -201963,15 +202595,35 @@ var init_login = __esm({
201963
202595
  "api-key": {
201964
202596
  type: "string",
201965
202597
  description: "API key value, or pass `--api-key` with no value to read from stdin / prompt."
202598
+ },
202599
+ device: {
202600
+ type: "boolean",
202601
+ description: "Use an attended device code (for SSH/headless terminals; never for CI)."
201966
202602
  }
201967
202603
  },
201968
202604
  // fallow-ignore-next-line complexity
201969
202605
  async run({ args }) {
201970
202606
  const inlineKey = args["api-key"];
202607
+ if (inlineKey !== void 0 && args.device) {
202608
+ console.error(c.error("Choose either --device or --api-key, not both."));
202609
+ failUsage();
202610
+ }
201971
202611
  if (inlineKey !== void 0) {
201972
202612
  await runApiKeyLogin(inlineKey);
201973
202613
  return;
201974
202614
  }
202615
+ if (args.device) {
202616
+ await runDeviceLogin();
202617
+ return;
202618
+ }
202619
+ if (isRemoteOrHeadless()) {
202620
+ console.error(
202621
+ c.error(
202622
+ "Browser callback login is unavailable in this remote/headless terminal. Run `hyperframes auth login --device`."
202623
+ )
202624
+ );
202625
+ failUsage();
202626
+ }
201975
202627
  await runOAuthLogin();
201976
202628
  }
201977
202629
  });
@@ -202394,6 +203046,7 @@ var init_auth3 = __esm({
202394
203046
  init_colors();
202395
203047
  examples37 = [
202396
203048
  ["Sign in via browser (OAuth)", "hyperframes auth login"],
203049
+ ["Sign in from SSH/headless terminal", "hyperframes auth login --device"],
202397
203050
  ["Save an API key (interactive)", "hyperframes auth login --api-key"],
202398
203051
  ["Save an API key from stdin", "echo $HEYGEN_API_KEY | hyperframes auth login --api-key"],
202399
203052
  ["Check who you're signed in as", "hyperframes auth status"],
@@ -202407,7 +203060,7 @@ Manage HeyGen credentials. Credentials live in
202407
203060
  ${c.accent("~/.heygen/credentials")} and are shared with heygen-cli.
202408
203061
 
202409
203062
  ${c.bold("SUBCOMMANDS:")}
202410
- ${c.accent("login")} ${c.dim("Sign in via browser (default) or --api-key for a long-lived key.")}
203063
+ ${c.accent("login")} ${c.dim("Sign in via browser, --device for SSH, or --api-key for a long-lived key.")}
202411
203064
  ${c.accent("status")} ${c.dim("Show the active credential's source, type, and identity.")}
202412
203065
  ${c.accent("refresh")} ${c.dim("Force-refresh the OAuth access token.")}
202413
203066
  ${c.accent("logout")} ${c.dim("Remove the stored credential (--keep-api-key for OAuth-only).")}
@@ -202418,6 +203071,7 @@ ${c.bold("ENV VARS:")}
202418
203071
  ${c.accent("HEYGEN_API_URL")} Override the API base URL (default https://api.heygen.com).
202419
203072
  ${c.accent("HEYGEN_CONFIG_DIR")} Override the credentials directory (default ~/.heygen).
202420
203073
  ${c.accent("HYPERFRAMES_OAUTH_CLIENT_ID")} Override the OAuth client_id (for dev/test).
203074
+ ${c.accent("HYPERFRAMES_OAUTH_DEVICE_URL")} Override the RFC 8628 device endpoint (for dev/test).
202421
203075
  `;
202422
203076
  auth_default = defineCommand({
202423
203077
  meta: { name: "auth", description: "Sign in to HeyGen and manage credentials" },
@@ -202699,7 +203353,7 @@ var init_parseFigmaRef = __esm({
202699
203353
  });
202700
203354
 
202701
203355
  // ../core/dist/figma/freeze.js
202702
- import { copyFileSync as copyFileSync11, mkdirSync as mkdirSync61, rmSync as rmSync34, statSync as statSync39, writeFileSync as writeFileSync55 } from "fs";
203356
+ import { copyFileSync as copyFileSync11, mkdirSync as mkdirSync61, rmSync as rmSync35, statSync as statSync39, writeFileSync as writeFileSync55 } from "fs";
202703
203357
  import { dirname as dirname59 } from "path";
202704
203358
  function exceedsFreezeCap(byteLength) {
202705
203359
  return byteLength > MAX_FREEZE_BYTES;
@@ -202715,7 +203369,7 @@ function freezeBytes(bytes, destPath) {
202715
203369
  } catch (err) {
202716
203370
  if (err.code !== "EEXIST")
202717
203371
  throw err;
202718
- rmSync34(destPath);
203372
+ rmSync35(destPath);
202719
203373
  writeFileSync55(destPath, bytes, { flag: "wx" });
202720
203374
  }
202721
203375
  return bytes.length;