@vibeframe/mcp-server 0.57.2 → 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 +553 -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
  //
@@ -446870,6 +446867,7 @@ var init_schema = __esm({
446870
446867
  elevenlabs: "ELEVENLABS_API_KEY",
446871
446868
  runway: "RUNWAY_API_SECRET",
446872
446869
  kling: "KLING_API_KEY",
446870
+ fal: "FAL_KEY",
446873
446871
  imgbb: "IMGBB_API_KEY",
446874
446872
  replicate: "REPLICATE_API_TOKEN",
446875
446873
  xai: "XAI_API_KEY",
@@ -450500,6 +450498,70 @@ function buildEmptyRootHtml(opts) {
450500
450498
  </html>
450501
450499
  `;
450502
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
+ }
450503
450565
  function buildProjectClaudeMd(name) {
450504
450566
  return `# ${name} \u2014 Scene Authoring Project
450505
450567
 
@@ -450507,6 +450569,16 @@ This project is **bilingual**: it works with both VibeFrame (\`vibe\`) and
450507
450569
  HeyGen Hyperframes (\`hyperframes\`). You can run either CLI inside this
450508
450570
  directory.
450509
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
+
450510
450582
  ## Skills \u2014 USE THESE FIRST
450511
450583
 
450512
450584
  **Always invoke the relevant skill before authoring scenes.** Skills encode
@@ -450515,14 +450587,23 @@ semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
450515
450587
 
450516
450588
  | Skill | Command | When to use |
450517
450589
  | ----------------- | ---------------- | ------------------------------------------------------------------------------------- |
450518
- | **vibe-scene** | \`/vibe-scene\` | Authoring / editing per-scene HTML in this project \u2014 preferred |
450519
- | **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 |
450520
450592
  | **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
450521
450593
 
450522
- 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.
450523
450603
 
450524
450604
  ## Project structure
450525
450605
 
450606
+ - \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
450526
450607
  - \`index.html\` \u2014 root composition (timeline)
450527
450608
  - \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
450528
450609
  - \`assets/\` \u2014 shared media (narration audio, images, video)
@@ -450640,6 +450721,17 @@ async function scaffoldSceneProject(opts) {
450640
450721
  await writeFile10(claudePath, buildProjectClaudeMd(name), "utf-8");
450641
450722
  created.push(claudePath);
450642
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
+ }
450643
450735
  const gitignorePath = resolve20(dir, ".gitignore");
450644
450736
  if (await pathExists(gitignorePath)) {
450645
450737
  skipped.push(gitignorePath);
@@ -450688,6 +450780,52 @@ function buildTranscriptTweens(transcript, targetSelector) {
450688
450780
  return `tl.fromTo('${sel}', { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.18, ease: 'power2.out' }, ${start});`;
450689
450781
  }).join("\n ");
450690
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
+ }
450691
450829
  function buildPreset(input3) {
450692
450830
  const id = input3.id;
450693
450831
  const scope = `[data-composition-id="${id}"]`;
@@ -450724,7 +450862,8 @@ function buildPreset(input3) {
450724
450862
  const captionInner = useWordSync ? renderTranscriptSpans(transcript) : esc(captionText);
450725
450863
  const wordCss = useWordSync ? `
450726
450864
  ${scope} .caption .word { display: inline-block; opacity: 0; }` : "";
450727
- 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);`;
450728
450867
  return {
450729
450868
  css: `${scope} {
450730
450869
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450732,21 +450871,27 @@ function buildPreset(input3) {
450732
450871
  color: #fff; overflow: hidden; background: #000;
450733
450872
  }
450734
450873
  ${backdrop}
450874
+ ${scope} .backdrop { transform-origin: center; }
450735
450875
  ${scope} .caption {
450736
450876
  position: absolute;
450737
450877
  left: 8%; right: 8%; bottom: 12%;
450738
450878
  text-align: center;
450739
- font-size: 56px;
450879
+ font-size: ${captionFontPx}px;
450740
450880
  font-weight: 700;
450741
450881
  line-height: 1.2;
450882
+ overflow-wrap: break-word;
450742
450883
  text-shadow: 0 4px 20px rgba(0,0,0,0.65);
450743
450884
  }${wordCss}`,
450744
450885
  body: `${backdropMarkup}
450745
450886
  <div class="caption" id="caption">${captionInner}</div>`,
450746
- timeline
450887
+ timeline: `${crossfadeTweens(scope, dur)}
450888
+ ${kenBurnsTween(scope, dur)}
450889
+ ${captionTween}
450890
+ ${idleHeroPulse(scope, "#caption", 1, dur)}`
450747
450891
  };
450748
450892
  }
450749
450893
  case "announcement": {
450894
+ const fontSize = fitFontSize(headline, 160, 72, 22);
450750
450895
  return {
450751
450896
  css: `${scope} {
450752
450897
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450754,6 +450899,7 @@ function buildPreset(input3) {
450754
450899
  color: #fff; overflow: hidden; background: #000;
450755
450900
  }
450756
450901
  ${backdrop}
450902
+ ${scope} .backdrop { transform-origin: center; }
450757
450903
  ${scope} .announce {
450758
450904
  position: absolute; inset: 0;
450759
450905
  display: flex; align-items: center; justify-content: center;
@@ -450762,10 +450908,11 @@ function buildPreset(input3) {
450762
450908
  }
450763
450909
  ${scope} .announce h1 {
450764
450910
  margin: 0;
450765
- font-size: 160px;
450911
+ font-size: ${fontSize}px;
450766
450912
  font-weight: 900;
450767
450913
  letter-spacing: -4px;
450768
- line-height: 1;
450914
+ line-height: 1.05;
450915
+ overflow-wrap: break-word;
450769
450916
  background: linear-gradient(90deg, #8e2de2, #00c9ff);
450770
450917
  -webkit-background-clip: text; background-clip: text; color: transparent;
450771
450918
  text-shadow: 0 8px 40px rgba(142,45,226,0.35);
@@ -450775,8 +450922,12 @@ function buildPreset(input3) {
450775
450922
  // Faster, snappier entrance and no tail fade-out — see the
450776
450923
  // matching note in the `simple` case above. The headline stays
450777
450924
  // on screen until the producer cuts to the next clip; the next
450778
- // scene's own fade-in handles the visual transition.
450779
- 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)}`
450780
450931
  };
450781
450932
  }
450782
450933
  case "explainer": {
@@ -450788,6 +450939,7 @@ function buildPreset(input3) {
450788
450939
  const wordCss = useWordSync ? `
450789
450940
  ${scope} #subtitle .word { display: inline-block; opacity: 0; }` : "";
450790
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);
450791
450943
  return {
450792
450944
  css: `${scope} {
450793
450945
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450795,6 +450947,7 @@ function buildPreset(input3) {
450795
450947
  color: #fff; overflow: hidden; background: #000;
450796
450948
  }
450797
450949
  ${backdrop}
450950
+ ${scope} .backdrop { transform-origin: center; }
450798
450951
  ${scope} .stage {
450799
450952
  position: absolute; inset: 0;
450800
450953
  display: flex; flex-direction: column; justify-content: center;
@@ -450805,11 +450958,13 @@ function buildPreset(input3) {
450805
450958
  color: #00c9ff; font-weight: 600;
450806
450959
  }
450807
450960
  ${scope} .title {
450808
- font-size: 110px; font-weight: 800; letter-spacing: -2px;
450961
+ font-size: ${titleFontPx}px; font-weight: 800; letter-spacing: -2px;
450809
450962
  line-height: 1.05; margin: 0;
450963
+ overflow-wrap: break-word;
450810
450964
  }
450811
450965
  ${scope} .subtitle {
450812
450966
  font-size: 38px; font-weight: 300; color: #c0c0d0; max-width: 80%;
450967
+ overflow-wrap: break-word;
450813
450968
  }${wordCss}`,
450814
450969
  body: `${backdropMarkup}
450815
450970
  <div class="stage">
@@ -450817,9 +450972,12 @@ function buildPreset(input3) {
450817
450972
  <h1 class="title" id="title">${esc(headline)}</h1>${sub ? `
450818
450973
  <div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
450819
450974
  </div>`,
450820
- 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);
450821
450978
  tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
450822
- ${subtitleTween}`
450979
+ ${subtitleTween}
450980
+ ${idleHeroPulse(scope, "#title", 1.2, dur)}`
450823
450981
  };
450824
450982
  }
450825
450983
  case "kinetic-type": {
@@ -450835,6 +450993,9 @@ function buildPreset(input3) {
450835
450993
  const start = (0.05 + i * stagger).toFixed(2);
450836
450994
  return `tl.from('${scope} #w-${i}', { opacity: 0, y: 80, scale: 0.8, duration: 0.45, ease: 'back.out(1.8)' }, ${start});`;
450837
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)));
450838
450999
  return {
450839
451000
  css: `${scope} {
450840
451001
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450842,21 +451003,28 @@ function buildPreset(input3) {
450842
451003
  color: #fff; overflow: hidden; background: #000;
450843
451004
  }
450844
451005
  ${backdrop}
451006
+ ${scope} .backdrop { transform-origin: center; }
450845
451007
  ${scope} .kinetic {
450846
451008
  position: absolute; inset: 0;
450847
451009
  display: flex; align-items: center; justify-content: center;
451010
+ flex-wrap: wrap; gap: 8px 16px;
450848
451011
  text-align: center; padding: 0 6%;
450849
- font-size: 180px; font-weight: 900; letter-spacing: -6px;
451012
+ font-size: ${kineticFontPx}px; font-weight: 900; letter-spacing: -6px;
450850
451013
  line-height: 1; text-shadow: 0 6px 30px rgba(0,0,0,0.6);
451014
+ overflow-wrap: break-word;
450851
451015
  }
450852
451016
  ${scope} .kinetic .word { display: inline-block; margin: 0 12px; }`,
450853
451017
  body: `${backdropMarkup}
450854
451018
  <div class="kinetic">${wordSpans}</div>`,
450855
- timeline: tweens
451019
+ timeline: `${crossfadeTweens(scope, dur)}
451020
+ ${kenBurnsTween(scope, dur)}
451021
+ ${tweens}
451022
+ ${kineticWordBobs(scope, words.length, idleStart, dur)}`
450856
451023
  };
450857
451024
  }
450858
451025
  case "product-shot": {
450859
451026
  const label = kicker || humanise(id);
451027
+ const headlineFontPx = fitFontSize(headline, 72, 40, 50);
450860
451028
  return {
450861
451029
  css: `${scope} {
450862
451030
  position: absolute; inset: 0; width: 100%; height: 100%;
@@ -450874,22 +451042,26 @@ function buildPreset(input3) {
450874
451042
  }
450875
451043
  ${scope} .product-headline {
450876
451044
  position: absolute; left: 8%; right: 8%; bottom: 14%;
450877
- font-size: 72px; font-weight: 800; letter-spacing: -1px;
450878
- 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);
450879
451048
  }${subhead ? `
450880
451049
  ${scope} .product-sub {
450881
451050
  position: absolute; left: 8%; right: 8%; bottom: 8%;
450882
451051
  font-size: 28px; font-weight: 400; color: #d0d0e0;
451052
+ overflow-wrap: break-word;
450883
451053
  text-shadow: 0 2px 10px rgba(0,0,0,0.7);
450884
451054
  }` : ""}`,
450885
451055
  body: `${backdropMarkup}
450886
451056
  <div class="label" id="label">${esc(label)}</div>
450887
451057
  <div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
450888
451058
  <div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
450889
- 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);
450890
451061
  tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
450891
451062
  tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
450892
- 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)}`
450893
451065
  };
450894
451066
  }
450895
451067
  }
@@ -450935,7 +451107,7 @@ ${audioBlock}
450935
451107
  </template>
450936
451108
  `;
450937
451109
  }
450938
- function nextSceneStart(rootHtml) {
451110
+ function nextSceneStart(rootHtml, overlap = 0) {
450939
451111
  const clipRegex = /<div\s+class="clip"[^>]*?\sdata-start="([\d.]+)"[^>]*?\sdata-duration="([\d.]+)"/gi;
450940
451112
  let maxEnd = 0;
450941
451113
  let match2;
@@ -450943,14 +451115,15 @@ function nextSceneStart(rootHtml) {
450943
451115
  const end = parseFloat(match2[1]) + parseFloat(match2[2]);
450944
451116
  if (Number.isFinite(end) && end > maxEnd) maxEnd = end;
450945
451117
  }
450946
- return maxEnd;
451118
+ return Math.max(0, maxEnd - overlap);
450947
451119
  }
450948
451120
  function buildClipReference(opts) {
450949
451121
  const start = Number(opts.start.toFixed(3));
450950
451122
  const duration = Number(opts.duration.toFixed(3));
450951
451123
  const track = opts.trackIndex ?? 1;
450952
451124
  const src = opts.src ?? `compositions/scene-${opts.id}.html`;
450953
- 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>`;
450954
451127
  }
450955
451128
  function insertClipIntoRoot(rootHtml, clip) {
450956
451129
  const clipDiv = buildClipReference(clip);
@@ -450983,7 +451156,7 @@ function readRootDims(rootHtml) {
450983
451156
  if (!widthMatch || !heightMatch) return null;
450984
451157
  return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
450985
451158
  }
450986
- var SCENE_PRESETS, GSAP_CDN;
451159
+ var SCENE_PRESETS, GSAP_CDN, SCENE_OVERLAP_SECONDS;
450987
451160
  var init_scene_html_emit = __esm({
450988
451161
  "../cli/src/commands/_shared/scene-html-emit.ts"() {
450989
451162
  "use strict";
@@ -450995,6 +451168,7 @@ var init_scene_html_emit = __esm({
450995
451168
  "product-shot"
450996
451169
  ];
450997
451170
  GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
451171
+ SCENE_OVERLAP_SECONDS = 0.4;
450998
451172
  }
450999
451173
  });
451000
451174
 
@@ -451166,7 +451340,12 @@ async function executeSegmentsToScenes(opts) {
451166
451340
  const dims = aspectToDims(aspect);
451167
451341
  const preset = opts.scenePreset ?? DEFAULT_PRESET;
451168
451342
  const projectName = opts.projectName ?? basename8(outputDir);
451169
- 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
+ })();
451170
451349
  const baseError = (error) => ({
451171
451350
  success: false,
451172
451351
  outputDir,
@@ -451239,9 +451418,10 @@ async function executeSegmentsToScenes(opts) {
451239
451418
  id,
451240
451419
  start: clipStart,
451241
451420
  duration,
451421
+ trackIndex: i % 2 + 1,
451242
451422
  src: `compositions/${id}.html`
451243
451423
  });
451244
- clipStart += duration;
451424
+ clipStart += duration - SCENE_OVERLAP_SECONDS;
451245
451425
  }
451246
451426
  await writeFile12(rootAbs, rootHtml, "utf-8");
451247
451427
  let lintResult;
@@ -464230,6 +464410,176 @@ function parseTtsProviderName(value) {
464230
464410
 
464231
464411
  // ../cli/src/commands/scene.ts
464232
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
464233
464583
  init_scene_html_emit();
464234
464584
  init_scene_lint();
464235
464585
  init_output();
@@ -464255,6 +464605,18 @@ function validatePreset(value) {
464255
464605
  }
464256
464606
  return value;
464257
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
+ }
464258
464620
  var sceneCommand = new Command("scene").description("Author and render per-scene HTML compositions (Hyperframes backend)").addHelpText("after", `
464259
464621
  Examples:
464260
464622
  $ vibe scene init my-video # Scaffold a new project
@@ -464272,21 +464634,28 @@ Examples:
464272
464634
 
464273
464635
  A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
464274
464636
  Run 'vibe schema scene.<command>' for structured parameter info.`);
464275
- 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) => {
464276
464638
  const aspect = validateAspect(options.ratio);
464277
464639
  const duration = validateDuration(options.duration);
464278
464640
  const name = options.name ?? basename18(dir.replace(/\/+$/, ""));
464641
+ const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
464279
464642
  if (options.dryRun) {
464280
464643
  outputResult({
464281
464644
  dryRun: true,
464282
464645
  command: "scene init",
464283
- params: { dir, name, aspect, duration }
464646
+ params: {
464647
+ dir,
464648
+ name,
464649
+ aspect,
464650
+ duration,
464651
+ visualStyle: visualStyle?.name ?? null
464652
+ }
464284
464653
  });
464285
464654
  return;
464286
464655
  }
464287
464656
  const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
464288
464657
  try {
464289
- const result = await scaffoldSceneProject({ dir, name, aspect, duration });
464658
+ const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
464290
464659
  if (isJsonMode()) {
464291
464660
  outputResult({
464292
464661
  success: true,
@@ -464295,6 +464664,7 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
464295
464664
  name,
464296
464665
  aspect,
464297
464666
  duration,
464667
+ visualStyle: visualStyle?.name ?? null,
464298
464668
  created: result.created,
464299
464669
  merged: result.merged,
464300
464670
  skipped: result.skipped
@@ -464311,7 +464681,13 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
464311
464681
  console.log();
464312
464682
  console.log(source_default.bold.cyan("Next steps"));
464313
464683
  console.log(source_default.dim("\u2500".repeat(60)));
464314
- 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")}`);
464315
464691
  console.log(` ${source_default.cyan("vibe scene lint")} ${source_default.dim("# validate HTML")}`);
464316
464692
  console.log(` ${source_default.cyan("vibe scene render")} ${source_default.dim("# render to MP4")}`);
464317
464693
  } catch (error) {
@@ -464320,6 +464696,69 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
464320
464696
  exitWithError(generalError(`Failed to scaffold: ${msg}`));
464321
464697
  }
464322
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
+ });
464323
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) => {
464324
464763
  if (options.style) options.style = validatePreset(options.style);
464325
464764
  if (options.duration !== void 0) options.duration = validateDuration(options.duration);
@@ -464630,7 +465069,20 @@ async function executeSceneAdd(opts) {
464630
465069
  }
464631
465070
  const cfg = await loadVibeProjectConfig(projectDir);
464632
465071
  const fallback2 = cfg?.defaultSceneDuration ?? 5;
464633
- 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));
464634
465086
  opts.onProgress?.("Emitting scene HTML...");
464635
465087
  const sceneHtml = emitSceneHtml({
464636
465088
  id,
@@ -464648,8 +465100,10 @@ async function executeSceneAdd(opts) {
464648
465100
  await mkdir19(dirname25(scenePath), { recursive: true });
464649
465101
  await writeFile25(scenePath, sceneHtml, "utf-8");
464650
465102
  opts.onProgress?.("Updating root composition...");
464651
- const start = nextSceneStart(rootHtmlBefore);
464652
- 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 });
464653
465107
  await writeFile25(rootPath, updated, "utf-8");
464654
465108
  const transcriptAbsPath = transcriptRelPath ? resolve40(projectDir, transcriptRelPath) : void 0;
464655
465109
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/mcp-server",
3
- "version": "0.57.2",
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/cli": "0.57.2",
61
- "@vibeframe/core": "0.57.2"
60
+ "@vibeframe/cli": "0.58.0",
61
+ "@vibeframe/core": "0.58.0"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=20"