hyperframes 0.5.0-alpha.12 → 0.5.0-alpha.14
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.5.0-alpha.
|
|
57
|
+
VERSION = true ? "0.5.0-alpha.14" : "0.0.0-dev";
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -10218,8 +10218,8 @@ function trackRenderError(props) {
|
|
|
10218
10218
|
memory_free_mb: props.memoryFreeMb
|
|
10219
10219
|
});
|
|
10220
10220
|
}
|
|
10221
|
-
function trackInitTemplate(templateId) {
|
|
10222
|
-
trackEvent("init_template", { template: templateId });
|
|
10221
|
+
function trackInitTemplate(templateId, props) {
|
|
10222
|
+
trackEvent("init_template", { template: templateId, tailwind: props?.tailwind });
|
|
10223
10223
|
}
|
|
10224
10224
|
function trackBrowserInstall() {
|
|
10225
10225
|
trackEvent("browser_install", {});
|
|
@@ -25757,6 +25757,13 @@ function resolveConfig(overrides) {
|
|
|
25757
25757
|
"PRODUCER_ENABLE_STREAMING_ENCODE",
|
|
25758
25758
|
DEFAULT_CONFIG2.enableStreamingEncode
|
|
25759
25759
|
),
|
|
25760
|
+
streamingEncodeMaxDurationSeconds: Math.max(
|
|
25761
|
+
0,
|
|
25762
|
+
envNum(
|
|
25763
|
+
"PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS",
|
|
25764
|
+
DEFAULT_CONFIG2.streamingEncodeMaxDurationSeconds
|
|
25765
|
+
)
|
|
25766
|
+
),
|
|
25760
25767
|
ffmpegEncodeTimeout: envNum("FFMPEG_ENCODE_TIMEOUT_MS", DEFAULT_CONFIG2.ffmpegEncodeTimeout),
|
|
25761
25768
|
ffmpegProcessTimeout: envNum("FFMPEG_PROCESS_TIMEOUT_MS", DEFAULT_CONFIG2.ffmpegProcessTimeout),
|
|
25762
25769
|
ffmpegStreamingTimeout: envNum(
|
|
@@ -25814,7 +25821,8 @@ var init_config2 = __esm({
|
|
|
25814
25821
|
forceScreenshot: false,
|
|
25815
25822
|
enableChunkedEncode: false,
|
|
25816
25823
|
chunkSizeFrames: 360,
|
|
25817
|
-
enableStreamingEncode:
|
|
25824
|
+
enableStreamingEncode: true,
|
|
25825
|
+
streamingEncodeMaxDurationSeconds: 240,
|
|
25818
25826
|
ffmpegEncodeTimeout: 6e5,
|
|
25819
25827
|
ffmpegProcessTimeout: 3e5,
|
|
25820
25828
|
ffmpegStreamingTimeout: 6e5,
|
|
@@ -26423,6 +26431,44 @@ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100)
|
|
|
26423
26431
|
}
|
|
26424
26432
|
return Boolean(await page.evaluate(expression));
|
|
26425
26433
|
}
|
|
26434
|
+
async function applyVideoMetadataHints(page, hints) {
|
|
26435
|
+
if (!hints || hints.length === 0) return;
|
|
26436
|
+
await page.evaluate(
|
|
26437
|
+
(metadataHints) => {
|
|
26438
|
+
for (const hint of metadataHints) {
|
|
26439
|
+
if (!hint.id || !Number.isFinite(hint.width) || !Number.isFinite(hint.height) || hint.width <= 0 || hint.height <= 0) {
|
|
26440
|
+
continue;
|
|
26441
|
+
}
|
|
26442
|
+
const video = document.getElementById(hint.id);
|
|
26443
|
+
if (!video) continue;
|
|
26444
|
+
if (!video.hasAttribute("width")) video.setAttribute("width", String(hint.width));
|
|
26445
|
+
if (!video.hasAttribute("height")) video.setAttribute("height", String(hint.height));
|
|
26446
|
+
const computed = window.getComputedStyle(video);
|
|
26447
|
+
if (!video.style.aspectRatio && (!computed.aspectRatio || computed.aspectRatio === "auto")) {
|
|
26448
|
+
video.style.aspectRatio = `${hint.width} / ${hint.height}`;
|
|
26449
|
+
}
|
|
26450
|
+
}
|
|
26451
|
+
},
|
|
26452
|
+
[...hints]
|
|
26453
|
+
);
|
|
26454
|
+
}
|
|
26455
|
+
async function waitForOptionalTailwindReady(page, timeoutMs) {
|
|
26456
|
+
const hasTailwindReady = await page.evaluate(
|
|
26457
|
+
`(() => { const ready = window.__tailwindReady; return !!ready && typeof ready.then === "function"; })()`
|
|
26458
|
+
);
|
|
26459
|
+
if (!hasTailwindReady) return;
|
|
26460
|
+
const ready = await Promise.race([
|
|
26461
|
+
page.evaluate(
|
|
26462
|
+
`Promise.resolve(window.__tailwindReady).then(() => true, () => false)`
|
|
26463
|
+
),
|
|
26464
|
+
new Promise((resolve39) => setTimeout(() => resolve39(false), timeoutMs))
|
|
26465
|
+
]);
|
|
26466
|
+
if (!ready) {
|
|
26467
|
+
throw new Error(
|
|
26468
|
+
`[FrameCapture] window.__tailwindReady not resolved after ${timeoutMs}ms. Tailwind browser runtime must finish before frame capture starts.`
|
|
26469
|
+
);
|
|
26470
|
+
}
|
|
26471
|
+
}
|
|
26426
26472
|
async function initializeSession(session) {
|
|
26427
26473
|
const { page, serverUrl } = session;
|
|
26428
26474
|
page.on("console", (msg) => {
|
|
@@ -26466,6 +26512,7 @@ async function initializeSession(session) {
|
|
|
26466
26512
|
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
|
|
26467
26513
|
);
|
|
26468
26514
|
}
|
|
26515
|
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
26469
26516
|
const skipIdsLiteral = JSON.stringify(session.options.skipReadinessVideoIds ?? []);
|
|
26470
26517
|
const videosReady = await pollPageExpression(
|
|
26471
26518
|
page,
|
|
@@ -26478,6 +26525,7 @@ async function initializeSession(session) {
|
|
|
26478
26525
|
);
|
|
26479
26526
|
}
|
|
26480
26527
|
await page.evaluate(`document.fonts?.ready`);
|
|
26528
|
+
await waitForOptionalTailwindReady(page, pageReadyTimeout2);
|
|
26481
26529
|
if (session.options.format === "png") {
|
|
26482
26530
|
await initTransparentBackground(session.page);
|
|
26483
26531
|
}
|
|
@@ -26532,6 +26580,7 @@ async function initializeSession(session) {
|
|
|
26532
26580
|
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout}ms. Page must expose window.__hf = { duration, seek }.`
|
|
26533
26581
|
);
|
|
26534
26582
|
}
|
|
26583
|
+
await applyVideoMetadataHints(page, session.options.videoMetadataHints);
|
|
26535
26584
|
const beginframeSkipIdsLiteral = JSON.stringify(session.options.skipReadinessVideoIds ?? []);
|
|
26536
26585
|
const videoDeadline = Date.now() + (session.config?.playerReadyTimeout ?? DEFAULT_CONFIG2.playerReadyTimeout);
|
|
26537
26586
|
while (Date.now() < videoDeadline) {
|
|
@@ -26542,6 +26591,7 @@ async function initializeSession(session) {
|
|
|
26542
26591
|
await new Promise((r2) => setTimeout(r2, 100));
|
|
26543
26592
|
}
|
|
26544
26593
|
await page.evaluate(`document.fonts?.ready`);
|
|
26594
|
+
await waitForOptionalTailwindReady(page, pageReadyTimeout);
|
|
26545
26595
|
warmupRunning = false;
|
|
26546
26596
|
session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
|
|
26547
26597
|
if (session.options.format === "png") {
|
|
@@ -34358,6 +34408,24 @@ function applyRenderModeHints(cfg, compiled, log2 = defaultLogger) {
|
|
|
34358
34408
|
reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
|
|
34359
34409
|
});
|
|
34360
34410
|
}
|
|
34411
|
+
function collectVideoReadinessSkipIds(nativeHdrVideoIds, extractedVideos) {
|
|
34412
|
+
return Array.from(
|
|
34413
|
+
/* @__PURE__ */ new Set([
|
|
34414
|
+
...nativeHdrVideoIds,
|
|
34415
|
+
...extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => video.videoId)
|
|
34416
|
+
])
|
|
34417
|
+
).sort();
|
|
34418
|
+
}
|
|
34419
|
+
function hasUsableVideoDimensions(metadata) {
|
|
34420
|
+
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
34421
|
+
}
|
|
34422
|
+
function collectVideoMetadataHints(extractedVideos) {
|
|
34423
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
34424
|
+
id: video.videoId,
|
|
34425
|
+
width: video.metadata.width,
|
|
34426
|
+
height: video.metadata.height
|
|
34427
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
34428
|
+
}
|
|
34361
34429
|
function resolveRenderWorkerCount(totalFrames, requestedWorkers, cfg, compiled, composition, log2 = defaultLogger, measuredCaptureCost) {
|
|
34362
34430
|
const captureCost = combineCaptureCostEstimates(
|
|
34363
34431
|
estimateCaptureCostMultiplier(compiled, composition),
|
|
@@ -35020,6 +35088,27 @@ function createRenderJob(config) {
|
|
|
35020
35088
|
function normalizeCompositionSrcPath(srcPath) {
|
|
35021
35089
|
return srcPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
35022
35090
|
}
|
|
35091
|
+
function createStandaloneEntryRenderClone(root, host) {
|
|
35092
|
+
const hostClone = host.cloneNode(true);
|
|
35093
|
+
hostClone.setAttribute("data-start", "0");
|
|
35094
|
+
if (root === host) return hostClone;
|
|
35095
|
+
const rootClone = root.cloneNode(false);
|
|
35096
|
+
rootClone.appendChild(hostClone);
|
|
35097
|
+
return rootClone;
|
|
35098
|
+
}
|
|
35099
|
+
function replaceBodyWithRenderClone(body, renderClone) {
|
|
35100
|
+
while (body.firstChild) {
|
|
35101
|
+
body.removeChild(body.firstChild);
|
|
35102
|
+
}
|
|
35103
|
+
body.appendChild(renderClone);
|
|
35104
|
+
}
|
|
35105
|
+
function shouldUseStreamingEncode(cfg, outputFormat, workerCount, durationSeconds) {
|
|
35106
|
+
if (!cfg.enableStreamingEncode) return false;
|
|
35107
|
+
if (outputFormat === "png-sequence") return false;
|
|
35108
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return false;
|
|
35109
|
+
if (durationSeconds > cfg.streamingEncodeMaxDurationSeconds) return false;
|
|
35110
|
+
return workerCount === 1;
|
|
35111
|
+
}
|
|
35023
35112
|
function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
35024
35113
|
const normalizedEntryFile = normalizeCompositionSrcPath(entryFile);
|
|
35025
35114
|
const { document: document2 } = parseHTML(indexHtml);
|
|
@@ -35034,16 +35123,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
35034
35123
|
(candidate) => candidate.hasAttribute("data-composition-id")
|
|
35035
35124
|
) ?? null;
|
|
35036
35125
|
if (!root) return null;
|
|
35037
|
-
const
|
|
35038
|
-
|
|
35039
|
-
body.innerHTML = "";
|
|
35040
|
-
if (root === host) {
|
|
35041
|
-
body.appendChild(hostClone);
|
|
35042
|
-
return document2.toString();
|
|
35043
|
-
}
|
|
35044
|
-
const rootClone = root.cloneNode(false);
|
|
35045
|
-
rootClone.appendChild(hostClone);
|
|
35046
|
-
body.appendChild(rootClone);
|
|
35126
|
+
const renderClone = createStandaloneEntryRenderClone(root, host);
|
|
35127
|
+
replaceBodyWithRenderClone(body, renderClone);
|
|
35047
35128
|
return document2.toString();
|
|
35048
35129
|
}
|
|
35049
35130
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
@@ -35075,7 +35156,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35075
35156
|
}
|
|
35076
35157
|
const enableChunkedEncode = cfg.enableChunkedEncode;
|
|
35077
35158
|
const chunkedEncodeSize = cfg.chunkSizeFrames;
|
|
35078
|
-
const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
|
|
35079
35159
|
let peakRssBytes = 0;
|
|
35080
35160
|
let peakHeapUsedBytes = 0;
|
|
35081
35161
|
const sampleMemory = () => {
|
|
@@ -35369,6 +35449,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35369
35449
|
let frameLookup = null;
|
|
35370
35450
|
const compiledDir = join36(workDir, "compiled");
|
|
35371
35451
|
let extractionResult = null;
|
|
35452
|
+
let videoReadinessSkipIds = [];
|
|
35453
|
+
let videoMetadataHints = [];
|
|
35372
35454
|
const nativeHdrVideoIds = /* @__PURE__ */ new Set();
|
|
35373
35455
|
const videoTransfers = /* @__PURE__ */ new Map();
|
|
35374
35456
|
if (job.config.hdrMode !== "force-sdr" && composition.videos.length > 0) {
|
|
@@ -35425,6 +35507,11 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35425
35507
|
if (extractionResult.extracted.length > 0) {
|
|
35426
35508
|
frameLookup = createFrameLookupTable(composition.videos, extractionResult.extracted);
|
|
35427
35509
|
}
|
|
35510
|
+
videoReadinessSkipIds = collectVideoReadinessSkipIds(
|
|
35511
|
+
nativeHdrVideoIds,
|
|
35512
|
+
extractionResult.extracted
|
|
35513
|
+
);
|
|
35514
|
+
videoMetadataHints = collectVideoMetadataHints(extractionResult.extracted);
|
|
35428
35515
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
35429
35516
|
const existingAudioSrcs = new Set(composition.audios.map((a) => a.src));
|
|
35430
35517
|
for (const ext of extractionResult.extracted) {
|
|
@@ -35538,9 +35625,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35538
35625
|
format: needsAlpha ? "png" : "jpeg",
|
|
35539
35626
|
quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95
|
|
35540
35627
|
};
|
|
35541
|
-
const
|
|
35628
|
+
const buildCaptureOptions = () => ({
|
|
35542
35629
|
...captureOptions,
|
|
35543
|
-
|
|
35630
|
+
videoMetadataHints,
|
|
35631
|
+
skipReadinessVideoIds: videoReadinessSkipIds
|
|
35544
35632
|
});
|
|
35545
35633
|
let captureCalibration;
|
|
35546
35634
|
let switchedToScreenshotAfterCalibration = false;
|
|
@@ -35553,7 +35641,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35553
35641
|
calibrationSession = await createCaptureSession(
|
|
35554
35642
|
fileServer.url,
|
|
35555
35643
|
calibrationDir,
|
|
35556
|
-
|
|
35644
|
+
buildCaptureOptions(),
|
|
35557
35645
|
videoInjector,
|
|
35558
35646
|
calibrationCfg
|
|
35559
35647
|
);
|
|
@@ -35636,6 +35724,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35636
35724
|
await closeCaptureSession(probeSession);
|
|
35637
35725
|
probeSession = null;
|
|
35638
35726
|
}
|
|
35727
|
+
let useStreamingEncode = shouldUseStreamingEncode(cfg, outputFormat, workerCount, job.duration);
|
|
35728
|
+
log2.info("streaming-encode gate", {
|
|
35729
|
+
enabled: useStreamingEncode,
|
|
35730
|
+
configFlag: cfg.enableStreamingEncode,
|
|
35731
|
+
outputFormat,
|
|
35732
|
+
workerCount,
|
|
35733
|
+
durationSeconds: job.duration,
|
|
35734
|
+
maxDurationSeconds: cfg.streamingEncodeMaxDurationSeconds
|
|
35735
|
+
});
|
|
35639
35736
|
const captureAttempts = [];
|
|
35640
35737
|
const FORMAT_EXT2 = {
|
|
35641
35738
|
mp4: ".mp4",
|
|
@@ -35677,7 +35774,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
35677
35774
|
const domSession = await createCaptureSession(
|
|
35678
35775
|
fileServer.url,
|
|
35679
35776
|
framesDir,
|
|
35680
|
-
|
|
35777
|
+
buildCaptureOptions(),
|
|
35681
35778
|
createVideoFrameInjector(frameLookup),
|
|
35682
35779
|
cfg
|
|
35683
35780
|
);
|
|
@@ -36175,28 +36272,47 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
36175
36272
|
} else {
|
|
36176
36273
|
let streamingEncoder = null;
|
|
36177
36274
|
let streamingEncoderClosed = false;
|
|
36178
|
-
if (
|
|
36179
|
-
|
|
36180
|
-
|
|
36181
|
-
|
|
36182
|
-
|
|
36183
|
-
|
|
36184
|
-
|
|
36185
|
-
|
|
36186
|
-
|
|
36187
|
-
|
|
36188
|
-
|
|
36189
|
-
|
|
36190
|
-
|
|
36191
|
-
|
|
36192
|
-
|
|
36193
|
-
|
|
36194
|
-
|
|
36195
|
-
|
|
36196
|
-
|
|
36275
|
+
if (useStreamingEncode) {
|
|
36276
|
+
try {
|
|
36277
|
+
streamingEncoder = await spawnStreamingEncoder(
|
|
36278
|
+
videoOnlyPath,
|
|
36279
|
+
{
|
|
36280
|
+
fps: job.config.fps,
|
|
36281
|
+
width,
|
|
36282
|
+
height,
|
|
36283
|
+
codec: preset.codec,
|
|
36284
|
+
preset: preset.preset,
|
|
36285
|
+
quality: effectiveQuality,
|
|
36286
|
+
bitrate: effectiveBitrate,
|
|
36287
|
+
pixelFormat: preset.pixelFormat,
|
|
36288
|
+
useGpu: job.config.useGpu,
|
|
36289
|
+
imageFormat: captureOptions.format || "jpeg",
|
|
36290
|
+
hdr: preset.hdr
|
|
36291
|
+
},
|
|
36292
|
+
abortSignal
|
|
36293
|
+
);
|
|
36294
|
+
assertNotAborted();
|
|
36295
|
+
} catch (err) {
|
|
36296
|
+
if (abortSignal?.aborted) {
|
|
36297
|
+
if (streamingEncoder && !streamingEncoderClosed) {
|
|
36298
|
+
await streamingEncoder.close().catch(() => {
|
|
36299
|
+
});
|
|
36300
|
+
streamingEncoderClosed = true;
|
|
36301
|
+
}
|
|
36302
|
+
throw err;
|
|
36303
|
+
}
|
|
36304
|
+
useStreamingEncode = false;
|
|
36305
|
+
streamingEncoder = null;
|
|
36306
|
+
log2.warn("[Render] Streaming encoder spawn failed; falling back to disk-frame encode.", {
|
|
36307
|
+
error: err instanceof Error ? err.message : String(err),
|
|
36308
|
+
outputFormat,
|
|
36309
|
+
workerCount,
|
|
36310
|
+
durationSeconds: job.duration
|
|
36311
|
+
});
|
|
36312
|
+
}
|
|
36197
36313
|
}
|
|
36198
36314
|
try {
|
|
36199
|
-
if (
|
|
36315
|
+
if (useStreamingEncode && streamingEncoder) {
|
|
36200
36316
|
const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
|
|
36201
36317
|
const currentEncoder = streamingEncoder;
|
|
36202
36318
|
if (workerCount > 1) {
|
|
@@ -36210,7 +36326,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
36210
36326
|
fileServer.url,
|
|
36211
36327
|
workDir,
|
|
36212
36328
|
tasks,
|
|
36213
|
-
|
|
36329
|
+
buildCaptureOptions(),
|
|
36214
36330
|
() => createVideoFrameInjector(frameLookup),
|
|
36215
36331
|
abortSignal,
|
|
36216
36332
|
(progress) => {
|
|
@@ -36240,7 +36356,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
36240
36356
|
const session = probeSession ?? await createCaptureSession(
|
|
36241
36357
|
fileServer.url,
|
|
36242
36358
|
framesDir,
|
|
36243
|
-
|
|
36359
|
+
buildCaptureOptions(),
|
|
36244
36360
|
videoInjector,
|
|
36245
36361
|
cfg
|
|
36246
36362
|
);
|
|
@@ -36295,7 +36411,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
36295
36411
|
initialWorkerCount: workerCount,
|
|
36296
36412
|
allowRetry: job.config.workers === void 0,
|
|
36297
36413
|
frameExt: needsAlpha ? "png" : "jpg",
|
|
36298
|
-
captureOptions:
|
|
36414
|
+
captureOptions: buildCaptureOptions(),
|
|
36299
36415
|
createBeforeCaptureHook: () => createVideoFrameInjector(frameLookup),
|
|
36300
36416
|
abortSignal,
|
|
36301
36417
|
onProgress: (progress) => {
|
|
@@ -36330,7 +36446,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
36330
36446
|
const session = probeSession ?? await createCaptureSession(
|
|
36331
36447
|
fileServer.url,
|
|
36332
36448
|
framesDir,
|
|
36333
|
-
|
|
36449
|
+
buildCaptureOptions(),
|
|
36334
36450
|
videoInjector,
|
|
36335
36451
|
cfg
|
|
36336
36452
|
);
|
|
@@ -38045,7 +38161,8 @@ var init_preview2 = __esm({
|
|
|
38045
38161
|
var init_exports = {};
|
|
38046
38162
|
__export(init_exports, {
|
|
38047
38163
|
default: () => init_default,
|
|
38048
|
-
examples: () => examples2
|
|
38164
|
+
examples: () => examples2,
|
|
38165
|
+
injectTailwindBrowserScript: () => injectTailwindBrowserScript
|
|
38049
38166
|
});
|
|
38050
38167
|
import {
|
|
38051
38168
|
existsSync as existsSync39,
|
|
@@ -38136,6 +38253,92 @@ function getStaticTemplateDir(templateId) {
|
|
|
38136
38253
|
function getSharedTemplateDir() {
|
|
38137
38254
|
return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
|
|
38138
38255
|
}
|
|
38256
|
+
function toPackageName(projectName) {
|
|
38257
|
+
const normalized = basename5(projectName).trim().toLowerCase().replace(/^[._]+/, "").replace(/[^a-z0-9._~-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
|
|
38258
|
+
return normalized || "hyperframes-project";
|
|
38259
|
+
}
|
|
38260
|
+
function getHyperframesPackageSpecifier() {
|
|
38261
|
+
return VERSION === "0.0.0-dev" ? "hyperframes" : `hyperframes@${VERSION}`;
|
|
38262
|
+
}
|
|
38263
|
+
function hyperframesScript(command2) {
|
|
38264
|
+
return `npx --yes ${getHyperframesPackageSpecifier()} ${command2}`;
|
|
38265
|
+
}
|
|
38266
|
+
function buildPackageScripts() {
|
|
38267
|
+
return {
|
|
38268
|
+
dev: hyperframesScript("preview"),
|
|
38269
|
+
check: `${hyperframesScript("lint")} && ${hyperframesScript("validate")} && ${hyperframesScript("inspect")}`,
|
|
38270
|
+
render: hyperframesScript("render"),
|
|
38271
|
+
publish: hyperframesScript("publish")
|
|
38272
|
+
};
|
|
38273
|
+
}
|
|
38274
|
+
function writeDefaultPackageJson(destDir, projectName) {
|
|
38275
|
+
const packageJsonPath = resolve22(destDir, "package.json");
|
|
38276
|
+
if (existsSync39(packageJsonPath)) return;
|
|
38277
|
+
writeFileSync18(
|
|
38278
|
+
packageJsonPath,
|
|
38279
|
+
`${JSON.stringify(
|
|
38280
|
+
{
|
|
38281
|
+
name: toPackageName(projectName),
|
|
38282
|
+
private: true,
|
|
38283
|
+
type: "module",
|
|
38284
|
+
scripts: buildPackageScripts()
|
|
38285
|
+
},
|
|
38286
|
+
null,
|
|
38287
|
+
2
|
|
38288
|
+
)}
|
|
38289
|
+
`,
|
|
38290
|
+
"utf-8"
|
|
38291
|
+
);
|
|
38292
|
+
}
|
|
38293
|
+
function listHtmlFiles(dir) {
|
|
38294
|
+
const files = [];
|
|
38295
|
+
const ignoredDirs = /* @__PURE__ */ new Set([".git", "dist", "node_modules"]);
|
|
38296
|
+
function walk(currentDir) {
|
|
38297
|
+
for (const entry of readdirSync14(currentDir, { withFileTypes: true })) {
|
|
38298
|
+
const entryPath = join41(currentDir, entry.name);
|
|
38299
|
+
if (entry.isDirectory()) {
|
|
38300
|
+
if (!ignoredDirs.has(entry.name)) walk(entryPath);
|
|
38301
|
+
continue;
|
|
38302
|
+
}
|
|
38303
|
+
if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
38304
|
+
files.push(entryPath);
|
|
38305
|
+
}
|
|
38306
|
+
}
|
|
38307
|
+
}
|
|
38308
|
+
walk(dir);
|
|
38309
|
+
return files;
|
|
38310
|
+
}
|
|
38311
|
+
function injectTailwindBrowserScript(html) {
|
|
38312
|
+
if (html.includes(TAILWIND_BROWSER_SRC)) return html;
|
|
38313
|
+
const script = [
|
|
38314
|
+
`<script>`,
|
|
38315
|
+
`window.__tailwindReady=new Promise(function(resolve){`,
|
|
38316
|
+
`var loaded=document.readyState==="complete";`,
|
|
38317
|
+
`var resolved=false;`,
|
|
38318
|
+
`var observer;`,
|
|
38319
|
+
`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 "";}`,
|
|
38320
|
+
`function finish(){if(resolved||!loaded||!readTailwindCss())return;resolved=true;if(observer)observer.disconnect();resolve(true);}`,
|
|
38321
|
+
`observer=new MutationObserver(finish);`,
|
|
38322
|
+
`observer.observe(document.documentElement,{childList:true,subtree:true,characterData:true});`,
|
|
38323
|
+
`if(loaded){finish();}else{window.addEventListener("load",function(){loaded=true;finish();},{once:true});}`,
|
|
38324
|
+
`});`,
|
|
38325
|
+
`</script>`,
|
|
38326
|
+
`<script src="${TAILWIND_BROWSER_SRC}" integrity="${TAILWIND_BROWSER_INTEGRITY}" crossorigin="anonymous"></script>`
|
|
38327
|
+
].join("\n");
|
|
38328
|
+
if (/<\/head>/i.test(html)) {
|
|
38329
|
+
return html.replace(/<\/head>/i, (closingHead) => `
|
|
38330
|
+
${script}
|
|
38331
|
+
${closingHead}`);
|
|
38332
|
+
}
|
|
38333
|
+
return `${script}
|
|
38334
|
+
${html}`;
|
|
38335
|
+
}
|
|
38336
|
+
function writeTailwindSupport(destDir) {
|
|
38337
|
+
for (const file of listHtmlFiles(destDir)) {
|
|
38338
|
+
const html = readFileSync27(file, "utf-8");
|
|
38339
|
+
writeFileSync18(file, injectTailwindBrowserScript(html), "utf-8");
|
|
38340
|
+
}
|
|
38341
|
+
}
|
|
38139
38342
|
function patchVideoSrc(dir, videoFilename, durationSeconds) {
|
|
38140
38343
|
const htmlFiles = readdirSync14(dir, { withFileTypes: true, recursive: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".html")).map((e2) => join41(e2.parentPath ?? e2.path, e2.name));
|
|
38141
38344
|
for (const file of htmlFiles) {
|
|
@@ -38241,7 +38444,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
38241
38444
|
}
|
|
38242
38445
|
return { meta, localVideoName };
|
|
38243
38446
|
}
|
|
38244
|
-
async function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
|
|
38447
|
+
async function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds, tailwind = false) {
|
|
38245
38448
|
mkdirSync23(destDir, { recursive: true });
|
|
38246
38449
|
const templateDir = getStaticTemplateDir(templateId);
|
|
38247
38450
|
if (existsSync39(templateDir)) {
|
|
@@ -38250,6 +38453,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
38250
38453
|
await fetchRemoteTemplate(templateId, destDir);
|
|
38251
38454
|
}
|
|
38252
38455
|
patchVideoSrc(destDir, localVideoName, durationSeconds);
|
|
38456
|
+
if (tailwind) writeTailwindSupport(destDir);
|
|
38253
38457
|
writeFileSync18(
|
|
38254
38458
|
resolve22(destDir, "meta.json"),
|
|
38255
38459
|
JSON.stringify(
|
|
@@ -38267,6 +38471,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
38267
38471
|
const { writeProjectConfig: writeProjectConfig2, DEFAULT_PROJECT_CONFIG: DEFAULT_PROJECT_CONFIG2 } = await Promise.resolve().then(() => (init_projectConfig(), projectConfig_exports));
|
|
38268
38472
|
writeProjectConfig2(destDir, DEFAULT_PROJECT_CONFIG2);
|
|
38269
38473
|
}
|
|
38474
|
+
writeDefaultPackageJson(destDir, name);
|
|
38270
38475
|
const sharedDir = getSharedTemplateDir();
|
|
38271
38476
|
if (existsSync39(sharedDir)) {
|
|
38272
38477
|
for (const entry of readdirSync14(sharedDir, { withFileTypes: true })) {
|
|
@@ -38278,7 +38483,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
38278
38483
|
}
|
|
38279
38484
|
}
|
|
38280
38485
|
}
|
|
38281
|
-
var examples2, WEB_CODECS, DEFAULT_META, init_default;
|
|
38486
|
+
var examples2, WEB_CODECS, DEFAULT_META, TAILWIND_BROWSER_VERSION, TAILWIND_BROWSER_SRC, TAILWIND_BROWSER_INTEGRITY, init_default;
|
|
38282
38487
|
var init_init = __esm({
|
|
38283
38488
|
"src/commands/init.ts"() {
|
|
38284
38489
|
"use strict";
|
|
@@ -38290,11 +38495,13 @@ var init_init = __esm({
|
|
|
38290
38495
|
init_remote2();
|
|
38291
38496
|
init_events();
|
|
38292
38497
|
init_manager();
|
|
38498
|
+
init_version();
|
|
38293
38499
|
examples2 = [
|
|
38294
38500
|
["Create a project with the interactive wizard", "hyperframes init my-video"],
|
|
38295
38501
|
["Pick a starter example", "hyperframes init my-video --example warm-grain"],
|
|
38296
38502
|
["Start from an existing video file", "hyperframes init my-video --video clip.mp4"],
|
|
38297
38503
|
["Start from an audio file", "hyperframes init my-video --audio track.mp3"],
|
|
38504
|
+
["Scaffold with Tailwind CSS", "hyperframes init my-video --example blank --tailwind"],
|
|
38298
38505
|
["Non-interactive mode (for CI or AI agents)", "hyperframes init my-video --non-interactive"],
|
|
38299
38506
|
["Skip AI coding skills installation", "hyperframes init my-video --skip-skills"]
|
|
38300
38507
|
];
|
|
@@ -38307,6 +38514,9 @@ var init_init = __esm({
|
|
|
38307
38514
|
hasAudio: false,
|
|
38308
38515
|
videoCodec: "h264"
|
|
38309
38516
|
};
|
|
38517
|
+
TAILWIND_BROWSER_VERSION = "4.2.4";
|
|
38518
|
+
TAILWIND_BROWSER_SRC = `https://cdn.jsdelivr.net/npm/@tailwindcss/browser@${TAILWIND_BROWSER_VERSION}/dist/index.global.js`;
|
|
38519
|
+
TAILWIND_BROWSER_INTEGRITY = "sha384-v5YF9xS+gLRWdvrQ0u/WRbCkjSIH0NjHIPe8tBL1ZRrmI7PiSH6LLdzs0aAIMCuh";
|
|
38310
38520
|
init_default = defineCommand({
|
|
38311
38521
|
meta: {
|
|
38312
38522
|
name: "init",
|
|
@@ -38359,6 +38569,10 @@ var init_init = __esm({
|
|
|
38359
38569
|
"skip-skills": {
|
|
38360
38570
|
type: "boolean",
|
|
38361
38571
|
description: "Skip AI coding skills installation"
|
|
38572
|
+
},
|
|
38573
|
+
tailwind: {
|
|
38574
|
+
type: "boolean",
|
|
38575
|
+
description: "Add Tailwind CSS browser-runtime support"
|
|
38362
38576
|
}
|
|
38363
38577
|
},
|
|
38364
38578
|
async run({ args }) {
|
|
@@ -38376,6 +38590,7 @@ var init_init = __esm({
|
|
|
38376
38590
|
const audioFlag = args.audio;
|
|
38377
38591
|
const skipTranscribe = args["skip-transcribe"] === true;
|
|
38378
38592
|
const skipSkills = args["skip-skills"] === true;
|
|
38593
|
+
const tailwind = args.tailwind === true;
|
|
38379
38594
|
const nonInteractive = args["non-interactive"] === true;
|
|
38380
38595
|
const modelFlag = args.model;
|
|
38381
38596
|
const languageFlag = args.language;
|
|
@@ -38445,7 +38660,8 @@ var init_init = __esm({
|
|
|
38445
38660
|
basename5(destDir2),
|
|
38446
38661
|
templateId2,
|
|
38447
38662
|
localVideoName2,
|
|
38448
|
-
videoDuration2
|
|
38663
|
+
videoDuration2,
|
|
38664
|
+
tailwind
|
|
38449
38665
|
);
|
|
38450
38666
|
} catch (err) {
|
|
38451
38667
|
console.error(
|
|
@@ -38456,7 +38672,7 @@ var init_init = __esm({
|
|
|
38456
38672
|
console.error(c.dim("Use --example blank for offline use."));
|
|
38457
38673
|
process.exit(1);
|
|
38458
38674
|
}
|
|
38459
|
-
trackInitTemplate(templateId2);
|
|
38675
|
+
trackInitTemplate(templateId2, { tailwind });
|
|
38460
38676
|
const transcriptFile2 = resolve22(destDir2, "transcript.json");
|
|
38461
38677
|
if (existsSync39(transcriptFile2)) {
|
|
38462
38678
|
await patchTranscript(destDir2, transcriptFile2);
|
|
@@ -38483,10 +38699,13 @@ var init_init = __esm({
|
|
|
38483
38699
|
console.log(` ${c.dim("More patterns: hyperframes.heygen.com/guides/prompting")}`);
|
|
38484
38700
|
console.log();
|
|
38485
38701
|
console.log(` ${c.accent("4.")} Preview in the browser:`);
|
|
38486
|
-
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("
|
|
38702
|
+
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npm run dev")}`);
|
|
38703
|
+
console.log();
|
|
38704
|
+
console.log(` ${c.accent("5.")} Check the composition:`);
|
|
38705
|
+
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npm run check")}`);
|
|
38487
38706
|
console.log();
|
|
38488
|
-
console.log(` ${c.accent("
|
|
38489
|
-
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("
|
|
38707
|
+
console.log(` ${c.accent("6.")} Render to MP4 when ready:`);
|
|
38708
|
+
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npm run render")}`);
|
|
38490
38709
|
console.log();
|
|
38491
38710
|
console.log(` ${c.dim("Full docs: hyperframes.heygen.com")}`);
|
|
38492
38711
|
return;
|
|
@@ -38614,7 +38833,7 @@ var init_init = __esm({
|
|
|
38614
38833
|
spin.start(`Downloading example ${c.accent(templateId)}...`);
|
|
38615
38834
|
}
|
|
38616
38835
|
try {
|
|
38617
|
-
await scaffoldProject(destDir, name, templateId, localVideoName, videoDuration);
|
|
38836
|
+
await scaffoldProject(destDir, name, templateId, localVideoName, videoDuration, tailwind);
|
|
38618
38837
|
if (!isBundled) {
|
|
38619
38838
|
spin.stop(c.success(`Downloaded ${templateId}`));
|
|
38620
38839
|
}
|
|
@@ -38628,7 +38847,7 @@ ${c.dim("Use --example blank for offline use.")}`
|
|
|
38628
38847
|
);
|
|
38629
38848
|
process.exit(1);
|
|
38630
38849
|
}
|
|
38631
|
-
trackInitTemplate(templateId);
|
|
38850
|
+
trackInitTemplate(templateId, { tailwind });
|
|
38632
38851
|
const transcriptFile = resolve22(destDir, "transcript.json");
|
|
38633
38852
|
if (existsSync39(transcriptFile)) {
|
|
38634
38853
|
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
|
|