@vibeframe/cli 0.57.3 → 0.59.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.
- package/dist/index.js +2055 -395
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -6746,7 +6746,10 @@ var init_output = __esm({
|
|
|
6746
6746
|
"pipeline highlights": { min: 0.05, max: 1, unit: "per analysis" },
|
|
6747
6747
|
"pipeline auto-shorts": { min: 0.1, max: 2, unit: "per batch" },
|
|
6748
6748
|
"pipeline animated-caption": { min: 0.01, max: 0.1, unit: "per video" },
|
|
6749
|
-
"pipeline regenerate-scene": { min: 0.5, max: 5, unit: "per scene" }
|
|
6749
|
+
"pipeline regenerate-scene": { min: 0.5, max: 5, unit: "per scene" },
|
|
6750
|
+
// Scene composition (v0.59+) — per-beat composer call ≈ $0.06 (PR #111
|
|
6751
|
+
// pre-flight). Range covers a 1-beat preview to a 10-beat long-form.
|
|
6752
|
+
"compose scenes with skills": { min: 0.05, max: 1.5, unit: "per pipeline" }
|
|
6750
6753
|
};
|
|
6751
6754
|
}
|
|
6752
6755
|
});
|
|
@@ -14966,7 +14969,7 @@ var init_GeminiProvider = __esm({
|
|
|
14966
14969
|
* Sleep helper
|
|
14967
14970
|
*/
|
|
14968
14971
|
sleep(ms) {
|
|
14969
|
-
return new Promise((
|
|
14972
|
+
return new Promise((resolve54) => setTimeout(resolve54, ms));
|
|
14970
14973
|
}
|
|
14971
14974
|
/**
|
|
14972
14975
|
* Extend a previously generated Veo video
|
|
@@ -19053,7 +19056,7 @@ var init_RunwayProvider = __esm({
|
|
|
19053
19056
|
* Sleep helper
|
|
19054
19057
|
*/
|
|
19055
19058
|
sleep(ms) {
|
|
19056
|
-
return new Promise((
|
|
19059
|
+
return new Promise((resolve54) => setTimeout(resolve54, ms));
|
|
19057
19060
|
}
|
|
19058
19061
|
};
|
|
19059
19062
|
RunwayProvider.API_VERSION = "2024-11-06";
|
|
@@ -19485,7 +19488,7 @@ var init_KlingProvider = __esm({
|
|
|
19485
19488
|
* Sleep helper
|
|
19486
19489
|
*/
|
|
19487
19490
|
sleep(ms) {
|
|
19488
|
-
return new Promise((
|
|
19491
|
+
return new Promise((resolve54) => setTimeout(resolve54, ms));
|
|
19489
19492
|
}
|
|
19490
19493
|
};
|
|
19491
19494
|
klingProvider = new KlingProvider();
|
|
@@ -19794,7 +19797,7 @@ var init_GrokProvider = __esm({
|
|
|
19794
19797
|
}
|
|
19795
19798
|
}
|
|
19796
19799
|
sleep(ms) {
|
|
19797
|
-
return new Promise((
|
|
19800
|
+
return new Promise((resolve54) => setTimeout(resolve54, ms));
|
|
19798
19801
|
}
|
|
19799
19802
|
};
|
|
19800
19803
|
grokProvider = new GrokProvider();
|
|
@@ -19815,11 +19818,11 @@ var require_middleware = __commonJS({
|
|
|
19815
19818
|
"use strict";
|
|
19816
19819
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
19817
19820
|
function adopt(value) {
|
|
19818
|
-
return value instanceof P ? value : new P(function(
|
|
19819
|
-
|
|
19821
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
19822
|
+
resolve54(value);
|
|
19820
19823
|
});
|
|
19821
19824
|
}
|
|
19822
|
-
return new (P || (P = Promise))(function(
|
|
19825
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
19823
19826
|
function fulfilled(value) {
|
|
19824
19827
|
try {
|
|
19825
19828
|
step(generator.next(value));
|
|
@@ -19835,7 +19838,7 @@ var require_middleware = __commonJS({
|
|
|
19835
19838
|
}
|
|
19836
19839
|
}
|
|
19837
19840
|
function step(result) {
|
|
19838
|
-
result.done ?
|
|
19841
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
19839
19842
|
}
|
|
19840
19843
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19841
19844
|
});
|
|
@@ -19920,11 +19923,11 @@ var require_response = __commonJS({
|
|
|
19920
19923
|
"use strict";
|
|
19921
19924
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
19922
19925
|
function adopt(value) {
|
|
19923
|
-
return value instanceof P ? value : new P(function(
|
|
19924
|
-
|
|
19926
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
19927
|
+
resolve54(value);
|
|
19925
19928
|
});
|
|
19926
19929
|
}
|
|
19927
|
-
return new (P || (P = Promise))(function(
|
|
19930
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
19928
19931
|
function fulfilled(value) {
|
|
19929
19932
|
try {
|
|
19930
19933
|
step(generator.next(value));
|
|
@@ -19940,7 +19943,7 @@ var require_response = __commonJS({
|
|
|
19940
19943
|
}
|
|
19941
19944
|
}
|
|
19942
19945
|
function step(result) {
|
|
19943
|
-
result.done ?
|
|
19946
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
19944
19947
|
}
|
|
19945
19948
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19946
19949
|
});
|
|
@@ -20047,11 +20050,11 @@ var require_utils = __commonJS({
|
|
|
20047
20050
|
"use strict";
|
|
20048
20051
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
20049
20052
|
function adopt(value) {
|
|
20050
|
-
return value instanceof P ? value : new P(function(
|
|
20051
|
-
|
|
20053
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
20054
|
+
resolve54(value);
|
|
20052
20055
|
});
|
|
20053
20056
|
}
|
|
20054
|
-
return new (P || (P = Promise))(function(
|
|
20057
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
20055
20058
|
function fulfilled(value) {
|
|
20056
20059
|
try {
|
|
20057
20060
|
step(generator.next(value));
|
|
@@ -20067,7 +20070,7 @@ var require_utils = __commonJS({
|
|
|
20067
20070
|
}
|
|
20068
20071
|
}
|
|
20069
20072
|
function step(result) {
|
|
20070
|
-
result.done ?
|
|
20073
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
20071
20074
|
}
|
|
20072
20075
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20073
20076
|
});
|
|
@@ -20160,7 +20163,7 @@ var require_utils = __commonJS({
|
|
|
20160
20163
|
}
|
|
20161
20164
|
function sleep2(ms) {
|
|
20162
20165
|
return __awaiter(this, void 0, void 0, function* () {
|
|
20163
|
-
return new Promise((
|
|
20166
|
+
return new Promise((resolve54) => setTimeout(resolve54, ms));
|
|
20164
20167
|
});
|
|
20165
20168
|
}
|
|
20166
20169
|
}
|
|
@@ -20172,11 +20175,11 @@ var require_retry = __commonJS({
|
|
|
20172
20175
|
"use strict";
|
|
20173
20176
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
20174
20177
|
function adopt(value) {
|
|
20175
|
-
return value instanceof P ? value : new P(function(
|
|
20176
|
-
|
|
20178
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
20179
|
+
resolve54(value);
|
|
20177
20180
|
});
|
|
20178
20181
|
}
|
|
20179
|
-
return new (P || (P = Promise))(function(
|
|
20182
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
20180
20183
|
function fulfilled(value) {
|
|
20181
20184
|
try {
|
|
20182
20185
|
step(generator.next(value));
|
|
@@ -20192,7 +20195,7 @@ var require_retry = __commonJS({
|
|
|
20192
20195
|
}
|
|
20193
20196
|
}
|
|
20194
20197
|
function step(result) {
|
|
20195
|
-
result.done ?
|
|
20198
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
20196
20199
|
}
|
|
20197
20200
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20198
20201
|
});
|
|
@@ -20399,11 +20402,11 @@ var require_request = __commonJS({
|
|
|
20399
20402
|
"use strict";
|
|
20400
20403
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
20401
20404
|
function adopt(value) {
|
|
20402
|
-
return value instanceof P ? value : new P(function(
|
|
20403
|
-
|
|
20405
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
20406
|
+
resolve54(value);
|
|
20404
20407
|
});
|
|
20405
20408
|
}
|
|
20406
|
-
return new (P || (P = Promise))(function(
|
|
20409
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
20407
20410
|
function fulfilled(value) {
|
|
20408
20411
|
try {
|
|
20409
20412
|
step(generator.next(value));
|
|
@@ -20419,7 +20422,7 @@ var require_request = __commonJS({
|
|
|
20419
20422
|
}
|
|
20420
20423
|
}
|
|
20421
20424
|
function step(result) {
|
|
20422
|
-
result.done ?
|
|
20425
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
20423
20426
|
}
|
|
20424
20427
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20425
20428
|
});
|
|
@@ -20506,11 +20509,11 @@ var require_storage = __commonJS({
|
|
|
20506
20509
|
"use strict";
|
|
20507
20510
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
20508
20511
|
function adopt(value) {
|
|
20509
|
-
return value instanceof P ? value : new P(function(
|
|
20510
|
-
|
|
20512
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
20513
|
+
resolve54(value);
|
|
20511
20514
|
});
|
|
20512
20515
|
}
|
|
20513
|
-
return new (P || (P = Promise))(function(
|
|
20516
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
20514
20517
|
function fulfilled(value) {
|
|
20515
20518
|
try {
|
|
20516
20519
|
step(generator.next(value));
|
|
@@ -20526,7 +20529,7 @@ var require_storage = __commonJS({
|
|
|
20526
20529
|
}
|
|
20527
20530
|
}
|
|
20528
20531
|
function step(result) {
|
|
20529
|
-
result.done ?
|
|
20532
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
20530
20533
|
}
|
|
20531
20534
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20532
20535
|
});
|
|
@@ -20854,11 +20857,11 @@ var require_auth = __commonJS({
|
|
|
20854
20857
|
"use strict";
|
|
20855
20858
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
20856
20859
|
function adopt(value) {
|
|
20857
|
-
return value instanceof P ? value : new P(function(
|
|
20858
|
-
|
|
20860
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
20861
|
+
resolve54(value);
|
|
20859
20862
|
});
|
|
20860
20863
|
}
|
|
20861
|
-
return new (P || (P = Promise))(function(
|
|
20864
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
20862
20865
|
function fulfilled(value) {
|
|
20863
20866
|
try {
|
|
20864
20867
|
step(generator.next(value));
|
|
@@ -20874,7 +20877,7 @@ var require_auth = __commonJS({
|
|
|
20874
20877
|
}
|
|
20875
20878
|
}
|
|
20876
20879
|
function step(result) {
|
|
20877
|
-
result.done ?
|
|
20880
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
20878
20881
|
}
|
|
20879
20882
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20880
20883
|
});
|
|
@@ -20913,11 +20916,11 @@ var require_streaming = __commonJS({
|
|
|
20913
20916
|
"use strict";
|
|
20914
20917
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
20915
20918
|
function adopt(value) {
|
|
20916
|
-
return value instanceof P ? value : new P(function(
|
|
20917
|
-
|
|
20919
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
20920
|
+
resolve54(value);
|
|
20918
20921
|
});
|
|
20919
20922
|
}
|
|
20920
|
-
return new (P || (P = Promise))(function(
|
|
20923
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
20921
20924
|
function fulfilled(value) {
|
|
20922
20925
|
try {
|
|
20923
20926
|
step(generator.next(value));
|
|
@@ -20933,7 +20936,7 @@ var require_streaming = __commonJS({
|
|
|
20933
20936
|
}
|
|
20934
20937
|
}
|
|
20935
20938
|
function step(result) {
|
|
20936
|
-
result.done ?
|
|
20939
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
20937
20940
|
}
|
|
20938
20941
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20939
20942
|
});
|
|
@@ -21166,7 +21169,7 @@ var require_streaming = __commonJS({
|
|
|
21166
21169
|
query: options.queryParams
|
|
21167
21170
|
});
|
|
21168
21171
|
this.options = options;
|
|
21169
|
-
this.donePromise = new Promise((
|
|
21172
|
+
this.donePromise = new Promise((resolve54, reject) => {
|
|
21170
21173
|
if (this.streamClosed) {
|
|
21171
21174
|
reject(new response_1.ApiError({
|
|
21172
21175
|
message: "Streaming connection is already closed.",
|
|
@@ -21177,11 +21180,11 @@ var require_streaming = __commonJS({
|
|
|
21177
21180
|
}
|
|
21178
21181
|
this.signal.addEventListener("abort", () => {
|
|
21179
21182
|
var _a2;
|
|
21180
|
-
|
|
21183
|
+
resolve54((_a2 = this.currentData) !== null && _a2 !== void 0 ? _a2 : {});
|
|
21181
21184
|
});
|
|
21182
21185
|
this.on("done", (data) => {
|
|
21183
21186
|
this.streamClosed = true;
|
|
21184
|
-
|
|
21187
|
+
resolve54(data);
|
|
21185
21188
|
});
|
|
21186
21189
|
this.on("error", (error) => {
|
|
21187
21190
|
this.streamClosed = true;
|
|
@@ -21206,7 +21209,7 @@ var require_streaming = __commonJS({
|
|
|
21206
21209
|
if (data) {
|
|
21207
21210
|
yield yield __await(data);
|
|
21208
21211
|
}
|
|
21209
|
-
yield __await(new Promise((
|
|
21212
|
+
yield __await(new Promise((resolve54) => setTimeout(resolve54, 16)));
|
|
21210
21213
|
}
|
|
21211
21214
|
});
|
|
21212
21215
|
}
|
|
@@ -21251,11 +21254,11 @@ var require_queue = __commonJS({
|
|
|
21251
21254
|
"use strict";
|
|
21252
21255
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
21253
21256
|
function adopt(value) {
|
|
21254
|
-
return value instanceof P ? value : new P(function(
|
|
21255
|
-
|
|
21257
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
21258
|
+
resolve54(value);
|
|
21256
21259
|
});
|
|
21257
21260
|
}
|
|
21258
|
-
return new (P || (P = Promise))(function(
|
|
21261
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
21259
21262
|
function fulfilled(value) {
|
|
21260
21263
|
try {
|
|
21261
21264
|
step(generator.next(value));
|
|
@@ -21271,7 +21274,7 @@ var require_queue = __commonJS({
|
|
|
21271
21274
|
}
|
|
21272
21275
|
}
|
|
21273
21276
|
function step(result) {
|
|
21274
|
-
result.done ?
|
|
21277
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
21275
21278
|
}
|
|
21276
21279
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21277
21280
|
});
|
|
@@ -21406,7 +21409,7 @@ var require_queue = __commonJS({
|
|
|
21406
21409
|
}
|
|
21407
21410
|
return doneStatus;
|
|
21408
21411
|
}
|
|
21409
|
-
return new Promise((
|
|
21412
|
+
return new Promise((resolve54, reject) => {
|
|
21410
21413
|
var _a;
|
|
21411
21414
|
let pollingTimeoutId;
|
|
21412
21415
|
const pollInterval = "pollInterval" in options && typeof options.pollInterval === "number" ? (_a = options.pollInterval) !== null && _a !== void 0 ? _a : DEFAULT_POLL_INTERVAL : DEFAULT_POLL_INTERVAL;
|
|
@@ -21438,7 +21441,7 @@ var require_queue = __commonJS({
|
|
|
21438
21441
|
}
|
|
21439
21442
|
if (requestStatus.status === "COMPLETED") {
|
|
21440
21443
|
clearScheduledTasks();
|
|
21441
|
-
|
|
21444
|
+
resolve54(requestStatus);
|
|
21442
21445
|
return;
|
|
21443
21446
|
}
|
|
21444
21447
|
pollingTimeoutId = setTimeout(poll, pollInterval);
|
|
@@ -23399,11 +23402,11 @@ var require_realtime = __commonJS({
|
|
|
23399
23402
|
"use strict";
|
|
23400
23403
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
23401
23404
|
function adopt(value) {
|
|
23402
|
-
return value instanceof P ? value : new P(function(
|
|
23403
|
-
|
|
23405
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
23406
|
+
resolve54(value);
|
|
23404
23407
|
});
|
|
23405
23408
|
}
|
|
23406
|
-
return new (P || (P = Promise))(function(
|
|
23409
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
23407
23410
|
function fulfilled(value) {
|
|
23408
23411
|
try {
|
|
23409
23412
|
step(generator.next(value));
|
|
@@ -23419,7 +23422,7 @@ var require_realtime = __commonJS({
|
|
|
23419
23422
|
}
|
|
23420
23423
|
}
|
|
23421
23424
|
function step(result) {
|
|
23422
|
-
result.done ?
|
|
23425
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
23423
23426
|
}
|
|
23424
23427
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23425
23428
|
});
|
|
@@ -23744,11 +23747,11 @@ var require_client = __commonJS({
|
|
|
23744
23747
|
"use strict";
|
|
23745
23748
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
23746
23749
|
function adopt(value) {
|
|
23747
|
-
return value instanceof P ? value : new P(function(
|
|
23748
|
-
|
|
23750
|
+
return value instanceof P ? value : new P(function(resolve54) {
|
|
23751
|
+
resolve54(value);
|
|
23749
23752
|
});
|
|
23750
23753
|
}
|
|
23751
|
-
return new (P || (P = Promise))(function(
|
|
23754
|
+
return new (P || (P = Promise))(function(resolve54, reject) {
|
|
23752
23755
|
function fulfilled(value) {
|
|
23753
23756
|
try {
|
|
23754
23757
|
step(generator.next(value));
|
|
@@ -23764,7 +23767,7 @@ var require_client = __commonJS({
|
|
|
23764
23767
|
}
|
|
23765
23768
|
}
|
|
23766
23769
|
function step(result) {
|
|
23767
|
-
result.done ?
|
|
23770
|
+
result.done ? resolve54(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
23768
23771
|
}
|
|
23769
23772
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23770
23773
|
});
|
|
@@ -24329,7 +24332,7 @@ var init_ReplicateProvider = __esm({
|
|
|
24329
24332
|
* Sleep helper
|
|
24330
24333
|
*/
|
|
24331
24334
|
sleep(ms) {
|
|
24332
|
-
return new Promise((
|
|
24335
|
+
return new Promise((resolve54) => setTimeout(resolve54, ms));
|
|
24333
24336
|
}
|
|
24334
24337
|
/**
|
|
24335
24338
|
* Generate music from text prompt using MusicGen
|
|
@@ -25315,7 +25318,7 @@ async function prompt(question, hidden = false) {
|
|
|
25315
25318
|
input: process.stdin,
|
|
25316
25319
|
output: process.stdout
|
|
25317
25320
|
});
|
|
25318
|
-
return new Promise((
|
|
25321
|
+
return new Promise((resolve54) => {
|
|
25319
25322
|
if (hidden && process.stdin.isTTY) {
|
|
25320
25323
|
process.stdout.write(question);
|
|
25321
25324
|
let input = "";
|
|
@@ -25329,7 +25332,7 @@ async function prompt(question, hidden = false) {
|
|
|
25329
25332
|
process.stdin.removeListener("data", onData);
|
|
25330
25333
|
process.stdout.write("\n");
|
|
25331
25334
|
rl.close();
|
|
25332
|
-
|
|
25335
|
+
resolve54(input);
|
|
25333
25336
|
} else if (char === "") {
|
|
25334
25337
|
process.exit(1);
|
|
25335
25338
|
} else if (char === "\x7F" || char === "\b") {
|
|
@@ -25344,7 +25347,7 @@ async function prompt(question, hidden = false) {
|
|
|
25344
25347
|
} else {
|
|
25345
25348
|
rl.question(question, (answer) => {
|
|
25346
25349
|
rl.close();
|
|
25347
|
-
|
|
25350
|
+
resolve54(answer);
|
|
25348
25351
|
});
|
|
25349
25352
|
}
|
|
25350
25353
|
});
|
|
@@ -25611,6 +25614,70 @@ function buildEmptyRootHtml(opts) {
|
|
|
25611
25614
|
</html>
|
|
25612
25615
|
`;
|
|
25613
25616
|
}
|
|
25617
|
+
function buildDesignMd(opts) {
|
|
25618
|
+
const { name, style } = opts;
|
|
25619
|
+
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.`;
|
|
25620
|
+
const moodLine = style ? `**Mood:** ${style.mood} \xB7 **Best for:** ${style.bestFor}` : `**Mood:** _(one line \u2014 what should the viewer FEEL?)_`;
|
|
25621
|
+
const palette = style ? `${style.palette.map((c) => `- \`${c}\``).join("\n")}
|
|
25622
|
+
|
|
25623
|
+
${style.paletteNotes}` : `- _hex_ \u2014 primary
|
|
25624
|
+
- _hex_ \u2014 accent
|
|
25625
|
+
|
|
25626
|
+
_2\u20133 colours max. Declare explicit hex values; never name colours abstractly._`;
|
|
25627
|
+
const typography = style ? style.typography : `_One family, two weights. State the role of each (headline / label / body)._`;
|
|
25628
|
+
const composition = style ? style.composition : `_Grid? Centered? Layered? How does negative space behave?_`;
|
|
25629
|
+
const motion = style ? `${style.motion}
|
|
25630
|
+
|
|
25631
|
+
**GSAP signature:** ${style.gsapSignature}` : `_How fast? Snappy or fluid? Overshoot or precision?_
|
|
25632
|
+
|
|
25633
|
+
**GSAP signature:** _e.g. \`expo.out\`, \`sine.inOut\`, \`back.out(1.8)\`_`;
|
|
25634
|
+
const transition = style ? style.transition : `_Which Hyperframes shader matches the energy? (Cinematic Zoom, Cross-Warp Morph, Glitch, Domain Warp, \u2026)_`;
|
|
25635
|
+
const avoid = style ? style.avoid.map((a) => `- ${a}`).join("\n") : `- _anti-pattern 1_
|
|
25636
|
+
- _anti-pattern 2_
|
|
25637
|
+
- _anti-pattern 3_`;
|
|
25638
|
+
return `# ${name} \u2014 Design
|
|
25639
|
+
|
|
25640
|
+
> **Hard-gate.** This file defines the visual identity of every scene.
|
|
25641
|
+
> Author it before generating any HTML, backdrop image, or motion.
|
|
25642
|
+
> The Hyperframes \`hyperframes\` skill enforces this: scenes that
|
|
25643
|
+
> contradict DESIGN.md are rejected.
|
|
25644
|
+
|
|
25645
|
+
${intro}
|
|
25646
|
+
|
|
25647
|
+
## Style
|
|
25648
|
+
|
|
25649
|
+
${moodLine}
|
|
25650
|
+
|
|
25651
|
+
## Palette
|
|
25652
|
+
|
|
25653
|
+
${palette}
|
|
25654
|
+
|
|
25655
|
+
## Typography
|
|
25656
|
+
|
|
25657
|
+
${typography}
|
|
25658
|
+
|
|
25659
|
+
## Composition
|
|
25660
|
+
|
|
25661
|
+
${composition}
|
|
25662
|
+
|
|
25663
|
+
## Motion
|
|
25664
|
+
|
|
25665
|
+
${motion}
|
|
25666
|
+
|
|
25667
|
+
## Transition
|
|
25668
|
+
|
|
25669
|
+
${transition}
|
|
25670
|
+
|
|
25671
|
+
## What NOT to do
|
|
25672
|
+
|
|
25673
|
+
${avoid}
|
|
25674
|
+
|
|
25675
|
+
---
|
|
25676
|
+
|
|
25677
|
+
_Browse other named styles: \`vibe scene styles\`_
|
|
25678
|
+
${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>"\`._`}
|
|
25679
|
+
`;
|
|
25680
|
+
}
|
|
25614
25681
|
function buildProjectClaudeMd(name) {
|
|
25615
25682
|
return `# ${name} \u2014 Scene Authoring Project
|
|
25616
25683
|
|
|
@@ -25618,6 +25685,16 @@ This project is **bilingual**: it works with both VibeFrame (\`vibe\`) and
|
|
|
25618
25685
|
HeyGen Hyperframes (\`hyperframes\`). You can run either CLI inside this
|
|
25619
25686
|
directory.
|
|
25620
25687
|
|
|
25688
|
+
## Visual identity hard-gate
|
|
25689
|
+
|
|
25690
|
+
**Author \`DESIGN.md\` before any scene HTML.** It defines palette,
|
|
25691
|
+
typography, motion, and transition rules. Both the agent-driven path and
|
|
25692
|
+
the fallback emit reference it; scenes that contradict DESIGN.md are
|
|
25693
|
+
rejected by the Hyperframes \`hyperframes\` skill.
|
|
25694
|
+
|
|
25695
|
+
Browse named styles: \`vibe scene styles\`. Re-seed from one with
|
|
25696
|
+
\`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
|
|
25697
|
+
|
|
25621
25698
|
## Skills \u2014 USE THESE FIRST
|
|
25622
25699
|
|
|
25623
25700
|
**Always invoke the relevant skill before authoring scenes.** Skills encode
|
|
@@ -25626,14 +25703,23 @@ semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
|
|
|
25626
25703
|
|
|
25627
25704
|
| Skill | Command | When to use |
|
|
25628
25705
|
| ----------------- | ---------------- | ------------------------------------------------------------------------------------- |
|
|
25629
|
-
| **
|
|
25630
|
-
| **
|
|
25706
|
+
| **hyperframes** | \`/hyperframes\` | Cinematic-quality composition \u2014 DESIGN.md hard-gate, named styles, motion principles |
|
|
25707
|
+
| **vibe-scene** | \`/vibe-scene\` | VibeFrame's authoring loop, AI assets, lint feedback, pipeline integration |
|
|
25631
25708
|
| **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
|
|
25632
25709
|
|
|
25633
|
-
|
|
25710
|
+
Install the Hyperframes skills once per machine:
|
|
25711
|
+
|
|
25712
|
+
\`\`\`bash
|
|
25713
|
+
npx skills add heygen-com/hyperframes
|
|
25714
|
+
\`\`\`
|
|
25715
|
+
|
|
25716
|
+
Restart your agent session (or reload the skill list) after installing.
|
|
25717
|
+
If skills aren't available, follow the **Key Rules** below \u2014 they cover
|
|
25718
|
+
the framework-level minimum, not the cinematic craft layer.
|
|
25634
25719
|
|
|
25635
25720
|
## Project structure
|
|
25636
25721
|
|
|
25722
|
+
- \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
|
|
25637
25723
|
- \`index.html\` \u2014 root composition (timeline)
|
|
25638
25724
|
- \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
|
|
25639
25725
|
- \`assets/\` \u2014 shared media (narration audio, images, video)
|
|
@@ -25751,6 +25837,17 @@ async function scaffoldSceneProject(opts) {
|
|
|
25751
25837
|
await writeFile4(claudePath, buildProjectClaudeMd(name), "utf-8");
|
|
25752
25838
|
created.push(claudePath);
|
|
25753
25839
|
}
|
|
25840
|
+
const designPath = resolve5(dir, "DESIGN.md");
|
|
25841
|
+
if (await pathExists(designPath)) {
|
|
25842
|
+
skipped.push(designPath);
|
|
25843
|
+
} else {
|
|
25844
|
+
await writeFile4(
|
|
25845
|
+
designPath,
|
|
25846
|
+
buildDesignMd({ name, style: opts.visualStyle }),
|
|
25847
|
+
"utf-8"
|
|
25848
|
+
);
|
|
25849
|
+
created.push(designPath);
|
|
25850
|
+
}
|
|
25754
25851
|
const gitignorePath = resolve5(dir, ".gitignore");
|
|
25755
25852
|
if (await pathExists(gitignorePath)) {
|
|
25756
25853
|
skipped.push(gitignorePath);
|
|
@@ -25774,6 +25871,180 @@ var init_scene_project = __esm({
|
|
|
25774
25871
|
}
|
|
25775
25872
|
});
|
|
25776
25873
|
|
|
25874
|
+
// src/commands/_shared/visual-styles.ts
|
|
25875
|
+
function listVisualStyles() {
|
|
25876
|
+
return STYLES;
|
|
25877
|
+
}
|
|
25878
|
+
function getVisualStyle(query) {
|
|
25879
|
+
const q = query.trim().toLowerCase();
|
|
25880
|
+
return STYLES.find(
|
|
25881
|
+
(s) => s.name.toLowerCase() === q || s.slug === q
|
|
25882
|
+
);
|
|
25883
|
+
}
|
|
25884
|
+
function visualStyleNames() {
|
|
25885
|
+
return STYLES.map((s) => `"${s.name}"`).join(", ");
|
|
25886
|
+
}
|
|
25887
|
+
var STYLES;
|
|
25888
|
+
var init_visual_styles = __esm({
|
|
25889
|
+
"src/commands/_shared/visual-styles.ts"() {
|
|
25890
|
+
"use strict";
|
|
25891
|
+
STYLES = [
|
|
25892
|
+
{
|
|
25893
|
+
name: "Swiss Pulse",
|
|
25894
|
+
slug: "swiss-pulse",
|
|
25895
|
+
designer: "Josef M\xFCller-Brockmann",
|
|
25896
|
+
mood: "Clinical, precise",
|
|
25897
|
+
bestFor: "SaaS dashboards, developer tools, APIs, metrics",
|
|
25898
|
+
palette: ["#1a1a1a", "#ffffff", "#0066FF"],
|
|
25899
|
+
paletteNotes: "Black, white, ONE accent \u2014 electric blue (#0066FF) or amber (#FFB300). Never both accents at once.",
|
|
25900
|
+
typography: "Helvetica or Inter Bold for headlines, Regular for labels. Numbers dominate at 80\u2013120px.",
|
|
25901
|
+
composition: "Grid-locked. Every element snaps to an invisible 12-column grid. Hard cuts only \u2014 no decorative transitions.",
|
|
25902
|
+
motion: "Animated counters count up from 0. Entries are fast and snap into place. Nothing floats.",
|
|
25903
|
+
transition: "Cinematic Zoom or SDF Iris (precise, geometric)",
|
|
25904
|
+
gsapSignature: "expo.out, power4.out \u2014 fast arrivals, hard stops",
|
|
25905
|
+
avoid: [
|
|
25906
|
+
"Decorative transitions (fades, dissolves) \u2014 use hard cuts",
|
|
25907
|
+
"Two accent colours competing in one frame",
|
|
25908
|
+
"Off-grid placement or floating elements"
|
|
25909
|
+
]
|
|
25910
|
+
},
|
|
25911
|
+
{
|
|
25912
|
+
name: "Velvet Standard",
|
|
25913
|
+
slug: "velvet-standard",
|
|
25914
|
+
designer: "Massimo Vignelli",
|
|
25915
|
+
mood: "Premium, timeless",
|
|
25916
|
+
bestFor: "Luxury products, enterprise software, keynotes, investor decks",
|
|
25917
|
+
palette: ["#000000", "#ffffff", "#1a237e"],
|
|
25918
|
+
paletteNotes: "Black, white, ONE rich accent \u2014 deep navy (#1a237e) or gold (#c9a84c).",
|
|
25919
|
+
typography: "Thin sans-serif, ALL CAPS, wide letter-spacing (0.15em+). Sequential reveals only.",
|
|
25920
|
+
composition: "Generous negative space. Symmetrical, centered, architectural precision. Nothing busy.",
|
|
25921
|
+
motion: "Slow, deliberate. Sequential reveals with long holds. No frantic motion.",
|
|
25922
|
+
transition: "Cross-Warp Morph (elegant, organic flow between scenes)",
|
|
25923
|
+
gsapSignature: "sine.inOut, power1 \u2014 nothing snaps, everything glides",
|
|
25924
|
+
avoid: [
|
|
25925
|
+
"Tight letter-spacing \u2014 kills the premium register",
|
|
25926
|
+
"Bouncy or elastic easings \u2014 too playful",
|
|
25927
|
+
"Multiple elements arriving at once \u2014 break sequence"
|
|
25928
|
+
]
|
|
25929
|
+
},
|
|
25930
|
+
{
|
|
25931
|
+
name: "Deconstructed",
|
|
25932
|
+
slug: "deconstructed",
|
|
25933
|
+
designer: "Neville Brody",
|
|
25934
|
+
mood: "Industrial, raw",
|
|
25935
|
+
bestFor: "Tech news, developer launches, security products, punk-energy reveals",
|
|
25936
|
+
palette: ["#1a1a1a", "#D4501E", "#f0f0f0"],
|
|
25937
|
+
paletteNotes: "Dark grey (#1a1a1a), rust orange (#D4501E), raw white (#f0f0f0).",
|
|
25938
|
+
typography: "Type at angles, overlapping edges, escaping frames. Bold industrial weight.",
|
|
25939
|
+
composition: "Gritty textures \u2014 scan-line effects, glitch artifacts baked into the design.",
|
|
25940
|
+
motion: "Text SLAMS and SHATTERS. Letters scramble then snap to final position.",
|
|
25941
|
+
transition: "Glitch shader or Whip Pan (breaks the rules, feels aggressive)",
|
|
25942
|
+
gsapSignature: "back.out(2.5), steps(8), elastic.out(1.2, 0.4) \u2014 intentional irregularity",
|
|
25943
|
+
avoid: [
|
|
25944
|
+
"Polished, centered compositions \u2014 must feel raw",
|
|
25945
|
+
"Smooth fades \u2014 use glitch / scramble entries",
|
|
25946
|
+
"Soft easings \u2014 every motion lands hard"
|
|
25947
|
+
]
|
|
25948
|
+
},
|
|
25949
|
+
{
|
|
25950
|
+
name: "Maximalist Type",
|
|
25951
|
+
slug: "maximalist-type",
|
|
25952
|
+
designer: "Paula Scher",
|
|
25953
|
+
mood: "Loud, kinetic",
|
|
25954
|
+
bestFor: "Big product launches, milestone announcements, high-energy hype videos",
|
|
25955
|
+
palette: ["#E63946", "#FFD60A", "#000000", "#ffffff"],
|
|
25956
|
+
paletteNotes: "Bold saturated: red (#E63946), yellow (#FFD60A), black, white \u2014 maximum contrast.",
|
|
25957
|
+
typography: "Text IS the visual. Overlapping type layers at different scales and angles, filling 50\u201380% of frame.",
|
|
25958
|
+
composition: "Text layered OVER footage \u2014 never empty backgrounds. 2\u20133 second rapid-fire scenes.",
|
|
25959
|
+
motion: "Everything is kinetic \u2014 slamming, sliding, scaling. No static moments.",
|
|
25960
|
+
transition: "Ridged Burn (explosive, dramatic, impossible to ignore)",
|
|
25961
|
+
gsapSignature: "expo.out, back.out(1.8) \u2014 fast arrivals, hard stops",
|
|
25962
|
+
avoid: [
|
|
25963
|
+
"Static moments \u2014 every frame must move",
|
|
25964
|
+
"Empty backgrounds \u2014 text and footage must layer",
|
|
25965
|
+
"Single typeface at a single size \u2014 must be layered"
|
|
25966
|
+
]
|
|
25967
|
+
},
|
|
25968
|
+
{
|
|
25969
|
+
name: "Data Drift",
|
|
25970
|
+
slug: "data-drift",
|
|
25971
|
+
designer: "Refik Anadol",
|
|
25972
|
+
mood: "Futuristic, immersive",
|
|
25973
|
+
bestFor: "AI products, ML platforms, data companies, speculative tech",
|
|
25974
|
+
palette: ["#0a0a0a", "#7c3aed", "#06b6d4"],
|
|
25975
|
+
paletteNotes: "Iridescent \u2014 deep black (#0a0a0a), electric purple (#7c3aed), cyan (#06b6d4).",
|
|
25976
|
+
typography: "Thin futuristic sans-serif \u2014 floating, weightless, minimal text.",
|
|
25977
|
+
composition: "Fluid morphing compositions. Extreme scale shifts (micro \u2192 macro). Particles coalesce into numbers.",
|
|
25978
|
+
motion: "Light traces data paths through the frame. Smooth, continuous, organic \u2014 nothing hard.",
|
|
25979
|
+
transition: "Gravitational Lens or Domain Warp (otherworldly distortion)",
|
|
25980
|
+
gsapSignature: "sine.inOut, power2.out \u2014 smooth, continuous, organic",
|
|
25981
|
+
avoid: [
|
|
25982
|
+
"Hard cuts \u2014 break the immersion",
|
|
25983
|
+
"Heavy/bold typography \u2014 too grounded",
|
|
25984
|
+
"Static compositions \u2014 must feel like flow"
|
|
25985
|
+
]
|
|
25986
|
+
},
|
|
25987
|
+
{
|
|
25988
|
+
name: "Soft Signal",
|
|
25989
|
+
slug: "soft-signal",
|
|
25990
|
+
designer: "Stefan Sagmeister",
|
|
25991
|
+
mood: "Intimate, warm",
|
|
25992
|
+
bestFor: "Wellness brands, personal stories, lifestyle products, human-centered apps",
|
|
25993
|
+
palette: ["#F5A623", "#FFF8EC", "#C4A3A3", "#8FAF8C"],
|
|
25994
|
+
paletteNotes: "Warm amber (#F5A623), cream (#FFF8EC), dusty rose (#C4A3A3), sage green (#8FAF8C).",
|
|
25995
|
+
typography: "Handwritten-style or humanist serif fonts. Personal, lowercase, delicate.",
|
|
25996
|
+
composition: "Close-up framing feel \u2014 single element fills the frame. Nothing feels corporate.",
|
|
25997
|
+
motion: "Slow drifts and floats, never snaps. Soft organic motion throughout.",
|
|
25998
|
+
transition: "Thermal Distortion (warm, flowing, like heat shimmer)",
|
|
25999
|
+
gsapSignature: "sine.inOut, power1.inOut \u2014 everything breathes",
|
|
26000
|
+
avoid: [
|
|
26001
|
+
"Sharp geometric layouts \u2014 break the warmth",
|
|
26002
|
+
"Hard easings or snaps \u2014 too clinical",
|
|
26003
|
+
"Cool tones (blue/green-blue) without warm balance"
|
|
26004
|
+
]
|
|
26005
|
+
},
|
|
26006
|
+
{
|
|
26007
|
+
name: "Folk Frequency",
|
|
26008
|
+
slug: "folk-frequency",
|
|
26009
|
+
designer: "Eduardo Terrazas",
|
|
26010
|
+
mood: "Cultural, vivid",
|
|
26011
|
+
bestFor: "Consumer apps, food platforms, community products, festive launches",
|
|
26012
|
+
palette: ["#FF1493", "#0047AB", "#FFE000", "#009B77"],
|
|
26013
|
+
paletteNotes: "Vivid folk: hot pink (#FF1493), cobalt blue (#0047AB), sun yellow (#FFE000), emerald (#009B77).",
|
|
26014
|
+
typography: "Bold warm rounded type. Every frame feels handcrafted.",
|
|
26015
|
+
composition: "Pattern and repetition \u2014 folk art rhythm and density. Layered compositions with rich visual texture.",
|
|
26016
|
+
motion: "Colorful motion \u2014 elements bounce, pop, and spin into place with joy.",
|
|
26017
|
+
transition: "Swirl Vortex or Ripple Waves (hypnotic, celebratory)",
|
|
26018
|
+
gsapSignature: "back.out(1.6), elastic.out(1, 0.5) \u2014 overshoots feel intentional",
|
|
26019
|
+
avoid: [
|
|
26020
|
+
"Muted or monochrome palettes \u2014 kill the celebration",
|
|
26021
|
+
"Pure flat / minimal compositions \u2014 must feel layered",
|
|
26022
|
+
"Linear easings \u2014 motion should feel joyful"
|
|
26023
|
+
]
|
|
26024
|
+
},
|
|
26025
|
+
{
|
|
26026
|
+
name: "Shadow Cut",
|
|
26027
|
+
slug: "shadow-cut",
|
|
26028
|
+
designer: "Hans Hillmann",
|
|
26029
|
+
mood: "Dark, cinematic",
|
|
26030
|
+
bestFor: "Security products, dramatic reveals, investigative content, intense launches",
|
|
26031
|
+
palette: ["#0a0a0a", "#3a3a3a", "#ffffff", "#C1121F"],
|
|
26032
|
+
paletteNotes: "Near-monochrome \u2014 deep blacks (#0a0a0a), cold greys (#3a3a3a), stark white + ONE accent (blood red #C1121F or toxic green #39FF14).",
|
|
26033
|
+
typography: "Sharp angular text like film noir title cards. Heavy contrast, no softness.",
|
|
26034
|
+
composition: "Heavy shadow \u2014 elements emerge from darkness. The reveal IS the narrative.",
|
|
26035
|
+
motion: "Slow creeping push-ins, dramatic scale reveals. Silence before the hit matters.",
|
|
26036
|
+
transition: "Domain Warp (dissolves reality before revealing the next scene)",
|
|
26037
|
+
gsapSignature: "power4.in for exits, power3.out for dramatic reveals \u2014 pause before the hit",
|
|
26038
|
+
avoid: [
|
|
26039
|
+
"Bright or saturated palettes \u2014 kill the cinematic mood",
|
|
26040
|
+
"Bouncy easings \u2014 break the tension",
|
|
26041
|
+
"Quick cuts \u2014 let the reveal breathe"
|
|
26042
|
+
]
|
|
26043
|
+
}
|
|
26044
|
+
];
|
|
26045
|
+
}
|
|
26046
|
+
});
|
|
26047
|
+
|
|
25777
26048
|
// src/commands/_shared/scene-html-emit.ts
|
|
25778
26049
|
function esc(text) {
|
|
25779
26050
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -25799,6 +26070,27 @@ function buildTranscriptTweens(transcript, targetSelector) {
|
|
|
25799
26070
|
return `tl.fromTo('${sel}', { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.18, ease: 'power2.out' }, ${start});`;
|
|
25800
26071
|
}).join("\n ");
|
|
25801
26072
|
}
|
|
26073
|
+
function crossfadeTweens(scope, dur) {
|
|
26074
|
+
return `tl.from('${scope}', { opacity: 0, duration: ${SCENE_OVERLAP_SECONDS}, ease: 'power2.out' }, 0);
|
|
26075
|
+
tl.to('${scope}', { opacity: 0, duration: ${SCENE_OVERLAP_SECONDS}, ease: 'power2.in' }, ${(dur - SCENE_OVERLAP_SECONDS).toFixed(2)});`;
|
|
26076
|
+
}
|
|
26077
|
+
function fitFontSize(text, baseMaxPx, minPx, comfortableChars) {
|
|
26078
|
+
const charCount = text.length;
|
|
26079
|
+
if (charCount <= comfortableChars) return baseMaxPx;
|
|
26080
|
+
const ratio = comfortableChars / charCount;
|
|
26081
|
+
return Math.max(minPx, Math.round(baseMaxPx * ratio));
|
|
26082
|
+
}
|
|
26083
|
+
function fitKineticFontSize(words) {
|
|
26084
|
+
const totalChars = words.join(" ").length;
|
|
26085
|
+
if (totalChars <= 18) return 180;
|
|
26086
|
+
if (totalChars <= 26) return 140;
|
|
26087
|
+
if (totalChars <= 38) return 110;
|
|
26088
|
+
if (totalChars <= 54) return 90;
|
|
26089
|
+
return 72;
|
|
26090
|
+
}
|
|
26091
|
+
function kenBurnsTween(scope, dur, endScale = 1.08) {
|
|
26092
|
+
return `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: ${endScale}, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);`;
|
|
26093
|
+
}
|
|
25802
26094
|
function buildPreset(input) {
|
|
25803
26095
|
const id = input.id;
|
|
25804
26096
|
const scope = `[data-composition-id="${id}"]`;
|
|
@@ -25835,7 +26127,8 @@ function buildPreset(input) {
|
|
|
25835
26127
|
const captionInner = useWordSync ? renderTranscriptSpans(transcript) : esc(captionText);
|
|
25836
26128
|
const wordCss = useWordSync ? `
|
|
25837
26129
|
${scope} .caption .word { display: inline-block; opacity: 0; }` : "";
|
|
25838
|
-
const
|
|
26130
|
+
const captionFontPx = fitFontSize(captionText, 56, 28, 60);
|
|
26131
|
+
const captionTween = useWordSync ? buildTranscriptTweens(transcript, `${scope} .caption .word`) : `tl.from('${scope} .caption', { opacity: 0, y: 28, duration: 0.45, ease: 'power3.out' }, 0.05);`;
|
|
25839
26132
|
return {
|
|
25840
26133
|
css: `${scope} {
|
|
25841
26134
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25843,21 +26136,26 @@ function buildPreset(input) {
|
|
|
25843
26136
|
color: #fff; overflow: hidden; background: #000;
|
|
25844
26137
|
}
|
|
25845
26138
|
${backdrop}
|
|
26139
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25846
26140
|
${scope} .caption {
|
|
25847
26141
|
position: absolute;
|
|
25848
26142
|
left: 8%; right: 8%; bottom: 12%;
|
|
25849
26143
|
text-align: center;
|
|
25850
|
-
font-size:
|
|
26144
|
+
font-size: ${captionFontPx}px;
|
|
25851
26145
|
font-weight: 700;
|
|
25852
26146
|
line-height: 1.2;
|
|
26147
|
+
overflow-wrap: break-word;
|
|
25853
26148
|
text-shadow: 0 4px 20px rgba(0,0,0,0.65);
|
|
25854
26149
|
}${wordCss}`,
|
|
25855
26150
|
body: `${backdropMarkup}
|
|
25856
26151
|
<div class="caption" id="caption">${captionInner}</div>`,
|
|
25857
|
-
timeline
|
|
26152
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26153
|
+
${kenBurnsTween(scope, dur)}
|
|
26154
|
+
${captionTween}`
|
|
25858
26155
|
};
|
|
25859
26156
|
}
|
|
25860
26157
|
case "announcement": {
|
|
26158
|
+
const fontSize = fitFontSize(headline, 160, 72, 22);
|
|
25861
26159
|
return {
|
|
25862
26160
|
css: `${scope} {
|
|
25863
26161
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25865,6 +26163,7 @@ function buildPreset(input) {
|
|
|
25865
26163
|
color: #fff; overflow: hidden; background: #000;
|
|
25866
26164
|
}
|
|
25867
26165
|
${backdrop}
|
|
26166
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25868
26167
|
${scope} .announce {
|
|
25869
26168
|
position: absolute; inset: 0;
|
|
25870
26169
|
display: flex; align-items: center; justify-content: center;
|
|
@@ -25873,10 +26172,11 @@ function buildPreset(input) {
|
|
|
25873
26172
|
}
|
|
25874
26173
|
${scope} .announce h1 {
|
|
25875
26174
|
margin: 0;
|
|
25876
|
-
font-size:
|
|
26175
|
+
font-size: ${fontSize}px;
|
|
25877
26176
|
font-weight: 900;
|
|
25878
26177
|
letter-spacing: -4px;
|
|
25879
|
-
line-height: 1;
|
|
26178
|
+
line-height: 1.05;
|
|
26179
|
+
overflow-wrap: break-word;
|
|
25880
26180
|
background: linear-gradient(90deg, #8e2de2, #00c9ff);
|
|
25881
26181
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
25882
26182
|
text-shadow: 0 8px 40px rgba(142,45,226,0.35);
|
|
@@ -25886,8 +26186,11 @@ function buildPreset(input) {
|
|
|
25886
26186
|
// Faster, snappier entrance and no tail fade-out — see the
|
|
25887
26187
|
// matching note in the `simple` case above. The headline stays
|
|
25888
26188
|
// on screen until the producer cuts to the next clip; the next
|
|
25889
|
-
// scene's own fade-in handles the visual transition.
|
|
25890
|
-
|
|
26189
|
+
// scene's own fade-in handles the visual transition. Backdrop
|
|
26190
|
+
// Ken-Burns fills what used to be 80% dead static frame.
|
|
26191
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26192
|
+
${kenBurnsTween(scope, dur)}
|
|
26193
|
+
tl.from('${scope} #headline', { opacity: 0, scale: 0.92, duration: 0.55, ease: 'power3.out' }, 0.05);`
|
|
25891
26194
|
};
|
|
25892
26195
|
}
|
|
25893
26196
|
case "explainer": {
|
|
@@ -25899,6 +26202,7 @@ function buildPreset(input) {
|
|
|
25899
26202
|
const wordCss = useWordSync ? `
|
|
25900
26203
|
${scope} #subtitle .word { display: inline-block; opacity: 0; }` : "";
|
|
25901
26204
|
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);` : "";
|
|
26205
|
+
const titleFontPx = fitFontSize(headline, 110, 56, 30);
|
|
25902
26206
|
return {
|
|
25903
26207
|
css: `${scope} {
|
|
25904
26208
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25906,6 +26210,7 @@ function buildPreset(input) {
|
|
|
25906
26210
|
color: #fff; overflow: hidden; background: #000;
|
|
25907
26211
|
}
|
|
25908
26212
|
${backdrop}
|
|
26213
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25909
26214
|
${scope} .stage {
|
|
25910
26215
|
position: absolute; inset: 0;
|
|
25911
26216
|
display: flex; flex-direction: column; justify-content: center;
|
|
@@ -25916,11 +26221,13 @@ function buildPreset(input) {
|
|
|
25916
26221
|
color: #00c9ff; font-weight: 600;
|
|
25917
26222
|
}
|
|
25918
26223
|
${scope} .title {
|
|
25919
|
-
font-size:
|
|
26224
|
+
font-size: ${titleFontPx}px; font-weight: 800; letter-spacing: -2px;
|
|
25920
26225
|
line-height: 1.05; margin: 0;
|
|
26226
|
+
overflow-wrap: break-word;
|
|
25921
26227
|
}
|
|
25922
26228
|
${scope} .subtitle {
|
|
25923
26229
|
font-size: 38px; font-weight: 300; color: #c0c0d0; max-width: 80%;
|
|
26230
|
+
overflow-wrap: break-word;
|
|
25924
26231
|
}${wordCss}`,
|
|
25925
26232
|
body: `${backdropMarkup}
|
|
25926
26233
|
<div class="stage">
|
|
@@ -25928,7 +26235,9 @@ function buildPreset(input) {
|
|
|
25928
26235
|
<h1 class="title" id="title">${esc(headline)}</h1>${sub ? `
|
|
25929
26236
|
<div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
|
|
25930
26237
|
</div>`,
|
|
25931
|
-
timeline:
|
|
26238
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26239
|
+
${kenBurnsTween(scope, dur)}
|
|
26240
|
+
tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
|
|
25932
26241
|
tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
|
|
25933
26242
|
${subtitleTween}`
|
|
25934
26243
|
};
|
|
@@ -25946,6 +26255,7 @@ function buildPreset(input) {
|
|
|
25946
26255
|
const start = (0.05 + i * stagger).toFixed(2);
|
|
25947
26256
|
return `tl.from('${scope} #w-${i}', { opacity: 0, y: 80, scale: 0.8, duration: 0.45, ease: 'back.out(1.8)' }, ${start});`;
|
|
25948
26257
|
}).join("\n ");
|
|
26258
|
+
const kineticFontPx = fitKineticFontSize(words);
|
|
25949
26259
|
return {
|
|
25950
26260
|
css: `${scope} {
|
|
25951
26261
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25953,21 +26263,27 @@ function buildPreset(input) {
|
|
|
25953
26263
|
color: #fff; overflow: hidden; background: #000;
|
|
25954
26264
|
}
|
|
25955
26265
|
${backdrop}
|
|
26266
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25956
26267
|
${scope} .kinetic {
|
|
25957
26268
|
position: absolute; inset: 0;
|
|
25958
26269
|
display: flex; align-items: center; justify-content: center;
|
|
26270
|
+
flex-wrap: wrap; gap: 8px 16px;
|
|
25959
26271
|
text-align: center; padding: 0 6%;
|
|
25960
|
-
font-size:
|
|
26272
|
+
font-size: ${kineticFontPx}px; font-weight: 900; letter-spacing: -6px;
|
|
25961
26273
|
line-height: 1; text-shadow: 0 6px 30px rgba(0,0,0,0.6);
|
|
26274
|
+
overflow-wrap: break-word;
|
|
25962
26275
|
}
|
|
25963
26276
|
${scope} .kinetic .word { display: inline-block; margin: 0 12px; }`,
|
|
25964
26277
|
body: `${backdropMarkup}
|
|
25965
26278
|
<div class="kinetic">${wordSpans}</div>`,
|
|
25966
|
-
timeline:
|
|
26279
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26280
|
+
${kenBurnsTween(scope, dur)}
|
|
26281
|
+
${tweens}`
|
|
25967
26282
|
};
|
|
25968
26283
|
}
|
|
25969
26284
|
case "product-shot": {
|
|
25970
26285
|
const label = kicker || humanise(id);
|
|
26286
|
+
const headlineFontPx = fitFontSize(headline, 72, 40, 50);
|
|
25971
26287
|
return {
|
|
25972
26288
|
css: `${scope} {
|
|
25973
26289
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25985,19 +26301,22 @@ function buildPreset(input) {
|
|
|
25985
26301
|
}
|
|
25986
26302
|
${scope} .product-headline {
|
|
25987
26303
|
position: absolute; left: 8%; right: 8%; bottom: 14%;
|
|
25988
|
-
font-size:
|
|
25989
|
-
line-height: 1.1;
|
|
26304
|
+
font-size: ${headlineFontPx}px; font-weight: 800; letter-spacing: -1px;
|
|
26305
|
+
line-height: 1.1; overflow-wrap: break-word;
|
|
26306
|
+
text-shadow: 0 4px 20px rgba(0,0,0,0.7);
|
|
25990
26307
|
}${subhead ? `
|
|
25991
26308
|
${scope} .product-sub {
|
|
25992
26309
|
position: absolute; left: 8%; right: 8%; bottom: 8%;
|
|
25993
26310
|
font-size: 28px; font-weight: 400; color: #d0d0e0;
|
|
26311
|
+
overflow-wrap: break-word;
|
|
25994
26312
|
text-shadow: 0 2px 10px rgba(0,0,0,0.7);
|
|
25995
26313
|
}` : ""}`,
|
|
25996
26314
|
body: `${backdropMarkup}
|
|
25997
26315
|
<div class="label" id="label">${esc(label)}</div>
|
|
25998
26316
|
<div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
|
|
25999
26317
|
<div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
|
|
26000
|
-
timeline:
|
|
26318
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26319
|
+
tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.12, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
|
|
26001
26320
|
tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
|
|
26002
26321
|
tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
|
|
26003
26322
|
tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}`
|
|
@@ -26046,7 +26365,7 @@ ${audioBlock}
|
|
|
26046
26365
|
</template>
|
|
26047
26366
|
`;
|
|
26048
26367
|
}
|
|
26049
|
-
function nextSceneStart(rootHtml) {
|
|
26368
|
+
function nextSceneStart(rootHtml, overlap = 0) {
|
|
26050
26369
|
const clipRegex = /<div\s+class="clip"[^>]*?\sdata-start="([\d.]+)"[^>]*?\sdata-duration="([\d.]+)"/gi;
|
|
26051
26370
|
let maxEnd = 0;
|
|
26052
26371
|
let match;
|
|
@@ -26054,14 +26373,15 @@ function nextSceneStart(rootHtml) {
|
|
|
26054
26373
|
const end = parseFloat(match[1]) + parseFloat(match[2]);
|
|
26055
26374
|
if (Number.isFinite(end) && end > maxEnd) maxEnd = end;
|
|
26056
26375
|
}
|
|
26057
|
-
return maxEnd;
|
|
26376
|
+
return Math.max(0, maxEnd - overlap);
|
|
26058
26377
|
}
|
|
26059
26378
|
function buildClipReference(opts) {
|
|
26060
26379
|
const start = Number(opts.start.toFixed(3));
|
|
26061
26380
|
const duration = Number(opts.duration.toFixed(3));
|
|
26062
26381
|
const track = opts.trackIndex ?? 1;
|
|
26063
26382
|
const src = opts.src ?? `compositions/scene-${opts.id}.html`;
|
|
26064
|
-
|
|
26383
|
+
const zIndex = Math.max(1, 1e6 - Math.floor(start * 1e3));
|
|
26384
|
+
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>`;
|
|
26065
26385
|
}
|
|
26066
26386
|
function insertClipIntoRoot(rootHtml, clip) {
|
|
26067
26387
|
const clipDiv = buildClipReference(clip);
|
|
@@ -26094,7 +26414,7 @@ function readRootDims(rootHtml) {
|
|
|
26094
26414
|
if (!widthMatch || !heightMatch) return null;
|
|
26095
26415
|
return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
|
|
26096
26416
|
}
|
|
26097
|
-
var SCENE_PRESETS, GSAP_CDN;
|
|
26417
|
+
var SCENE_PRESETS, GSAP_CDN, SCENE_OVERLAP_SECONDS;
|
|
26098
26418
|
var init_scene_html_emit = __esm({
|
|
26099
26419
|
"src/commands/_shared/scene-html-emit.ts"() {
|
|
26100
26420
|
"use strict";
|
|
@@ -26106,6 +26426,7 @@ var init_scene_html_emit = __esm({
|
|
|
26106
26426
|
"product-shot"
|
|
26107
26427
|
];
|
|
26108
26428
|
GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
|
|
26429
|
+
SCENE_OVERLAP_SECONDS = 0.4;
|
|
26109
26430
|
}
|
|
26110
26431
|
});
|
|
26111
26432
|
|
|
@@ -26789,6 +27110,18 @@ function validatePreset(value) {
|
|
|
26789
27110
|
}
|
|
26790
27111
|
return value;
|
|
26791
27112
|
}
|
|
27113
|
+
function validateVisualStyle(value) {
|
|
27114
|
+
const found = getVisualStyle(value);
|
|
27115
|
+
if (!found) {
|
|
27116
|
+
exitWithError(
|
|
27117
|
+
usageError(
|
|
27118
|
+
`Unknown visual style: ${value}`,
|
|
27119
|
+
`Valid: ${visualStyleNames()}. Browse details with \`vibe scene styles\`.`
|
|
27120
|
+
)
|
|
27121
|
+
);
|
|
27122
|
+
}
|
|
27123
|
+
return found;
|
|
27124
|
+
}
|
|
26792
27125
|
async function pathExists2(p) {
|
|
26793
27126
|
try {
|
|
26794
27127
|
await access4(p);
|
|
@@ -27002,7 +27335,20 @@ async function executeSceneAdd(opts) {
|
|
|
27002
27335
|
}
|
|
27003
27336
|
const cfg = await loadVibeProjectConfig(projectDir);
|
|
27004
27337
|
const fallback2 = cfg?.defaultSceneDuration ?? 5;
|
|
27005
|
-
const
|
|
27338
|
+
const NARRATION_TAIL_BUFFER = 0.5;
|
|
27339
|
+
const userDur = opts.duration;
|
|
27340
|
+
const audioMinDur = narrationDuration !== void 0 ? narrationDuration + SCENE_OVERLAP_SECONDS + NARRATION_TAIL_BUFFER : void 0;
|
|
27341
|
+
let duration;
|
|
27342
|
+
if (userDur !== void 0 && audioMinDur !== void 0) {
|
|
27343
|
+
duration = Math.max(userDur, audioMinDur);
|
|
27344
|
+
} else if (audioMinDur !== void 0) {
|
|
27345
|
+
duration = audioMinDur;
|
|
27346
|
+
} else if (userDur !== void 0) {
|
|
27347
|
+
duration = userDur;
|
|
27348
|
+
} else {
|
|
27349
|
+
duration = fallback2;
|
|
27350
|
+
}
|
|
27351
|
+
duration = Number(duration.toFixed(2));
|
|
27006
27352
|
opts.onProgress?.("Emitting scene HTML...");
|
|
27007
27353
|
const sceneHtml = emitSceneHtml({
|
|
27008
27354
|
id,
|
|
@@ -27020,8 +27366,10 @@ async function executeSceneAdd(opts) {
|
|
|
27020
27366
|
await mkdir4(dirname3(scenePath), { recursive: true });
|
|
27021
27367
|
await writeFile6(scenePath, sceneHtml, "utf-8");
|
|
27022
27368
|
opts.onProgress?.("Updating root composition...");
|
|
27023
|
-
const start = nextSceneStart(rootHtmlBefore);
|
|
27024
|
-
const
|
|
27369
|
+
const start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
|
|
27370
|
+
const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
|
|
27371
|
+
const trackIndex = existingClipCount % 2 + 1;
|
|
27372
|
+
const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
|
|
27025
27373
|
await writeFile6(rootPath, updated, "utf-8");
|
|
27026
27374
|
const transcriptAbsPath = transcriptRelPath ? resolve10(projectDir, transcriptRelPath) : void 0;
|
|
27027
27375
|
return {
|
|
@@ -27080,6 +27428,7 @@ var init_scene = __esm({
|
|
|
27080
27428
|
init_dist2();
|
|
27081
27429
|
init_tts_resolve();
|
|
27082
27430
|
init_scene_project();
|
|
27431
|
+
init_visual_styles();
|
|
27083
27432
|
init_scene_html_emit();
|
|
27084
27433
|
init_scene_lint();
|
|
27085
27434
|
init_scene_render();
|
|
@@ -27104,21 +27453,28 @@ Examples:
|
|
|
27104
27453
|
|
|
27105
27454
|
A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
|
|
27106
27455
|
Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
27107
|
-
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) => {
|
|
27456
|
+
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) => {
|
|
27108
27457
|
const aspect = validateAspect(options.ratio);
|
|
27109
27458
|
const duration = validateDuration(options.duration);
|
|
27110
27459
|
const name = options.name ?? basename4(dir.replace(/\/+$/, ""));
|
|
27460
|
+
const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
|
|
27111
27461
|
if (options.dryRun) {
|
|
27112
27462
|
outputResult({
|
|
27113
27463
|
dryRun: true,
|
|
27114
27464
|
command: "scene init",
|
|
27115
|
-
params: {
|
|
27465
|
+
params: {
|
|
27466
|
+
dir,
|
|
27467
|
+
name,
|
|
27468
|
+
aspect,
|
|
27469
|
+
duration,
|
|
27470
|
+
visualStyle: visualStyle?.name ?? null
|
|
27471
|
+
}
|
|
27116
27472
|
});
|
|
27117
27473
|
return;
|
|
27118
27474
|
}
|
|
27119
27475
|
const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
|
|
27120
27476
|
try {
|
|
27121
|
-
const result = await scaffoldSceneProject({ dir, name, aspect, duration });
|
|
27477
|
+
const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
|
|
27122
27478
|
if (isJsonMode()) {
|
|
27123
27479
|
outputResult({
|
|
27124
27480
|
success: true,
|
|
@@ -27127,6 +27483,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27127
27483
|
name,
|
|
27128
27484
|
aspect,
|
|
27129
27485
|
duration,
|
|
27486
|
+
visualStyle: visualStyle?.name ?? null,
|
|
27130
27487
|
created: result.created,
|
|
27131
27488
|
merged: result.merged,
|
|
27132
27489
|
skipped: result.skipped
|
|
@@ -27143,7 +27500,13 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27143
27500
|
console.log();
|
|
27144
27501
|
console.log(source_default.bold.cyan("Next steps"));
|
|
27145
27502
|
console.log(source_default.dim("\u2500".repeat(60)));
|
|
27146
|
-
|
|
27503
|
+
if (visualStyle) {
|
|
27504
|
+
console.log(` ${source_default.dim("DESIGN.md seeded with")} ${source_default.bold(visualStyle.name)} ${source_default.dim("\u2014 review and customise.")}`);
|
|
27505
|
+
} else {
|
|
27506
|
+
console.log(` ${source_default.cyan("vibe scene styles")} ${source_default.dim("# pick a named style for DESIGN.md")}`);
|
|
27507
|
+
}
|
|
27508
|
+
console.log(` ${source_default.cyan("npx skills add heygen-com/hyperframes")} ${source_default.dim("# load the cinematic-craft skill set")}`);
|
|
27509
|
+
console.log(` ${source_default.cyan("vibe scene add")} <name> ${source_default.dim("# fallback path: 5-preset emit")}`);
|
|
27147
27510
|
console.log(` ${source_default.cyan("vibe scene lint")} ${source_default.dim("# validate HTML")}`);
|
|
27148
27511
|
console.log(` ${source_default.cyan("vibe scene render")} ${source_default.dim("# render to MP4")}`);
|
|
27149
27512
|
} catch (error) {
|
|
@@ -27152,6 +27515,69 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27152
27515
|
exitWithError(generalError(`Failed to scaffold: ${msg}`));
|
|
27153
27516
|
}
|
|
27154
27517
|
});
|
|
27518
|
+
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) => {
|
|
27519
|
+
if (!name) {
|
|
27520
|
+
const all = listVisualStyles();
|
|
27521
|
+
if (isJsonMode()) {
|
|
27522
|
+
outputResult({
|
|
27523
|
+
success: true,
|
|
27524
|
+
command: "scene styles",
|
|
27525
|
+
count: all.length,
|
|
27526
|
+
styles: all.map((s) => ({
|
|
27527
|
+
name: s.name,
|
|
27528
|
+
slug: s.slug,
|
|
27529
|
+
designer: s.designer,
|
|
27530
|
+
mood: s.mood,
|
|
27531
|
+
bestFor: s.bestFor
|
|
27532
|
+
}))
|
|
27533
|
+
});
|
|
27534
|
+
return;
|
|
27535
|
+
}
|
|
27536
|
+
console.log();
|
|
27537
|
+
console.log(source_default.bold.cyan("Visual styles"));
|
|
27538
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
27539
|
+
for (const s of all) {
|
|
27540
|
+
console.log(
|
|
27541
|
+
` ${source_default.bold(s.name.padEnd(18))} ${source_default.dim(s.mood.padEnd(24))} ${source_default.dim(s.bestFor)}`
|
|
27542
|
+
);
|
|
27543
|
+
}
|
|
27544
|
+
console.log();
|
|
27545
|
+
console.log(source_default.dim("Show details: "), source_default.cyan('vibe scene styles "<name>"'));
|
|
27546
|
+
console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan('vibe scene init <dir> --visual-style "<name>"'));
|
|
27547
|
+
return;
|
|
27548
|
+
}
|
|
27549
|
+
const style = getVisualStyle(name);
|
|
27550
|
+
if (!style) {
|
|
27551
|
+
exitWithError(
|
|
27552
|
+
usageError(
|
|
27553
|
+
`Unknown visual style: ${name}`,
|
|
27554
|
+
`Valid: ${visualStyleNames()}.`
|
|
27555
|
+
)
|
|
27556
|
+
);
|
|
27557
|
+
return;
|
|
27558
|
+
}
|
|
27559
|
+
if (isJsonMode()) {
|
|
27560
|
+
outputResult({ success: true, command: "scene styles", style });
|
|
27561
|
+
return;
|
|
27562
|
+
}
|
|
27563
|
+
console.log();
|
|
27564
|
+
console.log(source_default.bold.cyan(style.name), source_default.dim(`\u2014 ${style.designer}`));
|
|
27565
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
27566
|
+
console.log(`${source_default.bold("Mood:")} ${style.mood}`);
|
|
27567
|
+
console.log(`${source_default.bold("Best for:")} ${style.bestFor}`);
|
|
27568
|
+
console.log(`${source_default.bold("Palette:")} ${style.palette.join(", ")}`);
|
|
27569
|
+
console.log(source_default.dim(" ") + style.paletteNotes);
|
|
27570
|
+
console.log(`${source_default.bold("Typography:")} ${style.typography}`);
|
|
27571
|
+
console.log(`${source_default.bold("Composition:")} ${style.composition}`);
|
|
27572
|
+
console.log(`${source_default.bold("Motion:")} ${style.motion}`);
|
|
27573
|
+
console.log(`${source_default.bold("GSAP:")} ${style.gsapSignature}`);
|
|
27574
|
+
console.log(`${source_default.bold("Transition:")} ${style.transition}`);
|
|
27575
|
+
console.log();
|
|
27576
|
+
console.log(source_default.bold("Avoid:"));
|
|
27577
|
+
for (const a of style.avoid) console.log(` ${source_default.red("\u2022")} ${a}`);
|
|
27578
|
+
console.log();
|
|
27579
|
+
console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan(`vibe scene init <dir> --visual-style "${style.name}"`));
|
|
27580
|
+
});
|
|
27155
27581
|
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) => {
|
|
27156
27582
|
if (options.style) options.style = validatePreset(options.style);
|
|
27157
27583
|
if (options.duration !== void 0) options.duration = validateDuration(options.duration);
|
|
@@ -28365,16 +28791,16 @@ async function prompt2(question) {
|
|
|
28365
28791
|
input,
|
|
28366
28792
|
output: process.stdout
|
|
28367
28793
|
});
|
|
28368
|
-
return new Promise((
|
|
28794
|
+
return new Promise((resolve54) => {
|
|
28369
28795
|
rl.question(question, (answer) => {
|
|
28370
28796
|
rl.close();
|
|
28371
|
-
|
|
28797
|
+
resolve54(answer);
|
|
28372
28798
|
});
|
|
28373
28799
|
});
|
|
28374
28800
|
}
|
|
28375
28801
|
async function promptHidden(question) {
|
|
28376
28802
|
const input = getTTYInputStream();
|
|
28377
|
-
return new Promise((
|
|
28803
|
+
return new Promise((resolve54) => {
|
|
28378
28804
|
process.stdout.write(question);
|
|
28379
28805
|
let value = "";
|
|
28380
28806
|
if (typeof input.setRawMode === "function") {
|
|
@@ -28386,7 +28812,7 @@ async function promptHidden(question) {
|
|
|
28386
28812
|
input.setRawMode(false);
|
|
28387
28813
|
input.removeListener("data", onData);
|
|
28388
28814
|
process.stdout.write("\n");
|
|
28389
|
-
|
|
28815
|
+
resolve54(value);
|
|
28390
28816
|
} else if (char === "") {
|
|
28391
28817
|
input.setRawMode(false);
|
|
28392
28818
|
process.stdout.write("\n");
|
|
@@ -28406,7 +28832,7 @@ async function promptHidden(question) {
|
|
|
28406
28832
|
const rl = createInterface2({ input, output: process.stdout });
|
|
28407
28833
|
rl.question("", (answer) => {
|
|
28408
28834
|
rl.close();
|
|
28409
|
-
|
|
28835
|
+
resolve54(answer);
|
|
28410
28836
|
});
|
|
28411
28837
|
}
|
|
28412
28838
|
});
|
|
@@ -28414,7 +28840,7 @@ async function promptHidden(question) {
|
|
|
28414
28840
|
async function promptSelect(_question, options, defaultIndex = 0) {
|
|
28415
28841
|
const input = getTTYInputStream();
|
|
28416
28842
|
if (typeof input.setRawMode === "function") {
|
|
28417
|
-
return new Promise((
|
|
28843
|
+
return new Promise((resolve54) => {
|
|
28418
28844
|
let selected = defaultIndex;
|
|
28419
28845
|
const render = () => {
|
|
28420
28846
|
if (renderCount > 0) {
|
|
@@ -28438,7 +28864,7 @@ async function promptSelect(_question, options, defaultIndex = 0) {
|
|
|
28438
28864
|
input.setRawMode(false);
|
|
28439
28865
|
input.removeListener("data", onData);
|
|
28440
28866
|
process.stdout.write("\n");
|
|
28441
|
-
|
|
28867
|
+
resolve54(selected);
|
|
28442
28868
|
} else if (char === "") {
|
|
28443
28869
|
input.setRawMode(false);
|
|
28444
28870
|
process.stdout.write("\n");
|
|
@@ -28455,7 +28881,7 @@ async function promptSelect(_question, options, defaultIndex = 0) {
|
|
|
28455
28881
|
input.removeListener("data", onData);
|
|
28456
28882
|
render();
|
|
28457
28883
|
process.stdout.write("\n");
|
|
28458
|
-
|
|
28884
|
+
resolve54(selected);
|
|
28459
28885
|
}
|
|
28460
28886
|
};
|
|
28461
28887
|
input.on("data", onData);
|
|
@@ -28625,13 +29051,10 @@ var init_subtitle = __esm({
|
|
|
28625
29051
|
// src/utils/remotion.ts
|
|
28626
29052
|
var remotion_exports = {};
|
|
28627
29053
|
__export(remotion_exports, {
|
|
28628
|
-
compositeOverlay: () => compositeOverlay,
|
|
28629
|
-
compositeWithImage: () => compositeWithImage,
|
|
28630
29054
|
ensureRemotionInstalled: () => ensureRemotionInstalled,
|
|
28631
29055
|
generateAnimatedCaptionComponent: () => generateAnimatedCaptionComponent,
|
|
28632
29056
|
generateCaptionComponent: () => generateCaptionComponent,
|
|
28633
29057
|
generateTextOverlayComponent: () => generateTextOverlayComponent,
|
|
28634
|
-
renderAndComposite: () => renderAndComposite,
|
|
28635
29058
|
renderMotion: () => renderMotion,
|
|
28636
29059
|
renderWithEmbeddedImage: () => renderWithEmbeddedImage,
|
|
28637
29060
|
renderWithEmbeddedVideo: () => renderWithEmbeddedVideo,
|
|
@@ -29379,92 +29802,6 @@ export const ${name} = () => {
|
|
|
29379
29802
|
`;
|
|
29380
29803
|
return { code, name };
|
|
29381
29804
|
}
|
|
29382
|
-
async function compositeOverlay(options) {
|
|
29383
|
-
try {
|
|
29384
|
-
await execSafe("ffmpeg", [
|
|
29385
|
-
"-y",
|
|
29386
|
-
"-i",
|
|
29387
|
-
options.baseVideo,
|
|
29388
|
-
"-i",
|
|
29389
|
-
options.overlayPath,
|
|
29390
|
-
"-filter_complex",
|
|
29391
|
-
"[0:v][1:v]overlay=0:0:shortest=1[out]",
|
|
29392
|
-
"-map",
|
|
29393
|
-
"[out]",
|
|
29394
|
-
"-map",
|
|
29395
|
-
"0:a?",
|
|
29396
|
-
"-c:a",
|
|
29397
|
-
"copy",
|
|
29398
|
-
"-c:v",
|
|
29399
|
-
"libx264",
|
|
29400
|
-
"-crf",
|
|
29401
|
-
"18",
|
|
29402
|
-
"-pix_fmt",
|
|
29403
|
-
"yuv420p",
|
|
29404
|
-
options.outputPath
|
|
29405
|
-
], { timeout: 3e5 });
|
|
29406
|
-
return { success: true, outputPath: options.outputPath };
|
|
29407
|
-
} catch (error) {
|
|
29408
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
29409
|
-
return { success: false, error: `FFmpeg composite failed: ${msg}` };
|
|
29410
|
-
}
|
|
29411
|
-
}
|
|
29412
|
-
async function compositeWithImage(options) {
|
|
29413
|
-
try {
|
|
29414
|
-
const fps = options.fps || 30;
|
|
29415
|
-
await execSafe("ffmpeg", [
|
|
29416
|
-
"-y",
|
|
29417
|
-
"-loop",
|
|
29418
|
-
"1",
|
|
29419
|
-
"-framerate",
|
|
29420
|
-
String(fps),
|
|
29421
|
-
"-i",
|
|
29422
|
-
options.baseImage,
|
|
29423
|
-
"-i",
|
|
29424
|
-
options.overlayPath,
|
|
29425
|
-
"-filter_complex",
|
|
29426
|
-
"[0:v]scale=iw:ih[base];[base][1:v]overlay=0:0:shortest=1[out]",
|
|
29427
|
-
"-map",
|
|
29428
|
-
"[out]",
|
|
29429
|
-
"-c:v",
|
|
29430
|
-
"libx264",
|
|
29431
|
-
"-crf",
|
|
29432
|
-
"18",
|
|
29433
|
-
"-pix_fmt",
|
|
29434
|
-
"yuv420p",
|
|
29435
|
-
"-t",
|
|
29436
|
-
String(options.durationSeconds),
|
|
29437
|
-
options.outputPath
|
|
29438
|
-
], { timeout: 3e5 });
|
|
29439
|
-
return { success: true, outputPath: options.outputPath };
|
|
29440
|
-
} catch (error) {
|
|
29441
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
29442
|
-
return { success: false, error: `FFmpeg image composite failed: ${msg}` };
|
|
29443
|
-
}
|
|
29444
|
-
}
|
|
29445
|
-
async function renderAndComposite(motionOpts, baseVideo, finalOutput) {
|
|
29446
|
-
const renderOpts = {
|
|
29447
|
-
...motionOpts,
|
|
29448
|
-
transparent: !!baseVideo,
|
|
29449
|
-
outputPath: baseVideo ? motionOpts.outputPath.replace(/\.\w+$/, "_overlay.webm") : motionOpts.outputPath
|
|
29450
|
-
};
|
|
29451
|
-
const renderResult = await renderMotion(renderOpts);
|
|
29452
|
-
if (!renderResult.success || !renderResult.outputPath) {
|
|
29453
|
-
return renderResult;
|
|
29454
|
-
}
|
|
29455
|
-
if (!baseVideo) {
|
|
29456
|
-
return renderResult;
|
|
29457
|
-
}
|
|
29458
|
-
const output = finalOutput || motionOpts.outputPath;
|
|
29459
|
-
const compositeResult = await compositeOverlay({
|
|
29460
|
-
baseVideo,
|
|
29461
|
-
overlayPath: renderResult.outputPath,
|
|
29462
|
-
outputPath: output
|
|
29463
|
-
});
|
|
29464
|
-
await rm(renderResult.outputPath, { force: true }).catch(() => {
|
|
29465
|
-
});
|
|
29466
|
-
return compositeResult;
|
|
29467
|
-
}
|
|
29468
29805
|
var REMOTION_VERSION, REMOTION_CACHE_DIR;
|
|
29469
29806
|
var init_remotion = __esm({
|
|
29470
29807
|
"src/utils/remotion.ts"() {
|
|
@@ -30822,7 +31159,12 @@ async function executeSegmentsToScenes(opts) {
|
|
|
30822
31159
|
const dims = aspectToDims(aspect);
|
|
30823
31160
|
const preset = opts.scenePreset ?? DEFAULT_PRESET;
|
|
30824
31161
|
const projectName = opts.projectName ?? basename7(outputDir);
|
|
30825
|
-
const totalDuration =
|
|
31162
|
+
const totalDuration = (() => {
|
|
31163
|
+
const sum = opts.segments.reduce((acc, s) => acc + (s.duration || 0), 0);
|
|
31164
|
+
if (sum <= 0) return 10;
|
|
31165
|
+
const joins = Math.max(0, opts.segments.length - 1);
|
|
31166
|
+
return Math.max(0.1, sum - joins * SCENE_OVERLAP_SECONDS);
|
|
31167
|
+
})();
|
|
30826
31168
|
const baseError = (error) => ({
|
|
30827
31169
|
success: false,
|
|
30828
31170
|
outputDir,
|
|
@@ -30895,9 +31237,10 @@ async function executeSegmentsToScenes(opts) {
|
|
|
30895
31237
|
id,
|
|
30896
31238
|
start: clipStart,
|
|
30897
31239
|
duration,
|
|
31240
|
+
trackIndex: i % 2 + 1,
|
|
30898
31241
|
src: `compositions/${id}.html`
|
|
30899
31242
|
});
|
|
30900
|
-
clipStart += duration;
|
|
31243
|
+
clipStart += duration - SCENE_OVERLAP_SECONDS;
|
|
30901
31244
|
}
|
|
30902
31245
|
await writeFile10(rootAbs, rootHtml, "utf-8");
|
|
30903
31246
|
let lintResult;
|
|
@@ -30957,7 +31300,7 @@ import { readFile as readFile13, writeFile as writeFile11, mkdir as mkdir8, unli
|
|
|
30957
31300
|
import { resolve as resolve15, basename as basename8, dirname as dirname6, extname as extname4 } from "node:path";
|
|
30958
31301
|
import { existsSync as existsSync9 } from "node:fs";
|
|
30959
31302
|
function sleep(ms) {
|
|
30960
|
-
return new Promise((
|
|
31303
|
+
return new Promise((resolve54) => setTimeout(resolve54, ms));
|
|
30961
31304
|
}
|
|
30962
31305
|
async function uploadToImgbb(imageBuffer, apiKey) {
|
|
30963
31306
|
try {
|
|
@@ -31322,7 +31665,6 @@ async function executeScriptToVideo(options) {
|
|
|
31322
31665
|
outputDir: absOutputDir,
|
|
31323
31666
|
scenes: segments.length,
|
|
31324
31667
|
storyboardPath,
|
|
31325
|
-
narrations: [],
|
|
31326
31668
|
narrationEntries: [],
|
|
31327
31669
|
images: [],
|
|
31328
31670
|
videos: [],
|
|
@@ -31383,7 +31725,6 @@ async function executeScriptToVideo(options) {
|
|
|
31383
31725
|
);
|
|
31384
31726
|
}
|
|
31385
31727
|
segment.duration = actualDuration;
|
|
31386
|
-
result.narrations.push(audioPath);
|
|
31387
31728
|
result.narrationEntries.push({
|
|
31388
31729
|
path: audioPath,
|
|
31389
31730
|
duration: actualDuration,
|
|
@@ -32366,6 +32707,34 @@ var init_provider_resolver = __esm({
|
|
|
32366
32707
|
}
|
|
32367
32708
|
});
|
|
32368
32709
|
|
|
32710
|
+
// src/commands/_shared/openai-image.ts
|
|
32711
|
+
function resolveOpenAIImageModel(modelAlias) {
|
|
32712
|
+
const isGptImage2 = modelAlias === "2" || modelAlias === "gpt-image-2";
|
|
32713
|
+
return {
|
|
32714
|
+
openaiModel: isGptImage2 ? "gpt-image-2" : void 0,
|
|
32715
|
+
modelLabel: isGptImage2 ? "GPT Image 2" : "GPT Image 1.5"
|
|
32716
|
+
};
|
|
32717
|
+
}
|
|
32718
|
+
async function executeOpenAIImageGenerate(prompt3, options, ctx) {
|
|
32719
|
+
const provider = new OpenAIImageProvider();
|
|
32720
|
+
await provider.initialize({ apiKey: ctx.apiKey });
|
|
32721
|
+
const { openaiModel, modelLabel } = resolveOpenAIImageModel(options.model);
|
|
32722
|
+
const result = await provider.generateImage(prompt3, {
|
|
32723
|
+
model: openaiModel,
|
|
32724
|
+
size: options.size,
|
|
32725
|
+
quality: options.quality,
|
|
32726
|
+
style: options.style,
|
|
32727
|
+
n: options.count !== void 0 ? parseInt(options.count, 10) : void 0
|
|
32728
|
+
});
|
|
32729
|
+
return { result, openaiModel, modelLabel };
|
|
32730
|
+
}
|
|
32731
|
+
var init_openai_image2 = __esm({
|
|
32732
|
+
"src/commands/_shared/openai-image.ts"() {
|
|
32733
|
+
"use strict";
|
|
32734
|
+
init_dist2();
|
|
32735
|
+
}
|
|
32736
|
+
});
|
|
32737
|
+
|
|
32369
32738
|
// src/commands/ai-image.ts
|
|
32370
32739
|
var ai_image_exports = {};
|
|
32371
32740
|
__export(ai_image_exports, {
|
|
@@ -32418,22 +32787,11 @@ function _registerImageCommands(aiCommand) {
|
|
|
32418
32787
|
}
|
|
32419
32788
|
const spinner2 = ora(`Generating image with ${providerName}...`).start();
|
|
32420
32789
|
if (provider === "dalle" || provider === "openai") {
|
|
32421
|
-
const
|
|
32422
|
-
await openaiImage.initialize({ apiKey });
|
|
32423
|
-
const modelAlias = options.model;
|
|
32424
|
-
const openaiModel = modelAlias === "2" || modelAlias === "gpt-image-2" ? "gpt-image-2" : void 0;
|
|
32425
|
-
const result = await openaiImage.generateImage(prompt3, {
|
|
32426
|
-
model: openaiModel,
|
|
32427
|
-
size: options.size,
|
|
32428
|
-
quality: options.quality,
|
|
32429
|
-
style: options.style,
|
|
32430
|
-
n: parseInt(options.count)
|
|
32431
|
-
});
|
|
32790
|
+
const { result, modelLabel } = await executeOpenAIImageGenerate(prompt3, options, { apiKey });
|
|
32432
32791
|
if (!result.success || !result.images) {
|
|
32433
32792
|
spinner2.fail("Image generation failed");
|
|
32434
32793
|
exitWithError(apiError(result.error || "Image generation failed", true));
|
|
32435
32794
|
}
|
|
32436
|
-
const modelLabel = openaiModel === "gpt-image-2" ? "GPT Image 2" : "GPT Image 1.5";
|
|
32437
32795
|
spinner2.succeed(source_default.green(`Generated ${result.images.length} image(s) with OpenAI ${modelLabel}`));
|
|
32438
32796
|
console.log();
|
|
32439
32797
|
console.log(source_default.bold.cyan("Generated Images"));
|
|
@@ -33120,6 +33478,7 @@ var init_ai_image = __esm({
|
|
|
33120
33478
|
init_exec_safe();
|
|
33121
33479
|
init_output();
|
|
33122
33480
|
init_validate();
|
|
33481
|
+
init_openai_image2();
|
|
33123
33482
|
}
|
|
33124
33483
|
});
|
|
33125
33484
|
|
|
@@ -33560,6 +33919,7 @@ var init_generate = __esm({
|
|
|
33560
33919
|
init_provider_resolver();
|
|
33561
33920
|
init_ai_image();
|
|
33562
33921
|
init_ai_motion();
|
|
33922
|
+
init_openai_image2();
|
|
33563
33923
|
generateCommand = new Command("generate").alias("gen").description(
|
|
33564
33924
|
"Generate assets using AI (images, videos, speech, music, motion)"
|
|
33565
33925
|
).addHelpText(
|
|
@@ -33659,22 +34019,11 @@ Examples:
|
|
|
33659
34019
|
const apiKey = await requireApiKey(envKey, providerName, options.apiKey);
|
|
33660
34020
|
const spinner2 = ora(`Generating image with ${providerName}...`).start();
|
|
33661
34021
|
if (provider === "dalle" || provider === "openai") {
|
|
33662
|
-
const
|
|
33663
|
-
await openaiImage.initialize({ apiKey });
|
|
33664
|
-
const modelAlias = options.model;
|
|
33665
|
-
const openaiModel = modelAlias === "2" || modelAlias === "gpt-image-2" ? "gpt-image-2" : void 0;
|
|
33666
|
-
const result = await openaiImage.generateImage(prompt3, {
|
|
33667
|
-
model: openaiModel,
|
|
33668
|
-
size: options.size,
|
|
33669
|
-
quality: options.quality,
|
|
33670
|
-
style: options.style,
|
|
33671
|
-
n: parseInt(options.count)
|
|
33672
|
-
});
|
|
34022
|
+
const { result, modelLabel } = await executeOpenAIImageGenerate(prompt3, options, { apiKey });
|
|
33673
34023
|
if (!result.success || !result.images) {
|
|
33674
34024
|
spinner2.fail(result.error || "Image generation failed");
|
|
33675
34025
|
exitWithError(apiError(result.error || "Image generation failed", true));
|
|
33676
34026
|
}
|
|
33677
|
-
const modelLabel = openaiModel === "gpt-image-2" ? "GPT Image 2" : "GPT Image 1.5";
|
|
33678
34027
|
spinner2.succeed(source_default.green(`Generated ${result.images.length} image(s) with OpenAI ${modelLabel}`));
|
|
33679
34028
|
if (isJsonMode()) {
|
|
33680
34029
|
const outputPath = options.output ? resolve18(process.cwd(), options.output) : void 0;
|
|
@@ -48740,7 +49089,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
|
|
|
48740
49089
|
return args;
|
|
48741
49090
|
}
|
|
48742
49091
|
function runFFmpegProcess(ffmpegPath, args, onProgress) {
|
|
48743
|
-
return new Promise((
|
|
49092
|
+
return new Promise((resolve54, reject) => {
|
|
48744
49093
|
const ffmpeg = spawn(ffmpegPath, args, {
|
|
48745
49094
|
stdio: ["pipe", "pipe", "pipe"]
|
|
48746
49095
|
});
|
|
@@ -48764,7 +49113,7 @@ function runFFmpegProcess(ffmpegPath, args, onProgress) {
|
|
|
48764
49113
|
});
|
|
48765
49114
|
ffmpeg.on("close", (code) => {
|
|
48766
49115
|
if (code === 0) {
|
|
48767
|
-
|
|
49116
|
+
resolve54();
|
|
48768
49117
|
} else {
|
|
48769
49118
|
const errorMatch = stderr.match(/Error.*$/m);
|
|
48770
49119
|
const errorMsg = errorMatch ? errorMatch[0] : `FFmpeg exited with code ${code}`;
|
|
@@ -48900,17 +49249,17 @@ function getPresetSettings(preset, aspectRatio) {
|
|
|
48900
49249
|
}
|
|
48901
49250
|
async function runHyperframesExport(projectPath, options, spinner2) {
|
|
48902
49251
|
spinner2.text = "Loading project...";
|
|
48903
|
-
const { readFile:
|
|
48904
|
-
const { resolve:
|
|
49252
|
+
const { readFile: readFile44 } = await import("node:fs/promises");
|
|
49253
|
+
const { resolve: resolve54, basename: basename28 } = await import("node:path");
|
|
48905
49254
|
const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
48906
49255
|
const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
|
|
48907
49256
|
const { exitWithError: exitWithError2, generalError: generalError2, outputResult: outputResult2 } = await Promise.resolve().then(() => (init_output(), output_exports));
|
|
48908
49257
|
const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
|
|
48909
|
-
const filePath =
|
|
48910
|
-
const content = await
|
|
49258
|
+
const filePath = resolve54(process.cwd(), projectPath);
|
|
49259
|
+
const content = await readFile44(filePath, "utf-8");
|
|
48911
49260
|
const project = Project2.fromJSON(JSON.parse(content));
|
|
48912
49261
|
const state = project.getState();
|
|
48913
|
-
const outputPath = options.output ?
|
|
49262
|
+
const outputPath = options.output ? resolve54(process.cwd(), options.output) : resolve54(process.cwd(), `${basename28(projectPath, ".vibe.json")}.${options.format ?? "mp4"}`);
|
|
48914
49263
|
const quality = ["draft", "standard", "high"].includes(options.preset ?? "") ? options.preset : "standard";
|
|
48915
49264
|
const backend = createHyperframesBackend2();
|
|
48916
49265
|
spinner2.text = "Rendering with Hyperframes...";
|
|
@@ -51439,6 +51788,1295 @@ var init_ai_audio = __esm({
|
|
|
51439
51788
|
}
|
|
51440
51789
|
});
|
|
51441
51790
|
|
|
51791
|
+
// src/commands/_shared/hf-skill-bundle/bundle-content.ts
|
|
51792
|
+
var SKILL_MD, HOUSE_STYLE_MD, MOTION_PRINCIPLES_MD, TYPOGRAPHY_MD, TRANSITIONS_MD;
|
|
51793
|
+
var init_bundle_content = __esm({
|
|
51794
|
+
"src/commands/_shared/hf-skill-bundle/bundle-content.ts"() {
|
|
51795
|
+
"use strict";
|
|
51796
|
+
SKILL_MD = `---
|
|
51797
|
+
name: hyperframes
|
|
51798
|
+
description: Create video compositions, animations, title cards, overlays, captions, voiceovers, audio-reactive visuals, and scene transitions in HyperFrames HTML. Use when asked to build any HTML-based video content, add captions or subtitles synced to audio, generate text-to-speech narration, create audio-reactive animation (beat sync, glow, pulse driven by music), add animated text highlighting (marker sweeps, hand-drawn circles, burst lines, scribble, sketchout), or add transitions between scenes (crossfades, wipes, reveals, shader transitions). Covers composition authoring, timing, media, and the full video production workflow. For CLI commands (init, lint, preview, render, transcribe, tts) see the hyperframes-cli skill.
|
|
51799
|
+
---
|
|
51800
|
+
|
|
51801
|
+
# HyperFrames
|
|
51802
|
+
|
|
51803
|
+
HTML is the source of truth for video. A composition is an HTML file with \`data-*\` attributes for timing, a GSAP timeline for animation, and CSS for appearance. The framework handles clip visibility, media playback, and timeline sync.
|
|
51804
|
+
|
|
51805
|
+
## Approach
|
|
51806
|
+
|
|
51807
|
+
Before writing HTML, think at a high level:
|
|
51808
|
+
|
|
51809
|
+
1. **What** \u2014 what should the viewer experience? Identify the narrative arc, key moments, and emotional beats.
|
|
51810
|
+
2. **Structure** \u2014 how many compositions, which are sub-compositions vs inline, what tracks carry what (video, audio, overlays, captions).
|
|
51811
|
+
3. **Timing** \u2014 which clips drive the duration, where do transitions land, what's the pacing.
|
|
51812
|
+
4. **Layout** \u2014 build the end-state first. See "Layout Before Animation" below.
|
|
51813
|
+
5. **Animate** \u2014 then add motion using the rules below.
|
|
51814
|
+
|
|
51815
|
+
For small edits (fix a color, adjust timing, add one element), skip straight to the rules.
|
|
51816
|
+
|
|
51817
|
+
### Visual Identity Gate
|
|
51818
|
+
|
|
51819
|
+
<HARD-GATE>
|
|
51820
|
+
Before writing ANY composition HTML, you MUST have a visual identity defined. Do NOT write compositions with default or generic colors.
|
|
51821
|
+
|
|
51822
|
+
Check in this order:
|
|
51823
|
+
|
|
51824
|
+
1. **DESIGN.md exists in the project?** \u2192 Read it. Use its exact colors, fonts, motion rules, and "What NOT to Do" constraints.
|
|
51825
|
+
2. **visual-style.md exists?** \u2192 Read it. Apply its \`style_prompt_full\` and structured fields. (Note: \`visual-style.md\` is a project-specific file. \`visual-styles.md\` is the style library with 8 named presets \u2014 different files.)
|
|
51826
|
+
3. **User named a style** (e.g., "Swiss Pulse", "dark and techy", "luxury brand")? \u2192 Read [visual-styles.md](./visual-styles.md) for the 8 named presets. Generate a minimal DESIGN.md with: \`## Style Prompt\` (one paragraph), \`## Colors\` (3-5 hex values with roles), \`## Typography\` (1-2 font families), \`## What NOT to Do\` (3-5 anti-patterns).
|
|
51827
|
+
4. **None of the above?** \u2192 Ask 3 questions before writing any HTML:
|
|
51828
|
+
- What's the mood? (explosive / cinematic / fluid / technical / chaotic / warm)
|
|
51829
|
+
- Light or dark canvas?
|
|
51830
|
+
- Any specific brand colors, fonts, or visual references?
|
|
51831
|
+
Then generate a minimal DESIGN.md from the answers.
|
|
51832
|
+
|
|
51833
|
+
Every composition must trace its palette and typography back to a DESIGN.md, visual-style.md, or explicit user direction. If you're reaching for \`#333\`, \`#3b82f6\`, or \`Roboto\` \u2014 you skipped this step.
|
|
51834
|
+
</HARD-GATE>
|
|
51835
|
+
|
|
51836
|
+
For motion defaults, sizing, entrance patterns, and easing \u2014 follow [house-style.md](./house-style.md). The house style handles HOW things move. The DESIGN.md handles WHAT things look like.
|
|
51837
|
+
|
|
51838
|
+
## Layout Before Animation
|
|
51839
|
+
|
|
51840
|
+
Position every element where it should be at its **most visible moment** \u2014 the frame where it's fully entered, correctly placed, and not yet exiting. Write this as static HTML+CSS first. No GSAP yet.
|
|
51841
|
+
|
|
51842
|
+
**Why this matters:** If you position elements at their animated start state (offscreen, scaled to 0, opacity 0) and tween them to where you think they should land, you're guessing the final layout. Overlaps are invisible until the video renders. By building the end state first, you can see and fix layout problems before adding any motion.
|
|
51843
|
+
|
|
51844
|
+
### The process
|
|
51845
|
+
|
|
51846
|
+
1. **Identify the hero frame** for each scene \u2014 the moment when the most elements are simultaneously visible. This is the layout you build.
|
|
51847
|
+
2. **Write static CSS** for that frame. The \`.scene-content\` container MUST fill the full scene using \`width: 100%; height: 100%; padding: Npx;\` with \`display: flex; flex-direction: column; gap: Npx; box-sizing: border-box\`. Use padding to push content inward \u2014 NEVER \`position: absolute; top: Npx\` on a content container. Absolute-positioned content containers overflow when content is taller than the remaining space. Reserve \`position: absolute\` for decoratives only.
|
|
51848
|
+
3. **Add entrances with \`gsap.from()\`** \u2014 animate FROM offscreen/invisible TO the CSS position. The CSS position is the ground truth; the tween describes the journey to get there.
|
|
51849
|
+
4. **Add exits with \`gsap.to()\`** \u2014 animate TO offscreen/invisible FROM the CSS position.
|
|
51850
|
+
|
|
51851
|
+
### Example
|
|
51852
|
+
|
|
51853
|
+
\`\`\`css
|
|
51854
|
+
/* scene-content fills the scene, padding positions content */
|
|
51855
|
+
.scene-content {
|
|
51856
|
+
display: flex;
|
|
51857
|
+
flex-direction: column;
|
|
51858
|
+
justify-content: center;
|
|
51859
|
+
width: 100%;
|
|
51860
|
+
height: 100%;
|
|
51861
|
+
padding: 120px 160px;
|
|
51862
|
+
gap: 24px;
|
|
51863
|
+
box-sizing: border-box;
|
|
51864
|
+
}
|
|
51865
|
+
.title {
|
|
51866
|
+
font-size: 120px;
|
|
51867
|
+
}
|
|
51868
|
+
.subtitle {
|
|
51869
|
+
font-size: 42px;
|
|
51870
|
+
}
|
|
51871
|
+
/* Container fills any scene size (1920x1080, 1080x1920, etc).
|
|
51872
|
+
Padding positions content. Flex + gap handles spacing. */
|
|
51873
|
+
\`\`\`
|
|
51874
|
+
|
|
51875
|
+
**WRONG \u2014 hardcoded dimensions and absolute positioning:**
|
|
51876
|
+
|
|
51877
|
+
\`\`\`css
|
|
51878
|
+
.scene-content {
|
|
51879
|
+
position: absolute;
|
|
51880
|
+
top: 200px;
|
|
51881
|
+
left: 160px;
|
|
51882
|
+
width: 1920px;
|
|
51883
|
+
height: 1080px;
|
|
51884
|
+
display: flex; /* ... */
|
|
51885
|
+
}
|
|
51886
|
+
\`\`\`
|
|
51887
|
+
|
|
51888
|
+
\`\`\`js
|
|
51889
|
+
// Step 3: Animate INTO those positions
|
|
51890
|
+
tl.from(".title", { y: 60, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
|
|
51891
|
+
tl.from(".subtitle", { y: 40, opacity: 0, duration: 0.5, ease: "power3.out" }, 0.2);
|
|
51892
|
+
tl.from(".logo", { scale: 0.8, opacity: 0, duration: 0.4, ease: "power2.out" }, 0.3);
|
|
51893
|
+
|
|
51894
|
+
// Step 4: Animate OUT from those positions
|
|
51895
|
+
tl.to(".title", { y: -40, opacity: 0, duration: 0.4, ease: "power2.in" }, 3);
|
|
51896
|
+
tl.to(".subtitle", { y: -30, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.1);
|
|
51897
|
+
tl.to(".logo", { scale: 0.9, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.2);
|
|
51898
|
+
\`\`\`
|
|
51899
|
+
|
|
51900
|
+
### When elements share space across time
|
|
51901
|
+
|
|
51902
|
+
If element A exits before element B enters in the same area, both should have correct CSS positions for their respective hero frames. The timeline ordering guarantees they never visually coexist \u2014 but if you skip the layout step, you won't catch the case where they accidentally overlap due to a timing error.
|
|
51903
|
+
|
|
51904
|
+
### What counts as intentional overlap
|
|
51905
|
+
|
|
51906
|
+
Layered effects (glow behind text, shadow elements, background patterns) and z-stacked designs (card stacks, depth layers) are intentional. The layout step is about catching **unintentional** overlap \u2014 two headlines landing on top of each other, a stat covering a label, content bleeding off-frame.
|
|
51907
|
+
|
|
51908
|
+
## Data Attributes
|
|
51909
|
+
|
|
51910
|
+
### All Clips
|
|
51911
|
+
|
|
51912
|
+
| Attribute | Required | Values |
|
|
51913
|
+
| ------------------ | --------------------------------- | ------------------------------------------------------ |
|
|
51914
|
+
| \`id\` | Yes | Unique identifier |
|
|
51915
|
+
| \`data-start\` | Yes | Seconds or clip ID reference (\`"el-1"\`, \`"intro + 2"\`) |
|
|
51916
|
+
| \`data-duration\` | Required for img/div/compositions | Seconds. Video/audio defaults to media duration. |
|
|
51917
|
+
| \`data-track-index\` | Yes | Integer. Same-track clips cannot overlap. |
|
|
51918
|
+
| \`data-media-start\` | No | Trim offset into source (seconds) |
|
|
51919
|
+
| \`data-volume\` | No | 0-1 (default 1) |
|
|
51920
|
+
|
|
51921
|
+
\`data-track-index\` does **not** affect visual layering \u2014 use CSS \`z-index\`.
|
|
51922
|
+
|
|
51923
|
+
### Composition Clips
|
|
51924
|
+
|
|
51925
|
+
| Attribute | Required | Values |
|
|
51926
|
+
| ---------------------------- | -------- | -------------------------------------------- |
|
|
51927
|
+
| \`data-composition-id\` | Yes | Unique composition ID |
|
|
51928
|
+
| \`data-start\` | Yes | Start time (root composition: use \`"0"\`) |
|
|
51929
|
+
| \`data-duration\` | Yes | Takes precedence over GSAP timeline duration |
|
|
51930
|
+
| \`data-width\` / \`data-height\` | Yes | Pixel dimensions (1920x1080 or 1080x1920) |
|
|
51931
|
+
| \`data-composition-src\` | No | Path to external HTML file |
|
|
51932
|
+
|
|
51933
|
+
## Composition Structure
|
|
51934
|
+
|
|
51935
|
+
Sub-compositions loaded via \`data-composition-src\` use a \`<template>\` wrapper. **Standalone compositions (the main index.html) do NOT use \`<template>\`** \u2014 they put the \`data-composition-id\` div directly in \`<body>\`. Using \`<template>\` on a standalone file hides all content from the browser and breaks rendering.
|
|
51936
|
+
|
|
51937
|
+
Sub-composition structure:
|
|
51938
|
+
|
|
51939
|
+
\`\`\`html
|
|
51940
|
+
<template id="my-comp-template">
|
|
51941
|
+
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
|
|
51942
|
+
<!-- content -->
|
|
51943
|
+
<style>
|
|
51944
|
+
[data-composition-id="my-comp"] {
|
|
51945
|
+
/* scoped styles */
|
|
51946
|
+
}
|
|
51947
|
+
</style>
|
|
51948
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
51949
|
+
<script>
|
|
51950
|
+
window.__timelines = window.__timelines || {};
|
|
51951
|
+
const tl = gsap.timeline({ paused: true });
|
|
51952
|
+
// tweens...
|
|
51953
|
+
window.__timelines["my-comp"] = tl;
|
|
51954
|
+
</script>
|
|
51955
|
+
</div>
|
|
51956
|
+
</template>
|
|
51957
|
+
\`\`\`
|
|
51958
|
+
|
|
51959
|
+
Load in root: \`<div id="el-1" data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="10" data-track-index="1"></div>\`
|
|
51960
|
+
|
|
51961
|
+
## Video and Audio
|
|
51962
|
+
|
|
51963
|
+
Video must be \`muted playsinline\`. Audio is always a separate \`<audio>\` element:
|
|
51964
|
+
|
|
51965
|
+
\`\`\`html
|
|
51966
|
+
<video
|
|
51967
|
+
id="el-v"
|
|
51968
|
+
data-start="0"
|
|
51969
|
+
data-duration="30"
|
|
51970
|
+
data-track-index="0"
|
|
51971
|
+
src="video.mp4"
|
|
51972
|
+
muted
|
|
51973
|
+
playsinline
|
|
51974
|
+
></video>
|
|
51975
|
+
<audio
|
|
51976
|
+
id="el-a"
|
|
51977
|
+
data-start="0"
|
|
51978
|
+
data-duration="30"
|
|
51979
|
+
data-track-index="2"
|
|
51980
|
+
src="video.mp4"
|
|
51981
|
+
data-volume="1"
|
|
51982
|
+
></audio>
|
|
51983
|
+
\`\`\`
|
|
51984
|
+
|
|
51985
|
+
## Timeline Contract
|
|
51986
|
+
|
|
51987
|
+
- All timelines start \`{ paused: true }\` \u2014 the player controls playback
|
|
51988
|
+
- Register every timeline: \`window.__timelines["<composition-id>"] = tl\`
|
|
51989
|
+
- Framework auto-nests sub-timelines \u2014 do NOT manually add them
|
|
51990
|
+
- Duration comes from \`data-duration\`, not from GSAP timeline length
|
|
51991
|
+
- Never create empty tweens to set duration
|
|
51992
|
+
|
|
51993
|
+
## Rules (Non-Negotiable)
|
|
51994
|
+
|
|
51995
|
+
**Deterministic:** No \`Math.random()\`, \`Date.now()\`, or time-based logic. Use a seeded PRNG if you need pseudo-random values (e.g. mulberry32).
|
|
51996
|
+
|
|
51997
|
+
**GSAP:** Only animate visual properties (\`opacity\`, \`x\`, \`y\`, \`scale\`, \`rotation\`, \`color\`, \`backgroundColor\`, \`borderRadius\`, transforms). Do NOT animate \`visibility\`, \`display\`, or call \`video.play()\`/\`audio.play()\`.
|
|
51998
|
+
|
|
51999
|
+
**Animation conflicts:** Never animate the same property on the same element from multiple timelines simultaneously.
|
|
52000
|
+
|
|
52001
|
+
**No \`repeat: -1\`:** Infinite-repeat timelines break the capture engine. Calculate the exact repeat count from composition duration: \`repeat: Math.ceil(duration / cycleDuration) - 1\`.
|
|
52002
|
+
|
|
52003
|
+
**Synchronous timeline construction:** Never build timelines inside \`async\`/\`await\`, \`setTimeout\`, or Promises. The capture engine reads \`window.__timelines\` synchronously after page load. Fonts are embedded by the compiler, so they're available immediately \u2014 no need to wait for font loading.
|
|
52004
|
+
|
|
52005
|
+
**Never do:**
|
|
52006
|
+
|
|
52007
|
+
1. Forget \`window.__timelines\` registration
|
|
52008
|
+
2. Use video for audio \u2014 always muted video + separate \`<audio>\`
|
|
52009
|
+
3. Nest video inside a timed div \u2014 use a non-timed wrapper
|
|
52010
|
+
4. Use \`data-layer\` (use \`data-track-index\`) or \`data-end\` (use \`data-duration\`)
|
|
52011
|
+
5. Animate video element dimensions \u2014 animate a wrapper div
|
|
52012
|
+
6. Call play/pause/seek on media \u2014 framework owns playback
|
|
52013
|
+
7. Create a top-level container without \`data-composition-id\`
|
|
52014
|
+
8. Use \`repeat: -1\` on any timeline or tween \u2014 always finite repeats
|
|
52015
|
+
9. Build timelines asynchronously (inside \`async\`, \`setTimeout\`, \`Promise\`)
|
|
52016
|
+
10. Use \`gsap.set()\` on clip elements from later scenes \u2014 they don't exist in the DOM at page load. Use \`tl.set(selector, vars, timePosition)\` inside the timeline at or after the clip's \`data-start\` time instead.
|
|
52017
|
+
11. Use \`<br>\` in content text \u2014 forced line breaks don't account for actual rendered font width. Text that wraps naturally + a \`<br>\` produces an extra unwanted break, causing overlap. Let text wrap via \`max-width\` instead. Exception: short display titles where each word is deliberately on its own line (e.g., "THE\\nIMMORTAL\\nGAME" at 130px).
|
|
52018
|
+
|
|
52019
|
+
## Scene Transitions (Non-Negotiable)
|
|
52020
|
+
|
|
52021
|
+
Every multi-scene composition MUST follow ALL of these rules. Violating any one of them is a broken composition.
|
|
52022
|
+
|
|
52023
|
+
1. **ALWAYS use transitions between scenes.** No jump cuts. No exceptions.
|
|
52024
|
+
2. **ALWAYS use entrance animations on every scene.** Every element animates IN via \`gsap.from()\`. No element may appear fully-formed. If a scene has 5 elements, it needs 5 entrance tweens.
|
|
52025
|
+
3. **NEVER use exit animations** except on the final scene. This means: NO \`gsap.to()\` that animates opacity to 0, y offscreen, scale to 0, or any other "out" animation before a transition fires. The transition IS the exit. The outgoing scene's content MUST be fully visible at the moment the transition starts.
|
|
52026
|
+
4. **Final scene only:** The last scene may fade elements out (e.g., fade to black). This is the ONLY scene where \`gsap.to(..., { opacity: 0 })\` is allowed.
|
|
52027
|
+
|
|
52028
|
+
**WRONG \u2014 exit animation before transition:**
|
|
52029
|
+
|
|
52030
|
+
\`\`\`js
|
|
52031
|
+
// BANNED \u2014 this empties the scene before the transition can use it
|
|
52032
|
+
tl.to("#s1-title", { opacity: 0, y: -40, duration: 0.4 }, 6.5);
|
|
52033
|
+
tl.to("#s1-subtitle", { opacity: 0, duration: 0.3 }, 6.7);
|
|
52034
|
+
// transition fires on empty frame
|
|
52035
|
+
\`\`\`
|
|
52036
|
+
|
|
52037
|
+
**RIGHT \u2014 entrance only, transition handles exit:**
|
|
52038
|
+
|
|
52039
|
+
\`\`\`js
|
|
52040
|
+
// Scene 1 entrance animations
|
|
52041
|
+
tl.from("#s1-title", { y: 50, opacity: 0, duration: 0.7, ease: "power3.out" }, 0.3);
|
|
52042
|
+
tl.from("#s1-subtitle", { y: 30, opacity: 0, duration: 0.5, ease: "power2.out" }, 0.6);
|
|
52043
|
+
// NO exit tweens \u2014 transition at 7.2s handles the scene change
|
|
52044
|
+
// Scene 2 entrance animations
|
|
52045
|
+
tl.from("#s2-heading", { x: -40, opacity: 0, duration: 0.6, ease: "expo.out" }, 8.0);
|
|
52046
|
+
\`\`\`
|
|
52047
|
+
|
|
52048
|
+
## Animation Guardrails
|
|
52049
|
+
|
|
52050
|
+
- Offset first animation 0.1-0.3s (not t=0)
|
|
52051
|
+
- Vary eases across entrance tweens \u2014 use at least 3 different eases per scene
|
|
52052
|
+
- Don't repeat an entrance pattern within a scene
|
|
52053
|
+
- Avoid full-screen linear gradients on dark backgrounds (H.264 banding \u2014 use radial or solid + localized glow)
|
|
52054
|
+
- 60px+ headlines, 20px+ body, 16px+ data labels for rendered video
|
|
52055
|
+
- \`font-variant-numeric: tabular-nums\` on number columns
|
|
52056
|
+
|
|
52057
|
+
When no \`visual-style.md\` or animation direction is provided, follow [house-style.md](./house-style.md) for aesthetic defaults.
|
|
52058
|
+
|
|
52059
|
+
## Typography and Assets
|
|
52060
|
+
|
|
52061
|
+
- **Fonts:** Just write the \`font-family\` you want in CSS \u2014 the compiler embeds supported fonts automatically. If a font isn't supported, the compiler warns.
|
|
52062
|
+
- Add \`crossorigin="anonymous"\` to external media
|
|
52063
|
+
- For dynamic text overflow, use \`window.__hyperframes.fitTextFontSize(text, { maxWidth, fontFamily, fontWeight })\`
|
|
52064
|
+
- All files live at the project root alongside \`index.html\`; sub-compositions use \`../\`
|
|
52065
|
+
|
|
52066
|
+
## Editing Existing Compositions
|
|
52067
|
+
|
|
52068
|
+
- Read the full composition first \u2014 match existing fonts, colors, animation patterns
|
|
52069
|
+
- Only change what was requested
|
|
52070
|
+
- Preserve timing of unrelated clips
|
|
52071
|
+
|
|
52072
|
+
## Output Checklist
|
|
52073
|
+
|
|
52074
|
+
- [ ] \`npx hyperframes lint\` and \`npx hyperframes validate\` both pass
|
|
52075
|
+
- [ ] \`npx hyperframes inspect\` passes, or every reported overflow is intentionally marked
|
|
52076
|
+
- [ ] Contrast warnings addressed (see Quality Checks below)
|
|
52077
|
+
- [ ] Layout issues addressed (see Quality Checks below)
|
|
52078
|
+
- [ ] Animation choreography verified (see Quality Checks below)
|
|
52079
|
+
|
|
52080
|
+
## Quality Checks
|
|
52081
|
+
|
|
52082
|
+
### Visual Inspect
|
|
52083
|
+
|
|
52084
|
+
\`hyperframes inspect\` runs the composition in headless Chrome, seeks through the timeline, and maps visual layout issues with timestamps, selectors, bounding boxes, and fix hints. Run it after \`lint\` and \`validate\`:
|
|
52085
|
+
|
|
52086
|
+
\`\`\`bash
|
|
52087
|
+
npx hyperframes inspect
|
|
52088
|
+
npx hyperframes inspect --json
|
|
52089
|
+
\`\`\`
|
|
52090
|
+
|
|
52091
|
+
Failures usually mean text is spilling out of a bubble/card, a fixed-size label is clipping dynamic copy, or text has moved off the canvas. Fix by increasing container size or padding, reducing font size or letter spacing, adding a real \`max-width\` so text wraps inside the container, or using \`window.__hyperframes.fitTextFontSize(...)\` for dynamic copy.
|
|
52092
|
+
|
|
52093
|
+
Use \`--samples 15\` for dense videos and \`--at 1.5,4,7.25\` for specific hero frames. Repeated static issues are collapsed by default to avoid flooding agent context. If overflow is intentional for an entrance/exit animation, mark the element or ancestor with \`data-layout-allow-overflow\`. If a decorative element should never be audited, mark it with \`data-layout-ignore\`.
|
|
52094
|
+
|
|
52095
|
+
\`hyperframes layout\` is the compatibility alias for the same check.
|
|
52096
|
+
|
|
52097
|
+
### Contrast
|
|
52098
|
+
|
|
52099
|
+
\`hyperframes validate\` runs a WCAG contrast audit by default. It seeks to 5 timestamps, screenshots the page, samples background pixels behind every text element, and computes contrast ratios. Failures appear as warnings:
|
|
52100
|
+
|
|
52101
|
+
\`\`\`
|
|
52102
|
+
\u26A0 WCAG AA contrast warnings (3):
|
|
52103
|
+
\xB7 .subtitle "secondary text" \u2014 2.67:1 (need 4.5:1, t=5.3s)
|
|
52104
|
+
\`\`\`
|
|
52105
|
+
|
|
52106
|
+
If warnings appear:
|
|
52107
|
+
|
|
52108
|
+
- On dark backgrounds: brighten the failing color until it clears 4.5:1 (normal text) or 3:1 (large text, 24px+ or 19px+ bold)
|
|
52109
|
+
- On light backgrounds: darken it
|
|
52110
|
+
- Stay within the palette family \u2014 don't invent a new color, adjust the existing one
|
|
52111
|
+
- Re-run \`hyperframes validate\` until clean
|
|
52112
|
+
|
|
52113
|
+
Use \`--no-contrast\` to skip if iterating rapidly and you'll check later.
|
|
52114
|
+
|
|
52115
|
+
### Animation Map
|
|
52116
|
+
|
|
52117
|
+
After authoring animations, run the animation map to verify choreography:
|
|
52118
|
+
|
|
52119
|
+
\`\`\`bash
|
|
52120
|
+
node skills/hyperframes/scripts/animation-map.mjs <composition-dir> \\
|
|
52121
|
+
--out <composition-dir>/.hyperframes/anim-map
|
|
52122
|
+
\`\`\`
|
|
52123
|
+
|
|
52124
|
+
Outputs a single \`animation-map.json\` with:
|
|
52125
|
+
|
|
52126
|
+
- **Per-tween summaries**: \`"#card1 animates opacity+y over 0.50s. moves 23px up. fades in. ends at (120, 200)"\`
|
|
52127
|
+
- **ASCII timeline**: Gantt chart of all tweens across the composition duration
|
|
52128
|
+
- **Stagger detection**: reports actual intervals (\`"3 elements stagger at 120ms"\`)
|
|
52129
|
+
- **Dead zones**: periods over 1s with no animation \u2014 intentional hold or missing entrance?
|
|
52130
|
+
- **Element lifecycles**: first/last animation time, final visibility
|
|
52131
|
+
- **Scene snapshots**: visible element state at 5 key timestamps
|
|
52132
|
+
- **Flags**: \`offscreen\`, \`collision\`, \`invisible\`, \`paced-fast\` (under 0.2s), \`paced-slow\` (over 2s)
|
|
52133
|
+
|
|
52134
|
+
Read the JSON. Scan summaries for anything unexpected. Check every flag \u2014 fix or justify. Verify the timeline shows the intended choreography rhythm. Re-run after fixes.
|
|
52135
|
+
|
|
52136
|
+
Skip on small edits (fixing a color, adjusting one duration). Run on new compositions and significant animation changes.
|
|
52137
|
+
|
|
52138
|
+
---
|
|
52139
|
+
|
|
52140
|
+
## References (loaded on demand)
|
|
52141
|
+
|
|
52142
|
+
- **[references/captions.md](references/captions.md)** \u2014 Captions, subtitles, lyrics, karaoke synced to audio. Tone-adaptive style detection, per-word styling, text overflow prevention, caption exit guarantees, word grouping. Read when adding any text synced to audio timing.
|
|
52143
|
+
- **[references/tts.md](references/tts.md)** \u2014 Text-to-speech with Kokoro-82M. Voice selection, speed tuning, TTS+captions workflow. Read when generating narration or voiceover.
|
|
52144
|
+
- **[references/audio-reactive.md](references/audio-reactive.md)** \u2014 Audio-reactive animation: map frequency bands and amplitude to GSAP properties. Read when visuals should respond to music, voice, or sound.
|
|
52145
|
+
- **[references/css-patterns.md](references/css-patterns.md)** \u2014 CSS+GSAP marker highlighting: highlight, circle, burst, scribble, sketchout. Deterministic, fully seekable. Read when adding visual emphasis to text.
|
|
52146
|
+
- **[references/typography.md](references/typography.md)** \u2014 Typography: font pairing, OpenType features, dark-background adjustments, font discovery script. **Always read** \u2014 every composition has text.
|
|
52147
|
+
- **[references/motion-principles.md](references/motion-principles.md)** \u2014 Motion design principles: easing as emotion, timing as weight, choreography as hierarchy, scene pacing, ambient motion, anti-patterns. Read when choreographing GSAP animations.
|
|
52148
|
+
- **[visual-styles.md](visual-styles.md)** \u2014 8 named visual styles (Swiss Pulse, Velvet Standard, Deconstructed, Maximalist Type, Data Drift, Soft Signal, Folk Frequency, Shadow Cut) with hex palettes, GSAP easing signatures, and shader pairings. Read when user names a style or when generating DESIGN.md.
|
|
52149
|
+
- **[house-style.md](house-style.md)** \u2014 Default motion, sizing, and color palettes when no style is specified.
|
|
52150
|
+
- **[patterns.md](patterns.md)** \u2014 PiP, title cards, slide show patterns.
|
|
52151
|
+
- **[data-in-motion.md](data-in-motion.md)** \u2014 Data, stats, and infographic patterns.
|
|
52152
|
+
- **[references/transcript-guide.md](references/transcript-guide.md)** \u2014 Transcription commands, whisper models, external APIs, troubleshooting.
|
|
52153
|
+
- **[references/dynamic-techniques.md](references/dynamic-techniques.md)** \u2014 Dynamic caption animation techniques (karaoke, clip-path, slam, scatter, elastic, 3D).
|
|
52154
|
+
|
|
52155
|
+
- **[references/transitions.md](references/transitions.md)** \u2014 Scene transitions: crossfades, wipes, reveals, shader transitions. Energy/mood selection, CSS vs WebGL guidance. **Always read for multi-scene compositions** \u2014 scenes without transitions feel like jump cuts.
|
|
52156
|
+
- [transitions/catalog.md](references/transitions/catalog.md) \u2014 Hard rules, scene template, and routing to per-type implementation code.
|
|
52157
|
+
- Shader transitions are in \`@hyperframes/shader-transitions\` (\`packages/shader-transitions/\`) \u2014 read package source, not skill files.
|
|
52158
|
+
|
|
52159
|
+
GSAP patterns and effects are in the \`/gsap\` skill.
|
|
52160
|
+
`;
|
|
52161
|
+
HOUSE_STYLE_MD = `# House Style
|
|
52162
|
+
|
|
52163
|
+
Creative direction for compositions when no \`visual-style.md\` is provided. These are starting points \u2014 override anything that doesn't serve the content.
|
|
52164
|
+
|
|
52165
|
+
## Before Writing HTML
|
|
52166
|
+
|
|
52167
|
+
1. **Interpret the prompt.** Generate real content. A recipe lists real ingredients. A HUD has real readouts.
|
|
52168
|
+
2. **Pick a palette.** Light or dark? Declare bg, fg, accent before writing code.
|
|
52169
|
+
3. **Pick typefaces.** Run the font discovery script in [references/typography.md](references/typography.md) \u2014 or pick a font you already know that fits the theme. The script broadens your options; it's not the only source.
|
|
52170
|
+
|
|
52171
|
+
## Lazy Defaults to Question
|
|
52172
|
+
|
|
52173
|
+
These patterns are AI design tells \u2014 the first thing every LLM reaches for. If you're about to use one, pause and ask: is this a deliberate choice for THIS content, or am I defaulting?
|
|
52174
|
+
|
|
52175
|
+
- Gradient text (\`background-clip: text\` + gradient)
|
|
52176
|
+
- Left-edge accent stripes on cards/callouts
|
|
52177
|
+
- Cyan-on-dark / purple-to-blue gradients / neon accents
|
|
52178
|
+
- Pure \`#000\` or \`#fff\` (tint toward your accent hue instead)
|
|
52179
|
+
- Identical card grids (same-size cards repeated)
|
|
52180
|
+
- Everything centered with equal weight (lead the eye somewhere)
|
|
52181
|
+
- Banned fonts (see [references/typography.md](references/typography.md) for full list)
|
|
52182
|
+
|
|
52183
|
+
If the content genuinely calls for one of these \u2014 centered layout for a solemn closing, cards for a real product UI mockup, a banned font because it's the perfect thematic match \u2014 use it. The goal is intentionality, not avoidance.
|
|
52184
|
+
|
|
52185
|
+
## Color
|
|
52186
|
+
|
|
52187
|
+
- Match light/dark to content: food, wellness, kids \u2192 light. Tech, cinema, finance \u2192 dark.
|
|
52188
|
+
- One accent hue. Same background across all scenes.
|
|
52189
|
+
- Tint neutrals toward your accent (even subtle warmth/coolness beats dead gray).
|
|
52190
|
+
- **Contrast:** enforced by \`hyperframes validate\` (WCAG AA). Text must be readable with decoratives removed.
|
|
52191
|
+
- Declare palette up front. Don't invent colors per-element.
|
|
52192
|
+
|
|
52193
|
+
## Background Layer
|
|
52194
|
+
|
|
52195
|
+
Every scene needs visual depth \u2014 persistent decorative elements that stay visible while content animates in. Without these, scenes feel empty during entrance staggering.
|
|
52196
|
+
|
|
52197
|
+
Ideas (mix and match, 2-5 per scene):
|
|
52198
|
+
|
|
52199
|
+
- Radial glows (accent-tinted, low opacity, breathing scale)
|
|
52200
|
+
- Ghost text (theme words at 3-8% opacity, very large, slow drift)
|
|
52201
|
+
- Accent lines (hairline rules, subtle pulse)
|
|
52202
|
+
- Grain/noise overlay, geometric shapes, grid patterns
|
|
52203
|
+
- Thematic decoratives (orbit rings for space, vinyl grooves for music, grid lines for data)
|
|
52204
|
+
|
|
52205
|
+
All decoratives should have slow ambient GSAP animation \u2014 breathing, drift, pulse. Static decoratives feel dead.
|
|
52206
|
+
|
|
52207
|
+
## Motion
|
|
52208
|
+
|
|
52209
|
+
See [references/motion-principles.md](references/motion-principles.md) for full rules. Quick: 0.3\u20130.6s, vary eases, combine transforms on entrances, overlap entries.
|
|
52210
|
+
|
|
52211
|
+
## Typography
|
|
52212
|
+
|
|
52213
|
+
See [references/typography.md](references/typography.md) for full rules. Quick: 700-900 headlines / 300-400 body, serif + sans (not two sans), 60px+ headlines / 20px+ body.
|
|
52214
|
+
|
|
52215
|
+
## Palettes
|
|
52216
|
+
|
|
52217
|
+
Declare one background, one foreground, one accent before writing HTML.
|
|
52218
|
+
|
|
52219
|
+
| Category | Use for | File |
|
|
52220
|
+
| ----------------- | --------------------------------------------- | ---------------------------------------------------------- |
|
|
52221
|
+
| Bold / Energetic | Product launches, social media, announcements | [palettes/bold-energetic.md](palettes/bold-energetic.md) |
|
|
52222
|
+
| Warm / Editorial | Storytelling, documentaries, case studies | [palettes/warm-editorial.md](palettes/warm-editorial.md) |
|
|
52223
|
+
| Dark / Premium | Tech, finance, luxury, cinematic | [palettes/dark-premium.md](palettes/dark-premium.md) |
|
|
52224
|
+
| Clean / Corporate | Explainers, tutorials, presentations | [palettes/clean-corporate.md](palettes/clean-corporate.md) |
|
|
52225
|
+
| Nature / Earth | Sustainability, outdoor, organic | [palettes/nature-earth.md](palettes/nature-earth.md) |
|
|
52226
|
+
| Neon / Electric | Gaming, tech, nightlife | [palettes/neon-electric.md](palettes/neon-electric.md) |
|
|
52227
|
+
| Pastel / Soft | Fashion, beauty, lifestyle, wellness | [palettes/pastel-soft.md](palettes/pastel-soft.md) |
|
|
52228
|
+
| Jewel / Rich | Luxury, events, sophisticated | [palettes/jewel-rich.md](palettes/jewel-rich.md) |
|
|
52229
|
+
| Monochrome | Dramatic, typography-focused | [palettes/monochrome.md](palettes/monochrome.md) |
|
|
52230
|
+
|
|
52231
|
+
Or derive from OKLCH \u2014 pick a hue, build bg/fg/accent at different lightnesses, tint everything toward that hue.
|
|
52232
|
+
`;
|
|
52233
|
+
MOTION_PRINCIPLES_MD = `# Motion Principles
|
|
52234
|
+
|
|
52235
|
+
## Guardrails
|
|
52236
|
+
|
|
52237
|
+
You know these rules but you violate them. Stop.
|
|
52238
|
+
|
|
52239
|
+
- **Don't use the same ease on every tween.** You default to \`power2.out\` on everything. Vary eases like you vary font weights \u2014 no more than 2 independent tweens with the same ease in a scene.
|
|
52240
|
+
- **Don't use the same speed on everything.** You default to 0.4-0.5s for everything. The slowest scene should be 3\xD7 slower than the fastest. Vary duration deliberately.
|
|
52241
|
+
- **Don't enter everything from the same direction.** You default to \`y: 30, opacity: 0\` on every element. Vary: from left, from right, from scale, opacity-only, letter-spacing.
|
|
52242
|
+
- **Don't use the same stagger on every scene.** Each scene needs its own rhythm.
|
|
52243
|
+
- **Don't use ambient zoom on every scene.** Pick different ambient motion per scene: slow pan, subtle rotation, scale push, color shift, or nothing. Stillness after motion is powerful.
|
|
52244
|
+
- **Don't start at t=0.** Offset the first animation 0.1-0.3s. Zero-delay feels like a jump cut.
|
|
52245
|
+
|
|
52246
|
+
## What You Don't Do Without Being Told
|
|
52247
|
+
|
|
52248
|
+
### Easing is emotion, not technique
|
|
52249
|
+
|
|
52250
|
+
The transition is the verb. The easing is the adverb. A slide-in with \`expo.out\` = confident. With \`sine.inOut\` = dreamy. With \`elastic.out\` = playful. Same motion, different meaning. Choose the adverb deliberately.
|
|
52251
|
+
|
|
52252
|
+
**Direction rules \u2014 these are not optional:**
|
|
52253
|
+
|
|
52254
|
+
- \`.out\` for elements entering. Starts fast, decelerates. Feels responsive. This is your default.
|
|
52255
|
+
- \`.in\` for elements leaving. Starts slow, accelerates away. Throws them off.
|
|
52256
|
+
- \`.inOut\` for elements moving between positions.
|
|
52257
|
+
|
|
52258
|
+
You get this backwards constantly. Ease-in for entrances feels sluggish. Ease-out for exits feels reluctant.
|
|
52259
|
+
|
|
52260
|
+
### Speed communicates weight
|
|
52261
|
+
|
|
52262
|
+
- Fast (0.15-0.3s) \u2014 energy, urgency, confidence
|
|
52263
|
+
- Medium (0.3-0.5s) \u2014 professional, most content
|
|
52264
|
+
- Slow (0.5-0.8s) \u2014 gravity, luxury, contemplation
|
|
52265
|
+
- Very slow (0.8-2.0s) \u2014 cinematic, emotional, atmospheric
|
|
52266
|
+
|
|
52267
|
+
### Scene structure: build / breathe / resolve
|
|
52268
|
+
|
|
52269
|
+
Every scene has three phases. You dump everything in the build and leave nothing for breathe or resolve.
|
|
52270
|
+
|
|
52271
|
+
- **Build (0-30%)** \u2014 elements enter, staggered. Don't dump everything at once.
|
|
52272
|
+
- **Breathe (30-70%)** \u2014 content visible, alive with ONE ambient motion.
|
|
52273
|
+
- **Resolve (70-100%)** \u2014 exit or decisive end. Exits are faster than entrances.
|
|
52274
|
+
|
|
52275
|
+
### Transitions are meaning
|
|
52276
|
+
|
|
52277
|
+
- **Crossfade** = "this continues"
|
|
52278
|
+
- **Hard cut** = "wake up" / disruption
|
|
52279
|
+
- **Slow dissolve** = "drift with me"
|
|
52280
|
+
|
|
52281
|
+
You crossfade everything. Use hard cuts for disruption and register shifts.
|
|
52282
|
+
|
|
52283
|
+
### Choreography is hierarchy
|
|
52284
|
+
|
|
52285
|
+
The element that moves first is perceived as most important. Stagger in order of importance, not DOM order. Don't wait for completion \u2014 overlap entries. Total stagger sequence under 500ms regardless of item count.
|
|
52286
|
+
|
|
52287
|
+
### Asymmetry
|
|
52288
|
+
|
|
52289
|
+
Entrances need longer than exits. A card takes 0.4s to appear but 0.25s to disappear.
|
|
52290
|
+
|
|
52291
|
+
## Visual Composition
|
|
52292
|
+
|
|
52293
|
+
You build for the web. Video frames are not pages.
|
|
52294
|
+
|
|
52295
|
+
- **Two focal points minimum per scene.** The eye needs somewhere to travel. Never a single text block floating in empty space.
|
|
52296
|
+
- **Fill the frame.** Hero text: 60-80% of width. You will try to use web-sized elements. Don't.
|
|
52297
|
+
- **Three layers minimum per scene.** Background treatment (glow, oversized faded type, color panel). Foreground content. Accent elements (dividers, labels, data bars).
|
|
52298
|
+
- **Background is not empty.** Radial glows, oversized faded type bleeding off-frame, subtle border panels, hairline rules. Pure solid #000 reads as "nothing loaded."
|
|
52299
|
+
- **Anchor to edges.** Pin content to left/top or right/bottom. Centered-and-floating is a web pattern.
|
|
52300
|
+
- **Split frames.** Data panel on the left, content on the right. Top bar with metadata, full-width below. Zone-based layouts, not centered stacks.
|
|
52301
|
+
- **Use structural elements.** Rules, dividers, border panels. They create paths for the eye and animate well (scaleX from 0).
|
|
52302
|
+
`;
|
|
52303
|
+
TYPOGRAPHY_MD = `# Typography
|
|
52304
|
+
|
|
52305
|
+
The compiler embeds supported fonts \u2014 just write \`font-family\` in CSS.
|
|
52306
|
+
|
|
52307
|
+
## Banned
|
|
52308
|
+
|
|
52309
|
+
Training-data defaults that every LLM reaches for. These produce monoculture across compositions.
|
|
52310
|
+
|
|
52311
|
+
Inter, Roboto, Open Sans, Noto Sans, Arimo, Lato, Source Sans, PT Sans, Nunito, Poppins, Outfit, Sora, Playfair Display, Cormorant Garamond, Bodoni Moda, EB Garamond, Cinzel, Prata, Syne
|
|
52312
|
+
|
|
52313
|
+
**Syne in particular** is the most overused "distinctive" display font. It is an instant AI design tell.
|
|
52314
|
+
|
|
52315
|
+
## Guardrails
|
|
52316
|
+
|
|
52317
|
+
You know these rules but you violate them. Stop.
|
|
52318
|
+
|
|
52319
|
+
- **Don't pair two sans-serifs.** You do this constantly \u2014 one for headlines, one for body. Cross the boundary: serif + sans, or sans + mono.
|
|
52320
|
+
- **One expressive font per scene.** You pick two interesting fonts trying to make it "better." One performs, one recedes.
|
|
52321
|
+
- **Weight contrast must be extreme.** You default to 400 vs 700. Video needs 300 vs 900. The difference must be visible in motion at a glance.
|
|
52322
|
+
- **Video sizes, not web sizes.** Body: 20px minimum. Headlines: 60px+. Data labels: 16px. You will try to use 14px. Don't.
|
|
52323
|
+
|
|
52324
|
+
## What You Don't Do Without Being Told
|
|
52325
|
+
|
|
52326
|
+
- **Tension should mean something.** Don't pattern-match pairings. Ask WHY these two fonts disagree. The pairing should embody the content's contradiction \u2014 mechanical vs human, public vs private, institutional vs personal. If you can't articulate the tension, it's arbitrary.
|
|
52327
|
+
- **Register switching.** Assign different fonts to different communicative modes \u2014 one voice for statements, another for data, another for attribution. Not hierarchy on a page. Voices in a conversation.
|
|
52328
|
+
- **Tension can live inside a single font.** A font that looks familiar but is secretly strange creates tension with the viewer's expectations, not with another font.
|
|
52329
|
+
- **One variable changed = dramatic contrast.** Same letterforms, monospaced vs proportional. Same family at different optical sizes. Changing only rhythm while everything else stays constant.
|
|
52330
|
+
- **Double personality works.** Two expressive fonts can coexist if they share an attitude (both irreverent, both precise) even when their forms are completely different.
|
|
52331
|
+
- **Time is hierarchy.** The first element to appear is the most important. In video, sequence replaces position.
|
|
52332
|
+
- **Motion is typography.** How a word enters carries as much meaning as the font. A 0.1s slam vs a 2s fade \u2014 same font, completely different message.
|
|
52333
|
+
- **Fixed reading time.** 3 seconds on screen = must be readable in 2. Fewer words, larger type.
|
|
52334
|
+
- **Tracking tighter than web.** -0.03em to -0.05em on display sizes. Video encoding compresses letter detail.
|
|
52335
|
+
|
|
52336
|
+
## Finding Fonts
|
|
52337
|
+
|
|
52338
|
+
Don't default to what you know. If the content is luxury, a grotesque sans might create more tension than the expected Didone serif. Decide the register first, then search.
|
|
52339
|
+
|
|
52340
|
+
Save this script to \`/tmp/fontquery.py\` and run with \`curl -s 'https://fonts.google.com/metadata/fonts' > /tmp/gfonts.json && python3 /tmp/fontquery.py /tmp/gfonts.json\`:
|
|
52341
|
+
|
|
52342
|
+
\`\`\`python
|
|
52343
|
+
import json, sys, random
|
|
52344
|
+
from collections import OrderedDict
|
|
52345
|
+
|
|
52346
|
+
random.seed() # true random each run
|
|
52347
|
+
|
|
52348
|
+
with open(sys.argv[1]) as f:
|
|
52349
|
+
data = json.load(f)
|
|
52350
|
+
fonts = data.get("familyMetadataList", [])
|
|
52351
|
+
|
|
52352
|
+
ban = {"Inter","Roboto","Open Sans","Noto Sans","Lato","Poppins","Source Sans 3",
|
|
52353
|
+
"PT Sans","Nunito","Outfit","Sora","Playfair Display","Cormorant Garamond",
|
|
52354
|
+
"Bodoni Moda","EB Garamond","Cinzel","Prata","Arimo","Source Sans Pro","Syne"}
|
|
52355
|
+
skip_pfx = ("Roboto","Noto ","Google Sans","Bpmf","Playwrite","Anek","BIZ ",
|
|
52356
|
+
"Nanum","Shippori","Sawarabi","Zen ","Kaisei","Kiwi ","Yuji ","Radio ")
|
|
52357
|
+
|
|
52358
|
+
def ok(f):
|
|
52359
|
+
if f["family"] in ban: return False
|
|
52360
|
+
if any(f["family"].startswith(b) for b in skip_pfx): return False
|
|
52361
|
+
if "latin" not in (f.get("subsets") or []): return False
|
|
52362
|
+
return True
|
|
52363
|
+
|
|
52364
|
+
seen = set()
|
|
52365
|
+
R = OrderedDict()
|
|
52366
|
+
|
|
52367
|
+
# Trending Sans \u2014 recent (2022+), popular (<300)
|
|
52368
|
+
R["Trending Sans"] = []
|
|
52369
|
+
for f in fonts:
|
|
52370
|
+
if not ok(f) or f["family"] in seen: continue
|
|
52371
|
+
if f.get("category") in ("Sans Serif","Display") and f.get("dateAdded","") >= "2022-01-01" and f.get("popularity",9999) < 300:
|
|
52372
|
+
R["Trending Sans"].append(f); seen.add(f["family"])
|
|
52373
|
+
|
|
52374
|
+
# Trending Serif \u2014 recent (2018+), popular (<600)
|
|
52375
|
+
R["Trending Serif"] = []
|
|
52376
|
+
for f in fonts:
|
|
52377
|
+
if not ok(f) or f["family"] in seen: continue
|
|
52378
|
+
if f.get("category") == "Serif" and f.get("dateAdded","") >= "2018-01-01" and f.get("popularity",9999) < 600:
|
|
52379
|
+
R["Trending Serif"].append(f); seen.add(f["family"])
|
|
52380
|
+
|
|
52381
|
+
# Monospace \u2014 recent (2018+), popular (<600)
|
|
52382
|
+
R["Monospace"] = []
|
|
52383
|
+
for f in fonts:
|
|
52384
|
+
if not ok(f) or f["family"] in seen: continue
|
|
52385
|
+
if f.get("category") == "Monospace" and f.get("dateAdded","") >= "2018-01-01" and f.get("popularity",9999) < 600:
|
|
52386
|
+
R["Monospace"].append(f); seen.add(f["family"])
|
|
52387
|
+
|
|
52388
|
+
# Impact & Condensed \u2014 heavy display fonts with 800+ weight
|
|
52389
|
+
R["Impact & Condensed"] = []
|
|
52390
|
+
for f in fonts:
|
|
52391
|
+
if not ok(f) or f["family"] in seen: continue
|
|
52392
|
+
has_heavy = any(k in list(f.get("fonts",{}).keys()) for k in ("800","900"))
|
|
52393
|
+
is_display = f.get("category") in ("Sans Serif","Display")
|
|
52394
|
+
if has_heavy and is_display and f.get("popularity",9999) < 400:
|
|
52395
|
+
R["Impact & Condensed"].append(f); seen.add(f["family"])
|
|
52396
|
+
|
|
52397
|
+
# Script & Handwriting \u2014 popular (<300)
|
|
52398
|
+
R["Script & Handwriting"] = []
|
|
52399
|
+
for f in fonts:
|
|
52400
|
+
if not ok(f) or f["family"] in seen: continue
|
|
52401
|
+
if f.get("category") == "Handwriting" and f.get("popularity",9999) < 300:
|
|
52402
|
+
R["Script & Handwriting"].append(f); seen.add(f["family"])
|
|
52403
|
+
|
|
52404
|
+
|
|
52405
|
+
# Randomize the top 5 in each category so the LLM doesn't always pick the same first result
|
|
52406
|
+
for cat in R:
|
|
52407
|
+
R[cat].sort(key=lambda x: x.get("popularity",9999))
|
|
52408
|
+
top5 = R[cat][:5]
|
|
52409
|
+
rest = R[cat][5:]
|
|
52410
|
+
random.shuffle(top5)
|
|
52411
|
+
R[cat] = top5 + rest
|
|
52412
|
+
limits = {"Trending Sans":15,"Trending Serif":12,"Monospace":8,
|
|
52413
|
+
"Impact & Condensed":12,"Script & Handwriting":10}
|
|
52414
|
+
for cat in R:
|
|
52415
|
+
items = R[cat][:limits.get(cat,10)]
|
|
52416
|
+
if not items: continue
|
|
52417
|
+
print(f"--- {cat} ({len(items)}) ---")
|
|
52418
|
+
for ff in items:
|
|
52419
|
+
var = "VAR" if ff.get("axes") else " "
|
|
52420
|
+
print(f' {ff.get("popularity"):4d} | {var} | {ff["family"]}')
|
|
52421
|
+
print()
|
|
52422
|
+
\`\`\`
|
|
52423
|
+
|
|
52424
|
+
Five categories: trending sans, trending serif, monospace, impact/condensed, script/handwriting. All dynamically filtered from Google Fonts metadata \u2014 no hardcoded font names. Cross classification boundaries when pairing.
|
|
52425
|
+
|
|
52426
|
+
## Selection Thinking
|
|
52427
|
+
|
|
52428
|
+
Don't pick fonts by category reflex (editorial \u2192 serif, tech \u2192 mono, modern \u2192 geometric sans). That's pattern matching, not design.
|
|
52429
|
+
|
|
52430
|
+
1. **Name the register.** What voice is the content speaking in? Institutional authority? Personal confession? Technical precision? Casual irreverence? The register narrows the field more than the category.
|
|
52431
|
+
2. **Think physically.** Imagine the font as a physical object the brand could ship \u2014 a museum exhibit caption, a hand-painted shop sign, a 1970s mainframe terminal manual, a fabric label inside a coat, a children's book printed on cheap newsprint, a tax form. Whichever physical object fits the register is pointing at the right _kind_ of typeface.
|
|
52432
|
+
3. **Reject your first instinct.** The first font that feels right is usually your training-data default for that register. If you picked it last time too, find something else.
|
|
52433
|
+
4. **Cross-check the assumption.** An editorial brief does NOT need a serif. A technical brief does NOT need a sans. A children's product does NOT need a rounded display font. The most distinctive choice often contradicts the category expectation.
|
|
52434
|
+
|
|
52435
|
+
## Similar-Font Pairing
|
|
52436
|
+
|
|
52437
|
+
Never pair two fonts that are similar but not identical \u2014 two geometric sans-serifs, two transitional serifs, two humanist sans. They create visual friction without clear hierarchy. The viewer senses something is "off" but can't articulate it. Either use one font at two weights, or pair fonts that contrast on multiple axes: serif + sans, condensed + wide, geometric + humanist.
|
|
52438
|
+
|
|
52439
|
+
## Dark Backgrounds
|
|
52440
|
+
|
|
52441
|
+
Light text on dark backgrounds creates two optical illusions you need to compensate for:
|
|
52442
|
+
|
|
52443
|
+
- **Increased apparent weight.** Light-on-dark reads heavier than dark-on-light at the same \`font-weight\`. Use 350 instead of 400 for body text. Headlines are less affected because size compensates.
|
|
52444
|
+
- **Tighter apparent spacing.** Light halos around letterforms reduce perceived gaps. Increase \`line-height\` by 0.05-0.1 beyond your light-background value. For display sizes, add 0.01em \`letter-spacing\` to counteract.
|
|
52445
|
+
|
|
52446
|
+
## OpenType Features for Data
|
|
52447
|
+
|
|
52448
|
+
Most fonts ship with OpenType features that are off by default. Turn them on for data compositions:
|
|
52449
|
+
|
|
52450
|
+
\`\`\`css
|
|
52451
|
+
/* Tabular numbers \u2014 digits align vertically in columns */
|
|
52452
|
+
.stat-value,
|
|
52453
|
+
.timer,
|
|
52454
|
+
.data-column {
|
|
52455
|
+
font-variant-numeric: tabular-nums;
|
|
52456
|
+
}
|
|
52457
|
+
|
|
52458
|
+
/* Diagonal fractions \u2014 renders 1/2 as \xBD */
|
|
52459
|
+
.recipe-amount,
|
|
52460
|
+
.ratio {
|
|
52461
|
+
font-variant-numeric: diagonal-fractions;
|
|
52462
|
+
}
|
|
52463
|
+
|
|
52464
|
+
/* Small caps for abbreviations \u2014 less visual shouting */
|
|
52465
|
+
.abbreviation,
|
|
52466
|
+
.unit {
|
|
52467
|
+
font-variant-caps: all-small-caps;
|
|
52468
|
+
}
|
|
52469
|
+
|
|
52470
|
+
/* Disable ligatures in code \u2014 fi, fl, ffi should stay separate */
|
|
52471
|
+
code,
|
|
52472
|
+
.code {
|
|
52473
|
+
font-variant-ligatures: none;
|
|
52474
|
+
}
|
|
52475
|
+
\`\`\`
|
|
52476
|
+
|
|
52477
|
+
\`tabular-nums\` is essential any time numbers are stacked vertically \u2014 stat callouts, timers, scoreboards, data tables. Without it, digits have proportional widths and columns don't align.
|
|
52478
|
+
`;
|
|
52479
|
+
TRANSITIONS_MD = `# Scene Transitions
|
|
52480
|
+
|
|
52481
|
+
A transition tells the viewer how two scenes relate. A crossfade says "this continues." A push slide says "next point." A blur crossfade says "drift with me." Choose transitions that match what the content is doing emotionally, not just technically.
|
|
52482
|
+
|
|
52483
|
+
## Animation Rules for Multi-Scene Compositions
|
|
52484
|
+
|
|
52485
|
+
These are non-negotiable for every multi-scene composition:
|
|
52486
|
+
|
|
52487
|
+
1. **Every composition uses transitions.** No exceptions. Scenes without transitions feel like jump cuts.
|
|
52488
|
+
2. **Every scene uses entrance animations.** Elements animate IN via \`gsap.from()\` \u2014 opacity, position, scale, etc. No scene should pop fully-formed onto screen.
|
|
52489
|
+
3. **Exit animations are BANNED** except on the final scene. Do NOT use \`gsap.to()\` to animate elements out before a transition fires. The transition IS the exit. Outgoing scene content must be fully visible when the transition starts \u2014 the transition handles the visual handoff.
|
|
52490
|
+
4. **Final scene exception:** The last scene MAY fade elements out (e.g., fade to black at the end of the composition). This is the only scene where exit animations are allowed.
|
|
52491
|
+
|
|
52492
|
+
## Energy \u2192 Primary Transition
|
|
52493
|
+
|
|
52494
|
+
| Energy | CSS Primary | Shader Primary | Accent | Duration | Easing |
|
|
52495
|
+
| ---------------------------------------- | ---------------------------- | ------------------------------------ | ------------------------------ | --------- | ---------------------- |
|
|
52496
|
+
| **Calm** (wellness, brand story, luxury) | Blur crossfade, focus pull | Cross-warp morph, thermal distortion | Light leak, circle iris | 0.5-0.8s | \`sine.inOut\`, \`power1\` |
|
|
52497
|
+
| **Medium** (corporate, SaaS, explainer) | Push slide, staggered blocks | Whip pan, cinematic zoom | Squeeze, vertical push | 0.3-0.5s | \`power2\`, \`power3\` |
|
|
52498
|
+
| **High** (promos, sports, music, launch) | Zoom through, overexposure | Ridged burn, glitch, chromatic split | Staggered blocks, gravity drop | 0.15-0.3s | \`power4\`, \`expo\` |
|
|
52499
|
+
|
|
52500
|
+
Pick ONE primary (60-70% of scene changes) + 1-2 accents. Never use a different transition for every scene.
|
|
52501
|
+
|
|
52502
|
+
## Mood \u2192 Transition Type
|
|
52503
|
+
|
|
52504
|
+
Think about what the transition _communicates_, not just what it looks like.
|
|
52505
|
+
|
|
52506
|
+
| Mood | Transitions | Why it works |
|
|
52507
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
52508
|
+
| **Warm / inviting** | Light leak, blur crossfade, focus pull, film burn \xB7 **Shader:** thermal distortion, light leak, cross-warp morph | Soft edges, warm color washes. Nothing sharp or mechanical. |
|
|
52509
|
+
| **Cold / clinical** | Squeeze, zoom out, blinds, shutter, grid dissolve \xB7 **Shader:** gravitational lens | Content transforms mechanically \u2014 compressed, shrunk, sliced, gridded. |
|
|
52510
|
+
| **Editorial / magazine** | Push slide, vertical push, diagonal split, shutter \xB7 **Shader:** whip pan | Like turning a page or slicing a layout. Clean directional movement. |
|
|
52511
|
+
| **Tech / futuristic** | Grid dissolve, staggered blocks, blinds, chromatic aberration \xB7 **Shader:** glitch, chromatic split | Grid dissolve is the core "data" transition. Shader glitch adds posterization + scan lines. |
|
|
52512
|
+
| **Tense / edgy** | Glitch, VHS, chromatic aberration, ripple \xB7 **Shader:** ridged burn, glitch, domain warp | Instability, distortion, digital breakdown. Ridged burn adds sharp lightning-crack edges. |
|
|
52513
|
+
| **Playful / fun** | Elastic push, 3D flip, circle iris, morph circle, clock wipe \xB7 **Shader:** ripple waves, swirl vortex | Overshoot, bounce, rotation, expansion. Swirl vortex adds organic spiral distortion. |
|
|
52514
|
+
| **Dramatic / cinematic** | Zoom through, zoom out, gravity drop, overexposure, color dip to black \xB7 **Shader:** cinematic zoom, gravitational lens, domain warp | Scale, weight, light extremes. Shader transitions add per-pixel depth. |
|
|
52515
|
+
| **Premium / luxury** | Focus pull, blur crossfade, color dip to black \xB7 **Shader:** cross-warp morph, thermal distortion | Restraint. Cross-warp morph flows both scenes into each other organically. |
|
|
52516
|
+
| **Retro / analog** | Film burn, light leak, VHS, clock wipe \xB7 **Shader:** light leak | Organic imperfection. Warm color bleeds, scan line displacement. |
|
|
52517
|
+
|
|
52518
|
+
## Narrative Position
|
|
52519
|
+
|
|
52520
|
+
| Position | Use | Why |
|
|
52521
|
+
| -------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
52522
|
+
| **Opening** | Your most distinctive transition. Match the mood. 0.4-0.6s | Sets the visual language for the entire piece. |
|
|
52523
|
+
| **Between related points** | Your primary transition. Consistent. 0.3s | Don't distract \u2014 the content is continuing. |
|
|
52524
|
+
| **Topic change** | Something different from your primary. Staggered blocks, shutter, squeeze. | Signals "new section" \u2014 the viewer's brain resets. |
|
|
52525
|
+
| **Climax / hero reveal** | Your boldest accent. Fastest or most dramatic. | This is the payoff \u2014 spend your best transition here. |
|
|
52526
|
+
| **Wind-down** | Return to gentle. Blur crossfade, crossfade. 0.5-0.7s | Let the viewer exhale after the climax. |
|
|
52527
|
+
| **Outro** | Slowest, simplest. Crossfade, color dip to black. 0.6-1.0s | Closure. Don't introduce new energy at the end. |
|
|
52528
|
+
|
|
52529
|
+
## Blur Intensity by Energy
|
|
52530
|
+
|
|
52531
|
+
| Energy | Blur | Duration | Hold at peak |
|
|
52532
|
+
| ---------- | ------- | -------- | ------------ |
|
|
52533
|
+
| **Calm** | 20-30px | 0.8-1.2s | 0.3-0.5s |
|
|
52534
|
+
| **Medium** | 8-15px | 0.4-0.6s | 0.1-0.2s |
|
|
52535
|
+
| **High** | 3-6px | 0.2-0.3s | 0s |
|
|
52536
|
+
|
|
52537
|
+
## Presets
|
|
52538
|
+
|
|
52539
|
+
| Preset | Duration | Easing |
|
|
52540
|
+
| ---------- | -------- | ----------------- |
|
|
52541
|
+
| \`snappy\` | 0.2s | \`power4.inOut\` |
|
|
52542
|
+
| \`smooth\` | 0.4s | \`power2.inOut\` |
|
|
52543
|
+
| \`gentle\` | 0.6s | \`sine.inOut\` |
|
|
52544
|
+
| \`dramatic\` | 0.5s | \`power3.in\` \u2192 out |
|
|
52545
|
+
| \`instant\` | 0.15s | \`expo.inOut\` |
|
|
52546
|
+
| \`luxe\` | 0.7s | \`power1.inOut\` |
|
|
52547
|
+
|
|
52548
|
+
## Implementation
|
|
52549
|
+
|
|
52550
|
+
Read [transitions/catalog.md](transitions/catalog.md) for GSAP code and hard rules for every transition type.
|
|
52551
|
+
|
|
52552
|
+
| Category | CSS | Shader (WebGL) |
|
|
52553
|
+
| ----------- | -------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
52554
|
+
| Push/slide | Push slide, vertical push, elastic push, squeeze | Whip pan |
|
|
52555
|
+
| Scale/zoom | Zoom through, zoom out, gravity drop, 3D flip | Cinematic zoom, gravitational lens |
|
|
52556
|
+
| Reveal/mask | Circle iris, diamond iris, diagonal split, clock wipe, shutter | SDF iris |
|
|
52557
|
+
| Dissolve | Crossfade, blur crossfade, focus pull, color dip | Cross-warp morph, domain warp |
|
|
52558
|
+
| Cover | Staggered blocks, horizontal blinds, vertical blinds | \u2014 |
|
|
52559
|
+
| Light | Light leak, overexposure burn, film burn | Light leak (shader), thermal distortion |
|
|
52560
|
+
| Distortion | Glitch, chromatic aberration, ripple, VHS tape | Glitch (shader), chromatic split, ridged burn, ripple waves, swirl vortex |
|
|
52561
|
+
| Pattern | Grid dissolve, morph circle | \u2014 |
|
|
52562
|
+
|
|
52563
|
+
## Transitions That Don't Work in CSS
|
|
52564
|
+
|
|
52565
|
+
Avoid: star iris, tilt-shift, lens flare, hinge/door. See catalog.md for why.
|
|
52566
|
+
|
|
52567
|
+
## CSS vs Shader
|
|
52568
|
+
|
|
52569
|
+
CSS transitions animate scene containers with opacity, transforms, clip-path, and filters. Shader transitions composite both scene textures per-pixel on a WebGL canvas \u2014 they can warp, dissolve, and morph in ways CSS cannot.
|
|
52570
|
+
|
|
52571
|
+
**Both are first-class options.** Shaders are provided by the \`@hyperframes/shader-transitions\` package \u2014 import from the package instead of writing raw GLSL. CSS transitions are simpler to set up. Choose based on the effect you want, not based on which is easier.
|
|
52572
|
+
|
|
52573
|
+
When a composition uses shader transitions, ALL transitions in that composition should be shader-based (the WebGL canvas replaces DOM-based scene switching). Don't mix CSS and shader transitions in the same composition.
|
|
52574
|
+
|
|
52575
|
+
## Shader-Compatible CSS Rules
|
|
52576
|
+
|
|
52577
|
+
Shader transitions capture DOM scenes to WebGL textures via html2canvas. The canvas 2D rendering pipeline doesn't match CSS exactly. Follow these rules to avoid visible artifacts at transition boundaries:
|
|
52578
|
+
|
|
52579
|
+
1. **No \`transparent\` keyword in gradients.** Canvas interpolates \`transparent\` as \`rgba(0,0,0,0)\` (black at zero alpha), creating dark fringes. Always use the target color at zero alpha: \`rgba(200,117,51,0)\` not \`transparent\`.
|
|
52580
|
+
2. **No gradient backgrounds on elements thinner than 4px.** Canvas can't match CSS gradient rendering on 1-2px elements. Use solid \`background-color\` on thin accent lines.
|
|
52581
|
+
3. **No CSS variables (\`var()\`) on elements visible during capture.** html2canvas doesn't reliably resolve custom properties. Use literal color values in inline styles.
|
|
52582
|
+
4. **Mark uncapturable decorative elements with \`data-no-capture\`.** The capture function skips these. They're present on the live DOM but absent from the shader texture. Use for elements that can't follow the rules above.
|
|
52583
|
+
5. **No gradient opacity below 0.15.** Gradient elements below 10% opacity render differently in canvas vs CSS. Increase to 0.15+ or use a solid color at equivalent brightness.
|
|
52584
|
+
6. **Every \`.scene\` div must have explicit \`background-color\`, AND pass the same color as \`bgColor\` in the \`init()\` config.** The package captures scene elements via html2canvas. Both the CSS \`background-color\` on \`.scene\` and the \`bgColor\` config must match. Without either, the texture renders as black.
|
|
52585
|
+
|
|
52586
|
+
These rules only apply to shader transition compositions. CSS-only compositions have no restrictions.
|
|
52587
|
+
|
|
52588
|
+
## Visual Pattern Warning
|
|
52589
|
+
|
|
52590
|
+
Avoid transitions that create visible repeating geometric patterns \u2014 grids of tiles, hexagonal cells, uniform dot arrays, evenly-spaced blob circles. These look cheap and artificial regardless of the math behind them. Organic noise (FBM, domain warping) is good because it's irregular. Geometric repetition is bad because the eye instantly sees the grid.
|
|
52591
|
+
`;
|
|
52592
|
+
}
|
|
52593
|
+
});
|
|
52594
|
+
|
|
52595
|
+
// src/commands/_shared/hf-skill-bundle/bundle.ts
|
|
52596
|
+
import { readFileSync, existsSync as existsSync27, statSync } from "node:fs";
|
|
52597
|
+
import { join as join7 } from "node:path";
|
|
52598
|
+
import { homedir as homedir3 } from "node:os";
|
|
52599
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
52600
|
+
function installedSkillPath() {
|
|
52601
|
+
const candidate = join7(homedir3(), ".claude", "skills", "hyperframes");
|
|
52602
|
+
if (!existsSync27(candidate)) return null;
|
|
52603
|
+
if (!statSync(candidate).isDirectory()) return null;
|
|
52604
|
+
return candidate;
|
|
52605
|
+
}
|
|
52606
|
+
function joinSections(sections) {
|
|
52607
|
+
return sections.map((s) => `
|
|
52608
|
+
|
|
52609
|
+
=== ${s.label} ===
|
|
52610
|
+
|
|
52611
|
+
${s.content}`).join("\n");
|
|
52612
|
+
}
|
|
52613
|
+
function buildVendored() {
|
|
52614
|
+
return {
|
|
52615
|
+
content: joinSections(VENDORED_SECTIONS),
|
|
52616
|
+
hint: `[skill] using vendored Hyperframes snapshot ${BUNDLE_VERSION}. For latest, run \`npx skills add heygen-com/hyperframes\`.`
|
|
52617
|
+
};
|
|
52618
|
+
}
|
|
52619
|
+
function buildInstalled(skillRoot) {
|
|
52620
|
+
const installedPaths = {
|
|
52621
|
+
"hyperframes/SKILL.md": join7(skillRoot, "SKILL.md"),
|
|
52622
|
+
"hyperframes/house-style.md": join7(skillRoot, "house-style.md"),
|
|
52623
|
+
"hyperframes/motion-principles.md": join7(skillRoot, "references", "motion-principles.md"),
|
|
52624
|
+
"hyperframes/typography.md": join7(skillRoot, "references", "typography.md"),
|
|
52625
|
+
"hyperframes/transitions.md": join7(skillRoot, "references", "transitions.md")
|
|
52626
|
+
};
|
|
52627
|
+
const sections = [];
|
|
52628
|
+
for (const [label, path2] of Object.entries(installedPaths)) {
|
|
52629
|
+
if (!existsSync27(path2)) return null;
|
|
52630
|
+
sections.push({ label, content: readFileSync(path2, "utf-8") });
|
|
52631
|
+
}
|
|
52632
|
+
return {
|
|
52633
|
+
content: joinSections(sections),
|
|
52634
|
+
hint: `[skill] using installed Hyperframes skill from ${skillRoot}.`
|
|
52635
|
+
};
|
|
52636
|
+
}
|
|
52637
|
+
function loadHyperframesSkillBundle() {
|
|
52638
|
+
const installedRoot = installedSkillPath();
|
|
52639
|
+
if (installedRoot) {
|
|
52640
|
+
const r = buildInstalled(installedRoot);
|
|
52641
|
+
if (r) {
|
|
52642
|
+
return {
|
|
52643
|
+
content: r.content,
|
|
52644
|
+
source: "installed",
|
|
52645
|
+
hint: r.hint,
|
|
52646
|
+
hash: hashBundle(BUNDLE_VERSION, r.content)
|
|
52647
|
+
};
|
|
52648
|
+
}
|
|
52649
|
+
}
|
|
52650
|
+
const v = buildVendored();
|
|
52651
|
+
return {
|
|
52652
|
+
content: v.content,
|
|
52653
|
+
source: "vendored",
|
|
52654
|
+
hint: v.hint,
|
|
52655
|
+
hash: hashBundle(BUNDLE_VERSION, v.content)
|
|
52656
|
+
};
|
|
52657
|
+
}
|
|
52658
|
+
function hashBundle(version, content) {
|
|
52659
|
+
return createHash2("sha256").update(version).update(" ").update(content).digest("hex");
|
|
52660
|
+
}
|
|
52661
|
+
var BUNDLE_VERSION, VENDORED_SECTIONS;
|
|
52662
|
+
var init_bundle = __esm({
|
|
52663
|
+
"src/commands/_shared/hf-skill-bundle/bundle.ts"() {
|
|
52664
|
+
"use strict";
|
|
52665
|
+
init_bundle_content();
|
|
52666
|
+
BUNDLE_VERSION = "970367f-2026-04-25";
|
|
52667
|
+
VENDORED_SECTIONS = [
|
|
52668
|
+
{ label: "hyperframes/SKILL.md", content: SKILL_MD },
|
|
52669
|
+
{ label: "hyperframes/house-style.md", content: HOUSE_STYLE_MD },
|
|
52670
|
+
{ label: "hyperframes/motion-principles.md", content: MOTION_PRINCIPLES_MD },
|
|
52671
|
+
{ label: "hyperframes/typography.md", content: TYPOGRAPHY_MD },
|
|
52672
|
+
{ label: "hyperframes/transitions.md", content: TRANSITIONS_MD }
|
|
52673
|
+
];
|
|
52674
|
+
}
|
|
52675
|
+
});
|
|
52676
|
+
|
|
52677
|
+
// src/commands/_shared/storyboard-parse.ts
|
|
52678
|
+
function parseStoryboard(md) {
|
|
52679
|
+
const text = md.replace(/\r\n/g, "\n");
|
|
52680
|
+
const headings = [];
|
|
52681
|
+
HEADING_RE.lastIndex = 0;
|
|
52682
|
+
let m;
|
|
52683
|
+
while ((m = HEADING_RE.exec(text)) !== null) {
|
|
52684
|
+
headings.push({
|
|
52685
|
+
start: m.index,
|
|
52686
|
+
end: m.index + m[0].length,
|
|
52687
|
+
line: m[1].trim()
|
|
52688
|
+
});
|
|
52689
|
+
}
|
|
52690
|
+
if (headings.length === 0) {
|
|
52691
|
+
return { global: text.trim(), beats: [] };
|
|
52692
|
+
}
|
|
52693
|
+
const global2 = text.slice(0, headings[0].start).trim();
|
|
52694
|
+
const beats = headings.map((h, i) => {
|
|
52695
|
+
const bodyStart = h.end;
|
|
52696
|
+
const bodyEnd = i + 1 < headings.length ? headings[i + 1].start : text.length;
|
|
52697
|
+
const body = text.slice(bodyStart, bodyEnd).trim();
|
|
52698
|
+
const id = deriveBeatId(h.line);
|
|
52699
|
+
const duration = parseBeatDuration(body);
|
|
52700
|
+
return {
|
|
52701
|
+
id,
|
|
52702
|
+
heading: h.line,
|
|
52703
|
+
body,
|
|
52704
|
+
...duration !== void 0 ? { duration } : {}
|
|
52705
|
+
};
|
|
52706
|
+
});
|
|
52707
|
+
return { global: global2, beats };
|
|
52708
|
+
}
|
|
52709
|
+
function deriveBeatId(headingLine) {
|
|
52710
|
+
const beatPrefix = headingLine.match(BEAT_PREFIX_RE);
|
|
52711
|
+
if (beatPrefix) {
|
|
52712
|
+
return slugify(beatPrefix[1]);
|
|
52713
|
+
}
|
|
52714
|
+
return slugify(headingLine);
|
|
52715
|
+
}
|
|
52716
|
+
function slugify(s) {
|
|
52717
|
+
return s.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/\([^)]*\)/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "beat";
|
|
52718
|
+
}
|
|
52719
|
+
function parseBeatDuration(body) {
|
|
52720
|
+
const sub = body.match(DURATION_SUBSECTION_RE);
|
|
52721
|
+
if (!sub) return void 0;
|
|
52722
|
+
const valueMatch = sub[1].match(DURATION_VALUE_RE);
|
|
52723
|
+
if (!valueMatch) return void 0;
|
|
52724
|
+
const n = parseFloat(valueMatch[1]);
|
|
52725
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
52726
|
+
}
|
|
52727
|
+
var HEADING_RE, BEAT_PREFIX_RE, DURATION_SUBSECTION_RE, DURATION_VALUE_RE;
|
|
52728
|
+
var init_storyboard_parse = __esm({
|
|
52729
|
+
"src/commands/_shared/storyboard-parse.ts"() {
|
|
52730
|
+
"use strict";
|
|
52731
|
+
HEADING_RE = /^##\s+(.+?)\s*$/gm;
|
|
52732
|
+
BEAT_PREFIX_RE = /^Beat\s+(.+?)\s+(?:—|:|-)\s+(.+)$/i;
|
|
52733
|
+
DURATION_SUBSECTION_RE = /###\s+Beat\s+duration\s*\n([\s\S]*?)(?=\n###\s|\n##\s|$)/i;
|
|
52734
|
+
DURATION_VALUE_RE = /(-?\d+(?:\.\d+)?)\s*(?:s|sec|seconds?)?/i;
|
|
52735
|
+
}
|
|
52736
|
+
});
|
|
52737
|
+
|
|
52738
|
+
// src/commands/_shared/compose-scenes-skills.ts
|
|
52739
|
+
var compose_scenes_skills_exports = {};
|
|
52740
|
+
__export(compose_scenes_skills_exports, {
|
|
52741
|
+
ComposeBeatError: () => ComposeBeatError,
|
|
52742
|
+
buildSystemPrompt: () => buildSystemPrompt,
|
|
52743
|
+
buildUserPrompt: () => buildUserPrompt,
|
|
52744
|
+
composeBeatHtml: () => composeBeatHtml,
|
|
52745
|
+
composeBeatWithRetry: () => composeBeatWithRetry,
|
|
52746
|
+
computeCacheKey: () => computeCacheKey,
|
|
52747
|
+
defaultCacheDir: () => defaultCacheDir,
|
|
52748
|
+
executeComposeScenesWithSkills: () => executeComposeScenesWithSkills,
|
|
52749
|
+
extractHtml: () => extractHtml,
|
|
52750
|
+
formatLintFeedback: () => formatLintFeedback,
|
|
52751
|
+
lintBeatHtml: () => lintBeatHtml
|
|
52752
|
+
});
|
|
52753
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
52754
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
52755
|
+
import { existsSync as existsSync28, mkdirSync } from "node:fs";
|
|
52756
|
+
import { mkdir as mkdir19, readFile as readFile35, writeFile as writeFile31 } from "node:fs/promises";
|
|
52757
|
+
import { homedir as homedir4 } from "node:os";
|
|
52758
|
+
import { dirname as dirname20, join as join8, resolve as resolve42 } from "node:path";
|
|
52759
|
+
import {
|
|
52760
|
+
runHyperframeLint as runHyperframeLint2
|
|
52761
|
+
} from "@hyperframes/producer";
|
|
52762
|
+
function buildSystemPrompt(ctx) {
|
|
52763
|
+
return `You are a Hyperframes composition author. The skill content below
|
|
52764
|
+
defines the framework rules, motion principles, and quality standards.
|
|
52765
|
+
Read it thoroughly before writing any HTML.
|
|
52766
|
+
|
|
52767
|
+
${ctx.skillBundle.content}
|
|
52768
|
+
|
|
52769
|
+
=== DESIGN.md (project-specific visual identity \u2014 HARD-GATE, every decision must trace back) ===
|
|
52770
|
+
|
|
52771
|
+
${ctx.designMd}`;
|
|
52772
|
+
}
|
|
52773
|
+
function buildUserPrompt(ctx) {
|
|
52774
|
+
const compositionId = `scene-${ctx.beat.id}`;
|
|
52775
|
+
const baseRequirements = `Build the Hyperframes sub-composition HTML for this beat. The composition
|
|
52776
|
+
will be loaded into a root index.html via
|
|
52777
|
+
\`data-composition-src="compositions/${compositionId}.html"\`.
|
|
52778
|
+
|
|
52779
|
+
Requirements (non-negotiable):
|
|
52780
|
+
- Use the \`<template>\` wrapper (this is a sub-composition, not standalone)
|
|
52781
|
+
- Composition id: \`${compositionId}\`
|
|
52782
|
+
- One paused GSAP timeline registered on \`window.__timelines["${compositionId}"]\`
|
|
52783
|
+
- All timed elements have \`class="clip"\` and \`data-start\`, \`data-duration\`, \`data-track-index\`
|
|
52784
|
+
- No \`Math.random()\`, \`Date.now()\`, \`repeat: -1\`, or \`<br>\` in content
|
|
52785
|
+
- Layout-before-animation: position elements at hero-frame state in CSS, animate FROM
|
|
52786
|
+
- No exit animations (transitions handle scene exits, except the final beat)
|
|
52787
|
+
- Strictly follow DESIGN.md palette, typography, motion signature
|
|
52788
|
+
|
|
52789
|
+
=== Storyboard \u2014 global direction ===
|
|
52790
|
+
|
|
52791
|
+
${ctx.storyboardGlobal || "(no global direction)"}
|
|
52792
|
+
|
|
52793
|
+
=== Beat to build ===
|
|
52794
|
+
|
|
52795
|
+
## ${ctx.beat.heading}
|
|
52796
|
+
|
|
52797
|
+
${ctx.beat.body}
|
|
52798
|
+
|
|
52799
|
+
=== Output format ===
|
|
52800
|
+
|
|
52801
|
+
Return ONE complete HTML file in a single \`\`\`html\`\`\` fenced code block.
|
|
52802
|
+
No prose, no explanations, no commentary outside the code block. Just the HTML.`;
|
|
52803
|
+
if (ctx.retryFeedback && ctx.retryFeedback.trim().length > 0) {
|
|
52804
|
+
return `${baseRequirements}
|
|
52805
|
+
|
|
52806
|
+
=== Previous attempt failed lint with the following findings \u2014 fix them ===
|
|
52807
|
+
|
|
52808
|
+
${ctx.retryFeedback.trim()}`;
|
|
52809
|
+
}
|
|
52810
|
+
return baseRequirements;
|
|
52811
|
+
}
|
|
52812
|
+
function computeCacheKey(parts) {
|
|
52813
|
+
return createHash3("sha256").update(parts.model).update("\u241E").update(parts.systemPrompt).update("\u241E").update(parts.userPrompt).digest("hex");
|
|
52814
|
+
}
|
|
52815
|
+
function defaultCacheDir() {
|
|
52816
|
+
return join8(homedir4(), ".vibeframe", "cache", "compose-scenes");
|
|
52817
|
+
}
|
|
52818
|
+
function extractHtml(responseText) {
|
|
52819
|
+
const fenced = responseText.match(/```html\s*\n([\s\S]*?)\n```/);
|
|
52820
|
+
if (fenced) return fenced[1].trim();
|
|
52821
|
+
const trimmed = responseText.trim();
|
|
52822
|
+
if (trimmed.startsWith("<")) return trimmed;
|
|
52823
|
+
throw new ComposeBeatError(
|
|
52824
|
+
"no-html-in-response",
|
|
52825
|
+
`Could not extract HTML from response. First 200 chars: ${trimmed.slice(0, 200)}`
|
|
52826
|
+
);
|
|
52827
|
+
}
|
|
52828
|
+
async function composeBeatHtml(ctx, overrides) {
|
|
52829
|
+
const effort = ctx.effort ?? "medium";
|
|
52830
|
+
const settings = MODEL_SETTINGS[effort];
|
|
52831
|
+
const systemPrompt = buildSystemPrompt(ctx);
|
|
52832
|
+
const userPrompt = buildUserPrompt(ctx);
|
|
52833
|
+
const cacheKey = computeCacheKey({
|
|
52834
|
+
systemPrompt,
|
|
52835
|
+
userPrompt,
|
|
52836
|
+
model: settings.model
|
|
52837
|
+
});
|
|
52838
|
+
const cacheDir = ctx.cacheDir ?? defaultCacheDir();
|
|
52839
|
+
const cachePath = join8(cacheDir, `${cacheKey}.html`);
|
|
52840
|
+
if (existsSync28(cachePath)) {
|
|
52841
|
+
const html2 = await readFile35(cachePath, "utf-8");
|
|
52842
|
+
return { html: html2, cached: true, cacheKey, model: settings.model };
|
|
52843
|
+
}
|
|
52844
|
+
if (!ctx.apiKey) {
|
|
52845
|
+
throw new ComposeBeatError(
|
|
52846
|
+
"missing-api-key",
|
|
52847
|
+
"ANTHROPIC_API_KEY required for compose-scenes-with-skills (set it in env or .env)."
|
|
52848
|
+
);
|
|
52849
|
+
}
|
|
52850
|
+
const client = overrides?.client ?? new Anthropic({ apiKey: ctx.apiKey });
|
|
52851
|
+
const now = overrides?.now ?? (() => Date.now());
|
|
52852
|
+
const t0 = now();
|
|
52853
|
+
let response;
|
|
52854
|
+
try {
|
|
52855
|
+
response = await client.messages.create({
|
|
52856
|
+
model: settings.model,
|
|
52857
|
+
max_tokens: settings.maxTokens,
|
|
52858
|
+
system: systemPrompt,
|
|
52859
|
+
messages: [{ role: "user", content: userPrompt }]
|
|
52860
|
+
});
|
|
52861
|
+
} catch (err) {
|
|
52862
|
+
throw new ComposeBeatError(
|
|
52863
|
+
"api-call-failed",
|
|
52864
|
+
`Anthropic API call failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
52865
|
+
err
|
|
52866
|
+
);
|
|
52867
|
+
}
|
|
52868
|
+
const latencyMs = now() - t0;
|
|
52869
|
+
if (!("content" in response) || !Array.isArray(response.content)) {
|
|
52870
|
+
throw new ComposeBeatError("unexpected-response-shape", "Anthropic response missing `content` array.");
|
|
52871
|
+
}
|
|
52872
|
+
const textBlock = response.content.find((b) => b.type === "text");
|
|
52873
|
+
if (!textBlock || textBlock.type !== "text") {
|
|
52874
|
+
throw new ComposeBeatError("no-text-block", "Anthropic response had no text block.");
|
|
52875
|
+
}
|
|
52876
|
+
const html = extractHtml(textBlock.text);
|
|
52877
|
+
try {
|
|
52878
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
52879
|
+
await writeFile31(cachePath, html, "utf-8");
|
|
52880
|
+
} catch {
|
|
52881
|
+
}
|
|
52882
|
+
const inputTokens = response.usage?.input_tokens ?? 0;
|
|
52883
|
+
const outputTokens = response.usage?.output_tokens ?? 0;
|
|
52884
|
+
const costUsd = inputTokens / 1e6 * settings.costPerMTokIn + outputTokens / 1e6 * settings.costPerMTokOut;
|
|
52885
|
+
return {
|
|
52886
|
+
html,
|
|
52887
|
+
cached: false,
|
|
52888
|
+
cacheKey,
|
|
52889
|
+
inputTokens,
|
|
52890
|
+
outputTokens,
|
|
52891
|
+
costUsd: Number(costUsd.toFixed(4)),
|
|
52892
|
+
latencyMs,
|
|
52893
|
+
model: settings.model
|
|
52894
|
+
};
|
|
52895
|
+
}
|
|
52896
|
+
function lintBeatHtml(html, beatId) {
|
|
52897
|
+
const prepared = {
|
|
52898
|
+
html,
|
|
52899
|
+
entryFile: `compositions/scene-${beatId}.html`,
|
|
52900
|
+
source: "projectDir"
|
|
52901
|
+
};
|
|
52902
|
+
const raw = runHyperframeLint2(prepared);
|
|
52903
|
+
const findings = filterSubCompFalsePositives(raw.findings, true);
|
|
52904
|
+
return {
|
|
52905
|
+
errorCount: findings.filter((f) => f.severity === "error").length,
|
|
52906
|
+
warningCount: findings.filter((f) => f.severity === "warning").length,
|
|
52907
|
+
findings
|
|
52908
|
+
};
|
|
52909
|
+
}
|
|
52910
|
+
function formatLintFeedback(findings) {
|
|
52911
|
+
const errors = findings.filter((f) => f.severity === "error");
|
|
52912
|
+
if (errors.length === 0) return "";
|
|
52913
|
+
return errors.map((f) => {
|
|
52914
|
+
const fix = f.fixHint ? `
|
|
52915
|
+
Fix hint: ${f.fixHint}` : "";
|
|
52916
|
+
return `ERROR [${f.code}] ${f.message}${fix}`;
|
|
52917
|
+
}).join("\n");
|
|
52918
|
+
}
|
|
52919
|
+
async function composeBeatWithRetry(ctx, overrides) {
|
|
52920
|
+
const first = await composeBeatHtml(ctx, overrides);
|
|
52921
|
+
const lint1 = lintBeatHtml(first.html, ctx.beat.id);
|
|
52922
|
+
if (lint1.errorCount === 0) {
|
|
52923
|
+
return { ...first, lintAttempts: 1, lint: lint1 };
|
|
52924
|
+
}
|
|
52925
|
+
const feedback = formatLintFeedback(lint1.findings);
|
|
52926
|
+
const second = await composeBeatHtml({ ...ctx, retryFeedback: feedback }, overrides);
|
|
52927
|
+
const lint2 = lintBeatHtml(second.html, ctx.beat.id);
|
|
52928
|
+
if (lint2.errorCount === 0) {
|
|
52929
|
+
return { ...second, lintAttempts: 2, lint: lint2 };
|
|
52930
|
+
}
|
|
52931
|
+
throw new ComposeBeatError(
|
|
52932
|
+
"lint-failed-after-retry",
|
|
52933
|
+
`Beat "${ctx.beat.id}" failed lint after retry. Final findings:
|
|
52934
|
+
${formatLintFeedback(lint2.findings)}`
|
|
52935
|
+
);
|
|
52936
|
+
}
|
|
52937
|
+
async function executeComposeScenesWithSkills(params, outputDir, overrides) {
|
|
52938
|
+
const projectRoot = params.project ? resolve42(outputDir, params.project) : resolve42(outputDir);
|
|
52939
|
+
const designPath = resolve42(projectRoot, params.design ?? "DESIGN.md");
|
|
52940
|
+
const storyboardPath = resolve42(projectRoot, params.storyboard ?? "STORYBOARD.md");
|
|
52941
|
+
if (!existsSync28(designPath)) {
|
|
52942
|
+
return { success: false, error: `DESIGN.md not found at ${designPath}` };
|
|
52943
|
+
}
|
|
52944
|
+
if (!existsSync28(storyboardPath)) {
|
|
52945
|
+
return { success: false, error: `STORYBOARD.md not found at ${storyboardPath}` };
|
|
52946
|
+
}
|
|
52947
|
+
const designMd = await readFile35(designPath, "utf-8");
|
|
52948
|
+
const storyboardMd = await readFile35(storyboardPath, "utf-8");
|
|
52949
|
+
const { global: storyboardGlobal, beats } = parseStoryboard(storyboardMd);
|
|
52950
|
+
if (beats.length === 0) {
|
|
52951
|
+
return {
|
|
52952
|
+
success: false,
|
|
52953
|
+
error: `STORYBOARD.md at ${storyboardPath} contains no \`## Beat \u2026\` headings.`
|
|
52954
|
+
};
|
|
52955
|
+
}
|
|
52956
|
+
const skillBundleLoaded = loadHyperframesSkillBundle();
|
|
52957
|
+
const skillBundle = { content: skillBundleLoaded.content, hash: skillBundleLoaded.hash };
|
|
52958
|
+
const apiKey = process.env.ANTHROPIC_API_KEY ?? "";
|
|
52959
|
+
const compositionsDir = join8(projectRoot, "compositions");
|
|
52960
|
+
await mkdir19(compositionsDir, { recursive: true });
|
|
52961
|
+
const onProgress = params.onProgress ?? (() => {
|
|
52962
|
+
});
|
|
52963
|
+
const totalBeats = beats.length;
|
|
52964
|
+
const tasks = beats.map(async (beat, beatIndex) => {
|
|
52965
|
+
onProgress({ type: "beat-start", beatId: beat.id, beatIndex, totalBeats });
|
|
52966
|
+
try {
|
|
52967
|
+
const result = await composeBeatWithRetry(
|
|
52968
|
+
{
|
|
52969
|
+
beat,
|
|
52970
|
+
designMd,
|
|
52971
|
+
storyboardGlobal,
|
|
52972
|
+
skillBundle,
|
|
52973
|
+
apiKey,
|
|
52974
|
+
effort: params.effort,
|
|
52975
|
+
cacheDir: params.cacheDir
|
|
52976
|
+
},
|
|
52977
|
+
overrides
|
|
52978
|
+
);
|
|
52979
|
+
const compositionPath = join8(compositionsDir, `scene-${beat.id}.html`);
|
|
52980
|
+
await mkdir19(dirname20(compositionPath), { recursive: true });
|
|
52981
|
+
await writeFile31(compositionPath, result.html, "utf-8");
|
|
52982
|
+
if (result.cached) {
|
|
52983
|
+
onProgress({
|
|
52984
|
+
type: "beat-cached",
|
|
52985
|
+
beatId: beat.id,
|
|
52986
|
+
beatIndex,
|
|
52987
|
+
totalBeats,
|
|
52988
|
+
lintAttempts: result.lintAttempts
|
|
52989
|
+
});
|
|
52990
|
+
} else {
|
|
52991
|
+
onProgress({
|
|
52992
|
+
type: "beat-fresh",
|
|
52993
|
+
beatId: beat.id,
|
|
52994
|
+
beatIndex,
|
|
52995
|
+
totalBeats,
|
|
52996
|
+
lintAttempts: result.lintAttempts,
|
|
52997
|
+
costUsd: result.costUsd,
|
|
52998
|
+
latencyMs: result.latencyMs
|
|
52999
|
+
});
|
|
53000
|
+
}
|
|
53001
|
+
return { ok: true, beatId: beat.id, beatIndex, path: compositionPath, result };
|
|
53002
|
+
} catch (err) {
|
|
53003
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53004
|
+
onProgress({ type: "beat-failed", beatId: beat.id, beatIndex, totalBeats, error: message });
|
|
53005
|
+
return { ok: false, beatId: beat.id, beatIndex, error: message };
|
|
53006
|
+
}
|
|
53007
|
+
});
|
|
53008
|
+
const outcomes = await Promise.all(tasks);
|
|
53009
|
+
const written = [];
|
|
53010
|
+
const failures = [];
|
|
53011
|
+
let totalCostUsd = 0;
|
|
53012
|
+
let totalTokensIn = 0;
|
|
53013
|
+
let totalTokensOut = 0;
|
|
53014
|
+
let cacheHits = 0;
|
|
53015
|
+
for (const outcome of outcomes) {
|
|
53016
|
+
if (!outcome.ok) {
|
|
53017
|
+
failures.push({ beatId: outcome.beatId, error: outcome.error });
|
|
53018
|
+
continue;
|
|
53019
|
+
}
|
|
53020
|
+
const r = outcome.result;
|
|
53021
|
+
written.push({
|
|
53022
|
+
beatId: outcome.beatId,
|
|
53023
|
+
path: outcome.path,
|
|
53024
|
+
cached: r.cached,
|
|
53025
|
+
lintAttempts: r.lintAttempts,
|
|
53026
|
+
costUsd: r.costUsd
|
|
53027
|
+
});
|
|
53028
|
+
if (r.cached) cacheHits++;
|
|
53029
|
+
if (r.costUsd) totalCostUsd += r.costUsd;
|
|
53030
|
+
if (r.inputTokens) totalTokensIn += r.inputTokens;
|
|
53031
|
+
if (r.outputTokens) totalTokensOut += r.outputTokens;
|
|
53032
|
+
}
|
|
53033
|
+
const aggregateData = {
|
|
53034
|
+
beats: beats.length,
|
|
53035
|
+
written,
|
|
53036
|
+
totalCostUsd: Number(totalCostUsd.toFixed(4)),
|
|
53037
|
+
totalTokensIn,
|
|
53038
|
+
totalTokensOut,
|
|
53039
|
+
cacheHits
|
|
53040
|
+
};
|
|
53041
|
+
if (failures.length > 0) {
|
|
53042
|
+
return {
|
|
53043
|
+
success: false,
|
|
53044
|
+
outputPath: projectRoot,
|
|
53045
|
+
error: failures.length === 1 ? `compose-scenes-with-skills failed at beat "${failures[0].beatId}": ${failures[0].error}` : `compose-scenes-with-skills failed at ${failures.length} beats:
|
|
53046
|
+
${failures.map((f) => ` - ${f.beatId}: ${f.error}`).join("\n")}`,
|
|
53047
|
+
data: aggregateData
|
|
53048
|
+
};
|
|
53049
|
+
}
|
|
53050
|
+
return {
|
|
53051
|
+
success: true,
|
|
53052
|
+
outputPath: projectRoot,
|
|
53053
|
+
data: aggregateData
|
|
53054
|
+
};
|
|
53055
|
+
}
|
|
53056
|
+
var MODEL_SETTINGS, ComposeBeatError;
|
|
53057
|
+
var init_compose_scenes_skills = __esm({
|
|
53058
|
+
"src/commands/_shared/compose-scenes-skills.ts"() {
|
|
53059
|
+
"use strict";
|
|
53060
|
+
init_bundle();
|
|
53061
|
+
init_scene_lint();
|
|
53062
|
+
init_storyboard_parse();
|
|
53063
|
+
MODEL_SETTINGS = {
|
|
53064
|
+
// Sonnet 4.6 — pre-flight validated default. $3/$15 per MTok.
|
|
53065
|
+
low: { model: "claude-sonnet-4-6", maxTokens: 4e3, costPerMTokIn: 3, costPerMTokOut: 15 },
|
|
53066
|
+
medium: { model: "claude-sonnet-4-6", maxTokens: 6e3, costPerMTokIn: 3, costPerMTokOut: 15 },
|
|
53067
|
+
high: { model: "claude-sonnet-4-6", maxTokens: 8e3, costPerMTokIn: 3, costPerMTokOut: 15 }
|
|
53068
|
+
};
|
|
53069
|
+
ComposeBeatError = class extends Error {
|
|
53070
|
+
constructor(code, message, cause) {
|
|
53071
|
+
super(message);
|
|
53072
|
+
this.code = code;
|
|
53073
|
+
this.cause = cause;
|
|
53074
|
+
this.name = "ComposeBeatError";
|
|
53075
|
+
}
|
|
53076
|
+
};
|
|
53077
|
+
}
|
|
53078
|
+
});
|
|
53079
|
+
|
|
51442
53080
|
// src/agent/adapters/openai.ts
|
|
51443
53081
|
var openai_exports = {};
|
|
51444
53082
|
__export(openai_exports, {
|
|
@@ -51540,7 +53178,7 @@ var claude_exports = {};
|
|
|
51540
53178
|
__export(claude_exports, {
|
|
51541
53179
|
ClaudeAdapter: () => ClaudeAdapter
|
|
51542
53180
|
});
|
|
51543
|
-
import
|
|
53181
|
+
import Anthropic2 from "@anthropic-ai/sdk";
|
|
51544
53182
|
var ClaudeAdapter;
|
|
51545
53183
|
var init_claude2 = __esm({
|
|
51546
53184
|
"src/agent/adapters/claude.ts"() {
|
|
@@ -51550,7 +53188,7 @@ var init_claude2 = __esm({
|
|
|
51550
53188
|
client = null;
|
|
51551
53189
|
model = "claude-sonnet-4-6";
|
|
51552
53190
|
async initialize(apiKey) {
|
|
51553
|
-
this.client = new
|
|
53191
|
+
this.client = new Anthropic2({ apiKey });
|
|
51554
53192
|
}
|
|
51555
53193
|
isInitialized() {
|
|
51556
53194
|
return this.client !== null;
|
|
@@ -52169,14 +53807,14 @@ var project_exports = {};
|
|
|
52169
53807
|
__export(project_exports, {
|
|
52170
53808
|
registerProjectTools: () => registerProjectTools
|
|
52171
53809
|
});
|
|
52172
|
-
import { readFile as
|
|
52173
|
-
import { resolve as
|
|
53810
|
+
import { readFile as readFile37, writeFile as writeFile33, stat as stat7 } from "node:fs/promises";
|
|
53811
|
+
import { resolve as resolve44 } from "node:path";
|
|
52174
53812
|
async function resolveProjectPath3(inputPath, cwd) {
|
|
52175
|
-
const filePath =
|
|
53813
|
+
const filePath = resolve44(cwd, inputPath);
|
|
52176
53814
|
try {
|
|
52177
53815
|
const stats = await stat7(filePath);
|
|
52178
53816
|
if (stats.isDirectory()) {
|
|
52179
|
-
return
|
|
53817
|
+
return resolve44(filePath, "project.vibe.json");
|
|
52180
53818
|
}
|
|
52181
53819
|
} catch {
|
|
52182
53820
|
}
|
|
@@ -52299,9 +53937,9 @@ var init_project2 = __esm({
|
|
|
52299
53937
|
const project = new Project(name);
|
|
52300
53938
|
project.setAspectRatio(aspectRatio);
|
|
52301
53939
|
project.setFrameRate(fps);
|
|
52302
|
-
const outputPath =
|
|
53940
|
+
const outputPath = resolve44(context.workingDirectory, output);
|
|
52303
53941
|
const data = JSON.stringify(project.toJSON(), null, 2);
|
|
52304
|
-
await
|
|
53942
|
+
await writeFile33(outputPath, data, "utf-8");
|
|
52305
53943
|
context.projectPath = outputPath;
|
|
52306
53944
|
return {
|
|
52307
53945
|
toolCallId: "",
|
|
@@ -52324,7 +53962,7 @@ Frame Rate: ${fps} fps`
|
|
|
52324
53962
|
const path2 = args.path || args.project;
|
|
52325
53963
|
try {
|
|
52326
53964
|
const filePath = await resolveProjectPath3(path2, context.workingDirectory);
|
|
52327
|
-
const content = await
|
|
53965
|
+
const content = await readFile37(filePath, "utf-8");
|
|
52328
53966
|
const data = JSON.parse(content);
|
|
52329
53967
|
const project = Project.fromJSON(data);
|
|
52330
53968
|
const summary = project.getSummary();
|
|
@@ -52359,7 +53997,7 @@ Frame Rate: ${fps} fps`
|
|
|
52359
53997
|
const updates = [];
|
|
52360
53998
|
try {
|
|
52361
53999
|
const filePath = await resolveProjectPath3(path2, context.workingDirectory);
|
|
52362
|
-
const content = await
|
|
54000
|
+
const content = await readFile37(filePath, "utf-8");
|
|
52363
54001
|
const data = JSON.parse(content);
|
|
52364
54002
|
const project = Project.fromJSON(data);
|
|
52365
54003
|
if (args.name) {
|
|
@@ -52374,7 +54012,7 @@ Frame Rate: ${fps} fps`
|
|
|
52374
54012
|
project.setFrameRate(args.fps);
|
|
52375
54013
|
updates.push(`Frame Rate: ${args.fps} fps`);
|
|
52376
54014
|
}
|
|
52377
|
-
await
|
|
54015
|
+
await writeFile33(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
52378
54016
|
return {
|
|
52379
54017
|
toolCallId: "",
|
|
52380
54018
|
success: true,
|
|
@@ -52394,7 +54032,7 @@ ${updates.join("\n")}`
|
|
|
52394
54032
|
const path2 = args.path || args.project;
|
|
52395
54033
|
try {
|
|
52396
54034
|
const filePath = await resolveProjectPath3(path2, context.workingDirectory);
|
|
52397
|
-
const content = await
|
|
54035
|
+
const content = await readFile37(filePath, "utf-8");
|
|
52398
54036
|
const data = JSON.parse(content);
|
|
52399
54037
|
const project = Project.fromJSON(data);
|
|
52400
54038
|
context.projectPath = filePath;
|
|
@@ -52428,10 +54066,10 @@ Duration: ${summary.duration.toFixed(1)}s`
|
|
|
52428
54066
|
}
|
|
52429
54067
|
try {
|
|
52430
54068
|
const filePath = await resolveProjectPath3(path2, context.workingDirectory);
|
|
52431
|
-
const content = await
|
|
54069
|
+
const content = await readFile37(filePath, "utf-8");
|
|
52432
54070
|
const data = JSON.parse(content);
|
|
52433
54071
|
const project = Project.fromJSON(data);
|
|
52434
|
-
await
|
|
54072
|
+
await writeFile33(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
52435
54073
|
return {
|
|
52436
54074
|
toolCallId: "",
|
|
52437
54075
|
success: true,
|
|
@@ -52454,8 +54092,8 @@ var timeline_exports = {};
|
|
|
52454
54092
|
__export(timeline_exports, {
|
|
52455
54093
|
registerTimelineTools: () => registerTimelineTools
|
|
52456
54094
|
});
|
|
52457
|
-
import { readFile as
|
|
52458
|
-
import { resolve as
|
|
54095
|
+
import { readFile as readFile38, writeFile as writeFile34, stat as stat8 } from "node:fs/promises";
|
|
54096
|
+
import { resolve as resolve45, basename as basename25, extname as extname15 } from "node:path";
|
|
52459
54097
|
function detectMediaType4(path2) {
|
|
52460
54098
|
const ext = extname15(path2).toLowerCase();
|
|
52461
54099
|
const videoExts = [".mp4", ".mov", ".webm", ".avi", ".mkv"];
|
|
@@ -52478,11 +54116,11 @@ async function getMediaDuration2(filePath, mediaType) {
|
|
|
52478
54116
|
}
|
|
52479
54117
|
}
|
|
52480
54118
|
async function resolveProjectPath4(inputPath, cwd) {
|
|
52481
|
-
const filePath =
|
|
54119
|
+
const filePath = resolve45(cwd, inputPath);
|
|
52482
54120
|
try {
|
|
52483
54121
|
const stats = await stat8(filePath);
|
|
52484
54122
|
if (stats.isDirectory()) {
|
|
52485
|
-
return
|
|
54123
|
+
return resolve45(filePath, "project.vibe.json");
|
|
52486
54124
|
}
|
|
52487
54125
|
} catch {
|
|
52488
54126
|
}
|
|
@@ -52490,13 +54128,13 @@ async function resolveProjectPath4(inputPath, cwd) {
|
|
|
52490
54128
|
}
|
|
52491
54129
|
async function loadProject(path2, cwd) {
|
|
52492
54130
|
const filePath = await resolveProjectPath4(path2, cwd);
|
|
52493
|
-
const content = await
|
|
54131
|
+
const content = await readFile38(filePath, "utf-8");
|
|
52494
54132
|
const data = JSON.parse(content);
|
|
52495
54133
|
const project = Project.fromJSON(data);
|
|
52496
54134
|
return { project, filePath };
|
|
52497
54135
|
}
|
|
52498
54136
|
async function saveProject(project, filePath) {
|
|
52499
|
-
await
|
|
54137
|
+
await writeFile34(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
52500
54138
|
}
|
|
52501
54139
|
function registerTimelineTools(registry) {
|
|
52502
54140
|
registry.register(addSourceDef, addSource);
|
|
@@ -52801,7 +54439,7 @@ var init_timeline = __esm({
|
|
|
52801
54439
|
const mediaPath = args.media;
|
|
52802
54440
|
try {
|
|
52803
54441
|
const { project, filePath } = await loadProject(projectPath, context.workingDirectory);
|
|
52804
|
-
const absMediaPath =
|
|
54442
|
+
const absMediaPath = resolve45(context.workingDirectory, mediaPath);
|
|
52805
54443
|
const mediaName = args.name || basename25(mediaPath);
|
|
52806
54444
|
const mediaType = args.type || detectMediaType4(mediaPath);
|
|
52807
54445
|
const duration = args.duration || await getMediaDuration2(absMediaPath, mediaType);
|
|
@@ -53319,8 +54957,8 @@ var filesystem_exports = {};
|
|
|
53319
54957
|
__export(filesystem_exports, {
|
|
53320
54958
|
registerFilesystemTools: () => registerFilesystemTools
|
|
53321
54959
|
});
|
|
53322
|
-
import { readFile as
|
|
53323
|
-
import { resolve as
|
|
54960
|
+
import { readFile as readFile39, writeFile as writeFile35, readdir as readdir5, stat as stat9, access as access8 } from "node:fs/promises";
|
|
54961
|
+
import { resolve as resolve46, join as join9 } from "node:path";
|
|
53324
54962
|
function matchPattern(filename, pattern) {
|
|
53325
54963
|
const regex2 = new RegExp(
|
|
53326
54964
|
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
|
|
@@ -53416,14 +55054,14 @@ var init_filesystem = __esm({
|
|
|
53416
55054
|
const dirPath = args.path || ".";
|
|
53417
55055
|
const pattern = args.pattern;
|
|
53418
55056
|
try {
|
|
53419
|
-
const absPath =
|
|
55057
|
+
const absPath = resolve46(context.workingDirectory, dirPath);
|
|
53420
55058
|
const entries = await readdir5(absPath, { withFileTypes: true });
|
|
53421
55059
|
const results = [];
|
|
53422
55060
|
for (const entry of entries) {
|
|
53423
55061
|
if (pattern && !matchPattern(entry.name, pattern)) {
|
|
53424
55062
|
continue;
|
|
53425
55063
|
}
|
|
53426
|
-
const fullPath =
|
|
55064
|
+
const fullPath = join9(absPath, entry.name);
|
|
53427
55065
|
const stats = await stat9(fullPath);
|
|
53428
55066
|
if (entry.isDirectory()) {
|
|
53429
55067
|
results.push(`[DIR] ${entry.name}/`);
|
|
@@ -53456,8 +55094,8 @@ ${results.join("\n")}`
|
|
|
53456
55094
|
read2 = async (args, context) => {
|
|
53457
55095
|
const filePath = args.path;
|
|
53458
55096
|
try {
|
|
53459
|
-
const absPath =
|
|
53460
|
-
const content = await
|
|
55097
|
+
const absPath = resolve46(context.workingDirectory, filePath);
|
|
55098
|
+
const content = await readFile39(absPath, "utf-8");
|
|
53461
55099
|
const maxLength = 4e3;
|
|
53462
55100
|
const truncated = content.length > maxLength;
|
|
53463
55101
|
const output = truncated ? content.substring(0, maxLength) + "\n... (truncated)" : content;
|
|
@@ -53480,8 +55118,8 @@ ${output}`
|
|
|
53480
55118
|
const filePath = args.path;
|
|
53481
55119
|
const content = args.content;
|
|
53482
55120
|
try {
|
|
53483
|
-
const absPath =
|
|
53484
|
-
await
|
|
55121
|
+
const absPath = resolve46(context.workingDirectory, filePath);
|
|
55122
|
+
await writeFile35(absPath, content, "utf-8");
|
|
53485
55123
|
return {
|
|
53486
55124
|
toolCallId: "",
|
|
53487
55125
|
success: true,
|
|
@@ -53499,7 +55137,7 @@ ${output}`
|
|
|
53499
55137
|
exists = async (args, context) => {
|
|
53500
55138
|
const filePath = args.path;
|
|
53501
55139
|
try {
|
|
53502
|
-
const absPath =
|
|
55140
|
+
const absPath = resolve46(context.workingDirectory, filePath);
|
|
53503
55141
|
await access8(absPath);
|
|
53504
55142
|
const stats = await stat9(absPath);
|
|
53505
55143
|
const type = stats.isDirectory() ? "directory" : "file";
|
|
@@ -53524,8 +55162,8 @@ var media_exports = {};
|
|
|
53524
55162
|
__export(media_exports, {
|
|
53525
55163
|
registerMediaTools: () => registerMediaTools
|
|
53526
55164
|
});
|
|
53527
|
-
import { readFile as
|
|
53528
|
-
import { resolve as
|
|
55165
|
+
import { readFile as readFile40, writeFile as writeFile36 } from "node:fs/promises";
|
|
55166
|
+
import { resolve as resolve47 } from "node:path";
|
|
53529
55167
|
function formatTimestamp2(seconds) {
|
|
53530
55168
|
const mins = Math.floor(seconds / 60);
|
|
53531
55169
|
const secs = (seconds % 60).toFixed(2);
|
|
@@ -53664,7 +55302,7 @@ var init_media = __esm({
|
|
|
53664
55302
|
mediaInfo = async (args, context) => {
|
|
53665
55303
|
const mediaPath = args.path;
|
|
53666
55304
|
try {
|
|
53667
|
-
const absPath =
|
|
55305
|
+
const absPath = resolve47(context.workingDirectory, mediaPath);
|
|
53668
55306
|
const { stdout } = await execSafe("ffprobe", ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", absPath], { maxBuffer: 10 * 1024 * 1024 });
|
|
53669
55307
|
const info = JSON.parse(stdout);
|
|
53670
55308
|
const format = info.format || {};
|
|
@@ -53718,7 +55356,7 @@ Audio:`);
|
|
|
53718
55356
|
const videoPath = args.video;
|
|
53719
55357
|
const threshold = args.threshold || 0.3;
|
|
53720
55358
|
try {
|
|
53721
|
-
const absPath =
|
|
55359
|
+
const absPath = resolve47(context.workingDirectory, videoPath);
|
|
53722
55360
|
const { stdout, stderr } = await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `select='gt(scene,${threshold})',showinfo`, "-f", "null", "-"], { maxBuffer: 50 * 1024 * 1024 });
|
|
53723
55361
|
const output = stdout + stderr;
|
|
53724
55362
|
const scenes = [];
|
|
@@ -53734,7 +55372,7 @@ Audio:`);
|
|
|
53734
55372
|
return `[${i + 1}] ${formatTimestamp2(s.timestamp)} - ${formatTimestamp2(end)} (${(end - s.timestamp).toFixed(1)}s)`;
|
|
53735
55373
|
}).join("\n");
|
|
53736
55374
|
if (args.output) {
|
|
53737
|
-
const outputPath =
|
|
55375
|
+
const outputPath = resolve47(context.workingDirectory, args.output);
|
|
53738
55376
|
const result = {
|
|
53739
55377
|
source: absPath,
|
|
53740
55378
|
totalDuration,
|
|
@@ -53746,7 +55384,7 @@ Audio:`);
|
|
|
53746
55384
|
duration: (i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration) - s.timestamp
|
|
53747
55385
|
}))
|
|
53748
55386
|
};
|
|
53749
|
-
await
|
|
55387
|
+
await writeFile36(outputPath, JSON.stringify(result, null, 2), "utf-8");
|
|
53750
55388
|
}
|
|
53751
55389
|
return {
|
|
53752
55390
|
toolCallId: "",
|
|
@@ -53768,7 +55406,7 @@ ${sceneList}`
|
|
|
53768
55406
|
const noise = args.noise || "-30";
|
|
53769
55407
|
const duration = args.duration || 0.5;
|
|
53770
55408
|
try {
|
|
53771
|
-
const absPath =
|
|
55409
|
+
const absPath = resolve47(context.workingDirectory, mediaPath);
|
|
53772
55410
|
const { stdout, stderr } = await execSafe("ffmpeg", ["-i", absPath, "-af", `silencedetect=noise=${noise}dB:d=${duration}`, "-f", "null", "-"], { maxBuffer: 50 * 1024 * 1024 });
|
|
53773
55411
|
const output = stdout + stderr;
|
|
53774
55412
|
const silences = [];
|
|
@@ -53794,8 +55432,8 @@ ${sceneList}`
|
|
|
53794
55432
|
(s, idx) => `[${idx + 1}] ${formatTimestamp2(s.start)} - ${formatTimestamp2(s.end)} (${s.duration.toFixed(1)}s)`
|
|
53795
55433
|
).join("\n");
|
|
53796
55434
|
if (args.output) {
|
|
53797
|
-
const outputPath =
|
|
53798
|
-
await
|
|
55435
|
+
const outputPath = resolve47(context.workingDirectory, args.output);
|
|
55436
|
+
await writeFile36(
|
|
53799
55437
|
outputPath,
|
|
53800
55438
|
JSON.stringify({ source: absPath, silences }, null, 2),
|
|
53801
55439
|
"utf-8"
|
|
@@ -53819,7 +55457,7 @@ ${silenceList}` : "No silence periods detected."
|
|
|
53819
55457
|
detectBeats = async (args, context) => {
|
|
53820
55458
|
const audioPath = args.audio;
|
|
53821
55459
|
try {
|
|
53822
|
-
const absPath =
|
|
55460
|
+
const absPath = resolve47(context.workingDirectory, audioPath);
|
|
53823
55461
|
const totalDuration = await ffprobeDuration(absPath);
|
|
53824
55462
|
const estimatedBPM = 120;
|
|
53825
55463
|
const beatInterval = 60 / estimatedBPM;
|
|
@@ -53831,8 +55469,8 @@ ${silenceList}` : "No silence periods detected."
|
|
|
53831
55469
|
(t, i) => `[${i + 1}] ${formatTimestamp2(t)}`
|
|
53832
55470
|
).join("\n");
|
|
53833
55471
|
if (args.output) {
|
|
53834
|
-
const outputPath =
|
|
53835
|
-
await
|
|
55472
|
+
const outputPath = resolve47(context.workingDirectory, args.output);
|
|
55473
|
+
await writeFile36(
|
|
53836
55474
|
outputPath,
|
|
53837
55475
|
JSON.stringify({ source: absPath, beatCount: beats.length, beats }, null, 2),
|
|
53838
55476
|
"utf-8"
|
|
@@ -53867,8 +55505,8 @@ ${beatList}${beats.length > 20 ? `
|
|
|
53867
55505
|
error: "OpenAI API key required for transcription. Set OPENAI_API_KEY or configure via 'vibe setup'."
|
|
53868
55506
|
};
|
|
53869
55507
|
}
|
|
53870
|
-
const absPath =
|
|
53871
|
-
const audioBuffer = await
|
|
55508
|
+
const absPath = resolve47(context.workingDirectory, audioPath);
|
|
55509
|
+
const audioBuffer = await readFile40(absPath);
|
|
53872
55510
|
const { WhisperProvider: WhisperProvider2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
53873
55511
|
const whisper = new WhisperProvider2();
|
|
53874
55512
|
await whisper.initialize({ apiKey });
|
|
@@ -53896,11 +55534,11 @@ ${result.fullText}`;
|
|
|
53896
55534
|
}
|
|
53897
55535
|
}
|
|
53898
55536
|
if (args.output) {
|
|
53899
|
-
const outputPath =
|
|
55537
|
+
const outputPath = resolve47(context.workingDirectory, args.output);
|
|
53900
55538
|
const { detectFormat: detectFormat2, formatTranscript: formatTranscript2 } = await Promise.resolve().then(() => (init_subtitle(), subtitle_exports));
|
|
53901
55539
|
const format = detectFormat2(args.output, void 0);
|
|
53902
55540
|
const content = formatTranscript2(result, format);
|
|
53903
|
-
await
|
|
55541
|
+
await writeFile36(outputPath, content, "utf-8");
|
|
53904
55542
|
output += `
|
|
53905
55543
|
|
|
53906
55544
|
Saved to: ${args.output}`;
|
|
@@ -53999,10 +55637,10 @@ Saved to: ${args.output}`;
|
|
|
53999
55637
|
const inputPath = args.input;
|
|
54000
55638
|
const quality = args.quality || "medium";
|
|
54001
55639
|
try {
|
|
54002
|
-
const absInput =
|
|
55640
|
+
const absInput = resolve47(context.workingDirectory, inputPath);
|
|
54003
55641
|
const ext = inputPath.split(".").pop() || "mp4";
|
|
54004
55642
|
const baseName = inputPath.replace(/\.[^/.]+$/, "");
|
|
54005
|
-
const outputPath = args.output ?
|
|
55643
|
+
const outputPath = args.output ? resolve47(context.workingDirectory, args.output) : resolve47(context.workingDirectory, `${baseName}-compressed.${ext}`);
|
|
54006
55644
|
const crfValues = {
|
|
54007
55645
|
low: 28,
|
|
54008
55646
|
medium: 23,
|
|
@@ -54010,8 +55648,8 @@ Saved to: ${args.output}`;
|
|
|
54010
55648
|
};
|
|
54011
55649
|
const crf = crfValues[quality] || 23;
|
|
54012
55650
|
await execSafe("ffmpeg", ["-i", absInput, "-c:v", "libx264", "-crf", String(crf), "-preset", "medium", "-c:a", "aac", "-b:a", "128k", outputPath, "-y"], { maxBuffer: 50 * 1024 * 1024 });
|
|
54013
|
-
const inputStats = await
|
|
54014
|
-
const outputStats = await
|
|
55651
|
+
const inputStats = await readFile40(absInput);
|
|
55652
|
+
const outputStats = await readFile40(outputPath);
|
|
54015
55653
|
const inputSize = inputStats.length;
|
|
54016
55654
|
const outputSize = outputStats.length;
|
|
54017
55655
|
const reduction = ((inputSize - outputSize) / inputSize * 100).toFixed(1);
|
|
@@ -54036,8 +55674,8 @@ Size: ${formatSize2(inputSize)} \u2192 ${formatSize2(outputSize)} (${reduction}%
|
|
|
54036
55674
|
const codec = args.codec;
|
|
54037
55675
|
const audioCodec = args.audioCodec;
|
|
54038
55676
|
try {
|
|
54039
|
-
const absInput =
|
|
54040
|
-
const absOutput =
|
|
55677
|
+
const absInput = resolve47(context.workingDirectory, inputPath);
|
|
55678
|
+
const absOutput = resolve47(context.workingDirectory, outputPath);
|
|
54041
55679
|
let videoCodecName = "copy";
|
|
54042
55680
|
let audioCodecName = "copy";
|
|
54043
55681
|
if (codec) {
|
|
@@ -54085,19 +55723,19 @@ Size: ${formatSize2(inputSize)} \u2192 ${formatSize2(outputSize)} (${reduction}%
|
|
|
54085
55723
|
};
|
|
54086
55724
|
}
|
|
54087
55725
|
try {
|
|
54088
|
-
const absOutput =
|
|
55726
|
+
const absOutput = resolve47(context.workingDirectory, outputPath);
|
|
54089
55727
|
if (reencode) {
|
|
54090
55728
|
const ffmpegArgs = [];
|
|
54091
55729
|
for (const i of inputs) {
|
|
54092
|
-
ffmpegArgs.push("-i",
|
|
55730
|
+
ffmpegArgs.push("-i", resolve47(context.workingDirectory, i));
|
|
54093
55731
|
}
|
|
54094
55732
|
const filterComplex = inputs.map((_, i) => `[${i}:v][${i}:a]`).join("");
|
|
54095
55733
|
ffmpegArgs.push("-filter_complex", `${filterComplex}concat=n=${inputs.length}:v=1:a=1[outv][outa]`, "-map", "[outv]", "-map", "[outa]", absOutput, "-y");
|
|
54096
55734
|
await execSafe("ffmpeg", ffmpegArgs, { maxBuffer: 100 * 1024 * 1024 });
|
|
54097
55735
|
} else {
|
|
54098
|
-
const tempList =
|
|
54099
|
-
const listContent = inputs.map((i) => `file '${
|
|
54100
|
-
await
|
|
55736
|
+
const tempList = resolve47(context.workingDirectory, `concat-list-${Date.now()}.txt`);
|
|
55737
|
+
const listContent = inputs.map((i) => `file '${resolve47(context.workingDirectory, i)}'`).join("\n");
|
|
55738
|
+
await writeFile36(tempList, listContent, "utf-8");
|
|
54101
55739
|
await execSafe("ffmpeg", ["-f", "concat", "-safe", "0", "-i", tempList, "-c", "copy", absOutput, "-y"], { maxBuffer: 100 * 1024 * 1024 });
|
|
54102
55740
|
const { unlink: unlink3 } = await import("node:fs/promises");
|
|
54103
55741
|
await unlink3(tempList).catch(() => {
|
|
@@ -54121,8 +55759,8 @@ Size: ${formatSize2(inputSize)} \u2192 ${formatSize2(outputSize)} (${reduction}%
|
|
|
54121
55759
|
});
|
|
54122
55760
|
|
|
54123
55761
|
// src/agent/tools/ai-generation.ts
|
|
54124
|
-
import { writeFile as
|
|
54125
|
-
import { resolve as
|
|
55762
|
+
import { writeFile as writeFile37, readFile as readFile41 } from "node:fs/promises";
|
|
55763
|
+
import { resolve as resolve48 } from "node:path";
|
|
54126
55764
|
function getTimestamp() {
|
|
54127
55765
|
return Date.now().toString();
|
|
54128
55766
|
}
|
|
@@ -54391,7 +56029,7 @@ var init_ai_generation = __esm({
|
|
|
54391
56029
|
error: `API key required for ${provider}. Configure via 'vibe setup'.`
|
|
54392
56030
|
};
|
|
54393
56031
|
}
|
|
54394
|
-
const outputPath =
|
|
56032
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
54395
56033
|
if (provider === "dalle" || provider === "openai") {
|
|
54396
56034
|
const { OpenAIImageProvider: OpenAIImageProvider2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
54397
56035
|
const openaiImage = new OpenAIImageProvider2();
|
|
@@ -54422,7 +56060,7 @@ var init_ai_generation = __esm({
|
|
|
54422
56060
|
error: "Image generated but no URL or base64 data returned"
|
|
54423
56061
|
};
|
|
54424
56062
|
}
|
|
54425
|
-
await
|
|
56063
|
+
await writeFile37(outputPath, buffer);
|
|
54426
56064
|
} else if (provider === "gemini") {
|
|
54427
56065
|
const { GeminiProvider: GeminiProvider2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
54428
56066
|
const gemini = new GeminiProvider2();
|
|
@@ -54439,7 +56077,7 @@ var init_ai_generation = __esm({
|
|
|
54439
56077
|
const image = result.images[0];
|
|
54440
56078
|
if (image.base64) {
|
|
54441
56079
|
const buffer = Buffer.from(image.base64, "base64");
|
|
54442
|
-
await
|
|
56080
|
+
await writeFile37(outputPath, buffer);
|
|
54443
56081
|
}
|
|
54444
56082
|
} else if (provider === "grok") {
|
|
54445
56083
|
const { GrokProvider: GrokProvider2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
@@ -54458,10 +56096,10 @@ var init_ai_generation = __esm({
|
|
|
54458
56096
|
if (image.url) {
|
|
54459
56097
|
const response = await fetch(image.url);
|
|
54460
56098
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
54461
|
-
await
|
|
56099
|
+
await writeFile37(outputPath, buffer);
|
|
54462
56100
|
} else if (image.base64) {
|
|
54463
56101
|
const buffer = Buffer.from(image.base64, "base64");
|
|
54464
|
-
await
|
|
56102
|
+
await writeFile37(outputPath, buffer);
|
|
54465
56103
|
}
|
|
54466
56104
|
}
|
|
54467
56105
|
return {
|
|
@@ -54497,8 +56135,8 @@ Size: ${size}`
|
|
|
54497
56135
|
};
|
|
54498
56136
|
}
|
|
54499
56137
|
async function prepareReferenceImage(imgPath) {
|
|
54500
|
-
const absImagePath =
|
|
54501
|
-
const imageBuffer = await
|
|
56138
|
+
const absImagePath = resolve48(context.workingDirectory, imgPath);
|
|
56139
|
+
const imageBuffer = await readFile41(absImagePath);
|
|
54502
56140
|
const base64 = imageBuffer.toString("base64");
|
|
54503
56141
|
const ext = imgPath.split(".").pop()?.toLowerCase() || "png";
|
|
54504
56142
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext}`;
|
|
@@ -54552,9 +56190,9 @@ Size: ${size}`
|
|
|
54552
56190
|
};
|
|
54553
56191
|
}
|
|
54554
56192
|
if (finalResult.videoUrl) {
|
|
54555
|
-
const outputPath =
|
|
56193
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
54556
56194
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
54557
|
-
await
|
|
56195
|
+
await writeFile37(outputPath, buffer);
|
|
54558
56196
|
}
|
|
54559
56197
|
}
|
|
54560
56198
|
return {
|
|
@@ -54613,9 +56251,9 @@ Duration: ${duration}s`
|
|
|
54613
56251
|
};
|
|
54614
56252
|
}
|
|
54615
56253
|
if (finalResult.videoUrl) {
|
|
54616
|
-
const outputPath =
|
|
56254
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
54617
56255
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
54618
|
-
await
|
|
56256
|
+
await writeFile37(outputPath, buffer);
|
|
54619
56257
|
}
|
|
54620
56258
|
}
|
|
54621
56259
|
return {
|
|
@@ -54669,9 +56307,9 @@ Duration: ${duration}s`
|
|
|
54669
56307
|
};
|
|
54670
56308
|
}
|
|
54671
56309
|
if (finalResult.videoUrl) {
|
|
54672
|
-
const outputPath =
|
|
56310
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
54673
56311
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
54674
|
-
await
|
|
56312
|
+
await writeFile37(outputPath, buffer);
|
|
54675
56313
|
}
|
|
54676
56314
|
}
|
|
54677
56315
|
return {
|
|
@@ -54754,9 +56392,9 @@ Duration: ${duration}s`
|
|
|
54754
56392
|
};
|
|
54755
56393
|
}
|
|
54756
56394
|
if (finalResult.videoUrl) {
|
|
54757
|
-
const outputPath =
|
|
56395
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
54758
56396
|
const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
|
|
54759
|
-
await
|
|
56397
|
+
await writeFile37(outputPath, buffer);
|
|
54760
56398
|
}
|
|
54761
56399
|
}
|
|
54762
56400
|
return {
|
|
@@ -54806,8 +56444,8 @@ Mode: ${mode}`
|
|
|
54806
56444
|
error: `TTS generation failed: ${result.error || "No audio generated"}`
|
|
54807
56445
|
};
|
|
54808
56446
|
}
|
|
54809
|
-
const outputPath =
|
|
54810
|
-
await
|
|
56447
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
56448
|
+
await writeFile37(outputPath, result.audioBuffer);
|
|
54811
56449
|
return {
|
|
54812
56450
|
toolCallId: "",
|
|
54813
56451
|
success: true,
|
|
@@ -54851,8 +56489,8 @@ Text: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`
|
|
|
54851
56489
|
error: `SFX generation failed: ${result.error || "No audio generated"}`
|
|
54852
56490
|
};
|
|
54853
56491
|
}
|
|
54854
|
-
const outputPath =
|
|
54855
|
-
await
|
|
56492
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
56493
|
+
await writeFile37(outputPath, result.audioBuffer);
|
|
54856
56494
|
return {
|
|
54857
56495
|
toolCallId: "",
|
|
54858
56496
|
success: true,
|
|
@@ -54900,8 +56538,8 @@ Prompt: ${prompt3}`
|
|
|
54900
56538
|
error: `Music generation failed: ${result.error || "No audio generated"}`
|
|
54901
56539
|
};
|
|
54902
56540
|
}
|
|
54903
|
-
const outputPath =
|
|
54904
|
-
await
|
|
56541
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
56542
|
+
await writeFile37(outputPath, result.audioBuffer);
|
|
54905
56543
|
return {
|
|
54906
56544
|
toolCallId: "",
|
|
54907
56545
|
success: true,
|
|
@@ -54953,10 +56591,10 @@ Provider: ElevenLabs${instrumental ? "\nMode: Instrumental" : ""}`
|
|
|
54953
56591
|
}
|
|
54954
56592
|
}
|
|
54955
56593
|
if (finalResult.audioUrl) {
|
|
54956
|
-
const outputPath =
|
|
56594
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
54957
56595
|
const response = await fetch(finalResult.audioUrl);
|
|
54958
56596
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
54959
|
-
await
|
|
56597
|
+
await writeFile37(outputPath, buffer);
|
|
54960
56598
|
} else {
|
|
54961
56599
|
return {
|
|
54962
56600
|
toolCallId: "",
|
|
@@ -55011,8 +56649,8 @@ Provider: Replicate`
|
|
|
55011
56649
|
error: "Failed to generate storyboard"
|
|
55012
56650
|
};
|
|
55013
56651
|
}
|
|
55014
|
-
const outputPath =
|
|
55015
|
-
await
|
|
56652
|
+
const outputPath = resolve48(context.workingDirectory, output);
|
|
56653
|
+
await writeFile37(outputPath, JSON.stringify(result, null, 2), "utf-8");
|
|
55016
56654
|
const sanitizedResult = sanitizeAIResult(result);
|
|
55017
56655
|
const summary = sanitizedResult.map(
|
|
55018
56656
|
(scene, i) => `Scene ${i + 1}: ${scene.description.substring(0, 60)}...`
|
|
@@ -55036,8 +56674,8 @@ ${summary}`
|
|
|
55036
56674
|
generateMotion3 = async (args, context) => {
|
|
55037
56675
|
try {
|
|
55038
56676
|
const { executeMotion: executeMotion2 } = await Promise.resolve().then(() => (init_ai_motion(), ai_motion_exports));
|
|
55039
|
-
const video = args.video ?
|
|
55040
|
-
const output = args.output ?
|
|
56677
|
+
const video = args.video ? resolve48(context.workingDirectory, args.video) : void 0;
|
|
56678
|
+
const output = args.output ? resolve48(context.workingDirectory, args.output) : void 0;
|
|
55041
56679
|
const result = await executeMotion2({
|
|
55042
56680
|
description: args.description,
|
|
55043
56681
|
duration: args.duration,
|
|
@@ -55079,8 +56717,8 @@ ${summary}`
|
|
|
55079
56717
|
});
|
|
55080
56718
|
|
|
55081
56719
|
// src/agent/tools/ai-pipeline.ts
|
|
55082
|
-
import { writeFile as
|
|
55083
|
-
import { resolve as
|
|
56720
|
+
import { writeFile as writeFile38, readFile as readFile42 } from "node:fs/promises";
|
|
56721
|
+
import { resolve as resolve49 } from "node:path";
|
|
55084
56722
|
function getTimestamp2() {
|
|
55085
56723
|
return Date.now().toString();
|
|
55086
56724
|
}
|
|
@@ -55493,7 +57131,7 @@ This tool re-creates videos for failed scenes using image-to-video (if ImgBB key
|
|
|
55493
57131
|
};
|
|
55494
57132
|
scriptToVideoHandler = async (args, context) => {
|
|
55495
57133
|
const script = args.script;
|
|
55496
|
-
const outputDir = args.outputDir ?
|
|
57134
|
+
const outputDir = args.outputDir ? resolve49(context.workingDirectory, args.outputDir) : resolve49(context.workingDirectory, "script-video-output");
|
|
55497
57135
|
try {
|
|
55498
57136
|
const result = await executeScriptToVideo({
|
|
55499
57137
|
script,
|
|
@@ -55565,9 +57203,9 @@ This tool re-creates videos for failed scenes using image-to-video (if ImgBB key
|
|
|
55565
57203
|
}
|
|
55566
57204
|
};
|
|
55567
57205
|
highlightsHandler = async (args, context) => {
|
|
55568
|
-
const media =
|
|
55569
|
-
const output = args.output ?
|
|
55570
|
-
const project = args.project ?
|
|
57206
|
+
const media = resolve49(context.workingDirectory, args.media);
|
|
57207
|
+
const output = args.output ? resolve49(context.workingDirectory, args.output) : void 0;
|
|
57208
|
+
const project = args.project ? resolve49(context.workingDirectory, args.project) : void 0;
|
|
55571
57209
|
try {
|
|
55572
57210
|
const result = await executeHighlights({
|
|
55573
57211
|
media,
|
|
@@ -55629,8 +57267,8 @@ This tool re-creates videos for failed scenes using image-to-video (if ImgBB key
|
|
|
55629
57267
|
}
|
|
55630
57268
|
};
|
|
55631
57269
|
autoShortsHandler = async (args, context) => {
|
|
55632
|
-
const video =
|
|
55633
|
-
const outputDir = args.outputDir ?
|
|
57270
|
+
const video = resolve49(context.workingDirectory, args.video);
|
|
57271
|
+
const outputDir = args.outputDir ? resolve49(context.workingDirectory, args.outputDir) : void 0;
|
|
55634
57272
|
try {
|
|
55635
57273
|
const result = await executeAutoShorts({
|
|
55636
57274
|
video,
|
|
@@ -55694,7 +57332,7 @@ This tool re-creates videos for failed scenes using image-to-video (if ImgBB key
|
|
|
55694
57332
|
geminiVideoHandler = async (args, context) => {
|
|
55695
57333
|
let source = args.source;
|
|
55696
57334
|
if (!source.includes("youtube.com") && !source.includes("youtu.be")) {
|
|
55697
|
-
source =
|
|
57335
|
+
source = resolve49(context.workingDirectory, source);
|
|
55698
57336
|
}
|
|
55699
57337
|
try {
|
|
55700
57338
|
const result = await executeGeminiVideo({
|
|
@@ -55742,7 +57380,7 @@ This tool re-creates videos for failed scenes using image-to-video (if ImgBB key
|
|
|
55742
57380
|
analyzeHandler = async (args, context) => {
|
|
55743
57381
|
let source = args.source;
|
|
55744
57382
|
if (!source.startsWith("http://") && !source.startsWith("https://")) {
|
|
55745
|
-
source =
|
|
57383
|
+
source = resolve49(context.workingDirectory, source);
|
|
55746
57384
|
}
|
|
55747
57385
|
try {
|
|
55748
57386
|
const result = await executeAnalyze({
|
|
@@ -55844,8 +57482,8 @@ This tool re-creates videos for failed scenes using image-to-video (if ImgBB key
|
|
|
55844
57482
|
}
|
|
55845
57483
|
const imageBuffers = [];
|
|
55846
57484
|
for (const imagePath of images) {
|
|
55847
|
-
const absPath =
|
|
55848
|
-
const buffer = await
|
|
57485
|
+
const absPath = resolve49(context.workingDirectory, imagePath);
|
|
57486
|
+
const buffer = await readFile42(absPath);
|
|
55849
57487
|
imageBuffers.push(buffer);
|
|
55850
57488
|
}
|
|
55851
57489
|
let result;
|
|
@@ -55882,14 +57520,14 @@ This tool re-creates videos for failed scenes using image-to-video (if ImgBB key
|
|
|
55882
57520
|
};
|
|
55883
57521
|
}
|
|
55884
57522
|
const img = result.images[0];
|
|
55885
|
-
const outputPath =
|
|
57523
|
+
const outputPath = resolve49(context.workingDirectory, output);
|
|
55886
57524
|
if (img.base64) {
|
|
55887
57525
|
const buffer = Buffer.from(img.base64, "base64");
|
|
55888
|
-
await
|
|
57526
|
+
await writeFile38(outputPath, buffer);
|
|
55889
57527
|
} else if (img.url) {
|
|
55890
57528
|
const resp = await fetch(img.url);
|
|
55891
57529
|
const arrayBuf = await resp.arrayBuffer();
|
|
55892
|
-
await
|
|
57530
|
+
await writeFile38(outputPath, Buffer.from(arrayBuf));
|
|
55893
57531
|
}
|
|
55894
57532
|
return {
|
|
55895
57533
|
toolCallId: "",
|
|
@@ -55956,8 +57594,8 @@ Failed scenes: ${result.failedScenes.join(", ")}`;
|
|
|
55956
57594
|
};
|
|
55957
57595
|
};
|
|
55958
57596
|
animatedCaptionHandler = async (args, context) => {
|
|
55959
|
-
const videoPath =
|
|
55960
|
-
const outputPath =
|
|
57597
|
+
const videoPath = resolve49(context.workingDirectory, args.videoPath);
|
|
57598
|
+
const outputPath = resolve49(context.workingDirectory, args.outputPath);
|
|
55961
57599
|
try {
|
|
55962
57600
|
const result = await executeAnimatedCaption({
|
|
55963
57601
|
videoPath,
|
|
@@ -56002,7 +57640,7 @@ Tier: ${result.tier}`
|
|
|
56002
57640
|
});
|
|
56003
57641
|
|
|
56004
57642
|
// src/agent/tools/ai-editing.ts
|
|
56005
|
-
import { resolve as
|
|
57643
|
+
import { resolve as resolve50 } from "node:path";
|
|
56006
57644
|
function registerEditingTools(registry) {
|
|
56007
57645
|
registry.register(textOverlayDef, textOverlayHandler);
|
|
56008
57646
|
registry.register(reviewDef, reviewHandler);
|
|
@@ -56431,10 +58069,10 @@ var init_ai_editing = __esm({
|
|
|
56431
58069
|
};
|
|
56432
58070
|
};
|
|
56433
58071
|
silenceCutHandler = async (args, context) => {
|
|
56434
|
-
const videoPath =
|
|
58072
|
+
const videoPath = resolve50(context.workingDirectory, args.videoPath);
|
|
56435
58073
|
const ext = videoPath.split(".").pop() || "mp4";
|
|
56436
58074
|
const name = videoPath.replace(/\.[^.]+$/, "");
|
|
56437
|
-
const outputPath = args.outputPath ?
|
|
58075
|
+
const outputPath = args.outputPath ? resolve50(context.workingDirectory, args.outputPath) : `${name}-cut.${ext}`;
|
|
56438
58076
|
try {
|
|
56439
58077
|
const result = await executeSilenceCut({
|
|
56440
58078
|
videoPath,
|
|
@@ -56479,10 +58117,10 @@ var init_ai_editing = __esm({
|
|
|
56479
58117
|
}
|
|
56480
58118
|
};
|
|
56481
58119
|
jumpCutHandler = async (args, context) => {
|
|
56482
|
-
const videoPath =
|
|
58120
|
+
const videoPath = resolve50(context.workingDirectory, args.videoPath);
|
|
56483
58121
|
const ext = videoPath.split(".").pop() || "mp4";
|
|
56484
58122
|
const name = videoPath.replace(/\.[^.]+$/, "");
|
|
56485
|
-
const outputPath = args.outputPath ?
|
|
58123
|
+
const outputPath = args.outputPath ? resolve50(context.workingDirectory, args.outputPath) : `${name}-jumpcut.${ext}`;
|
|
56486
58124
|
try {
|
|
56487
58125
|
const result = await executeJumpCut({
|
|
56488
58126
|
videoPath,
|
|
@@ -56530,10 +58168,10 @@ var init_ai_editing = __esm({
|
|
|
56530
58168
|
}
|
|
56531
58169
|
};
|
|
56532
58170
|
captionHandler = async (args, context) => {
|
|
56533
|
-
const videoPath =
|
|
58171
|
+
const videoPath = resolve50(context.workingDirectory, args.videoPath);
|
|
56534
58172
|
const ext = videoPath.split(".").pop() || "mp4";
|
|
56535
58173
|
const name = videoPath.replace(/\.[^.]+$/, "");
|
|
56536
|
-
const outputPath = args.outputPath ?
|
|
58174
|
+
const outputPath = args.outputPath ? resolve50(context.workingDirectory, args.outputPath) : `${name}-captioned.${ext}`;
|
|
56537
58175
|
try {
|
|
56538
58176
|
const result = await executeCaption({
|
|
56539
58177
|
videoPath,
|
|
@@ -56573,10 +58211,10 @@ var init_ai_editing = __esm({
|
|
|
56573
58211
|
}
|
|
56574
58212
|
};
|
|
56575
58213
|
noiseReduceHandler = async (args, context) => {
|
|
56576
|
-
const inputPath =
|
|
58214
|
+
const inputPath = resolve50(context.workingDirectory, args.inputPath);
|
|
56577
58215
|
const ext = inputPath.split(".").pop() || "mp4";
|
|
56578
58216
|
const name = inputPath.replace(/\.[^.]+$/, "");
|
|
56579
|
-
const outputPath = args.outputPath ?
|
|
58217
|
+
const outputPath = args.outputPath ? resolve50(context.workingDirectory, args.outputPath) : `${name}-denoised.${ext}`;
|
|
56580
58218
|
try {
|
|
56581
58219
|
const result = await executeNoiseReduce({
|
|
56582
58220
|
inputPath,
|
|
@@ -56610,10 +58248,10 @@ var init_ai_editing = __esm({
|
|
|
56610
58248
|
}
|
|
56611
58249
|
};
|
|
56612
58250
|
fadeHandler = async (args, context) => {
|
|
56613
|
-
const videoPath =
|
|
58251
|
+
const videoPath = resolve50(context.workingDirectory, args.videoPath);
|
|
56614
58252
|
const ext = videoPath.split(".").pop() || "mp4";
|
|
56615
58253
|
const name = videoPath.replace(/\.[^.]+$/, "");
|
|
56616
|
-
const outputPath = args.outputPath ?
|
|
58254
|
+
const outputPath = args.outputPath ? resolve50(context.workingDirectory, args.outputPath) : `${name}-faded.${ext}`;
|
|
56617
58255
|
try {
|
|
56618
58256
|
const result = await executeFade({
|
|
56619
58257
|
videoPath,
|
|
@@ -56651,9 +58289,9 @@ var init_ai_editing = __esm({
|
|
|
56651
58289
|
}
|
|
56652
58290
|
};
|
|
56653
58291
|
thumbnailBestFrameHandler = async (args, context) => {
|
|
56654
|
-
const videoPath =
|
|
58292
|
+
const videoPath = resolve50(context.workingDirectory, args.videoPath);
|
|
56655
58293
|
const name = videoPath.replace(/\.[^.]+$/, "");
|
|
56656
|
-
const outputPath = args.outputPath ?
|
|
58294
|
+
const outputPath = args.outputPath ? resolve50(context.workingDirectory, args.outputPath) : `${name}-thumbnail.png`;
|
|
56657
58295
|
try {
|
|
56658
58296
|
const result = await executeThumbnailBestFrame({
|
|
56659
58297
|
videoPath,
|
|
@@ -56688,11 +58326,11 @@ var init_ai_editing = __esm({
|
|
|
56688
58326
|
}
|
|
56689
58327
|
};
|
|
56690
58328
|
translateSrtHandler = async (args, context) => {
|
|
56691
|
-
const srtPath =
|
|
58329
|
+
const srtPath = resolve50(context.workingDirectory, args.srtPath);
|
|
56692
58330
|
const target = args.targetLanguage;
|
|
56693
58331
|
const ext = srtPath.split(".").pop() || "srt";
|
|
56694
58332
|
const name = srtPath.replace(/\.[^.]+$/, "");
|
|
56695
|
-
const outputPath = args.outputPath ?
|
|
58333
|
+
const outputPath = args.outputPath ? resolve50(context.workingDirectory, args.outputPath) : `${name}-${target}.${ext}`;
|
|
56696
58334
|
try {
|
|
56697
58335
|
const result = await executeTranslateSrt({
|
|
56698
58336
|
srtPath,
|
|
@@ -56754,7 +58392,7 @@ var export_exports = {};
|
|
|
56754
58392
|
__export(export_exports, {
|
|
56755
58393
|
registerExportTools: () => registerExportTools
|
|
56756
58394
|
});
|
|
56757
|
-
import { resolve as
|
|
58395
|
+
import { resolve as resolve51, basename as basename26 } from "node:path";
|
|
56758
58396
|
function registerExportTools(registry) {
|
|
56759
58397
|
registry.register(exportVideoDef, exportVideo);
|
|
56760
58398
|
registry.register(exportAudioDef, exportAudio);
|
|
@@ -56849,8 +58487,8 @@ var init_export2 = __esm({
|
|
|
56849
58487
|
const preset = args.preset || "standard";
|
|
56850
58488
|
const overwrite = args.overwrite || false;
|
|
56851
58489
|
const output = args.output || `${basename26(projectPath, ".vibe.json")}.${format}`;
|
|
56852
|
-
const absProjectPath =
|
|
56853
|
-
const absOutputPath =
|
|
58490
|
+
const absProjectPath = resolve51(context.workingDirectory, projectPath);
|
|
58491
|
+
const absOutputPath = resolve51(context.workingDirectory, output);
|
|
56854
58492
|
try {
|
|
56855
58493
|
const result = await runExport(absProjectPath, absOutputPath, {
|
|
56856
58494
|
format,
|
|
@@ -56923,8 +58561,8 @@ var batch_exports = {};
|
|
|
56923
58561
|
__export(batch_exports, {
|
|
56924
58562
|
registerBatchTools: () => registerBatchTools
|
|
56925
58563
|
});
|
|
56926
|
-
import { readFile as
|
|
56927
|
-
import { resolve as
|
|
58564
|
+
import { readFile as readFile43, writeFile as writeFile39, readdir as readdir6 } from "node:fs/promises";
|
|
58565
|
+
import { resolve as resolve52, basename as basename27, extname as extname16, join as join10 } from "node:path";
|
|
56928
58566
|
function detectMediaType5(filePath) {
|
|
56929
58567
|
const ext = extname16(filePath).toLowerCase();
|
|
56930
58568
|
const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
@@ -57085,17 +58723,17 @@ var init_batch = __esm({
|
|
|
57085
58723
|
const filterStr = args.filter;
|
|
57086
58724
|
const imageDuration = args.imageDuration || 5;
|
|
57087
58725
|
try {
|
|
57088
|
-
const filePath =
|
|
57089
|
-
const content = await
|
|
58726
|
+
const filePath = resolve52(context.workingDirectory, projectPath);
|
|
58727
|
+
const content = await readFile43(filePath, "utf-8");
|
|
57090
58728
|
const data = JSON.parse(content);
|
|
57091
58729
|
const project = Project.fromJSON(data);
|
|
57092
|
-
const dirPath =
|
|
58730
|
+
const dirPath = resolve52(context.workingDirectory, directory);
|
|
57093
58731
|
const filterExts = filterStr ? filterStr.split(",").map((e) => e.trim().toLowerCase()) : null;
|
|
57094
58732
|
const mediaFiles = [];
|
|
57095
58733
|
const scanDir = async (dir) => {
|
|
57096
58734
|
const entries = await readdir6(dir, { withFileTypes: true });
|
|
57097
58735
|
for (const entry of entries) {
|
|
57098
|
-
const entryPath =
|
|
58736
|
+
const entryPath = join10(dir, entry.name);
|
|
57099
58737
|
if (entry.isDirectory() && recursive) {
|
|
57100
58738
|
await scanDir(entryPath);
|
|
57101
58739
|
} else if (entry.isFile()) {
|
|
@@ -57136,7 +58774,7 @@ var init_batch = __esm({
|
|
|
57136
58774
|
});
|
|
57137
58775
|
addedSources.push({ id: source.id, name: mediaName, type: mediaType });
|
|
57138
58776
|
}
|
|
57139
|
-
await
|
|
58777
|
+
await writeFile39(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
57140
58778
|
const output = [
|
|
57141
58779
|
`Imported ${addedSources.length} media files:`,
|
|
57142
58780
|
"",
|
|
@@ -57164,8 +58802,8 @@ var init_batch = __esm({
|
|
|
57164
58802
|
const startTime = args.startTime || 0;
|
|
57165
58803
|
const gap = args.gap || 0;
|
|
57166
58804
|
try {
|
|
57167
|
-
const filePath =
|
|
57168
|
-
const content = await
|
|
58805
|
+
const filePath = resolve52(context.workingDirectory, projectPath);
|
|
58806
|
+
const content = await readFile43(filePath, "utf-8");
|
|
57169
58807
|
const data = JSON.parse(content);
|
|
57170
58808
|
const project = Project.fromJSON(data);
|
|
57171
58809
|
const sourcesToConcat = useAll ? project.getSources() : sourceIds.map((id) => project.getSource(id)).filter(Boolean);
|
|
@@ -57212,7 +58850,7 @@ var init_batch = __esm({
|
|
|
57212
58850
|
});
|
|
57213
58851
|
currentTime += source.duration + gap;
|
|
57214
58852
|
}
|
|
57215
|
-
await
|
|
58853
|
+
await writeFile39(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
57216
58854
|
const totalDuration = currentTime - gap - startTime;
|
|
57217
58855
|
const output = [
|
|
57218
58856
|
`Created ${createdClips.length} clips (total: ${totalDuration.toFixed(1)}s):`,
|
|
@@ -57241,8 +58879,8 @@ var init_batch = __esm({
|
|
|
57241
58879
|
const duration = args.duration;
|
|
57242
58880
|
const rawParams = args.params || {};
|
|
57243
58881
|
try {
|
|
57244
|
-
const filePath =
|
|
57245
|
-
const content = await
|
|
58882
|
+
const filePath = resolve52(context.workingDirectory, projectPath);
|
|
58883
|
+
const content = await readFile43(filePath, "utf-8");
|
|
57246
58884
|
const data = JSON.parse(content);
|
|
57247
58885
|
const project = Project.fromJSON(data);
|
|
57248
58886
|
const targetClips = useAll ? project.getClips() : clipIds.map((id) => project.getClip(id)).filter(Boolean);
|
|
@@ -57274,7 +58912,7 @@ var init_batch = __esm({
|
|
|
57274
58912
|
appliedEffects.push({ clipId: clip.id, effectId: effect.id });
|
|
57275
58913
|
}
|
|
57276
58914
|
}
|
|
57277
|
-
await
|
|
58915
|
+
await writeFile39(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
57278
58916
|
return {
|
|
57279
58917
|
toolCallId: "",
|
|
57280
58918
|
success: true,
|
|
@@ -57298,7 +58936,7 @@ __export(scene_exports, {
|
|
|
57298
58936
|
registerSceneTools: () => registerSceneTools,
|
|
57299
58937
|
sceneToolDefinitions: () => sceneToolDefinitions
|
|
57300
58938
|
});
|
|
57301
|
-
import { resolve as
|
|
58939
|
+
import { resolve as resolve53, relative as relative7 } from "node:path";
|
|
57302
58940
|
function summariseLint(result) {
|
|
57303
58941
|
const lines = [
|
|
57304
58942
|
`${result.ok ? "\u2705" : "\u274C"} Lint ${result.ok ? "clean" : "failed"} \u2014 ${result.errorCount} error(s), ${result.warningCount} warning(s), ${result.infoCount} info`
|
|
@@ -57409,7 +59047,7 @@ var init_scene2 = __esm({
|
|
|
57409
59047
|
}
|
|
57410
59048
|
};
|
|
57411
59049
|
sceneInitHandler = async (args, context) => {
|
|
57412
|
-
const dir =
|
|
59050
|
+
const dir = resolve53(context.workingDirectory, args.dir);
|
|
57413
59051
|
try {
|
|
57414
59052
|
const result = await scaffoldSceneProject({
|
|
57415
59053
|
dir,
|
|
@@ -57436,7 +59074,7 @@ var init_scene2 = __esm({
|
|
|
57436
59074
|
}
|
|
57437
59075
|
};
|
|
57438
59076
|
sceneAddHandler = async (args, context) => {
|
|
57439
|
-
const projectDir = args.projectDir ?
|
|
59077
|
+
const projectDir = args.projectDir ? resolve53(context.workingDirectory, args.projectDir) : context.workingDirectory;
|
|
57440
59078
|
const result = await executeSceneAdd({
|
|
57441
59079
|
name: args.name,
|
|
57442
59080
|
preset: args.preset ?? "simple",
|
|
@@ -57473,7 +59111,7 @@ var init_scene2 = __esm({
|
|
|
57473
59111
|
return { toolCallId: "", success: true, output: lines.join("\n") };
|
|
57474
59112
|
};
|
|
57475
59113
|
sceneLintHandler = async (args, context) => {
|
|
57476
|
-
const projectDir = args.projectDir ?
|
|
59114
|
+
const projectDir = args.projectDir ? resolve53(context.workingDirectory, args.projectDir) : context.workingDirectory;
|
|
57477
59115
|
try {
|
|
57478
59116
|
const result = await runProjectLint({
|
|
57479
59117
|
projectDir,
|
|
@@ -57496,7 +59134,7 @@ var init_scene2 = __esm({
|
|
|
57496
59134
|
}
|
|
57497
59135
|
};
|
|
57498
59136
|
sceneRenderHandler = async (args, context) => {
|
|
57499
|
-
const projectDir = args.projectDir ?
|
|
59137
|
+
const projectDir = args.projectDir ? resolve53(context.workingDirectory, args.projectDir) : context.workingDirectory;
|
|
57500
59138
|
const result = await executeSceneRender({
|
|
57501
59139
|
projectDir,
|
|
57502
59140
|
root: args.root,
|
|
@@ -57538,7 +59176,7 @@ init_source();
|
|
|
57538
59176
|
// package.json
|
|
57539
59177
|
var package_default = {
|
|
57540
59178
|
name: "@vibeframe/cli",
|
|
57541
|
-
version: "0.
|
|
59179
|
+
version: "0.59.0",
|
|
57542
59180
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
57543
59181
|
private: false,
|
|
57544
59182
|
license: "MIT",
|
|
@@ -57764,8 +59402,8 @@ projectCommand.command("create").description("Create a new project").argument("<
|
|
|
57764
59402
|
outputPath = resolve2(process.cwd(), options.output);
|
|
57765
59403
|
} else if (name.includes("/")) {
|
|
57766
59404
|
const dirPath = resolve2(process.cwd(), name);
|
|
57767
|
-
const { mkdir:
|
|
57768
|
-
await
|
|
59405
|
+
const { mkdir: mkdir22 } = await import("node:fs/promises");
|
|
59406
|
+
await mkdir22(dirPath, { recursive: true });
|
|
57769
59407
|
outputPath = resolve2(dirPath, "project.vibe.json");
|
|
57770
59408
|
} else {
|
|
57771
59409
|
outputPath = resolve2(process.cwd(), "project.vibe.json");
|
|
@@ -59650,9 +61288,9 @@ function registerViralCommand(ai) {
|
|
|
59650
61288
|
exitWithError(authError("ANTHROPIC_API_KEY", "Anthropic"));
|
|
59651
61289
|
}
|
|
59652
61290
|
let filePath = resolve28(process.cwd(), projectPath);
|
|
59653
|
-
const { statSync } = await import("node:fs");
|
|
61291
|
+
const { statSync: statSync2 } = await import("node:fs");
|
|
59654
61292
|
try {
|
|
59655
|
-
if (
|
|
61293
|
+
if (statSync2(filePath).isDirectory()) {
|
|
59656
61294
|
const candidates = ["project.vibe.json", ".vibe.json"];
|
|
59657
61295
|
let found = false;
|
|
59658
61296
|
for (const candidate of candidates) {
|
|
@@ -65776,20 +67414,20 @@ async function runDiagnostics() {
|
|
|
65776
67414
|
installed = false;
|
|
65777
67415
|
}
|
|
65778
67416
|
} else if (toolName === "chrome") {
|
|
65779
|
-
const { existsSync:
|
|
65780
|
-
const { homedir:
|
|
65781
|
-
const { join:
|
|
67417
|
+
const { existsSync: existsSync30 } = await import("node:fs");
|
|
67418
|
+
const { homedir: homedir5 } = await import("node:os");
|
|
67419
|
+
const { join: join11 } = await import("node:path");
|
|
65782
67420
|
const chromePaths = [
|
|
65783
67421
|
process.env.HYPERFRAMES_CHROME_PATH,
|
|
65784
67422
|
process.env.CHROME_PATH,
|
|
65785
|
-
|
|
67423
|
+
join11(homedir5(), ".cache", "puppeteer", "chrome-headless-shell", "mac_arm-147.0.7727.56", "chrome-headless-shell-mac_arm", "chrome-headless-shell"),
|
|
65786
67424
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
65787
67425
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
65788
67426
|
"/usr/bin/google-chrome",
|
|
65789
67427
|
"/usr/bin/chromium",
|
|
65790
67428
|
"/usr/bin/chromium-browser"
|
|
65791
67429
|
];
|
|
65792
|
-
installed = chromePaths.some((p) => p &&
|
|
67430
|
+
installed = chromePaths.some((p) => p && existsSync30(p));
|
|
65793
67431
|
} else {
|
|
65794
67432
|
installed = commandExists(toolName);
|
|
65795
67433
|
}
|
|
@@ -66170,9 +67808,9 @@ init_ora();
|
|
|
66170
67808
|
|
|
66171
67809
|
// src/pipeline/executor.ts
|
|
66172
67810
|
var import_yaml7 = __toESM(require_dist(), 1);
|
|
66173
|
-
import { resolve as
|
|
66174
|
-
import { readFile as
|
|
66175
|
-
import { existsSync as
|
|
67811
|
+
import { resolve as resolve43 } from "node:path";
|
|
67812
|
+
import { readFile as readFile36, writeFile as writeFile32, mkdir as mkdir20 } from "node:fs/promises";
|
|
67813
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
66176
67814
|
|
|
66177
67815
|
// src/pipeline/resolver.ts
|
|
66178
67816
|
function resolveStepParams(params, completedSteps) {
|
|
@@ -66258,7 +67896,8 @@ var ACTION_TO_COMMAND = {
|
|
|
66258
67896
|
"detect-beats": "detect beats",
|
|
66259
67897
|
"analyze-media": "analyze media",
|
|
66260
67898
|
"analyze-video": "analyze video",
|
|
66261
|
-
"review-video": "analyze review"
|
|
67899
|
+
"review-video": "analyze review",
|
|
67900
|
+
"compose-scenes-with-skills": "compose scenes with skills"
|
|
66262
67901
|
};
|
|
66263
67902
|
function maxCostFor(action) {
|
|
66264
67903
|
const cmd = ACTION_TO_COMMAND[action];
|
|
@@ -66271,9 +67910,9 @@ function registerAction(action, handler) {
|
|
|
66271
67910
|
}
|
|
66272
67911
|
function getOutput(params, outputDir, defaultName) {
|
|
66273
67912
|
if (params.output && typeof params.output === "string") {
|
|
66274
|
-
return
|
|
67913
|
+
return resolve43(outputDir, params.output);
|
|
66275
67914
|
}
|
|
66276
|
-
return
|
|
67915
|
+
return resolve43(outputDir, defaultName);
|
|
66277
67916
|
}
|
|
66278
67917
|
async function ensureActionsRegistered() {
|
|
66279
67918
|
if (Object.keys(ACTION_HANDLERS).length > 0) return;
|
|
@@ -66393,13 +68032,33 @@ async function ensureActionsRegistered() {
|
|
|
66393
68032
|
const r = await executeReview2({ videoPath: params.input, autoApply: params.autoApply, model: params.model });
|
|
66394
68033
|
return { id: "", action: "review-video", success: r.success, output: r.outputPath, data: { feedback: r.feedback }, error: r.error };
|
|
66395
68034
|
});
|
|
68035
|
+
registerAction("compose-scenes-with-skills", async (params, outputDir) => {
|
|
68036
|
+
const { executeComposeScenesWithSkills: executeComposeScenesWithSkills2 } = await Promise.resolve().then(() => (init_compose_scenes_skills(), compose_scenes_skills_exports));
|
|
68037
|
+
const r = await executeComposeScenesWithSkills2(
|
|
68038
|
+
{
|
|
68039
|
+
design: params.design,
|
|
68040
|
+
storyboard: params.storyboard,
|
|
68041
|
+
project: params.project,
|
|
68042
|
+
effort: params.effort
|
|
68043
|
+
},
|
|
68044
|
+
outputDir
|
|
68045
|
+
);
|
|
68046
|
+
return {
|
|
68047
|
+
id: "",
|
|
68048
|
+
action: "compose-scenes-with-skills",
|
|
68049
|
+
success: r.success,
|
|
68050
|
+
output: r.outputPath,
|
|
68051
|
+
data: r.data,
|
|
68052
|
+
error: r.error
|
|
68053
|
+
};
|
|
68054
|
+
});
|
|
66396
68055
|
}
|
|
66397
68056
|
async function loadPipeline(filePath) {
|
|
66398
|
-
const absPath =
|
|
66399
|
-
if (!
|
|
68057
|
+
const absPath = resolve43(process.cwd(), filePath);
|
|
68058
|
+
if (!existsSync29(absPath)) {
|
|
66400
68059
|
throw new Error(`Pipeline file not found: ${absPath}`);
|
|
66401
68060
|
}
|
|
66402
|
-
const content = await
|
|
68061
|
+
const content = await readFile36(absPath, "utf-8");
|
|
66403
68062
|
const manifest = (0, import_yaml7.parse)(content);
|
|
66404
68063
|
if (!manifest.name) throw new Error("Pipeline missing 'name' field");
|
|
66405
68064
|
if (!manifest.steps || !Array.isArray(manifest.steps)) throw new Error("Pipeline missing 'steps' array");
|
|
@@ -66415,15 +68074,15 @@ async function loadPipeline(filePath) {
|
|
|
66415
68074
|
var CHECKPOINT_FILE = ".pipeline-state.yaml";
|
|
66416
68075
|
async function executePipeline(manifest, options = {}) {
|
|
66417
68076
|
await ensureActionsRegistered();
|
|
66418
|
-
const outputDir =
|
|
66419
|
-
await
|
|
68077
|
+
const outputDir = resolve43(process.cwd(), options.outputDir || `${manifest.name}-output`);
|
|
68078
|
+
await mkdir20(outputDir, { recursive: true });
|
|
66420
68079
|
const completedSteps = /* @__PURE__ */ new Map();
|
|
66421
68080
|
const results = [];
|
|
66422
68081
|
const startTime = Date.now();
|
|
66423
68082
|
if (options.resume) {
|
|
66424
|
-
const checkpointPath =
|
|
66425
|
-
if (
|
|
66426
|
-
const checkpointContent = await
|
|
68083
|
+
const checkpointPath = resolve43(outputDir, CHECKPOINT_FILE);
|
|
68084
|
+
if (existsSync29(checkpointPath)) {
|
|
68085
|
+
const checkpointContent = await readFile36(checkpointPath, "utf-8");
|
|
66427
68086
|
const checkpoint = (0, import_yaml7.parse)(checkpointContent);
|
|
66428
68087
|
for (const cs of checkpoint.completedSteps) {
|
|
66429
68088
|
completedSteps.set(cs.id, {
|
|
@@ -66541,8 +68200,8 @@ async function executePipeline(manifest, options = {}) {
|
|
|
66541
68200
|
data: s.data
|
|
66542
68201
|
}))
|
|
66543
68202
|
};
|
|
66544
|
-
await
|
|
66545
|
-
|
|
68203
|
+
await writeFile32(
|
|
68204
|
+
resolve43(outputDir, CHECKPOINT_FILE),
|
|
66546
68205
|
(0, import_yaml7.stringify)(checkpoint, { indent: 2 }),
|
|
66547
68206
|
"utf-8"
|
|
66548
68207
|
);
|
|
@@ -66710,7 +68369,8 @@ Run 'vibe schema run' for structured parameter info.
|
|
|
66710
68369
|
success: s.success,
|
|
66711
68370
|
output: s.output,
|
|
66712
68371
|
duration: s.duration,
|
|
66713
|
-
error: s.error
|
|
68372
|
+
error: s.error,
|
|
68373
|
+
data: s.data
|
|
66714
68374
|
}))
|
|
66715
68375
|
});
|
|
66716
68376
|
} else {
|
|
@@ -67299,14 +68959,14 @@ async function promptConfirm2(toolName, args) {
|
|
|
67299
68959
|
output: process.stdout,
|
|
67300
68960
|
terminal: process.stdin.isTTY ?? false
|
|
67301
68961
|
});
|
|
67302
|
-
return new Promise((
|
|
68962
|
+
return new Promise((resolve54) => {
|
|
67303
68963
|
const argsStr = JSON.stringify(args, null, 2);
|
|
67304
68964
|
console.log();
|
|
67305
68965
|
console.log(source_default.yellow(`Execute ${source_default.bold(toolName)}?`));
|
|
67306
68966
|
console.log(source_default.dim(argsStr));
|
|
67307
68967
|
rl.question(source_default.cyan("(y/n): "), (answer) => {
|
|
67308
68968
|
rl.close();
|
|
67309
|
-
|
|
68969
|
+
resolve54(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
67310
68970
|
});
|
|
67311
68971
|
});
|
|
67312
68972
|
}
|
|
@@ -67395,7 +69055,7 @@ async function startAgent(options = {}) {
|
|
|
67395
69055
|
console.log();
|
|
67396
69056
|
console.log(source_default.dim(" Commands: exit \xB7 reset \xB7 tools \xB7 context"));
|
|
67397
69057
|
console.log();
|
|
67398
|
-
return new Promise((
|
|
69058
|
+
return new Promise((resolve54) => {
|
|
67399
69059
|
if (typeof process.stdin.ref === "function") {
|
|
67400
69060
|
process.stdin.ref();
|
|
67401
69061
|
}
|
|
@@ -67497,7 +69157,7 @@ async function startAgent(options = {}) {
|
|
|
67497
69157
|
clearInterval(keepalive);
|
|
67498
69158
|
console.log();
|
|
67499
69159
|
console.log(source_default.dim("Goodbye!"));
|
|
67500
|
-
|
|
69160
|
+
resolve54();
|
|
67501
69161
|
});
|
|
67502
69162
|
rl.prompt();
|
|
67503
69163
|
});
|
|
@@ -67525,8 +69185,8 @@ init_source();
|
|
|
67525
69185
|
init_config();
|
|
67526
69186
|
init_schema();
|
|
67527
69187
|
init_api_key();
|
|
67528
|
-
import { access as access9, writeFile as
|
|
67529
|
-
import { dirname as
|
|
69188
|
+
import { access as access9, writeFile as writeFile40, mkdir as mkdir21 } from "node:fs/promises";
|
|
69189
|
+
import { dirname as dirname21 } from "node:path";
|
|
67530
69190
|
var BANNER_SHOWN_PATH = CONFIG_PATH.replace(/config\.yaml$/, ".banner-shown");
|
|
67531
69191
|
async function isFirstRun() {
|
|
67532
69192
|
try {
|
|
@@ -67549,8 +69209,8 @@ async function isFirstRun() {
|
|
|
67549
69209
|
}
|
|
67550
69210
|
async function markBannerShown() {
|
|
67551
69211
|
try {
|
|
67552
|
-
await
|
|
67553
|
-
await
|
|
69212
|
+
await mkdir21(dirname21(BANNER_SHOWN_PATH), { recursive: true });
|
|
69213
|
+
await writeFile40(BANNER_SHOWN_PATH, (/* @__PURE__ */ new Date()).toISOString());
|
|
67554
69214
|
} catch {
|
|
67555
69215
|
}
|
|
67556
69216
|
}
|