hyperframes 0.4.39 → 0.4.41
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.
|
|
57
|
+
VERSION = true ? "0.4.41" : "0.0.0-dev";
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -10206,8 +10206,8 @@ function trackRenderError(props) {
|
|
|
10206
10206
|
memory_free_mb: props.memoryFreeMb
|
|
10207
10207
|
});
|
|
10208
10208
|
}
|
|
10209
|
-
function trackInitTemplate(templateId) {
|
|
10210
|
-
trackEvent("init_template", { template: templateId });
|
|
10209
|
+
function trackInitTemplate(templateId, props) {
|
|
10210
|
+
trackEvent("init_template", { template: templateId, tailwind: props?.tailwind });
|
|
10211
10211
|
}
|
|
10212
10212
|
function trackBrowserInstall() {
|
|
10213
10213
|
trackEvent("browser_install", {});
|
|
@@ -25516,6 +25516,13 @@ function resolveConfig(overrides) {
|
|
|
25516
25516
|
"PRODUCER_ENABLE_STREAMING_ENCODE",
|
|
25517
25517
|
DEFAULT_CONFIG2.enableStreamingEncode
|
|
25518
25518
|
),
|
|
25519
|
+
streamingEncodeMaxDurationSeconds: Math.max(
|
|
25520
|
+
0,
|
|
25521
|
+
envNum(
|
|
25522
|
+
"PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS",
|
|
25523
|
+
DEFAULT_CONFIG2.streamingEncodeMaxDurationSeconds
|
|
25524
|
+
)
|
|
25525
|
+
),
|
|
25519
25526
|
ffmpegEncodeTimeout: envNum("FFMPEG_ENCODE_TIMEOUT_MS", DEFAULT_CONFIG2.ffmpegEncodeTimeout),
|
|
25520
25527
|
ffmpegProcessTimeout: envNum("FFMPEG_PROCESS_TIMEOUT_MS", DEFAULT_CONFIG2.ffmpegProcessTimeout),
|
|
25521
25528
|
ffmpegStreamingTimeout: envNum(
|
|
@@ -25573,7 +25580,8 @@ var init_config2 = __esm({
|
|
|
25573
25580
|
forceScreenshot: false,
|
|
25574
25581
|
enableChunkedEncode: false,
|
|
25575
25582
|
chunkSizeFrames: 360,
|
|
25576
|
-
enableStreamingEncode:
|
|
25583
|
+
enableStreamingEncode: true,
|
|
25584
|
+
streamingEncodeMaxDurationSeconds: 240,
|
|
25577
25585
|
ffmpegEncodeTimeout: 6e5,
|
|
25578
25586
|
ffmpegProcessTimeout: 3e5,
|
|
25579
25587
|
ffmpegStreamingTimeout: 6e5,
|
|
@@ -26182,6 +26190,44 @@ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100)
|
|
|
26182
26190
|
}
|
|
26183
26191
|
return Boolean(await page.evaluate(expression));
|
|
26184
26192
|
}
|
|
26193
|
+
async function applyVideoMetadataHints(page, hints) {
|
|
26194
|
+
if (!hints || hints.length === 0) return;
|
|
26195
|
+
await page.evaluate(
|
|
26196
|
+
(metadataHints) => {
|
|
26197
|
+
for (const hint of metadataHints) {
|
|
26198
|
+
if (!hint.id || !Number.isFinite(hint.width) || !Number.isFinite(hint.height) || hint.width <= 0 || hint.height <= 0) {
|
|
26199
|
+
continue;
|
|
26200
|
+
}
|
|
26201
|
+
const video = document.getElementById(hint.id);
|
|
26202
|
+
if (!video) continue;
|
|
26203
|
+
if (!video.hasAttribute("width")) video.setAttribute("width", String(hint.width));
|
|
26204
|
+
if (!video.hasAttribute("height")) video.setAttribute("height", String(hint.height));
|
|
26205
|
+
const computed = window.getComputedStyle(video);
|
|
26206
|
+
if (!video.style.aspectRatio && (!computed.aspectRatio || computed.aspectRatio === "auto")) {
|
|
26207
|
+
video.style.aspectRatio = `${hint.width} / ${hint.height}`;
|
|
26208
|
+
}
|
|
26209
|
+
}
|
|
26210
|
+
},
|
|
26211
|
+
[...hints]
|
|
26212
|
+
);
|
|
26213
|
+
}
|
|
26214
|
+
async function waitForOptionalTailwindReady(page, timeoutMs) {
|
|
26215
|
+
const hasTailwindReady = await page.evaluate(
|
|
26216
|
+
`(() => { const ready = window.__tailwindReady; return !!ready && typeof ready.then === "function"; })()`
|
|
26217
|
+
);
|
|
26218
|
+
if (!hasTailwindReady) return;
|
|
26219
|
+
const ready = await Promise.race([
|
|
26220
|
+
page.evaluate(
|
|
26221
|
+
`Promise.resolve(window.__tailwindReady).then(() => true, () => false)`
|
|
26222
|
+
),
|
|
26223
|
+
new Promise((resolve39) => setTimeout(() => resolve39(false), timeoutMs))
|
|
26224
|
+
]);
|
|
26225
|
+
if (!ready) {
|
|
26226
|
+
throw new Error(
|
|
26227
|
+
`[FrameCapture] window.__tailwindReady not resolved after ${timeoutMs}ms. Tailwind browser runtime must finish before frame capture starts.`
|
|
26228
|
+
);
|
|
26229
|
+
}
|
|
26230
|
+
}
|
|
26185
26231
|
async function initializeSession(session) {
|
|
26186
26232
|
const { page, serverUrl } = session;
|
|
26187
26233
|
page.on("console", (msg) => {
|
|
@@ -26225,6 +26271,7 @@ async function initializeSession(session) {
|
|
|
26225
26271
|
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
|
|
26226
26272
|
);
|
|
26227
26273
|
}
|
|
26274
|
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
26228
26275
|
const skipIdsLiteral = JSON.stringify(session.options.skipReadinessVideoIds ?? []);
|
|
26229
26276
|
const videosReady = await pollPageExpression(
|
|
26230
26277
|
page,
|
|
@@ -26237,6 +26284,7 @@ async function initializeSession(session) {
|
|
|
26237
26284
|
);
|
|
26238
26285
|
}
|
|
26239
26286
|
await page.evaluate(`document.fonts?.ready`);
|
|
26287
|
+
await waitForOptionalTailwindReady(page, pageReadyTimeout2);
|
|
26240
26288
|
if (session.options.format === "png") {
|
|
26241
26289
|
await initTransparentBackground(session.page);
|
|
26242
26290
|
}
|
|
@@ -26291,6 +26339,7 @@ async function initializeSession(session) {
|
|
|
26291
26339
|
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout}ms. Page must expose window.__hf = { duration, seek }.`
|
|
26292
26340
|
);
|
|
26293
26341
|
}
|
|
26342
|
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
26294
26343
|
const beginframeSkipIdsLiteral = JSON.stringify(session.options.skipReadinessVideoIds ?? []);
|
|
26295
26344
|
const videoDeadline = Date.now() + (session.config?.playerReadyTimeout ?? DEFAULT_CONFIG2.playerReadyTimeout);
|
|
26296
26345
|
while (Date.now() < videoDeadline) {
|
|
@@ -26301,6 +26350,7 @@ async function initializeSession(session) {
|
|
|
26301
26350
|
await new Promise((r2) => setTimeout(r2, 100));
|
|
26302
26351
|
}
|
|
26303
26352
|
await page.evaluate(`document.fonts?.ready`);
|
|
26353
|
+
await waitForOptionalTailwindReady(page, pageReadyTimeout);
|
|
26304
26354
|
warmupRunning = false;
|
|
26305
26355
|
session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
|
|
26306
26356
|
if (session.options.format === "png") {
|
|
@@ -34108,6 +34158,24 @@ function applyRenderModeHints(cfg, compiled, log2 = defaultLogger) {
|
|
|
34108
34158
|
reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
|
|
34109
34159
|
});
|
|
34110
34160
|
}
|
|
34161
|
+
function collectVideoReadinessSkipIds(nativeHdrVideoIds, extractedVideos) {
|
|
34162
|
+
return Array.from(
|
|
34163
|
+
/* @__PURE__ */ new Set([
|
|
34164
|
+
...nativeHdrVideoIds,
|
|
34165
|
+
...extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => video.videoId)
|
|
34166
|
+
])
|
|
34167
|
+
).sort();
|
|
34168
|
+
}
|
|
34169
|
+
function hasUsableVideoDimensions(metadata) {
|
|
34170
|
+
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
34171
|
+
}
|
|
34172
|
+
function collectVideoMetadataHints(extractedVideos) {
|
|
34173
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
34174
|
+
id: video.videoId,
|
|
34175
|
+
width: video.metadata.width,
|
|
34176
|
+
height: video.metadata.height
|
|
34177
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
34178
|
+
}
|
|
34111
34179
|
function resolveRenderWorkerCount(totalFrames, requestedWorkers, cfg, compiled, composition, log2 = defaultLogger, measuredCaptureCost) {
|
|
34112
34180
|
const captureCost = combineCaptureCostEstimates(
|
|
34113
34181
|
estimateCaptureCostMultiplier(compiled, composition),
|
|
@@ -34770,6 +34838,27 @@ function createRenderJob(config) {
|
|
|
34770
34838
|
function normalizeCompositionSrcPath(srcPath) {
|
|
34771
34839
|
return srcPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
34772
34840
|
}
|
|
34841
|
+
function createStandaloneEntryRenderClone(root, host) {
|
|
34842
|
+
const hostClone = host.cloneNode(true);
|
|
34843
|
+
hostClone.setAttribute("data-start", "0");
|
|
34844
|
+
if (root === host) return hostClone;
|
|
34845
|
+
const rootClone = root.cloneNode(false);
|
|
34846
|
+
rootClone.appendChild(hostClone);
|
|
34847
|
+
return rootClone;
|
|
34848
|
+
}
|
|
34849
|
+
function replaceBodyWithRenderClone(body, renderClone) {
|
|
34850
|
+
while (body.firstChild) {
|
|
34851
|
+
body.removeChild(body.firstChild);
|
|
34852
|
+
}
|
|
34853
|
+
body.appendChild(renderClone);
|
|
34854
|
+
}
|
|
34855
|
+
function shouldUseStreamingEncode(cfg, outputFormat, workerCount, durationSeconds) {
|
|
34856
|
+
if (!cfg.enableStreamingEncode) return false;
|
|
34857
|
+
if (outputFormat === "png-sequence") return false;
|
|
34858
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return false;
|
|
34859
|
+
if (durationSeconds > cfg.streamingEncodeMaxDurationSeconds) return false;
|
|
34860
|
+
return workerCount === 1;
|
|
34861
|
+
}
|
|
34773
34862
|
function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
34774
34863
|
const normalizedEntryFile = normalizeCompositionSrcPath(entryFile);
|
|
34775
34864
|
const { document: document2 } = parseHTML(indexHtml);
|
|
@@ -34784,16 +34873,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
34784
34873
|
(candidate) => candidate.hasAttribute("data-composition-id")
|
|
34785
34874
|
) ?? null;
|
|
34786
34875
|
if (!root) return null;
|
|
34787
|
-
const
|
|
34788
|
-
|
|
34789
|
-
body.innerHTML = "";
|
|
34790
|
-
if (root === host) {
|
|
34791
|
-
body.appendChild(hostClone);
|
|
34792
|
-
return document2.toString();
|
|
34793
|
-
}
|
|
34794
|
-
const rootClone = root.cloneNode(false);
|
|
34795
|
-
rootClone.appendChild(hostClone);
|
|
34796
|
-
body.appendChild(rootClone);
|
|
34876
|
+
const renderClone = createStandaloneEntryRenderClone(root, host);
|
|
34877
|
+
replaceBodyWithRenderClone(body, renderClone);
|
|
34797
34878
|
return document2.toString();
|
|
34798
34879
|
}
|
|
34799
34880
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
@@ -34825,7 +34906,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
34825
34906
|
}
|
|
34826
34907
|
const enableChunkedEncode = cfg.enableChunkedEncode;
|
|
34827
34908
|
const chunkedEncodeSize = cfg.chunkSizeFrames;
|
|
34828
|
-
const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
|
|
34829
34909
|
let peakRssBytes = 0;
|
|
34830
34910
|
let peakHeapUsedBytes = 0;
|
|
34831
34911
|
const sampleMemory = () => {
|
|
@@ -35119,6 +35199,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35119
35199
|
let frameLookup = null;
|
|
35120
35200
|
const compiledDir = join35(workDir, "compiled");
|
|
35121
35201
|
let extractionResult = null;
|
|
35202
|
+
let videoReadinessSkipIds = [];
|
|
35203
|
+
let videoMetadataHints = [];
|
|
35122
35204
|
const nativeHdrVideoIds = /* @__PURE__ */ new Set();
|
|
35123
35205
|
const videoTransfers = /* @__PURE__ */ new Map();
|
|
35124
35206
|
if (job.config.hdrMode !== "force-sdr" && composition.videos.length > 0) {
|
|
@@ -35175,6 +35257,11 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35175
35257
|
if (extractionResult.extracted.length > 0) {
|
|
35176
35258
|
frameLookup = createFrameLookupTable(composition.videos, extractionResult.extracted);
|
|
35177
35259
|
}
|
|
35260
|
+
videoReadinessSkipIds = collectVideoReadinessSkipIds(
|
|
35261
|
+
nativeHdrVideoIds,
|
|
35262
|
+
extractionResult.extracted
|
|
35263
|
+
);
|
|
35264
|
+
videoMetadataHints = collectVideoMetadataHints(extractionResult.extracted);
|
|
35178
35265
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
35179
35266
|
const existingAudioSrcs = new Set(composition.audios.map((a) => a.src));
|
|
35180
35267
|
for (const ext of extractionResult.extracted) {
|
|
@@ -35288,9 +35375,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35288
35375
|
format: needsAlpha ? "png" : "jpeg",
|
|
35289
35376
|
quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95
|
|
35290
35377
|
};
|
|
35291
|
-
const
|
|
35378
|
+
const buildCaptureOptions = () => ({
|
|
35292
35379
|
...captureOptions,
|
|
35293
|
-
|
|
35380
|
+
videoMetadataHints,
|
|
35381
|
+
skipReadinessVideoIds: videoReadinessSkipIds
|
|
35294
35382
|
});
|
|
35295
35383
|
let captureCalibration;
|
|
35296
35384
|
let switchedToScreenshotAfterCalibration = false;
|
|
@@ -35303,7 +35391,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35303
35391
|
calibrationSession = await createCaptureSession(
|
|
35304
35392
|
fileServer.url,
|
|
35305
35393
|
calibrationDir,
|
|
35306
|
-
|
|
35394
|
+
buildCaptureOptions(),
|
|
35307
35395
|
videoInjector,
|
|
35308
35396
|
calibrationCfg
|
|
35309
35397
|
);
|
|
@@ -35386,6 +35474,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35386
35474
|
await closeCaptureSession(probeSession);
|
|
35387
35475
|
probeSession = null;
|
|
35388
35476
|
}
|
|
35477
|
+
let useStreamingEncode = shouldUseStreamingEncode(cfg, outputFormat, workerCount, job.duration);
|
|
35478
|
+
log2.info("streaming-encode gate", {
|
|
35479
|
+
enabled: useStreamingEncode,
|
|
35480
|
+
configFlag: cfg.enableStreamingEncode,
|
|
35481
|
+
outputFormat,
|
|
35482
|
+
workerCount,
|
|
35483
|
+
durationSeconds: job.duration,
|
|
35484
|
+
maxDurationSeconds: cfg.streamingEncodeMaxDurationSeconds
|
|
35485
|
+
});
|
|
35389
35486
|
const captureAttempts = [];
|
|
35390
35487
|
const FORMAT_EXT2 = {
|
|
35391
35488
|
mp4: ".mp4",
|
|
@@ -35427,7 +35524,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35427
35524
|
const domSession = await createCaptureSession(
|
|
35428
35525
|
fileServer.url,
|
|
35429
35526
|
framesDir,
|
|
35430
|
-
|
|
35527
|
+
buildCaptureOptions(),
|
|
35431
35528
|
createVideoFrameInjector(frameLookup),
|
|
35432
35529
|
cfg
|
|
35433
35530
|
);
|
|
@@ -35925,28 +36022,47 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35925
36022
|
} else {
|
|
35926
36023
|
let streamingEncoder = null;
|
|
35927
36024
|
let streamingEncoderClosed = false;
|
|
35928
|
-
if (
|
|
35929
|
-
|
|
35930
|
-
|
|
35931
|
-
|
|
35932
|
-
|
|
35933
|
-
|
|
35934
|
-
|
|
35935
|
-
|
|
35936
|
-
|
|
35937
|
-
|
|
35938
|
-
|
|
35939
|
-
|
|
35940
|
-
|
|
35941
|
-
|
|
35942
|
-
|
|
35943
|
-
|
|
35944
|
-
|
|
35945
|
-
|
|
35946
|
-
|
|
36025
|
+
if (useStreamingEncode) {
|
|
36026
|
+
try {
|
|
36027
|
+
streamingEncoder = await spawnStreamingEncoder(
|
|
36028
|
+
videoOnlyPath,
|
|
36029
|
+
{
|
|
36030
|
+
fps: job.config.fps,
|
|
36031
|
+
width,
|
|
36032
|
+
height,
|
|
36033
|
+
codec: preset.codec,
|
|
36034
|
+
preset: preset.preset,
|
|
36035
|
+
quality: effectiveQuality,
|
|
36036
|
+
bitrate: effectiveBitrate,
|
|
36037
|
+
pixelFormat: preset.pixelFormat,
|
|
36038
|
+
useGpu: job.config.useGpu,
|
|
36039
|
+
imageFormat: captureOptions.format || "jpeg",
|
|
36040
|
+
hdr: preset.hdr
|
|
36041
|
+
},
|
|
36042
|
+
abortSignal
|
|
36043
|
+
);
|
|
36044
|
+
assertNotAborted();
|
|
36045
|
+
} catch (err) {
|
|
36046
|
+
if (abortSignal?.aborted) {
|
|
36047
|
+
if (streamingEncoder && !streamingEncoderClosed) {
|
|
36048
|
+
await streamingEncoder.close().catch(() => {
|
|
36049
|
+
});
|
|
36050
|
+
streamingEncoderClosed = true;
|
|
36051
|
+
}
|
|
36052
|
+
throw err;
|
|
36053
|
+
}
|
|
36054
|
+
useStreamingEncode = false;
|
|
36055
|
+
streamingEncoder = null;
|
|
36056
|
+
log2.warn("[Render] Streaming encoder spawn failed; falling back to disk-frame encode.", {
|
|
36057
|
+
error: err instanceof Error ? err.message : String(err),
|
|
36058
|
+
outputFormat,
|
|
36059
|
+
workerCount,
|
|
36060
|
+
durationSeconds: job.duration
|
|
36061
|
+
});
|
|
36062
|
+
}
|
|
35947
36063
|
}
|
|
35948
36064
|
try {
|
|
35949
|
-
if (
|
|
36065
|
+
if (useStreamingEncode && streamingEncoder) {
|
|
35950
36066
|
const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
|
|
35951
36067
|
const currentEncoder = streamingEncoder;
|
|
35952
36068
|
if (workerCount > 1) {
|
|
@@ -35960,7 +36076,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35960
36076
|
fileServer.url,
|
|
35961
36077
|
workDir,
|
|
35962
36078
|
tasks,
|
|
35963
|
-
|
|
36079
|
+
buildCaptureOptions(),
|
|
35964
36080
|
() => createVideoFrameInjector(frameLookup),
|
|
35965
36081
|
abortSignal,
|
|
35966
36082
|
(progress) => {
|
|
@@ -35990,7 +36106,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35990
36106
|
const session = probeSession ?? await createCaptureSession(
|
|
35991
36107
|
fileServer.url,
|
|
35992
36108
|
framesDir,
|
|
35993
|
-
|
|
36109
|
+
buildCaptureOptions(),
|
|
35994
36110
|
videoInjector,
|
|
35995
36111
|
cfg
|
|
35996
36112
|
);
|
|
@@ -36045,7 +36161,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
36045
36161
|
initialWorkerCount: workerCount,
|
|
36046
36162
|
allowRetry: job.config.workers === void 0,
|
|
36047
36163
|
frameExt: needsAlpha ? "png" : "jpg",
|
|
36048
|
-
captureOptions:
|
|
36164
|
+
captureOptions: buildCaptureOptions(),
|
|
36049
36165
|
createBeforeCaptureHook: () => createVideoFrameInjector(frameLookup),
|
|
36050
36166
|
abortSignal,
|
|
36051
36167
|
onProgress: (progress) => {
|
|
@@ -36080,7 +36196,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
36080
36196
|
const session = probeSession ?? await createCaptureSession(
|
|
36081
36197
|
fileServer.url,
|
|
36082
36198
|
framesDir,
|
|
36083
|
-
|
|
36199
|
+
buildCaptureOptions(),
|
|
36084
36200
|
videoInjector,
|
|
36085
36201
|
cfg
|
|
36086
36202
|
);
|
|
@@ -37784,7 +37900,8 @@ var init_preview2 = __esm({
|
|
|
37784
37900
|
var init_exports = {};
|
|
37785
37901
|
__export(init_exports, {
|
|
37786
37902
|
default: () => init_default,
|
|
37787
|
-
examples: () => examples2
|
|
37903
|
+
examples: () => examples2,
|
|
37904
|
+
injectTailwindBrowserScript: () => injectTailwindBrowserScript
|
|
37788
37905
|
});
|
|
37789
37906
|
import {
|
|
37790
37907
|
existsSync as existsSync38,
|
|
@@ -37875,6 +37992,92 @@ function getStaticTemplateDir(templateId) {
|
|
|
37875
37992
|
function getSharedTemplateDir() {
|
|
37876
37993
|
return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
|
|
37877
37994
|
}
|
|
37995
|
+
function toPackageName(projectName) {
|
|
37996
|
+
const normalized = basename5(projectName).trim().toLowerCase().replace(/^[._]+/, "").replace(/[^a-z0-9._~-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
|
|
37997
|
+
return normalized || "hyperframes-project";
|
|
37998
|
+
}
|
|
37999
|
+
function getHyperframesPackageSpecifier() {
|
|
38000
|
+
return VERSION === "0.0.0-dev" ? "hyperframes" : `hyperframes@${VERSION}`;
|
|
38001
|
+
}
|
|
38002
|
+
function hyperframesScript(command2) {
|
|
38003
|
+
return `npx --yes ${getHyperframesPackageSpecifier()} ${command2}`;
|
|
38004
|
+
}
|
|
38005
|
+
function buildPackageScripts() {
|
|
38006
|
+
return {
|
|
38007
|
+
dev: hyperframesScript("preview"),
|
|
38008
|
+
check: `${hyperframesScript("lint")} && ${hyperframesScript("validate")} && ${hyperframesScript("inspect")}`,
|
|
38009
|
+
render: hyperframesScript("render"),
|
|
38010
|
+
publish: hyperframesScript("publish")
|
|
38011
|
+
};
|
|
38012
|
+
}
|
|
38013
|
+
function writeDefaultPackageJson(destDir, projectName) {
|
|
38014
|
+
const packageJsonPath = resolve22(destDir, "package.json");
|
|
38015
|
+
if (existsSync38(packageJsonPath)) return;
|
|
38016
|
+
writeFileSync18(
|
|
38017
|
+
packageJsonPath,
|
|
38018
|
+
`${JSON.stringify(
|
|
38019
|
+
{
|
|
38020
|
+
name: toPackageName(projectName),
|
|
38021
|
+
private: true,
|
|
38022
|
+
type: "module",
|
|
38023
|
+
scripts: buildPackageScripts()
|
|
38024
|
+
},
|
|
38025
|
+
null,
|
|
38026
|
+
2
|
|
38027
|
+
)}
|
|
38028
|
+
`,
|
|
38029
|
+
"utf-8"
|
|
38030
|
+
);
|
|
38031
|
+
}
|
|
38032
|
+
function listHtmlFiles(dir) {
|
|
38033
|
+
const files = [];
|
|
38034
|
+
const ignoredDirs = /* @__PURE__ */ new Set([".git", "dist", "node_modules"]);
|
|
38035
|
+
function walk(currentDir) {
|
|
38036
|
+
for (const entry of readdirSync13(currentDir, { withFileTypes: true })) {
|
|
38037
|
+
const entryPath = join40(currentDir, entry.name);
|
|
38038
|
+
if (entry.isDirectory()) {
|
|
38039
|
+
if (!ignoredDirs.has(entry.name)) walk(entryPath);
|
|
38040
|
+
continue;
|
|
38041
|
+
}
|
|
38042
|
+
if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
38043
|
+
files.push(entryPath);
|
|
38044
|
+
}
|
|
38045
|
+
}
|
|
38046
|
+
}
|
|
38047
|
+
walk(dir);
|
|
38048
|
+
return files;
|
|
38049
|
+
}
|
|
38050
|
+
function injectTailwindBrowserScript(html) {
|
|
38051
|
+
if (html.includes(TAILWIND_BROWSER_SRC)) return html;
|
|
38052
|
+
const script = [
|
|
38053
|
+
`<script>`,
|
|
38054
|
+
`window.__tailwindReady=new Promise(function(resolve){`,
|
|
38055
|
+
`var loaded=document.readyState==="complete";`,
|
|
38056
|
+
`var resolved=false;`,
|
|
38057
|
+
`var observer;`,
|
|
38058
|
+
`function readTailwindCss(){var styles=document.querySelectorAll("style");for(var i=styles.length-1;i>=0;i--){var text=styles[i].textContent||"";if(text.indexOf("tailwindcss v")!==-1)return text;}return "";}`,
|
|
38059
|
+
`function finish(){if(resolved||!loaded||!readTailwindCss())return;resolved=true;if(observer)observer.disconnect();resolve(true);}`,
|
|
38060
|
+
`observer=new MutationObserver(finish);`,
|
|
38061
|
+
`observer.observe(document.documentElement,{childList:true,subtree:true,characterData:true});`,
|
|
38062
|
+
`if(loaded){finish();}else{window.addEventListener("load",function(){loaded=true;finish();},{once:true});}`,
|
|
38063
|
+
`});`,
|
|
38064
|
+
`</script>`,
|
|
38065
|
+
`<script src="${TAILWIND_BROWSER_SRC}" integrity="${TAILWIND_BROWSER_INTEGRITY}" crossorigin="anonymous"></script>`
|
|
38066
|
+
].join("\n");
|
|
38067
|
+
if (/<\/head>/i.test(html)) {
|
|
38068
|
+
return html.replace(/<\/head>/i, (closingHead) => `
|
|
38069
|
+
${script}
|
|
38070
|
+
${closingHead}`);
|
|
38071
|
+
}
|
|
38072
|
+
return `${script}
|
|
38073
|
+
${html}`;
|
|
38074
|
+
}
|
|
38075
|
+
function writeTailwindSupport(destDir) {
|
|
38076
|
+
for (const file of listHtmlFiles(destDir)) {
|
|
38077
|
+
const html = readFileSync27(file, "utf-8");
|
|
38078
|
+
writeFileSync18(file, injectTailwindBrowserScript(html), "utf-8");
|
|
38079
|
+
}
|
|
38080
|
+
}
|
|
37878
38081
|
function patchVideoSrc(dir, videoFilename, durationSeconds) {
|
|
37879
38082
|
const htmlFiles = readdirSync13(dir, { withFileTypes: true, recursive: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".html")).map((e2) => join40(e2.parentPath ?? e2.path, e2.name));
|
|
37880
38083
|
for (const file of htmlFiles) {
|
|
@@ -37980,7 +38183,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
37980
38183
|
}
|
|
37981
38184
|
return { meta, localVideoName };
|
|
37982
38185
|
}
|
|
37983
|
-
async function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
|
|
38186
|
+
async function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds, tailwind = false) {
|
|
37984
38187
|
mkdirSync23(destDir, { recursive: true });
|
|
37985
38188
|
const templateDir = getStaticTemplateDir(templateId);
|
|
37986
38189
|
if (existsSync38(templateDir)) {
|
|
@@ -37989,6 +38192,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
37989
38192
|
await fetchRemoteTemplate(templateId, destDir);
|
|
37990
38193
|
}
|
|
37991
38194
|
patchVideoSrc(destDir, localVideoName, durationSeconds);
|
|
38195
|
+
if (tailwind) writeTailwindSupport(destDir);
|
|
37992
38196
|
writeFileSync18(
|
|
37993
38197
|
resolve22(destDir, "meta.json"),
|
|
37994
38198
|
JSON.stringify(
|
|
@@ -38006,6 +38210,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
38006
38210
|
const { writeProjectConfig: writeProjectConfig2, DEFAULT_PROJECT_CONFIG: DEFAULT_PROJECT_CONFIG2 } = await Promise.resolve().then(() => (init_projectConfig(), projectConfig_exports));
|
|
38007
38211
|
writeProjectConfig2(destDir, DEFAULT_PROJECT_CONFIG2);
|
|
38008
38212
|
}
|
|
38213
|
+
writeDefaultPackageJson(destDir, name);
|
|
38009
38214
|
const sharedDir = getSharedTemplateDir();
|
|
38010
38215
|
if (existsSync38(sharedDir)) {
|
|
38011
38216
|
for (const entry of readdirSync13(sharedDir, { withFileTypes: true })) {
|
|
@@ -38017,7 +38222,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
38017
38222
|
}
|
|
38018
38223
|
}
|
|
38019
38224
|
}
|
|
38020
|
-
var examples2, WEB_CODECS, DEFAULT_META, init_default;
|
|
38225
|
+
var examples2, WEB_CODECS, DEFAULT_META, TAILWIND_BROWSER_VERSION, TAILWIND_BROWSER_SRC, TAILWIND_BROWSER_INTEGRITY, init_default;
|
|
38021
38226
|
var init_init = __esm({
|
|
38022
38227
|
"src/commands/init.ts"() {
|
|
38023
38228
|
"use strict";
|
|
@@ -38029,11 +38234,13 @@ var init_init = __esm({
|
|
|
38029
38234
|
init_remote2();
|
|
38030
38235
|
init_events();
|
|
38031
38236
|
init_manager();
|
|
38237
|
+
init_version();
|
|
38032
38238
|
examples2 = [
|
|
38033
38239
|
["Create a project with the interactive wizard", "hyperframes init my-video"],
|
|
38034
38240
|
["Pick a starter example", "hyperframes init my-video --example warm-grain"],
|
|
38035
38241
|
["Start from an existing video file", "hyperframes init my-video --video clip.mp4"],
|
|
38036
38242
|
["Start from an audio file", "hyperframes init my-video --audio track.mp3"],
|
|
38243
|
+
["Scaffold with Tailwind CSS", "hyperframes init my-video --example blank --tailwind"],
|
|
38037
38244
|
["Non-interactive mode (for CI or AI agents)", "hyperframes init my-video --non-interactive"],
|
|
38038
38245
|
["Skip AI coding skills installation", "hyperframes init my-video --skip-skills"]
|
|
38039
38246
|
];
|
|
@@ -38046,6 +38253,9 @@ var init_init = __esm({
|
|
|
38046
38253
|
hasAudio: false,
|
|
38047
38254
|
videoCodec: "h264"
|
|
38048
38255
|
};
|
|
38256
|
+
TAILWIND_BROWSER_VERSION = "4.2.4";
|
|
38257
|
+
TAILWIND_BROWSER_SRC = `https://cdn.jsdelivr.net/npm/@tailwindcss/browser@${TAILWIND_BROWSER_VERSION}/dist/index.global.js`;
|
|
38258
|
+
TAILWIND_BROWSER_INTEGRITY = "sha384-v5YF9xS+gLRWdvrQ0u/WRbCkjSIH0NjHIPe8tBL1ZRrmI7PiSH6LLdzs0aAIMCuh";
|
|
38049
38259
|
init_default = defineCommand({
|
|
38050
38260
|
meta: {
|
|
38051
38261
|
name: "init",
|
|
@@ -38098,6 +38308,10 @@ var init_init = __esm({
|
|
|
38098
38308
|
"skip-skills": {
|
|
38099
38309
|
type: "boolean",
|
|
38100
38310
|
description: "Skip AI coding skills installation"
|
|
38311
|
+
},
|
|
38312
|
+
tailwind: {
|
|
38313
|
+
type: "boolean",
|
|
38314
|
+
description: "Add Tailwind CSS browser-runtime support"
|
|
38101
38315
|
}
|
|
38102
38316
|
},
|
|
38103
38317
|
async run({ args }) {
|
|
@@ -38115,6 +38329,7 @@ var init_init = __esm({
|
|
|
38115
38329
|
const audioFlag = args.audio;
|
|
38116
38330
|
const skipTranscribe = args["skip-transcribe"] === true;
|
|
38117
38331
|
const skipSkills = args["skip-skills"] === true;
|
|
38332
|
+
const tailwind = args.tailwind === true;
|
|
38118
38333
|
const nonInteractive = args["non-interactive"] === true;
|
|
38119
38334
|
const modelFlag = args.model;
|
|
38120
38335
|
const languageFlag = args.language;
|
|
@@ -38184,7 +38399,8 @@ var init_init = __esm({
|
|
|
38184
38399
|
basename5(destDir2),
|
|
38185
38400
|
templateId2,
|
|
38186
38401
|
localVideoName2,
|
|
38187
|
-
videoDuration2
|
|
38402
|
+
videoDuration2,
|
|
38403
|
+
tailwind
|
|
38188
38404
|
);
|
|
38189
38405
|
} catch (err) {
|
|
38190
38406
|
console.error(
|
|
@@ -38195,7 +38411,7 @@ var init_init = __esm({
|
|
|
38195
38411
|
console.error(c.dim("Use --example blank for offline use."));
|
|
38196
38412
|
process.exit(1);
|
|
38197
38413
|
}
|
|
38198
|
-
trackInitTemplate(templateId2);
|
|
38414
|
+
trackInitTemplate(templateId2, { tailwind });
|
|
38199
38415
|
const transcriptFile2 = resolve22(destDir2, "transcript.json");
|
|
38200
38416
|
if (existsSync38(transcriptFile2)) {
|
|
38201
38417
|
await patchTranscript(destDir2, transcriptFile2);
|
|
@@ -38222,10 +38438,13 @@ var init_init = __esm({
|
|
|
38222
38438
|
console.log(` ${c.dim("More patterns: hyperframes.heygen.com/guides/prompting")}`);
|
|
38223
38439
|
console.log();
|
|
38224
38440
|
console.log(` ${c.accent("4.")} Preview in the browser:`);
|
|
38225
|
-
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("
|
|
38441
|
+
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npm run dev")}`);
|
|
38442
|
+
console.log();
|
|
38443
|
+
console.log(` ${c.accent("5.")} Check the composition:`);
|
|
38444
|
+
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npm run check")}`);
|
|
38226
38445
|
console.log();
|
|
38227
|
-
console.log(` ${c.accent("
|
|
38228
|
-
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("
|
|
38446
|
+
console.log(` ${c.accent("6.")} Render to MP4 when ready:`);
|
|
38447
|
+
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npm run render")}`);
|
|
38229
38448
|
console.log();
|
|
38230
38449
|
console.log(` ${c.dim("Full docs: hyperframes.heygen.com")}`);
|
|
38231
38450
|
return;
|
|
@@ -38353,7 +38572,7 @@ var init_init = __esm({
|
|
|
38353
38572
|
spin.start(`Downloading example ${c.accent(templateId)}...`);
|
|
38354
38573
|
}
|
|
38355
38574
|
try {
|
|
38356
|
-
await scaffoldProject(destDir, name, templateId, localVideoName, videoDuration);
|
|
38575
|
+
await scaffoldProject(destDir, name, templateId, localVideoName, videoDuration, tailwind);
|
|
38357
38576
|
if (!isBundled) {
|
|
38358
38577
|
spin.stop(c.success(`Downloaded ${templateId}`));
|
|
38359
38578
|
}
|
|
@@ -38367,7 +38586,7 @@ ${c.dim("Use --example blank for offline use.")}`
|
|
|
38367
38586
|
);
|
|
38368
38587
|
process.exit(1);
|
|
38369
38588
|
}
|
|
38370
|
-
trackInitTemplate(templateId);
|
|
38589
|
+
trackInitTemplate(templateId, { tailwind });
|
|
38371
38590
|
const transcriptFile = resolve22(destDir, "transcript.json");
|
|
38372
38591
|
if (existsSync38(transcriptFile)) {
|
|
38373
38592
|
await patchTranscript(destDir, transcriptFile);
|
|
@@ -5,6 +5,28 @@ description: GSAP animation reference for HyperFrames. Covers gsap.to(), from(),
|
|
|
5
5
|
|
|
6
6
|
# GSAP
|
|
7
7
|
|
|
8
|
+
## HyperFrames Contract
|
|
9
|
+
|
|
10
|
+
HyperFrames controls GSAP through its `gsap` runtime adapter. Create a paused timeline synchronously, register it on `window.__timelines` with the exact `data-composition-id`, and let HyperFrames seek it.
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
14
|
+
<script>
|
|
15
|
+
window.__timelines = window.__timelines || {};
|
|
16
|
+
const tl = gsap.timeline({ paused: true });
|
|
17
|
+
|
|
18
|
+
tl.from(".title", { y: 48, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
|
|
19
|
+
tl.to(".accent", { scaleX: 1, duration: 0.5, ease: "power2.out" }, 0.25);
|
|
20
|
+
|
|
21
|
+
window.__timelines["main"] = tl; // key must equal data-composition-id on the composition root
|
|
22
|
+
</script>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- The registry key must match the composition root's `data-composition-id`.
|
|
26
|
+
- Do not call `tl.play()` for render-critical motion.
|
|
27
|
+
- Do not build timelines inside async code, timers, or event handlers.
|
|
28
|
+
- Keep loops finite. HyperFrames renders finite video durations.
|
|
29
|
+
|
|
8
30
|
## Core Tween Methods
|
|
9
31
|
|
|
10
32
|
- **gsap.to(targets, vars)** — animate from current state to `vars`. Most common.
|
|
@@ -21,7 +43,7 @@ Always use **camelCase** property names (e.g. `backgroundColor`, `rotationX`).
|
|
|
21
43
|
- **ease** — `"power1.out"` (default), `"power3.inOut"`, `"back.out(1.7)"`, `"elastic.out(1, 0.3)"`, `"none"`.
|
|
22
44
|
- **stagger** — number `0.1` or object: `{ amount: 0.3, from: "center" }`, `{ each: 0.1, from: "random" }`.
|
|
23
45
|
- **overwrite** — `false` (default), `true`, or `"auto"`.
|
|
24
|
-
- **repeat** — number
|
|
46
|
+
- **repeat** — finite number; never `-1` in HyperFrames. Compute repeats from the visible duration. **yoyo** — alternates direction with repeat.
|
|
25
47
|
- **onComplete**, **onStart**, **onUpdate** — callbacks.
|
|
26
48
|
- **immediateRender** — default `true` for from()/fromTo(). Set `false` on later tweens targeting the same property+element to avoid overwrite.
|
|
27
49
|
|
|
@@ -209,3 +231,10 @@ Pause or kill off-screen animations.
|
|
|
209
231
|
- Chain animations with delay when a timeline can sequence them.
|
|
210
232
|
- Create tweens before the DOM exists.
|
|
211
233
|
- Skip cleanup — always kill tweens when no longer needed.
|
|
234
|
+
- Use infinite repeat values in HyperFrames compositions. Use finite repeat counts computed from the visible duration.
|
|
235
|
+
|
|
236
|
+
## Credits And References
|
|
237
|
+
|
|
238
|
+
- HyperFrames adapter source: `packages/core/src/runtime/adapters/gsap.ts`.
|
|
239
|
+
- GSAP documentation: https://gsap.com/docs/v3/
|
|
240
|
+
- GSAP timeline pause and seek behavior: https://gsap.com/docs/v3/GSAP/Timeline/pause%28%29/
|
|
@@ -25,6 +25,7 @@ npx hyperframes init my-video # interactive wizard
|
|
|
25
25
|
npx hyperframes init my-video --example warm-grain # pick an example
|
|
26
26
|
npx hyperframes init my-video --video clip.mp4 # with video file
|
|
27
27
|
npx hyperframes init my-video --audio track.mp3 # with audio file
|
|
28
|
+
npx hyperframes init my-video --example blank --tailwind # with Tailwind v4 browser runtime
|
|
28
29
|
npx hyperframes init my-video --non-interactive # skip prompts (CI/agents)
|
|
29
30
|
```
|
|
30
31
|
|
|
@@ -32,6 +33,8 @@ Templates: `blank`, `warm-grain`, `play-mode`, `swiss-grid`, `vignelli`, `decisi
|
|
|
32
33
|
|
|
33
34
|
`init` creates the right file structure, copies media, transcribes audio with Whisper, and installs AI coding skills. Use it instead of creating files by hand.
|
|
34
35
|
|
|
36
|
+
When using `--tailwind`, invoke the `tailwind` skill before editing classes or theme tokens. The scaffold uses Tailwind v4.2 via the browser runtime, not Studio's Tailwind v3 setup.
|
|
37
|
+
|
|
35
38
|
## Linting
|
|
36
39
|
|
|
37
40
|
```bash
|
|
@@ -8,15 +8,15 @@ This project uses AI agent skills for framework-specific patterns. Install them
|
|
|
8
8
|
npx skills add heygen-com/hyperframes
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Skills encode patterns like `window.__timelines` registration, `data-*` attribute semantics, and shader-compatible CSS rules that are not in generic web docs. Using them produces correct compositions from the start.
|
|
11
|
+
Skills encode patterns like `window.__timelines` registration, `data-*` attribute semantics, Tailwind v4 browser-runtime styling for `--tailwind` projects, and shader-compatible CSS rules that are not in generic web docs. Using them produces correct compositions from the start.
|
|
12
12
|
|
|
13
13
|
## Commands
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
npm run dev # preview in browser (studio editor)
|
|
17
|
+
npm run check # lint + validate + inspect
|
|
18
|
+
npm run render # render to MP4
|
|
19
|
+
npm run publish # publish and get a shareable link
|
|
20
20
|
npx hyperframes docs <topic> # reference docs in terminal
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -30,10 +30,10 @@ npx hyperframes docs <topic> # reference docs in terminal
|
|
|
30
30
|
|
|
31
31
|
## Linting — Always Run After Changes
|
|
32
32
|
|
|
33
|
-
After creating or editing any `.html` composition, run the
|
|
33
|
+
After creating or editing any `.html` composition, run the full check before considering the task complete:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
|
|
36
|
+
npm run check
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Fix all errors before presenting the result.
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
| **hyperframes-cli** | `/hyperframes-cli` | CLI commands: init, lint, preview, render, transcribe, tts |
|
|
11
11
|
| **hyperframes-registry** | `/hyperframes-registry` | Installing blocks and components via `hyperframes add` |
|
|
12
12
|
| **website-to-hyperframes** | `/website-to-hyperframes` | Capturing a URL and turning it into a video — full website-to-video pipeline |
|
|
13
|
+
| **tailwind** | `/tailwind` | Tailwind v4 browser-runtime styles for projects created with `hyperframes init --tailwind` |
|
|
13
14
|
| **gsap** | `/gsap` | GSAP animations for HyperFrames — tweens, timelines, easing, performance |
|
|
15
|
+
| **animejs** | `/animejs` | Anime.js animations registered on `window.__hfAnime` |
|
|
16
|
+
| **css-animations** | `/css-animations` | CSS keyframes that HyperFrames can pause and seek |
|
|
17
|
+
| **lottie** | `/lottie` | `lottie-web` and dotLottie players registered on `window.__hfLottie` |
|
|
18
|
+
| **three** | `/three` | Three.js scenes rendered from HyperFrames `hf-seek` events |
|
|
19
|
+
| **waapi** | `/waapi` | Web Animations API motion driven through `document.getAnimations()` |
|
|
14
20
|
|
|
15
21
|
> **Skills not available?** Ask the user to run `npx hyperframes skills` and restart their
|
|
16
22
|
> agent session, or install manually: `npx skills add heygen-com/hyperframes`.
|
|
@@ -18,9 +24,10 @@
|
|
|
18
24
|
## Commands
|
|
19
25
|
|
|
20
26
|
```bash
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
npm run dev # preview in browser (studio editor)
|
|
28
|
+
npm run check # lint + validate + inspect
|
|
29
|
+
npm run render # render to MP4
|
|
30
|
+
npm run publish # publish and get a shareable link
|
|
24
31
|
npx hyperframes lint --verbose # include info-level findings
|
|
25
32
|
npx hyperframes lint --json # machine-readable output for CI
|
|
26
33
|
npx hyperframes docs <topic> # reference docs in terminal
|
|
@@ -51,13 +58,13 @@ https://hyperframes.heygen.com/llms.txt
|
|
|
51
58
|
|
|
52
59
|
## Linting — ALWAYS RUN AFTER CHANGES
|
|
53
60
|
|
|
54
|
-
After creating or editing any `.html` composition, **always** run the
|
|
61
|
+
After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
|
|
55
62
|
|
|
56
63
|
```bash
|
|
57
|
-
|
|
64
|
+
npm run check
|
|
58
65
|
```
|
|
59
66
|
|
|
60
|
-
Fix all errors before presenting the result.
|
|
67
|
+
Fix all errors before presenting the result. Inspect warnings should be reviewed before rendering.
|
|
61
68
|
|
|
62
69
|
## Key Rules
|
|
63
70
|
|