@vibeframe/mcp-server 0.57.3 → 0.58.0

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.
Files changed (2) hide show
  1. package/dist/index.js +552 -99
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -7126,9 +7126,9 @@ var init_project_builder = __esm({
7126
7126
  }
7127
7127
  });
7128
7128
 
7129
- // ../../node_modules/.pnpm/esbuild@0.24.2/node_modules/esbuild/lib/main.js
7129
+ // ../../node_modules/.pnpm/esbuild@0.27.3/node_modules/esbuild/lib/main.js
7130
7130
  var require_main = __commonJS({
7131
- "../../node_modules/.pnpm/esbuild@0.24.2/node_modules/esbuild/lib/main.js"(exports, module) {
7131
+ "../../node_modules/.pnpm/esbuild@0.27.3/node_modules/esbuild/lib/main.js"(exports, module) {
7132
7132
  "use strict";
7133
7133
  var __defProp3 = Object.defineProperty;
7134
7134
  var __getOwnPropDesc3 = Object.getOwnPropertyDescriptor;
@@ -7333,25 +7333,31 @@ is not a problem with esbuild. You need to fix your environment instead.
7333
7333
  var quote = JSON.stringify;
7334
7334
  var buildLogLevelDefault = "warning";
7335
7335
  var transformLogLevelDefault = "silent";
7336
- function validateTarget(target) {
7337
- validateStringValue(target, "target");
7338
- if (target.indexOf(",") >= 0) throw new Error(`Invalid target: ${target}`);
7339
- return target;
7336
+ function validateAndJoinStringArray(values, what) {
7337
+ const toJoin = [];
7338
+ for (const value of values) {
7339
+ validateStringValue(value, what);
7340
+ if (value.indexOf(",") >= 0) throw new Error(`Invalid ${what}: ${value}`);
7341
+ toJoin.push(value);
7342
+ }
7343
+ return toJoin.join(",");
7340
7344
  }
7341
7345
  var canBeAnything = () => null;
7342
7346
  var mustBeBoolean = (value) => typeof value === "boolean" ? null : "a boolean";
7343
7347
  var mustBeString = (value) => typeof value === "string" ? null : "a string";
7344
7348
  var mustBeRegExp = (value) => value instanceof RegExp ? null : "a RegExp object";
7345
7349
  var mustBeInteger = (value) => typeof value === "number" && value === (value | 0) ? null : "an integer";
7350
+ var mustBeValidPortNumber = (value) => typeof value === "number" && value === (value | 0) && value >= 0 && value <= 65535 ? null : "a valid port number";
7346
7351
  var mustBeFunction = (value) => typeof value === "function" ? null : "a function";
7347
7352
  var mustBeArray = (value) => Array.isArray(value) ? null : "an array";
7353
+ var mustBeArrayOfStrings = (value) => Array.isArray(value) && value.every((x) => typeof x === "string") ? null : "an array of strings";
7348
7354
  var mustBeObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? null : "an object";
7349
7355
  var mustBeEntryPoints = (value) => typeof value === "object" && value !== null ? null : "an array or an object";
7350
7356
  var mustBeWebAssemblyModule = (value) => value instanceof WebAssembly.Module ? null : "a WebAssembly.Module";
7351
7357
  var mustBeObjectOrNull = (value) => typeof value === "object" && !Array.isArray(value) ? null : "an object or null";
7352
7358
  var mustBeStringOrBoolean = (value) => typeof value === "string" || typeof value === "boolean" ? null : "a string or a boolean";
7353
7359
  var mustBeStringOrObject = (value) => typeof value === "string" || typeof value === "object" && value !== null && !Array.isArray(value) ? null : "a string or an object";
7354
- var mustBeStringOrArray = (value) => typeof value === "string" || Array.isArray(value) ? null : "a string or an array";
7360
+ var mustBeStringOrArrayOfStrings = (value) => typeof value === "string" || Array.isArray(value) && value.every((x) => typeof x === "string") ? null : "a string or an array of strings";
7355
7361
  var mustBeStringOrUint8Array = (value) => typeof value === "string" || value instanceof Uint8Array ? null : "a string or a Uint8Array";
7356
7362
  var mustBeStringOrURL = (value) => typeof value === "string" || value instanceof URL ? null : "a string or a URL";
7357
7363
  function getFlag3(object, keys2, key2, mustBeFn) {
@@ -7415,7 +7421,7 @@ is not a problem with esbuild. You need to fix your environment instead.
7415
7421
  let legalComments = getFlag3(options, keys2, "legalComments", mustBeString);
7416
7422
  let sourceRoot = getFlag3(options, keys2, "sourceRoot", mustBeString);
7417
7423
  let sourcesContent = getFlag3(options, keys2, "sourcesContent", mustBeBoolean);
7418
- let target = getFlag3(options, keys2, "target", mustBeStringOrArray);
7424
+ let target = getFlag3(options, keys2, "target", mustBeStringOrArrayOfStrings);
7419
7425
  let format4 = getFlag3(options, keys2, "format", mustBeString);
7420
7426
  let globalName = getFlag3(options, keys2, "globalName", mustBeString);
7421
7427
  let mangleProps = getFlag3(options, keys2, "mangleProps", mustBeRegExp);
@@ -7426,8 +7432,8 @@ is not a problem with esbuild. You need to fix your environment instead.
7426
7432
  let minifyWhitespace = getFlag3(options, keys2, "minifyWhitespace", mustBeBoolean);
7427
7433
  let minifyIdentifiers = getFlag3(options, keys2, "minifyIdentifiers", mustBeBoolean);
7428
7434
  let lineLimit = getFlag3(options, keys2, "lineLimit", mustBeInteger);
7429
- let drop = getFlag3(options, keys2, "drop", mustBeArray);
7430
- let dropLabels = getFlag3(options, keys2, "dropLabels", mustBeArray);
7435
+ let drop = getFlag3(options, keys2, "drop", mustBeArrayOfStrings);
7436
+ let dropLabels = getFlag3(options, keys2, "dropLabels", mustBeArrayOfStrings);
7431
7437
  let charset = getFlag3(options, keys2, "charset", mustBeString);
7432
7438
  let treeShaking = getFlag3(options, keys2, "treeShaking", mustBeBoolean);
7433
7439
  let ignoreAnnotations = getFlag3(options, keys2, "ignoreAnnotations", mustBeBoolean);
@@ -7440,17 +7446,15 @@ is not a problem with esbuild. You need to fix your environment instead.
7440
7446
  let define2 = getFlag3(options, keys2, "define", mustBeObject);
7441
7447
  let logOverride = getFlag3(options, keys2, "logOverride", mustBeObject);
7442
7448
  let supported = getFlag3(options, keys2, "supported", mustBeObject);
7443
- let pure = getFlag3(options, keys2, "pure", mustBeArray);
7449
+ let pure = getFlag3(options, keys2, "pure", mustBeArrayOfStrings);
7444
7450
  let keepNames = getFlag3(options, keys2, "keepNames", mustBeBoolean);
7445
7451
  let platform = getFlag3(options, keys2, "platform", mustBeString);
7446
7452
  let tsconfigRaw = getFlag3(options, keys2, "tsconfigRaw", mustBeStringOrObject);
7453
+ let absPaths = getFlag3(options, keys2, "absPaths", mustBeArrayOfStrings);
7447
7454
  if (legalComments) flags.push(`--legal-comments=${legalComments}`);
7448
7455
  if (sourceRoot !== void 0) flags.push(`--source-root=${sourceRoot}`);
7449
7456
  if (sourcesContent !== void 0) flags.push(`--sources-content=${sourcesContent}`);
7450
- if (target) {
7451
- if (Array.isArray(target)) flags.push(`--target=${Array.from(target).map(validateTarget).join(",")}`);
7452
- else flags.push(`--target=${validateTarget(target)}`);
7453
- }
7457
+ if (target) flags.push(`--target=${validateAndJoinStringArray(Array.isArray(target) ? target : [target], "target")}`);
7454
7458
  if (format4) flags.push(`--format=${format4}`);
7455
7459
  if (globalName) flags.push(`--global-name=${globalName}`);
7456
7460
  if (platform) flags.push(`--platform=${platform}`);
@@ -7464,9 +7468,10 @@ is not a problem with esbuild. You need to fix your environment instead.
7464
7468
  if (treeShaking !== void 0) flags.push(`--tree-shaking=${treeShaking}`);
7465
7469
  if (ignoreAnnotations) flags.push(`--ignore-annotations`);
7466
7470
  if (drop) for (let what of drop) flags.push(`--drop:${validateStringValue(what, "drop")}`);
7467
- if (dropLabels) flags.push(`--drop-labels=${Array.from(dropLabels).map((what) => validateStringValue(what, "dropLabels")).join(",")}`);
7468
- if (mangleProps) flags.push(`--mangle-props=${mangleProps.source}`);
7469
- if (reserveProps) flags.push(`--reserve-props=${reserveProps.source}`);
7471
+ if (dropLabels) flags.push(`--drop-labels=${validateAndJoinStringArray(dropLabels, "drop label")}`);
7472
+ if (absPaths) flags.push(`--abs-paths=${validateAndJoinStringArray(absPaths, "abs paths")}`);
7473
+ if (mangleProps) flags.push(`--mangle-props=${jsRegExpToGoRegExp(mangleProps)}`);
7474
+ if (reserveProps) flags.push(`--reserve-props=${jsRegExpToGoRegExp(reserveProps)}`);
7470
7475
  if (mangleQuoted !== void 0) flags.push(`--mangle-quoted=${mangleQuoted}`);
7471
7476
  if (jsx) flags.push(`--jsx=${jsx}`);
7472
7477
  if (jsxFactory) flags.push(`--jsx-factory=${jsxFactory}`);
@@ -7515,11 +7520,11 @@ is not a problem with esbuild. You need to fix your environment instead.
7515
7520
  let outdir = getFlag3(options, keys2, "outdir", mustBeString);
7516
7521
  let outbase = getFlag3(options, keys2, "outbase", mustBeString);
7517
7522
  let tsconfig = getFlag3(options, keys2, "tsconfig", mustBeString);
7518
- let resolveExtensions = getFlag3(options, keys2, "resolveExtensions", mustBeArray);
7519
- let nodePathsInput = getFlag3(options, keys2, "nodePaths", mustBeArray);
7520
- let mainFields = getFlag3(options, keys2, "mainFields", mustBeArray);
7521
- let conditions = getFlag3(options, keys2, "conditions", mustBeArray);
7522
- let external = getFlag3(options, keys2, "external", mustBeArray);
7523
+ let resolveExtensions = getFlag3(options, keys2, "resolveExtensions", mustBeArrayOfStrings);
7524
+ let nodePathsInput = getFlag3(options, keys2, "nodePaths", mustBeArrayOfStrings);
7525
+ let mainFields = getFlag3(options, keys2, "mainFields", mustBeArrayOfStrings);
7526
+ let conditions = getFlag3(options, keys2, "conditions", mustBeArrayOfStrings);
7527
+ let external = getFlag3(options, keys2, "external", mustBeArrayOfStrings);
7523
7528
  let packages = getFlag3(options, keys2, "packages", mustBeString);
7524
7529
  let alias = getFlag3(options, keys2, "alias", mustBeObject);
7525
7530
  let loader = getFlag3(options, keys2, "loader", mustBeObject);
@@ -7528,7 +7533,7 @@ is not a problem with esbuild. You need to fix your environment instead.
7528
7533
  let entryNames = getFlag3(options, keys2, "entryNames", mustBeString);
7529
7534
  let chunkNames = getFlag3(options, keys2, "chunkNames", mustBeString);
7530
7535
  let assetNames = getFlag3(options, keys2, "assetNames", mustBeString);
7531
- let inject = getFlag3(options, keys2, "inject", mustBeArray);
7536
+ let inject = getFlag3(options, keys2, "inject", mustBeArrayOfStrings);
7532
7537
  let banner = getFlag3(options, keys2, "banner", mustBeObject);
7533
7538
  let footer = getFlag3(options, keys2, "footer", mustBeObject);
7534
7539
  let entryPoints = getFlag3(options, keys2, "entryPoints", mustBeEntryPoints);
@@ -7550,37 +7555,13 @@ is not a problem with esbuild. You need to fix your environment instead.
7550
7555
  if (outbase) flags.push(`--outbase=${outbase}`);
7551
7556
  if (tsconfig) flags.push(`--tsconfig=${tsconfig}`);
7552
7557
  if (packages) flags.push(`--packages=${packages}`);
7553
- if (resolveExtensions) {
7554
- let values = [];
7555
- for (let value of resolveExtensions) {
7556
- validateStringValue(value, "resolve extension");
7557
- if (value.indexOf(",") >= 0) throw new Error(`Invalid resolve extension: ${value}`);
7558
- values.push(value);
7559
- }
7560
- flags.push(`--resolve-extensions=${values.join(",")}`);
7561
- }
7558
+ if (resolveExtensions) flags.push(`--resolve-extensions=${validateAndJoinStringArray(resolveExtensions, "resolve extension")}`);
7562
7559
  if (publicPath) flags.push(`--public-path=${publicPath}`);
7563
7560
  if (entryNames) flags.push(`--entry-names=${entryNames}`);
7564
7561
  if (chunkNames) flags.push(`--chunk-names=${chunkNames}`);
7565
7562
  if (assetNames) flags.push(`--asset-names=${assetNames}`);
7566
- if (mainFields) {
7567
- let values = [];
7568
- for (let value of mainFields) {
7569
- validateStringValue(value, "main field");
7570
- if (value.indexOf(",") >= 0) throw new Error(`Invalid main field: ${value}`);
7571
- values.push(value);
7572
- }
7573
- flags.push(`--main-fields=${values.join(",")}`);
7574
- }
7575
- if (conditions) {
7576
- let values = [];
7577
- for (let value of conditions) {
7578
- validateStringValue(value, "condition");
7579
- if (value.indexOf(",") >= 0) throw new Error(`Invalid condition: ${value}`);
7580
- values.push(value);
7581
- }
7582
- flags.push(`--conditions=${values.join(",")}`);
7583
- }
7563
+ if (mainFields) flags.push(`--main-fields=${validateAndJoinStringArray(mainFields, "main field")}`);
7564
+ if (conditions) flags.push(`--conditions=${validateAndJoinStringArray(conditions, "condition")}`);
7584
7565
  if (external) for (let name of external) flags.push(`--external:${validateStringValue(name, "external")}`);
7585
7566
  if (alias) {
7586
7567
  for (let old in alias) {
@@ -7777,8 +7758,8 @@ is not a problem with esbuild. You need to fix your environment instead.
7777
7758
  if (isFirstPacket) {
7778
7759
  isFirstPacket = false;
7779
7760
  let binaryVersion = String.fromCharCode(...bytes);
7780
- if (binaryVersion !== "0.24.2") {
7781
- throw new Error(`Cannot start service: Host version "${"0.24.2"}" does not match binary version ${quote(binaryVersion)}`);
7761
+ if (binaryVersion !== "0.27.3") {
7762
+ throw new Error(`Cannot start service: Host version "${"0.27.3"}" does not match binary version ${quote(binaryVersion)}`);
7782
7763
  }
7783
7764
  return;
7784
7765
  }
@@ -8120,11 +8101,13 @@ is not a problem with esbuild. You need to fix your environment instead.
8120
8101
  watch: (options2 = {}) => new Promise((resolve44, reject) => {
8121
8102
  if (!streamIn.hasFS) throw new Error(`Cannot use the "watch" API in this environment`);
8122
8103
  const keys2 = {};
8104
+ const delay = getFlag3(options2, keys2, "delay", mustBeInteger);
8123
8105
  checkForInvalidFlags(options2, keys2, `in watch() call`);
8124
8106
  const request22 = {
8125
8107
  command: "watch",
8126
8108
  key: buildKey
8127
8109
  };
8110
+ if (delay) request22.delay = delay;
8128
8111
  sendRequest(refs3, request22, (error2) => {
8129
8112
  if (error2) reject(new Error(error2));
8130
8113
  else resolve44(void 0);
@@ -8133,12 +8116,13 @@ is not a problem with esbuild. You need to fix your environment instead.
8133
8116
  serve: (options2 = {}) => new Promise((resolve44, reject) => {
8134
8117
  if (!streamIn.hasFS) throw new Error(`Cannot use the "serve" API in this environment`);
8135
8118
  const keys2 = {};
8136
- const port = getFlag3(options2, keys2, "port", mustBeInteger);
8119
+ const port = getFlag3(options2, keys2, "port", mustBeValidPortNumber);
8137
8120
  const host = getFlag3(options2, keys2, "host", mustBeString);
8138
8121
  const servedir = getFlag3(options2, keys2, "servedir", mustBeString);
8139
8122
  const keyfile = getFlag3(options2, keys2, "keyfile", mustBeString);
8140
8123
  const certfile = getFlag3(options2, keys2, "certfile", mustBeString);
8141
8124
  const fallback2 = getFlag3(options2, keys2, "fallback", mustBeString);
8125
+ const cors = getFlag3(options2, keys2, "cors", mustBeObject);
8142
8126
  const onRequest = getFlag3(options2, keys2, "onRequest", mustBeFunction);
8143
8127
  checkForInvalidFlags(options2, keys2, `in serve() call`);
8144
8128
  const request22 = {
@@ -8152,6 +8136,13 @@ is not a problem with esbuild. You need to fix your environment instead.
8152
8136
  if (keyfile !== void 0) request22.keyfile = keyfile;
8153
8137
  if (certfile !== void 0) request22.certfile = certfile;
8154
8138
  if (fallback2 !== void 0) request22.fallback = fallback2;
8139
+ if (cors) {
8140
+ const corsKeys = {};
8141
+ const origin = getFlag3(cors, corsKeys, "origin", mustBeStringOrArrayOfStrings);
8142
+ checkForInvalidFlags(cors, corsKeys, `on "cors" object`);
8143
+ if (Array.isArray(origin)) request22.corsOrigin = origin;
8144
+ else if (origin !== void 0) request22.corsOrigin = [origin];
8145
+ }
8155
8146
  sendRequest(refs3, request22, (error2, response2) => {
8156
8147
  if (error2) return reject(new Error(error2));
8157
8148
  if (onRequest) {
@@ -8287,7 +8278,7 @@ is not a problem with esbuild. You need to fix your environment instead.
8287
8278
  if (filter4 == null) throw new Error(`onResolve() call is missing a filter`);
8288
8279
  let id = nextCallbackID++;
8289
8280
  onResolveCallbacks[id] = { name, callback, note: registeredNote };
8290
- plugin2.onResolve.push({ id, filter: filter4.source, namespace: namespace || "" });
8281
+ plugin2.onResolve.push({ id, filter: jsRegExpToGoRegExp(filter4), namespace: namespace || "" });
8291
8282
  },
8292
8283
  onLoad(options, callback) {
8293
8284
  let registeredText = `This error came from the "onLoad" callback registered here:`;
@@ -8299,7 +8290,7 @@ is not a problem with esbuild. You need to fix your environment instead.
8299
8290
  if (filter4 == null) throw new Error(`onLoad() call is missing a filter`);
8300
8291
  let id = nextCallbackID++;
8301
8292
  onLoadCallbacks[id] = { name, callback, note: registeredNote };
8302
- plugin2.onLoad.push({ id, filter: filter4.source, namespace: namespace || "" });
8293
+ plugin2.onLoad.push({ id, filter: jsRegExpToGoRegExp(filter4), namespace: namespace || "" });
8303
8294
  },
8304
8295
  onDispose(callback) {
8305
8296
  onDisposeCallbacks.push(callback);
@@ -8359,8 +8350,8 @@ is not a problem with esbuild. You need to fix your environment instead.
8359
8350
  let pluginData = getFlag3(result, keys2, "pluginData", canBeAnything);
8360
8351
  let errors = getFlag3(result, keys2, "errors", mustBeArray);
8361
8352
  let warnings = getFlag3(result, keys2, "warnings", mustBeArray);
8362
- let watchFiles = getFlag3(result, keys2, "watchFiles", mustBeArray);
8363
- let watchDirs = getFlag3(result, keys2, "watchDirs", mustBeArray);
8353
+ let watchFiles = getFlag3(result, keys2, "watchFiles", mustBeArrayOfStrings);
8354
+ let watchDirs = getFlag3(result, keys2, "watchDirs", mustBeArrayOfStrings);
8364
8355
  checkForInvalidFlags(result, keys2, `from onResolve() callback in plugin ${quote(name)}`);
8365
8356
  response.id = id2;
8366
8357
  if (pluginName != null) response.pluginName = pluginName;
@@ -8405,8 +8396,8 @@ is not a problem with esbuild. You need to fix your environment instead.
8405
8396
  let loader = getFlag3(result, keys2, "loader", mustBeString);
8406
8397
  let errors = getFlag3(result, keys2, "errors", mustBeArray);
8407
8398
  let warnings = getFlag3(result, keys2, "warnings", mustBeArray);
8408
- let watchFiles = getFlag3(result, keys2, "watchFiles", mustBeArray);
8409
- let watchDirs = getFlag3(result, keys2, "watchDirs", mustBeArray);
8399
+ let watchFiles = getFlag3(result, keys2, "watchFiles", mustBeArrayOfStrings);
8400
+ let watchDirs = getFlag3(result, keys2, "watchDirs", mustBeArrayOfStrings);
8410
8401
  checkForInvalidFlags(result, keys2, `from onLoad() callback in plugin ${quote(name)}`);
8411
8402
  response.id = id2;
8412
8403
  if (pluginName != null) response.pluginName = pluginName;
@@ -8710,6 +8701,11 @@ ${file}:${line}:${column}: ERROR: ${pluginText}${e.text}`;
8710
8701
  }
8711
8702
  };
8712
8703
  }
8704
+ function jsRegExpToGoRegExp(regexp) {
8705
+ let result = regexp.source;
8706
+ if (regexp.flags) result = `(?${regexp.flags})${result}`;
8707
+ return result;
8708
+ }
8713
8709
  var fs8 = __require("fs");
8714
8710
  var os11 = __require("os");
8715
8711
  var path14 = __require("path");
@@ -8746,7 +8742,8 @@ ${file}:${line}:${column}: ERROR: ${pluginText}${e.text}`;
8746
8742
  };
8747
8743
  var knownWebAssemblyFallbackPackages = {
8748
8744
  "android arm LE": "@esbuild/android-arm",
8749
- "android x64 LE": "@esbuild/android-x64"
8745
+ "android x64 LE": "@esbuild/android-x64",
8746
+ "openharmony arm64 LE": "@esbuild/openharmony-arm64"
8750
8747
  };
8751
8748
  function pkgAndSubpathForCurrentPlatform() {
8752
8749
  let pkg;
@@ -8886,7 +8883,7 @@ for your current platform.`);
8886
8883
  "node_modules",
8887
8884
  ".cache",
8888
8885
  "esbuild",
8889
- `pnpapi-${pkg.replace("/", "-")}-${"0.24.2"}-${path14.basename(subpath)}`
8886
+ `pnpapi-${pkg.replace("/", "-")}-${"0.27.3"}-${path14.basename(subpath)}`
8890
8887
  );
8891
8888
  if (!fs8.existsSync(binTargetPath)) {
8892
8889
  fs8.mkdirSync(path14.dirname(binTargetPath), { recursive: true });
@@ -8919,7 +8916,7 @@ for your current platform.`);
8919
8916
  }
8920
8917
  }
8921
8918
  var _a7;
8922
- var isInternalWorkerThread = ((_a7 = worker_threads == null ? void 0 : worker_threads.workerData) == null ? void 0 : _a7.esbuildVersion) === "0.24.2";
8919
+ var isInternalWorkerThread = ((_a7 = worker_threads == null ? void 0 : worker_threads.workerData) == null ? void 0 : _a7.esbuildVersion) === "0.27.3";
8923
8920
  var esbuildCommandAndArgs = () => {
8924
8921
  if ((!ESBUILD_BINARY_PATH || false) && (path23.basename(__filename) !== "main.js" || path23.basename(__dirname) !== "lib")) {
8925
8922
  throw new Error(
@@ -8986,7 +8983,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
8986
8983
  }
8987
8984
  }
8988
8985
  };
8989
- var version = "0.24.2";
8986
+ var version = "0.27.3";
8990
8987
  var build = (options) => ensureServiceIsRunning().build(options);
8991
8988
  var context3 = (buildOptions) => ensureServiceIsRunning().context(buildOptions);
8992
8989
  var transform = (input3, options) => ensureServiceIsRunning().transform(input3, options);
@@ -9089,7 +9086,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
9089
9086
  var ensureServiceIsRunning = () => {
9090
9087
  if (longLivedService) return longLivedService;
9091
9088
  let [command3, args] = esbuildCommandAndArgs();
9092
- let child = child_process.spawn(command3, args.concat(`--service=${"0.24.2"}`, "--ping"), {
9089
+ let child = child_process.spawn(command3, args.concat(`--service=${"0.27.3"}`, "--ping"), {
9093
9090
  windowsHide: true,
9094
9091
  stdio: ["pipe", "pipe", "inherit"],
9095
9092
  cwd: defaultWD
@@ -9193,7 +9190,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
9193
9190
  esbuild: node_exports
9194
9191
  });
9195
9192
  callback(service);
9196
- let stdout = child_process.execFileSync(command3, args.concat(`--service=${"0.24.2"}`), {
9193
+ let stdout = child_process.execFileSync(command3, args.concat(`--service=${"0.27.3"}`), {
9197
9194
  cwd: defaultWD,
9198
9195
  windowsHide: true,
9199
9196
  input: stdin,
@@ -9213,7 +9210,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
9213
9210
  var startWorkerThreadService = (worker_threads2) => {
9214
9211
  let { port1: mainPort, port2: workerPort } = new worker_threads2.MessageChannel();
9215
9212
  let worker = new worker_threads2.Worker(__filename, {
9216
- workerData: { workerPort, defaultWD, esbuildVersion: "0.24.2" },
9213
+ workerData: { workerPort, defaultWD, esbuildVersion: "0.27.3" },
9217
9214
  transferList: [workerPort],
9218
9215
  // From node's documentation: https://nodejs.org/api/worker_threads.html
9219
9216
  //
@@ -450501,6 +450498,70 @@ function buildEmptyRootHtml(opts) {
450501
450498
  </html>
450502
450499
  `;
450503
450500
  }
450501
+ function buildDesignMd(opts) {
450502
+ const { name, style } = opts;
450503
+ const intro = style ? `Visual identity for **${name}**, scaffolded from the **${style.name}** style (after ${style.designer}). Customise freely \u2014 this file is the single source of truth for every scene's palette, typography, and motion.` : `Visual identity for **${name}**. Fill the sections below before authoring any scene HTML or generating any backdrop. Pick a named style with \`vibe scene styles\` if you want a credible starting point.`;
450504
+ const moodLine = style ? `**Mood:** ${style.mood} \xB7 **Best for:** ${style.bestFor}` : `**Mood:** _(one line \u2014 what should the viewer FEEL?)_`;
450505
+ const palette = style ? `${style.palette.map((c) => `- \`${c}\``).join("\n")}
450506
+
450507
+ ${style.paletteNotes}` : `- _hex_ \u2014 primary
450508
+ - _hex_ \u2014 accent
450509
+
450510
+ _2\u20133 colours max. Declare explicit hex values; never name colours abstractly._`;
450511
+ const typography = style ? style.typography : `_One family, two weights. State the role of each (headline / label / body)._`;
450512
+ const composition = style ? style.composition : `_Grid? Centered? Layered? How does negative space behave?_`;
450513
+ const motion = style ? `${style.motion}
450514
+
450515
+ **GSAP signature:** ${style.gsapSignature}` : `_How fast? Snappy or fluid? Overshoot or precision?_
450516
+
450517
+ **GSAP signature:** _e.g. \`expo.out\`, \`sine.inOut\`, \`back.out(1.8)\`_`;
450518
+ const transition = style ? style.transition : `_Which Hyperframes shader matches the energy? (Cinematic Zoom, Cross-Warp Morph, Glitch, Domain Warp, \u2026)_`;
450519
+ const avoid = style ? style.avoid.map((a) => `- ${a}`).join("\n") : `- _anti-pattern 1_
450520
+ - _anti-pattern 2_
450521
+ - _anti-pattern 3_`;
450522
+ return `# ${name} \u2014 Design
450523
+
450524
+ > **Hard-gate.** This file defines the visual identity of every scene.
450525
+ > Author it before generating any HTML, backdrop image, or motion.
450526
+ > The Hyperframes \`hyperframes\` skill enforces this: scenes that
450527
+ > contradict DESIGN.md are rejected.
450528
+
450529
+ ${intro}
450530
+
450531
+ ## Style
450532
+
450533
+ ${moodLine}
450534
+
450535
+ ## Palette
450536
+
450537
+ ${palette}
450538
+
450539
+ ## Typography
450540
+
450541
+ ${typography}
450542
+
450543
+ ## Composition
450544
+
450545
+ ${composition}
450546
+
450547
+ ## Motion
450548
+
450549
+ ${motion}
450550
+
450551
+ ## Transition
450552
+
450553
+ ${transition}
450554
+
450555
+ ## What NOT to do
450556
+
450557
+ ${avoid}
450558
+
450559
+ ---
450560
+
450561
+ _Browse other named styles: \`vibe scene styles\`_
450562
+ ${style ? `_This file was seeded by \`vibe scene init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe scene init <dir> --visual-style "<name>"\`._`}
450563
+ `;
450564
+ }
450504
450565
  function buildProjectClaudeMd(name) {
450505
450566
  return `# ${name} \u2014 Scene Authoring Project
450506
450567
 
@@ -450508,6 +450569,16 @@ This project is **bilingual**: it works with both VibeFrame (\`vibe\`) and
450508
450569
  HeyGen Hyperframes (\`hyperframes\`). You can run either CLI inside this
450509
450570
  directory.
450510
450571
 
450572
+ ## Visual identity hard-gate
450573
+
450574
+ **Author \`DESIGN.md\` before any scene HTML.** It defines palette,
450575
+ typography, motion, and transition rules. Both the agent-driven path and
450576
+ the fallback emit reference it; scenes that contradict DESIGN.md are
450577
+ rejected by the Hyperframes \`hyperframes\` skill.
450578
+
450579
+ Browse named styles: \`vibe scene styles\`. Re-seed from one with
450580
+ \`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
450581
+
450511
450582
  ## Skills \u2014 USE THESE FIRST
450512
450583
 
450513
450584
  **Always invoke the relevant skill before authoring scenes.** Skills encode
@@ -450516,14 +450587,23 @@ semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
450516
450587
 
450517
450588
  | Skill | Command | When to use |
450518
450589
  | ----------------- | ---------------- | ------------------------------------------------------------------------------------- |
450519
- | **vibe-scene** | \`/vibe-scene\` | Authoring / editing per-scene HTML in this project \u2014 preferred |
450520
- | **hyperframes** | \`/hyperframes\` | Fallback: direct HF authoring (install via \`npx skills add heygen-com/hyperframes\`) |
450590
+ | **hyperframes** | \`/hyperframes\` | Cinematic-quality composition \u2014 DESIGN.md hard-gate, named styles, motion principles |
450591
+ | **vibe-scene** | \`/vibe-scene\` | VibeFrame's authoring loop, AI assets, lint feedback, pipeline integration |
450521
450592
  | **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
450522
450593
 
450523
- If the skill is not available, follow the **Key Rules** below.
450594
+ Install the Hyperframes skills once per machine:
450595
+
450596
+ \`\`\`bash
450597
+ npx skills add heygen-com/hyperframes
450598
+ \`\`\`
450599
+
450600
+ Restart your agent session (or reload the skill list) after installing.
450601
+ If skills aren't available, follow the **Key Rules** below \u2014 they cover
450602
+ the framework-level minimum, not the cinematic craft layer.
450524
450603
 
450525
450604
  ## Project structure
450526
450605
 
450606
+ - \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
450527
450607
  - \`index.html\` \u2014 root composition (timeline)
450528
450608
  - \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
450529
450609
  - \`assets/\` \u2014 shared media (narration audio, images, video)
@@ -450641,6 +450721,17 @@ async function scaffoldSceneProject(opts) {
450641
450721
  await writeFile10(claudePath, buildProjectClaudeMd(name), "utf-8");
450642
450722
  created.push(claudePath);
450643
450723
  }
450724
+ const designPath = resolve20(dir, "DESIGN.md");
450725
+ if (await pathExists(designPath)) {
450726
+ skipped.push(designPath);
450727
+ } else {
450728
+ await writeFile10(
450729
+ designPath,
450730
+ buildDesignMd({ name, style: opts.visualStyle }),
450731
+ "utf-8"
450732
+ );
450733
+ created.push(designPath);
450734
+ }
450644
450735
  const gitignorePath = resolve20(dir, ".gitignore");
450645
450736
  if (await pathExists(gitignorePath)) {
450646
450737
  skipped.push(gitignorePath);
@@ -450689,6 +450780,52 @@ function buildTranscriptTweens(transcript, targetSelector) {
450689
450780
  return `tl.fromTo('${sel}', { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.18, ease: 'power2.out' }, ${start});`;
450690
450781
  }).join("\n ");
450691
450782
  }
450783
+ function crossfadeTweens(scope, dur) {
450784
+ return `tl.from('${scope}', { opacity: 0, duration: ${SCENE_OVERLAP_SECONDS}, ease: 'power2.out' }, 0);
450785
+ tl.to('${scope}', { opacity: 0, duration: ${SCENE_OVERLAP_SECONDS}, ease: 'power2.in' }, ${(dur - SCENE_OVERLAP_SECONDS).toFixed(2)});`;
450786
+ }
450787
+ function fitFontSize(text, baseMaxPx, minPx, comfortableChars) {
450788
+ const charCount = text.length;
450789
+ if (charCount <= comfortableChars) return baseMaxPx;
450790
+ const ratio = comfortableChars / charCount;
450791
+ return Math.max(minPx, Math.round(baseMaxPx * ratio));
450792
+ }
450793
+ function fitKineticFontSize(words) {
450794
+ const totalChars = words.join(" ").length;
450795
+ if (totalChars <= 18) return 180;
450796
+ if (totalChars <= 26) return 140;
450797
+ if (totalChars <= 38) return 110;
450798
+ if (totalChars <= 54) return 90;
450799
+ return 72;
450800
+ }
450801
+ function kenBurnsTween(scope, dur, endScale = 1.08) {
450802
+ return `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: ${endScale}, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);`;
450803
+ }
450804
+ function idleHeroPulse(scope, selector, idleStart, dur) {
450805
+ const cycleDur = 1.5;
450806
+ const idleEnd = dur - SCENE_OVERLAP_SECONDS;
450807
+ const window3 = idleEnd - idleStart;
450808
+ if (window3 < cycleDur) return "";
450809
+ const plays = Math.max(1, Math.floor(window3 / cycleDur));
450810
+ const repeat = plays - 1;
450811
+ return `tl.to('${scope} ${selector}', { scale: 1.04, y: -8, duration: ${cycleDur}, yoyo: true, repeat: ${repeat}, ease: 'sine.inOut' }, ${idleStart.toFixed(2)});`;
450812
+ }
450813
+ function kineticWordBobs(scope, wordCount, idleStart, dur) {
450814
+ const cycleDur = 1.8;
450815
+ const idleEnd = dur - SCENE_OVERLAP_SECONDS;
450816
+ const window3 = idleEnd - idleStart;
450817
+ if (window3 < cycleDur) return "";
450818
+ const plays = Math.max(1, Math.floor(window3 / cycleDur));
450819
+ const repeat = plays - 1;
450820
+ const lines = [];
450821
+ for (let i = 0; i < wordCount; i++) {
450822
+ const start = (idleStart + i * 0.18).toFixed(2);
450823
+ lines.push(
450824
+ `tl.to('${scope} #w-${i}', { y: -12, duration: ${cycleDur}, yoyo: true, repeat: ${repeat}, ease: 'sine.inOut' }, ${start});`
450825
+ );
450826
+ }
450827
+ return lines.join("\n ");
450828
+ }
450692
450829
  function buildPreset(input3) {
450693
450830
  const id = input3.id;
450694
450831
  const scope = `[data-composition-id="${id}"]`;
@@ -450725,7 +450862,8 @@ function buildPreset(input3) {
450725
450862
  const captionInner = useWordSync ? renderTranscriptSpans(transcript) : esc(captionText);
450726
450863
  const wordCss = useWordSync ? `
450727
450864
  ${scope} .caption .word { display: inline-block; opacity: 0; }` : "";
450728
- const timeline = useWordSync ? buildTranscriptTweens(transcript, `${scope} .caption .word`) : `tl.from('${scope} .caption', { opacity: 0, y: 28, duration: 0.45, ease: 'power3.out' }, 0.05);`;
450865
+ const captionFontPx = fitFontSize(captionText, 56, 28, 60);
450866
+ const captionTween = useWordSync ? buildTranscriptTweens(transcript, `${scope} .caption .word`) : `tl.from('${scope} .caption', { opacity: 0, y: 28, duration: 0.45, ease: 'power3.out' }, 0.05);`;
450729
450867
  return {
450730
450868
  css: `${scope} {
450731
450869
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450733,21 +450871,27 @@ function buildPreset(input3) {
450733
450871
  color: #fff; overflow: hidden; background: #000;
450734
450872
  }
450735
450873
  ${backdrop}
450874
+ ${scope} .backdrop { transform-origin: center; }
450736
450875
  ${scope} .caption {
450737
450876
  position: absolute;
450738
450877
  left: 8%; right: 8%; bottom: 12%;
450739
450878
  text-align: center;
450740
- font-size: 56px;
450879
+ font-size: ${captionFontPx}px;
450741
450880
  font-weight: 700;
450742
450881
  line-height: 1.2;
450882
+ overflow-wrap: break-word;
450743
450883
  text-shadow: 0 4px 20px rgba(0,0,0,0.65);
450744
450884
  }${wordCss}`,
450745
450885
  body: `${backdropMarkup}
450746
450886
  <div class="caption" id="caption">${captionInner}</div>`,
450747
- timeline
450887
+ timeline: `${crossfadeTweens(scope, dur)}
450888
+ ${kenBurnsTween(scope, dur)}
450889
+ ${captionTween}
450890
+ ${idleHeroPulse(scope, "#caption", 1, dur)}`
450748
450891
  };
450749
450892
  }
450750
450893
  case "announcement": {
450894
+ const fontSize = fitFontSize(headline, 160, 72, 22);
450751
450895
  return {
450752
450896
  css: `${scope} {
450753
450897
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450755,6 +450899,7 @@ function buildPreset(input3) {
450755
450899
  color: #fff; overflow: hidden; background: #000;
450756
450900
  }
450757
450901
  ${backdrop}
450902
+ ${scope} .backdrop { transform-origin: center; }
450758
450903
  ${scope} .announce {
450759
450904
  position: absolute; inset: 0;
450760
450905
  display: flex; align-items: center; justify-content: center;
@@ -450763,10 +450908,11 @@ function buildPreset(input3) {
450763
450908
  }
450764
450909
  ${scope} .announce h1 {
450765
450910
  margin: 0;
450766
- font-size: 160px;
450911
+ font-size: ${fontSize}px;
450767
450912
  font-weight: 900;
450768
450913
  letter-spacing: -4px;
450769
- line-height: 1;
450914
+ line-height: 1.05;
450915
+ overflow-wrap: break-word;
450770
450916
  background: linear-gradient(90deg, #8e2de2, #00c9ff);
450771
450917
  -webkit-background-clip: text; background-clip: text; color: transparent;
450772
450918
  text-shadow: 0 8px 40px rgba(142,45,226,0.35);
@@ -450776,8 +450922,12 @@ function buildPreset(input3) {
450776
450922
  // Faster, snappier entrance and no tail fade-out — see the
450777
450923
  // matching note in the `simple` case above. The headline stays
450778
450924
  // on screen until the producer cuts to the next clip; the next
450779
- // scene's own fade-in handles the visual transition.
450780
- timeline: `tl.from('${scope} #headline', { opacity: 0, scale: 0.92, duration: 0.55, ease: 'power3.out' }, 0.05);`
450925
+ // scene's own fade-in handles the visual transition. Backdrop
450926
+ // Ken-Burns fills what used to be 80% dead static frame.
450927
+ timeline: `${crossfadeTweens(scope, dur)}
450928
+ ${kenBurnsTween(scope, dur)}
450929
+ tl.from('${scope} #headline', { opacity: 0, scale: 0.92, duration: 0.55, ease: 'power3.out' }, 0.05);
450930
+ ${idleHeroPulse(scope, "#headline", 1, dur)}`
450781
450931
  };
450782
450932
  }
450783
450933
  case "explainer": {
@@ -450789,6 +450939,7 @@ function buildPreset(input3) {
450789
450939
  const wordCss = useWordSync ? `
450790
450940
  ${scope} #subtitle .word { display: inline-block; opacity: 0; }` : "";
450791
450941
  const subtitleTween = useWordSync ? buildTranscriptTweens(transcript, `${scope} #subtitle .word`) : sub ? `tl.from('${scope} #subtitle', { opacity: 0, y: 30, duration: 0.55, ease: 'power3.out' }, 0.55);` : "";
450942
+ const titleFontPx = fitFontSize(headline, 110, 56, 30);
450792
450943
  return {
450793
450944
  css: `${scope} {
450794
450945
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450796,6 +450947,7 @@ function buildPreset(input3) {
450796
450947
  color: #fff; overflow: hidden; background: #000;
450797
450948
  }
450798
450949
  ${backdrop}
450950
+ ${scope} .backdrop { transform-origin: center; }
450799
450951
  ${scope} .stage {
450800
450952
  position: absolute; inset: 0;
450801
450953
  display: flex; flex-direction: column; justify-content: center;
@@ -450806,11 +450958,13 @@ function buildPreset(input3) {
450806
450958
  color: #00c9ff; font-weight: 600;
450807
450959
  }
450808
450960
  ${scope} .title {
450809
- font-size: 110px; font-weight: 800; letter-spacing: -2px;
450961
+ font-size: ${titleFontPx}px; font-weight: 800; letter-spacing: -2px;
450810
450962
  line-height: 1.05; margin: 0;
450963
+ overflow-wrap: break-word;
450811
450964
  }
450812
450965
  ${scope} .subtitle {
450813
450966
  font-size: 38px; font-weight: 300; color: #c0c0d0; max-width: 80%;
450967
+ overflow-wrap: break-word;
450814
450968
  }${wordCss}`,
450815
450969
  body: `${backdropMarkup}
450816
450970
  <div class="stage">
@@ -450818,9 +450972,12 @@ function buildPreset(input3) {
450818
450972
  <h1 class="title" id="title">${esc(headline)}</h1>${sub ? `
450819
450973
  <div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
450820
450974
  </div>`,
450821
- timeline: `tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
450975
+ timeline: `${crossfadeTweens(scope, dur)}
450976
+ ${kenBurnsTween(scope, dur)}
450977
+ tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
450822
450978
  tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
450823
- ${subtitleTween}`
450979
+ ${subtitleTween}
450980
+ ${idleHeroPulse(scope, "#title", 1.2, dur)}`
450824
450981
  };
450825
450982
  }
450826
450983
  case "kinetic-type": {
@@ -450836,6 +450993,9 @@ function buildPreset(input3) {
450836
450993
  const start = (0.05 + i * stagger).toFixed(2);
450837
450994
  return `tl.from('${scope} #w-${i}', { opacity: 0, y: 80, scale: 0.8, duration: 0.45, ease: 'back.out(1.8)' }, ${start});`;
450838
450995
  }).join("\n ");
450996
+ const kineticFontPx = fitKineticFontSize(words);
450997
+ const lastWordEntryEnd = useWordSync ? Math.max(...transcript.map((w) => w.end + 0.2)) : 0.05 + (words.length - 1) * stagger + 0.45 + 0.2;
450998
+ const idleStart = Math.max(1, Number(lastWordEntryEnd.toFixed(2)));
450839
450999
  return {
450840
451000
  css: `${scope} {
450841
451001
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450843,21 +451003,28 @@ function buildPreset(input3) {
450843
451003
  color: #fff; overflow: hidden; background: #000;
450844
451004
  }
450845
451005
  ${backdrop}
451006
+ ${scope} .backdrop { transform-origin: center; }
450846
451007
  ${scope} .kinetic {
450847
451008
  position: absolute; inset: 0;
450848
451009
  display: flex; align-items: center; justify-content: center;
451010
+ flex-wrap: wrap; gap: 8px 16px;
450849
451011
  text-align: center; padding: 0 6%;
450850
- font-size: 180px; font-weight: 900; letter-spacing: -6px;
451012
+ font-size: ${kineticFontPx}px; font-weight: 900; letter-spacing: -6px;
450851
451013
  line-height: 1; text-shadow: 0 6px 30px rgba(0,0,0,0.6);
451014
+ overflow-wrap: break-word;
450852
451015
  }
450853
451016
  ${scope} .kinetic .word { display: inline-block; margin: 0 12px; }`,
450854
451017
  body: `${backdropMarkup}
450855
451018
  <div class="kinetic">${wordSpans}</div>`,
450856
- timeline: tweens
451019
+ timeline: `${crossfadeTweens(scope, dur)}
451020
+ ${kenBurnsTween(scope, dur)}
451021
+ ${tweens}
451022
+ ${kineticWordBobs(scope, words.length, idleStart, dur)}`
450857
451023
  };
450858
451024
  }
450859
451025
  case "product-shot": {
450860
451026
  const label = kicker || humanise(id);
451027
+ const headlineFontPx = fitFontSize(headline, 72, 40, 50);
450861
451028
  return {
450862
451029
  css: `${scope} {
450863
451030
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450875,22 +451042,26 @@ function buildPreset(input3) {
450875
451042
  }
450876
451043
  ${scope} .product-headline {
450877
451044
  position: absolute; left: 8%; right: 8%; bottom: 14%;
450878
- font-size: 72px; font-weight: 800; letter-spacing: -1px;
450879
- line-height: 1.1; text-shadow: 0 4px 20px rgba(0,0,0,0.7);
451045
+ font-size: ${headlineFontPx}px; font-weight: 800; letter-spacing: -1px;
451046
+ line-height: 1.1; overflow-wrap: break-word;
451047
+ text-shadow: 0 4px 20px rgba(0,0,0,0.7);
450880
451048
  }${subhead ? `
450881
451049
  ${scope} .product-sub {
450882
451050
  position: absolute; left: 8%; right: 8%; bottom: 8%;
450883
451051
  font-size: 28px; font-weight: 400; color: #d0d0e0;
451052
+ overflow-wrap: break-word;
450884
451053
  text-shadow: 0 2px 10px rgba(0,0,0,0.7);
450885
451054
  }` : ""}`,
450886
451055
  body: `${backdropMarkup}
450887
451056
  <div class="label" id="label">${esc(label)}</div>
450888
451057
  <div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
450889
451058
  <div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
450890
- timeline: `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.08, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
451059
+ timeline: `${crossfadeTweens(scope, dur)}
451060
+ tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.12, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
450891
451061
  tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
450892
451062
  tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
450893
- tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}`
451063
+ tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}
451064
+ ${idleHeroPulse(scope, "#headline", 1.2, dur)}`
450894
451065
  };
450895
451066
  }
450896
451067
  }
@@ -450936,7 +451107,7 @@ ${audioBlock}
450936
451107
  </template>
450937
451108
  `;
450938
451109
  }
450939
- function nextSceneStart(rootHtml) {
451110
+ function nextSceneStart(rootHtml, overlap = 0) {
450940
451111
  const clipRegex = /<div\s+class="clip"[^>]*?\sdata-start="([\d.]+)"[^>]*?\sdata-duration="([\d.]+)"/gi;
450941
451112
  let maxEnd = 0;
450942
451113
  let match2;
@@ -450944,14 +451115,15 @@ function nextSceneStart(rootHtml) {
450944
451115
  const end = parseFloat(match2[1]) + parseFloat(match2[2]);
450945
451116
  if (Number.isFinite(end) && end > maxEnd) maxEnd = end;
450946
451117
  }
450947
- return maxEnd;
451118
+ return Math.max(0, maxEnd - overlap);
450948
451119
  }
450949
451120
  function buildClipReference(opts) {
450950
451121
  const start = Number(opts.start.toFixed(3));
450951
451122
  const duration = Number(opts.duration.toFixed(3));
450952
451123
  const track = opts.trackIndex ?? 1;
450953
451124
  const src = opts.src ?? `compositions/scene-${opts.id}.html`;
450954
- return `<div class="clip" data-composition-id="${esc(opts.id)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}"></div>`;
451125
+ const zIndex = Math.max(1, 1e6 - Math.floor(start * 1e3));
451126
+ return `<div class="clip" data-composition-id="${esc(opts.id)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}" style="z-index: ${zIndex};"></div>`;
450955
451127
  }
450956
451128
  function insertClipIntoRoot(rootHtml, clip) {
450957
451129
  const clipDiv = buildClipReference(clip);
@@ -450984,7 +451156,7 @@ function readRootDims(rootHtml) {
450984
451156
  if (!widthMatch || !heightMatch) return null;
450985
451157
  return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
450986
451158
  }
450987
- var SCENE_PRESETS, GSAP_CDN;
451159
+ var SCENE_PRESETS, GSAP_CDN, SCENE_OVERLAP_SECONDS;
450988
451160
  var init_scene_html_emit = __esm({
450989
451161
  "../cli/src/commands/_shared/scene-html-emit.ts"() {
450990
451162
  "use strict";
@@ -450996,6 +451168,7 @@ var init_scene_html_emit = __esm({
450996
451168
  "product-shot"
450997
451169
  ];
450998
451170
  GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
451171
+ SCENE_OVERLAP_SECONDS = 0.4;
450999
451172
  }
451000
451173
  });
451001
451174
 
@@ -451167,7 +451340,12 @@ async function executeSegmentsToScenes(opts) {
451167
451340
  const dims = aspectToDims(aspect);
451168
451341
  const preset = opts.scenePreset ?? DEFAULT_PRESET;
451169
451342
  const projectName = opts.projectName ?? basename8(outputDir);
451170
- const totalDuration = opts.segments.reduce((sum, s) => sum + (s.duration || 0), 0) || 10;
451343
+ const totalDuration = (() => {
451344
+ const sum = opts.segments.reduce((acc, s) => acc + (s.duration || 0), 0);
451345
+ if (sum <= 0) return 10;
451346
+ const joins = Math.max(0, opts.segments.length - 1);
451347
+ return Math.max(0.1, sum - joins * SCENE_OVERLAP_SECONDS);
451348
+ })();
451171
451349
  const baseError = (error) => ({
451172
451350
  success: false,
451173
451351
  outputDir,
@@ -451240,9 +451418,10 @@ async function executeSegmentsToScenes(opts) {
451240
451418
  id,
451241
451419
  start: clipStart,
451242
451420
  duration,
451421
+ trackIndex: i % 2 + 1,
451243
451422
  src: `compositions/${id}.html`
451244
451423
  });
451245
- clipStart += duration;
451424
+ clipStart += duration - SCENE_OVERLAP_SECONDS;
451246
451425
  }
451247
451426
  await writeFile12(rootAbs, rootHtml, "utf-8");
451248
451427
  let lintResult;
@@ -464231,6 +464410,176 @@ function parseTtsProviderName(value) {
464231
464410
 
464232
464411
  // ../cli/src/commands/scene.ts
464233
464412
  init_scene_project();
464413
+
464414
+ // ../cli/src/commands/_shared/visual-styles.ts
464415
+ var STYLES = [
464416
+ {
464417
+ name: "Swiss Pulse",
464418
+ slug: "swiss-pulse",
464419
+ designer: "Josef M\xFCller-Brockmann",
464420
+ mood: "Clinical, precise",
464421
+ bestFor: "SaaS dashboards, developer tools, APIs, metrics",
464422
+ palette: ["#1a1a1a", "#ffffff", "#0066FF"],
464423
+ paletteNotes: "Black, white, ONE accent \u2014 electric blue (#0066FF) or amber (#FFB300). Never both accents at once.",
464424
+ typography: "Helvetica or Inter Bold for headlines, Regular for labels. Numbers dominate at 80\u2013120px.",
464425
+ composition: "Grid-locked. Every element snaps to an invisible 12-column grid. Hard cuts only \u2014 no decorative transitions.",
464426
+ motion: "Animated counters count up from 0. Entries are fast and snap into place. Nothing floats.",
464427
+ transition: "Cinematic Zoom or SDF Iris (precise, geometric)",
464428
+ gsapSignature: "expo.out, power4.out \u2014 fast arrivals, hard stops",
464429
+ avoid: [
464430
+ "Decorative transitions (fades, dissolves) \u2014 use hard cuts",
464431
+ "Two accent colours competing in one frame",
464432
+ "Off-grid placement or floating elements"
464433
+ ]
464434
+ },
464435
+ {
464436
+ name: "Velvet Standard",
464437
+ slug: "velvet-standard",
464438
+ designer: "Massimo Vignelli",
464439
+ mood: "Premium, timeless",
464440
+ bestFor: "Luxury products, enterprise software, keynotes, investor decks",
464441
+ palette: ["#000000", "#ffffff", "#1a237e"],
464442
+ paletteNotes: "Black, white, ONE rich accent \u2014 deep navy (#1a237e) or gold (#c9a84c).",
464443
+ typography: "Thin sans-serif, ALL CAPS, wide letter-spacing (0.15em+). Sequential reveals only.",
464444
+ composition: "Generous negative space. Symmetrical, centered, architectural precision. Nothing busy.",
464445
+ motion: "Slow, deliberate. Sequential reveals with long holds. No frantic motion.",
464446
+ transition: "Cross-Warp Morph (elegant, organic flow between scenes)",
464447
+ gsapSignature: "sine.inOut, power1 \u2014 nothing snaps, everything glides",
464448
+ avoid: [
464449
+ "Tight letter-spacing \u2014 kills the premium register",
464450
+ "Bouncy or elastic easings \u2014 too playful",
464451
+ "Multiple elements arriving at once \u2014 break sequence"
464452
+ ]
464453
+ },
464454
+ {
464455
+ name: "Deconstructed",
464456
+ slug: "deconstructed",
464457
+ designer: "Neville Brody",
464458
+ mood: "Industrial, raw",
464459
+ bestFor: "Tech news, developer launches, security products, punk-energy reveals",
464460
+ palette: ["#1a1a1a", "#D4501E", "#f0f0f0"],
464461
+ paletteNotes: "Dark grey (#1a1a1a), rust orange (#D4501E), raw white (#f0f0f0).",
464462
+ typography: "Type at angles, overlapping edges, escaping frames. Bold industrial weight.",
464463
+ composition: "Gritty textures \u2014 scan-line effects, glitch artifacts baked into the design.",
464464
+ motion: "Text SLAMS and SHATTERS. Letters scramble then snap to final position.",
464465
+ transition: "Glitch shader or Whip Pan (breaks the rules, feels aggressive)",
464466
+ gsapSignature: "back.out(2.5), steps(8), elastic.out(1.2, 0.4) \u2014 intentional irregularity",
464467
+ avoid: [
464468
+ "Polished, centered compositions \u2014 must feel raw",
464469
+ "Smooth fades \u2014 use glitch / scramble entries",
464470
+ "Soft easings \u2014 every motion lands hard"
464471
+ ]
464472
+ },
464473
+ {
464474
+ name: "Maximalist Type",
464475
+ slug: "maximalist-type",
464476
+ designer: "Paula Scher",
464477
+ mood: "Loud, kinetic",
464478
+ bestFor: "Big product launches, milestone announcements, high-energy hype videos",
464479
+ palette: ["#E63946", "#FFD60A", "#000000", "#ffffff"],
464480
+ paletteNotes: "Bold saturated: red (#E63946), yellow (#FFD60A), black, white \u2014 maximum contrast.",
464481
+ typography: "Text IS the visual. Overlapping type layers at different scales and angles, filling 50\u201380% of frame.",
464482
+ composition: "Text layered OVER footage \u2014 never empty backgrounds. 2\u20133 second rapid-fire scenes.",
464483
+ motion: "Everything is kinetic \u2014 slamming, sliding, scaling. No static moments.",
464484
+ transition: "Ridged Burn (explosive, dramatic, impossible to ignore)",
464485
+ gsapSignature: "expo.out, back.out(1.8) \u2014 fast arrivals, hard stops",
464486
+ avoid: [
464487
+ "Static moments \u2014 every frame must move",
464488
+ "Empty backgrounds \u2014 text and footage must layer",
464489
+ "Single typeface at a single size \u2014 must be layered"
464490
+ ]
464491
+ },
464492
+ {
464493
+ name: "Data Drift",
464494
+ slug: "data-drift",
464495
+ designer: "Refik Anadol",
464496
+ mood: "Futuristic, immersive",
464497
+ bestFor: "AI products, ML platforms, data companies, speculative tech",
464498
+ palette: ["#0a0a0a", "#7c3aed", "#06b6d4"],
464499
+ paletteNotes: "Iridescent \u2014 deep black (#0a0a0a), electric purple (#7c3aed), cyan (#06b6d4).",
464500
+ typography: "Thin futuristic sans-serif \u2014 floating, weightless, minimal text.",
464501
+ composition: "Fluid morphing compositions. Extreme scale shifts (micro \u2192 macro). Particles coalesce into numbers.",
464502
+ motion: "Light traces data paths through the frame. Smooth, continuous, organic \u2014 nothing hard.",
464503
+ transition: "Gravitational Lens or Domain Warp (otherworldly distortion)",
464504
+ gsapSignature: "sine.inOut, power2.out \u2014 smooth, continuous, organic",
464505
+ avoid: [
464506
+ "Hard cuts \u2014 break the immersion",
464507
+ "Heavy/bold typography \u2014 too grounded",
464508
+ "Static compositions \u2014 must feel like flow"
464509
+ ]
464510
+ },
464511
+ {
464512
+ name: "Soft Signal",
464513
+ slug: "soft-signal",
464514
+ designer: "Stefan Sagmeister",
464515
+ mood: "Intimate, warm",
464516
+ bestFor: "Wellness brands, personal stories, lifestyle products, human-centered apps",
464517
+ palette: ["#F5A623", "#FFF8EC", "#C4A3A3", "#8FAF8C"],
464518
+ paletteNotes: "Warm amber (#F5A623), cream (#FFF8EC), dusty rose (#C4A3A3), sage green (#8FAF8C).",
464519
+ typography: "Handwritten-style or humanist serif fonts. Personal, lowercase, delicate.",
464520
+ composition: "Close-up framing feel \u2014 single element fills the frame. Nothing feels corporate.",
464521
+ motion: "Slow drifts and floats, never snaps. Soft organic motion throughout.",
464522
+ transition: "Thermal Distortion (warm, flowing, like heat shimmer)",
464523
+ gsapSignature: "sine.inOut, power1.inOut \u2014 everything breathes",
464524
+ avoid: [
464525
+ "Sharp geometric layouts \u2014 break the warmth",
464526
+ "Hard easings or snaps \u2014 too clinical",
464527
+ "Cool tones (blue/green-blue) without warm balance"
464528
+ ]
464529
+ },
464530
+ {
464531
+ name: "Folk Frequency",
464532
+ slug: "folk-frequency",
464533
+ designer: "Eduardo Terrazas",
464534
+ mood: "Cultural, vivid",
464535
+ bestFor: "Consumer apps, food platforms, community products, festive launches",
464536
+ palette: ["#FF1493", "#0047AB", "#FFE000", "#009B77"],
464537
+ paletteNotes: "Vivid folk: hot pink (#FF1493), cobalt blue (#0047AB), sun yellow (#FFE000), emerald (#009B77).",
464538
+ typography: "Bold warm rounded type. Every frame feels handcrafted.",
464539
+ composition: "Pattern and repetition \u2014 folk art rhythm and density. Layered compositions with rich visual texture.",
464540
+ motion: "Colorful motion \u2014 elements bounce, pop, and spin into place with joy.",
464541
+ transition: "Swirl Vortex or Ripple Waves (hypnotic, celebratory)",
464542
+ gsapSignature: "back.out(1.6), elastic.out(1, 0.5) \u2014 overshoots feel intentional",
464543
+ avoid: [
464544
+ "Muted or monochrome palettes \u2014 kill the celebration",
464545
+ "Pure flat / minimal compositions \u2014 must feel layered",
464546
+ "Linear easings \u2014 motion should feel joyful"
464547
+ ]
464548
+ },
464549
+ {
464550
+ name: "Shadow Cut",
464551
+ slug: "shadow-cut",
464552
+ designer: "Hans Hillmann",
464553
+ mood: "Dark, cinematic",
464554
+ bestFor: "Security products, dramatic reveals, investigative content, intense launches",
464555
+ palette: ["#0a0a0a", "#3a3a3a", "#ffffff", "#C1121F"],
464556
+ paletteNotes: "Near-monochrome \u2014 deep blacks (#0a0a0a), cold greys (#3a3a3a), stark white + ONE accent (blood red #C1121F or toxic green #39FF14).",
464557
+ typography: "Sharp angular text like film noir title cards. Heavy contrast, no softness.",
464558
+ composition: "Heavy shadow \u2014 elements emerge from darkness. The reveal IS the narrative.",
464559
+ motion: "Slow creeping push-ins, dramatic scale reveals. Silence before the hit matters.",
464560
+ transition: "Domain Warp (dissolves reality before revealing the next scene)",
464561
+ gsapSignature: "power4.in for exits, power3.out for dramatic reveals \u2014 pause before the hit",
464562
+ avoid: [
464563
+ "Bright or saturated palettes \u2014 kill the cinematic mood",
464564
+ "Bouncy easings \u2014 break the tension",
464565
+ "Quick cuts \u2014 let the reveal breathe"
464566
+ ]
464567
+ }
464568
+ ];
464569
+ function listVisualStyles() {
464570
+ return STYLES;
464571
+ }
464572
+ function getVisualStyle(query2) {
464573
+ const q = query2.trim().toLowerCase();
464574
+ return STYLES.find(
464575
+ (s) => s.name.toLowerCase() === q || s.slug === q
464576
+ );
464577
+ }
464578
+ function visualStyleNames() {
464579
+ return STYLES.map((s) => `"${s.name}"`).join(", ");
464580
+ }
464581
+
464582
+ // ../cli/src/commands/scene.ts
464234
464583
  init_scene_html_emit();
464235
464584
  init_scene_lint();
464236
464585
  init_output();
@@ -464256,6 +464605,18 @@ function validatePreset(value) {
464256
464605
  }
464257
464606
  return value;
464258
464607
  }
464608
+ function validateVisualStyle(value) {
464609
+ const found = getVisualStyle(value);
464610
+ if (!found) {
464611
+ exitWithError(
464612
+ usageError(
464613
+ `Unknown visual style: ${value}`,
464614
+ `Valid: ${visualStyleNames()}. Browse details with \`vibe scene styles\`.`
464615
+ )
464616
+ );
464617
+ }
464618
+ return found;
464619
+ }
464259
464620
  var sceneCommand = new Command("scene").description("Author and render per-scene HTML compositions (Hyperframes backend)").addHelpText("after", `
464260
464621
  Examples:
464261
464622
  $ vibe scene init my-video # Scaffold a new project
@@ -464273,21 +464634,28 @@ Examples:
464273
464634
 
464274
464635
  A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
464275
464636
  Run 'vibe schema scene.<command>' for structured parameter info.`);
464276
- sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing Hyperframes project)").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
464637
+ sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing Hyperframes project)").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--visual-style <name>", `Seed DESIGN.md from a named style (browse via \`vibe scene styles\`). E.g. "Swiss Pulse"`).option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
464277
464638
  const aspect = validateAspect(options.ratio);
464278
464639
  const duration = validateDuration(options.duration);
464279
464640
  const name = options.name ?? basename18(dir.replace(/\/+$/, ""));
464641
+ const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
464280
464642
  if (options.dryRun) {
464281
464643
  outputResult({
464282
464644
  dryRun: true,
464283
464645
  command: "scene init",
464284
- params: { dir, name, aspect, duration }
464646
+ params: {
464647
+ dir,
464648
+ name,
464649
+ aspect,
464650
+ duration,
464651
+ visualStyle: visualStyle?.name ?? null
464652
+ }
464285
464653
  });
464286
464654
  return;
464287
464655
  }
464288
464656
  const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
464289
464657
  try {
464290
- const result = await scaffoldSceneProject({ dir, name, aspect, duration });
464658
+ const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
464291
464659
  if (isJsonMode()) {
464292
464660
  outputResult({
464293
464661
  success: true,
@@ -464296,6 +464664,7 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
464296
464664
  name,
464297
464665
  aspect,
464298
464666
  duration,
464667
+ visualStyle: visualStyle?.name ?? null,
464299
464668
  created: result.created,
464300
464669
  merged: result.merged,
464301
464670
  skipped: result.skipped
@@ -464312,7 +464681,13 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
464312
464681
  console.log();
464313
464682
  console.log(source_default.bold.cyan("Next steps"));
464314
464683
  console.log(source_default.dim("\u2500".repeat(60)));
464315
- console.log(` ${source_default.cyan("vibe scene add")} <name> ${source_default.dim("# author a scene via AI")}`);
464684
+ if (visualStyle) {
464685
+ console.log(` ${source_default.dim("DESIGN.md seeded with")} ${source_default.bold(visualStyle.name)} ${source_default.dim("\u2014 review and customise.")}`);
464686
+ } else {
464687
+ console.log(` ${source_default.cyan("vibe scene styles")} ${source_default.dim("# pick a named style for DESIGN.md")}`);
464688
+ }
464689
+ console.log(` ${source_default.cyan("npx skills add heygen-com/hyperframes")} ${source_default.dim("# load the cinematic-craft skill set")}`);
464690
+ console.log(` ${source_default.cyan("vibe scene add")} <name> ${source_default.dim("# fallback path: 5-preset emit")}`);
464316
464691
  console.log(` ${source_default.cyan("vibe scene lint")} ${source_default.dim("# validate HTML")}`);
464317
464692
  console.log(` ${source_default.cyan("vibe scene render")} ${source_default.dim("# render to MP4")}`);
464318
464693
  } catch (error) {
@@ -464321,6 +464696,69 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
464321
464696
  exitWithError(generalError(`Failed to scaffold: ${msg}`));
464322
464697
  }
464323
464698
  });
464699
+ sceneCommand.command("styles").description("List vendored visual styles (or show one) for DESIGN.md seeding").argument("[name]", "Style name to inspect (omit to list all)").action((name) => {
464700
+ if (!name) {
464701
+ const all = listVisualStyles();
464702
+ if (isJsonMode()) {
464703
+ outputResult({
464704
+ success: true,
464705
+ command: "scene styles",
464706
+ count: all.length,
464707
+ styles: all.map((s) => ({
464708
+ name: s.name,
464709
+ slug: s.slug,
464710
+ designer: s.designer,
464711
+ mood: s.mood,
464712
+ bestFor: s.bestFor
464713
+ }))
464714
+ });
464715
+ return;
464716
+ }
464717
+ console.log();
464718
+ console.log(source_default.bold.cyan("Visual styles"));
464719
+ console.log(source_default.dim("\u2500".repeat(60)));
464720
+ for (const s of all) {
464721
+ console.log(
464722
+ ` ${source_default.bold(s.name.padEnd(18))} ${source_default.dim(s.mood.padEnd(24))} ${source_default.dim(s.bestFor)}`
464723
+ );
464724
+ }
464725
+ console.log();
464726
+ console.log(source_default.dim("Show details: "), source_default.cyan('vibe scene styles "<name>"'));
464727
+ console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan('vibe scene init <dir> --visual-style "<name>"'));
464728
+ return;
464729
+ }
464730
+ const style = getVisualStyle(name);
464731
+ if (!style) {
464732
+ exitWithError(
464733
+ usageError(
464734
+ `Unknown visual style: ${name}`,
464735
+ `Valid: ${visualStyleNames()}.`
464736
+ )
464737
+ );
464738
+ return;
464739
+ }
464740
+ if (isJsonMode()) {
464741
+ outputResult({ success: true, command: "scene styles", style });
464742
+ return;
464743
+ }
464744
+ console.log();
464745
+ console.log(source_default.bold.cyan(style.name), source_default.dim(`\u2014 ${style.designer}`));
464746
+ console.log(source_default.dim("\u2500".repeat(60)));
464747
+ console.log(`${source_default.bold("Mood:")} ${style.mood}`);
464748
+ console.log(`${source_default.bold("Best for:")} ${style.bestFor}`);
464749
+ console.log(`${source_default.bold("Palette:")} ${style.palette.join(", ")}`);
464750
+ console.log(source_default.dim(" ") + style.paletteNotes);
464751
+ console.log(`${source_default.bold("Typography:")} ${style.typography}`);
464752
+ console.log(`${source_default.bold("Composition:")} ${style.composition}`);
464753
+ console.log(`${source_default.bold("Motion:")} ${style.motion}`);
464754
+ console.log(`${source_default.bold("GSAP:")} ${style.gsapSignature}`);
464755
+ console.log(`${source_default.bold("Transition:")} ${style.transition}`);
464756
+ console.log();
464757
+ console.log(source_default.bold("Avoid:"));
464758
+ for (const a of style.avoid) console.log(` ${source_default.red("\u2022")} ${a}`);
464759
+ console.log();
464760
+ console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan(`vibe scene init <dir> --visual-style "${style.name}"`));
464761
+ });
464324
464762
  sceneCommand.command("add").description("Add a new scene to a project: AI narration + image + per-scene HTML").argument("<name>", "Scene name (slugified into the composition id)").option("--style <preset>", `Style preset: ${SCENE_PRESETS.join(", ")}`, "simple").option("--narration <text>", "Narration text (or path to a .txt file). Drives TTS + scene duration.").option("--narration-file <path>", "Existing narration audio file (.wav/.mp3). Skips TTS \u2014 useful with hyperframes tts, Mac say, or other external tools.").option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option("--visuals <prompt>", "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider").option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option("--tts <provider>", "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)", "auto").option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option("--no-audio", "Skip TTS even when --narration is provided (useful for tests/agent dry runs)").option("--no-image", "Skip image generation even when --visuals is provided").option("--no-transcribe", "Skip Whisper word-level transcribe step (no transcript-<id>.json emitted)").option("--transcribe-language <code>", "BCP-47 language code passed to Whisper (e.g. en, ko)").option("--force", "Overwrite an existing compositions/scene-<id>.html").option("--dry-run", "Preview parameters without writing files or calling APIs").action(async (name, options) => {
464325
464763
  if (options.style) options.style = validatePreset(options.style);
464326
464764
  if (options.duration !== void 0) options.duration = validateDuration(options.duration);
@@ -464631,7 +465069,20 @@ async function executeSceneAdd(opts) {
464631
465069
  }
464632
465070
  const cfg = await loadVibeProjectConfig(projectDir);
464633
465071
  const fallback2 = cfg?.defaultSceneDuration ?? 5;
464634
- const duration = opts.duration ?? narrationDuration ?? fallback2;
465072
+ const NARRATION_TAIL_BUFFER = 0.5;
465073
+ const userDur = opts.duration;
465074
+ const audioMinDur = narrationDuration !== void 0 ? narrationDuration + SCENE_OVERLAP_SECONDS + NARRATION_TAIL_BUFFER : void 0;
465075
+ let duration;
465076
+ if (userDur !== void 0 && audioMinDur !== void 0) {
465077
+ duration = Math.max(userDur, audioMinDur);
465078
+ } else if (audioMinDur !== void 0) {
465079
+ duration = audioMinDur;
465080
+ } else if (userDur !== void 0) {
465081
+ duration = userDur;
465082
+ } else {
465083
+ duration = fallback2;
465084
+ }
465085
+ duration = Number(duration.toFixed(2));
464635
465086
  opts.onProgress?.("Emitting scene HTML...");
464636
465087
  const sceneHtml = emitSceneHtml({
464637
465088
  id,
@@ -464649,8 +465100,10 @@ async function executeSceneAdd(opts) {
464649
465100
  await mkdir19(dirname25(scenePath), { recursive: true });
464650
465101
  await writeFile25(scenePath, sceneHtml, "utf-8");
464651
465102
  opts.onProgress?.("Updating root composition...");
464652
- const start = nextSceneStart(rootHtmlBefore);
464653
- const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration });
465103
+ const start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
465104
+ const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
465105
+ const trackIndex = existingClipCount % 2 + 1;
465106
+ const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
464654
465107
  await writeFile25(rootPath, updated, "utf-8");
464655
465108
  const transcriptAbsPath = transcriptRelPath ? resolve40(projectDir, transcriptRelPath) : void 0;
464656
465109
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/mcp-server",
3
- "version": "0.57.3",
3
+ "version": "0.58.0",
4
4
  "description": "VibeFrame MCP Server - AI-native video editing via Model Context Protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -57,8 +57,8 @@
57
57
  "tsx": "^4.21.0",
58
58
  "typescript": "^5.3.3",
59
59
  "vitest": "^1.2.2",
60
- "@vibeframe/core": "0.57.3",
61
- "@vibeframe/cli": "0.57.3"
60
+ "@vibeframe/cli": "0.58.0",
61
+ "@vibeframe/core": "0.58.0"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=20"