@conceptcraft/mindframes 0.1.9 → 0.1.11
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/bin/postinstall.js +4 -1
- package/dist/index.js +556 -1299
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,17 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { Command as Command20 } from "commander";
|
|
6
|
+
import chalk13 from "chalk";
|
|
12
7
|
|
|
13
8
|
// src/lib/brand.ts
|
|
14
9
|
import path from "path";
|
|
10
|
+
var BRANDS = {
|
|
11
|
+
conceptcraft: {
|
|
12
|
+
id: "conceptcraft",
|
|
13
|
+
name: "conceptcraft",
|
|
14
|
+
displayName: "ConceptCraft",
|
|
15
|
+
description: "CLI tool for ConceptCraft presentation generation",
|
|
16
|
+
commands: ["cc", "conceptcraft"],
|
|
17
|
+
apiUrl: "https://conceptcraft.ai",
|
|
18
|
+
docsUrl: "https://docs.conceptcraft.ai",
|
|
19
|
+
packageName: "@conceptcraft/cli",
|
|
20
|
+
configDir: ".conceptcraft",
|
|
21
|
+
apiKeyEnvVar: "CONCEPTCRAFT_API_KEY",
|
|
22
|
+
apiUrlEnvVar: "CONCEPTCRAFT_API_URL"
|
|
23
|
+
},
|
|
24
|
+
mindframes: {
|
|
25
|
+
id: "mindframes",
|
|
26
|
+
name: "mindframes",
|
|
27
|
+
displayName: "Mindframes",
|
|
28
|
+
description: "CLI tool for Mindframes presentation generation",
|
|
29
|
+
commands: ["mf", "mindframes"],
|
|
30
|
+
apiUrl: "https://mindframes.app",
|
|
31
|
+
docsUrl: "https://docs.mindframes.app",
|
|
32
|
+
packageName: "@conceptcraft/mindframes",
|
|
33
|
+
configDir: ".mindframes",
|
|
34
|
+
apiKeyEnvVar: "MINDFRAMES_API_KEY",
|
|
35
|
+
apiUrlEnvVar: "MINDFRAMES_API_URL"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var COMMAND_TO_BRAND = {
|
|
39
|
+
cc: "conceptcraft",
|
|
40
|
+
conceptcraft: "conceptcraft",
|
|
41
|
+
mf: "mindframes",
|
|
42
|
+
mindframes: "mindframes"
|
|
43
|
+
};
|
|
15
44
|
function detectBrand() {
|
|
16
45
|
const binaryPath = process.argv[1] || "";
|
|
17
46
|
const binaryName = path.basename(binaryPath).replace(/\.(js|ts)$/, "");
|
|
@@ -26,50 +55,52 @@ function detectBrand() {
|
|
|
26
55
|
}
|
|
27
56
|
return BRANDS.mindframes;
|
|
28
57
|
}
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
description: "CLI tool for ConceptCraft presentation generation",
|
|
39
|
-
commands: ["cc", "conceptcraft"],
|
|
40
|
-
apiUrl: "https://conceptcraft.ai",
|
|
41
|
-
docsUrl: "https://docs.conceptcraft.ai",
|
|
42
|
-
packageName: "@conceptcraft/cli",
|
|
43
|
-
configDir: ".conceptcraft",
|
|
44
|
-
apiKeyEnvVar: "CONCEPTCRAFT_API_KEY",
|
|
45
|
-
apiUrlEnvVar: "CONCEPTCRAFT_API_URL"
|
|
46
|
-
},
|
|
47
|
-
mindframes: {
|
|
48
|
-
id: "mindframes",
|
|
49
|
-
name: "mindframes",
|
|
50
|
-
displayName: "Mindframes",
|
|
51
|
-
description: "CLI tool for Mindframes presentation generation",
|
|
52
|
-
commands: ["mf", "mindframes"],
|
|
53
|
-
apiUrl: "https://mindframes.app",
|
|
54
|
-
docsUrl: "https://docs.mindframes.app",
|
|
55
|
-
packageName: "@conceptcraft/mindframes",
|
|
56
|
-
configDir: ".mindframes",
|
|
57
|
-
apiKeyEnvVar: "MINDFRAMES_API_KEY",
|
|
58
|
-
apiUrlEnvVar: "MINDFRAMES_API_URL"
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
COMMAND_TO_BRAND = {
|
|
62
|
-
cc: "conceptcraft",
|
|
63
|
-
conceptcraft: "conceptcraft",
|
|
64
|
-
mf: "mindframes",
|
|
65
|
-
mindframes: "mindframes"
|
|
66
|
-
};
|
|
67
|
-
brand = detectBrand();
|
|
68
|
-
}
|
|
69
|
-
});
|
|
58
|
+
var brand = detectBrand();
|
|
59
|
+
|
|
60
|
+
// src/commands/login.ts
|
|
61
|
+
import { Command } from "commander";
|
|
62
|
+
import chalk3 from "chalk";
|
|
63
|
+
import ora from "ora";
|
|
64
|
+
import http from "http";
|
|
65
|
+
import { randomBytes, createHash } from "crypto";
|
|
66
|
+
import open from "open";
|
|
70
67
|
|
|
71
68
|
// src/lib/config.ts
|
|
72
69
|
import Conf from "conf";
|
|
70
|
+
var DEFAULT_API_URL = "https://www.mindframes.app";
|
|
71
|
+
var schema = {
|
|
72
|
+
apiKey: {
|
|
73
|
+
type: "string"
|
|
74
|
+
},
|
|
75
|
+
apiUrl: {
|
|
76
|
+
type: "string",
|
|
77
|
+
default: DEFAULT_API_URL
|
|
78
|
+
},
|
|
79
|
+
defaultTeamId: {
|
|
80
|
+
type: "string"
|
|
81
|
+
},
|
|
82
|
+
// OAuth tokens (preferred over API key)
|
|
83
|
+
accessToken: {
|
|
84
|
+
type: "string"
|
|
85
|
+
},
|
|
86
|
+
refreshToken: {
|
|
87
|
+
type: "string"
|
|
88
|
+
},
|
|
89
|
+
tokenExpiresAt: {
|
|
90
|
+
type: "number"
|
|
91
|
+
},
|
|
92
|
+
// OAuth client registration (cached per-server)
|
|
93
|
+
clientId: {
|
|
94
|
+
type: "string"
|
|
95
|
+
},
|
|
96
|
+
clientSecret: {
|
|
97
|
+
type: "string"
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var config = new Conf({
|
|
101
|
+
projectName: brand.name,
|
|
102
|
+
schema
|
|
103
|
+
});
|
|
73
104
|
function getConfig() {
|
|
74
105
|
return {
|
|
75
106
|
apiKey: getApiKey(),
|
|
@@ -151,47 +182,6 @@ function setOAuthClient(clientId, clientSecret) {
|
|
|
151
182
|
config.set("clientId", clientId);
|
|
152
183
|
config.set("clientSecret", clientSecret);
|
|
153
184
|
}
|
|
154
|
-
var DEFAULT_API_URL, schema, config;
|
|
155
|
-
var init_config = __esm({
|
|
156
|
-
"src/lib/config.ts"() {
|
|
157
|
-
"use strict";
|
|
158
|
-
init_brand();
|
|
159
|
-
DEFAULT_API_URL = "https://www.mindframes.app";
|
|
160
|
-
schema = {
|
|
161
|
-
apiKey: {
|
|
162
|
-
type: "string"
|
|
163
|
-
},
|
|
164
|
-
apiUrl: {
|
|
165
|
-
type: "string",
|
|
166
|
-
default: DEFAULT_API_URL
|
|
167
|
-
},
|
|
168
|
-
defaultTeamId: {
|
|
169
|
-
type: "string"
|
|
170
|
-
},
|
|
171
|
-
// OAuth tokens (preferred over API key)
|
|
172
|
-
accessToken: {
|
|
173
|
-
type: "string"
|
|
174
|
-
},
|
|
175
|
-
refreshToken: {
|
|
176
|
-
type: "string"
|
|
177
|
-
},
|
|
178
|
-
tokenExpiresAt: {
|
|
179
|
-
type: "number"
|
|
180
|
-
},
|
|
181
|
-
// OAuth client registration (cached per-server)
|
|
182
|
-
clientId: {
|
|
183
|
-
type: "string"
|
|
184
|
-
},
|
|
185
|
-
clientSecret: {
|
|
186
|
-
type: "string"
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
config = new Conf({
|
|
190
|
-
projectName: brand.name,
|
|
191
|
-
schema
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
185
|
|
|
196
186
|
// src/lib/output.ts
|
|
197
187
|
import chalk from "chalk";
|
|
@@ -358,40 +348,25 @@ function formatJson(data) {
|
|
|
358
348
|
function printJson(data) {
|
|
359
349
|
console.log(formatJson(data));
|
|
360
350
|
}
|
|
361
|
-
var init_output = __esm({
|
|
362
|
-
"src/lib/output.ts"() {
|
|
363
|
-
"use strict";
|
|
364
|
-
init_config();
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
351
|
|
|
368
|
-
// src/
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
});
|
|
352
|
+
// src/lib/feature-cache.ts
|
|
353
|
+
import Conf2 from "conf";
|
|
354
|
+
|
|
355
|
+
// src/lib/auth.ts
|
|
356
|
+
import chalk2 from "chalk";
|
|
374
357
|
|
|
375
358
|
// src/types/index.ts
|
|
376
|
-
var EXIT_CODES
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
NOT_FOUND: 3,
|
|
386
|
-
RATE_LIMIT: 4,
|
|
387
|
-
NETWORK_ERROR: 5,
|
|
388
|
-
INVALID_INPUT: 6
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
});
|
|
359
|
+
var EXIT_CODES = {
|
|
360
|
+
SUCCESS: 0,
|
|
361
|
+
GENERAL_ERROR: 1,
|
|
362
|
+
AUTH_ERROR: 2,
|
|
363
|
+
NOT_FOUND: 3,
|
|
364
|
+
RATE_LIMIT: 4,
|
|
365
|
+
NETWORK_ERROR: 5,
|
|
366
|
+
INVALID_INPUT: 6
|
|
367
|
+
};
|
|
392
368
|
|
|
393
369
|
// src/lib/auth.ts
|
|
394
|
-
import chalk2 from "chalk";
|
|
395
370
|
async function refreshAccessToken() {
|
|
396
371
|
const refreshToken = getRefreshToken();
|
|
397
372
|
const clientId = getClientId();
|
|
@@ -440,21 +415,15 @@ function hasAuth() {
|
|
|
440
415
|
}
|
|
441
416
|
async function requireAuth() {
|
|
442
417
|
if (!hasAuth()) {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
const { runLoginFlow: runLoginFlow2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
454
|
-
await runLoginFlow2({ browser: true });
|
|
455
|
-
} catch {
|
|
456
|
-
process.exit(EXIT_CODES.AUTH_ERROR);
|
|
457
|
-
}
|
|
418
|
+
const cmd2 = brand.commands[0];
|
|
419
|
+
const envVar = brand.apiKeyEnvVar;
|
|
420
|
+
console.error(chalk2.red("\u2717 Not authenticated."));
|
|
421
|
+
console.error();
|
|
422
|
+
console.error(chalk2.bold("To authenticate, run:"));
|
|
423
|
+
console.error(chalk2.cyan(` ${cmd2} login`));
|
|
424
|
+
console.error();
|
|
425
|
+
console.error(chalk2.gray(`Or set ${envVar} environment variable.`));
|
|
426
|
+
process.exit(EXIT_CODES.AUTH_ERROR);
|
|
458
427
|
}
|
|
459
428
|
}
|
|
460
429
|
function maskApiKey(key) {
|
|
@@ -472,13 +441,6 @@ function isValidApiKeyFormat(key) {
|
|
|
472
441
|
const validPrefixes = ["cc_slides_", "mcp_", "cc_sk_", "sk_"];
|
|
473
442
|
return validPrefixes.some((prefix) => key.startsWith(prefix));
|
|
474
443
|
}
|
|
475
|
-
var init_auth = __esm({
|
|
476
|
-
"src/lib/auth.ts"() {
|
|
477
|
-
"use strict";
|
|
478
|
-
init_config();
|
|
479
|
-
init_types();
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
444
|
|
|
483
445
|
// src/lib/api.ts
|
|
484
446
|
import { readFileSync, statSync } from "fs";
|
|
@@ -495,6 +457,14 @@ async function getAuthHeaders() {
|
|
|
495
457
|
}
|
|
496
458
|
return {};
|
|
497
459
|
}
|
|
460
|
+
var ApiError = class extends Error {
|
|
461
|
+
constructor(message, statusCode, exitCode = 1) {
|
|
462
|
+
super(message);
|
|
463
|
+
this.statusCode = statusCode;
|
|
464
|
+
this.exitCode = exitCode;
|
|
465
|
+
this.name = "ApiError";
|
|
466
|
+
}
|
|
467
|
+
};
|
|
498
468
|
async function request(endpoint, options = {}) {
|
|
499
469
|
const apiUrl = getApiUrl();
|
|
500
470
|
if (!hasAuth()) {
|
|
@@ -870,13 +840,13 @@ async function importPresentation(fileBuffer, fileName, options = {}) {
|
|
|
870
840
|
return result;
|
|
871
841
|
}
|
|
872
842
|
async function listBrandings() {
|
|
873
|
-
return request("/api/branding");
|
|
843
|
+
return request("/api/cli/branding");
|
|
874
844
|
}
|
|
875
845
|
async function getBranding(id) {
|
|
876
846
|
return request(`/api/branding/${id}`);
|
|
877
847
|
}
|
|
878
848
|
async function extractBranding(url, teamId) {
|
|
879
|
-
return request("/api/branding/extract", {
|
|
849
|
+
return request("/api/cli/branding/extract", {
|
|
880
850
|
method: "POST",
|
|
881
851
|
body: { url, teamId }
|
|
882
852
|
});
|
|
@@ -1022,6 +992,51 @@ async function generateSpeech(ttsRequest) {
|
|
|
1022
992
|
timestamps
|
|
1023
993
|
};
|
|
1024
994
|
}
|
|
995
|
+
async function generateSpeechBatch(batchRequest) {
|
|
996
|
+
const apiUrl = getApiUrl();
|
|
997
|
+
if (!hasAuth()) {
|
|
998
|
+
throw new ApiError(
|
|
999
|
+
`Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
|
|
1000
|
+
401,
|
|
1001
|
+
2
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
const authHeaders = await getAuthHeaders();
|
|
1005
|
+
let response;
|
|
1006
|
+
try {
|
|
1007
|
+
response = await fetch(`${apiUrl}/api/cli/tts/batch`, {
|
|
1008
|
+
method: "POST",
|
|
1009
|
+
headers: {
|
|
1010
|
+
"Content-Type": "application/json",
|
|
1011
|
+
...authHeaders
|
|
1012
|
+
},
|
|
1013
|
+
body: JSON.stringify(batchRequest)
|
|
1014
|
+
});
|
|
1015
|
+
} catch (error2) {
|
|
1016
|
+
throw new ApiError(
|
|
1017
|
+
`Network error: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
|
|
1018
|
+
0,
|
|
1019
|
+
5
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
if (!response.ok) {
|
|
1023
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
1024
|
+
let errorMessage;
|
|
1025
|
+
try {
|
|
1026
|
+
const errorJson = JSON.parse(errorText);
|
|
1027
|
+
errorMessage = errorJson.error || errorJson.message || errorText;
|
|
1028
|
+
} catch {
|
|
1029
|
+
errorMessage = errorText;
|
|
1030
|
+
}
|
|
1031
|
+
throw new ApiError(errorMessage, response.status, response.status === 401 ? 2 : 1);
|
|
1032
|
+
}
|
|
1033
|
+
const result = await response.json();
|
|
1034
|
+
result.results = result.results.map((r) => ({
|
|
1035
|
+
...r,
|
|
1036
|
+
audioData: Buffer.from(r.audioData, "base64")
|
|
1037
|
+
}));
|
|
1038
|
+
return result;
|
|
1039
|
+
}
|
|
1025
1040
|
async function getVoices() {
|
|
1026
1041
|
return request("/api/cli/tts");
|
|
1027
1042
|
}
|
|
@@ -1088,26 +1103,14 @@ async function pollForCompletion(checkFn, maxAttempts = 60, intervalMs = 2e3) {
|
|
|
1088
1103
|
}
|
|
1089
1104
|
throw new ApiError("Operation timed out", 408, 1);
|
|
1090
1105
|
}
|
|
1091
|
-
var ApiError;
|
|
1092
|
-
var init_api = __esm({
|
|
1093
|
-
"src/lib/api.ts"() {
|
|
1094
|
-
"use strict";
|
|
1095
|
-
init_config();
|
|
1096
|
-
init_auth();
|
|
1097
|
-
init_brand();
|
|
1098
|
-
ApiError = class extends Error {
|
|
1099
|
-
constructor(message, statusCode, exitCode = 1) {
|
|
1100
|
-
super(message);
|
|
1101
|
-
this.statusCode = statusCode;
|
|
1102
|
-
this.exitCode = exitCode;
|
|
1103
|
-
this.name = "ApiError";
|
|
1104
|
-
}
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
1106
|
|
|
1109
1107
|
// src/lib/feature-cache.ts
|
|
1110
|
-
|
|
1108
|
+
var cache = new Conf2({
|
|
1109
|
+
projectName: "conceptcraft",
|
|
1110
|
+
configName: "feature-cache"
|
|
1111
|
+
});
|
|
1112
|
+
var FRESH_TTL = 60 * 60 * 1e3;
|
|
1113
|
+
var STALE_TTL = 24 * 60 * 60 * 1e3;
|
|
1111
1114
|
function hashApiKey(key) {
|
|
1112
1115
|
let hash = 0;
|
|
1113
1116
|
for (let i = 0; i < key.length; i++) {
|
|
@@ -1153,33 +1156,11 @@ function invalidateCache() {
|
|
|
1153
1156
|
function getCachePath() {
|
|
1154
1157
|
return cache.path;
|
|
1155
1158
|
}
|
|
1156
|
-
var cache, FRESH_TTL, STALE_TTL;
|
|
1157
|
-
var init_feature_cache = __esm({
|
|
1158
|
-
"src/lib/feature-cache.ts"() {
|
|
1159
|
-
"use strict";
|
|
1160
|
-
init_api();
|
|
1161
|
-
init_config();
|
|
1162
|
-
cache = new Conf2({
|
|
1163
|
-
projectName: "conceptcraft",
|
|
1164
|
-
configName: "feature-cache"
|
|
1165
|
-
});
|
|
1166
|
-
FRESH_TTL = 60 * 60 * 1e3;
|
|
1167
|
-
STALE_TTL = 24 * 60 * 60 * 1e3;
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
1170
1159
|
|
|
1171
1160
|
// src/commands/login.ts
|
|
1172
|
-
var
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
runLoginFlow: () => runLoginFlow
|
|
1176
|
-
});
|
|
1177
|
-
import { Command } from "commander";
|
|
1178
|
-
import chalk3 from "chalk";
|
|
1179
|
-
import ora from "ora";
|
|
1180
|
-
import http from "http";
|
|
1181
|
-
import { randomBytes, createHash } from "crypto";
|
|
1182
|
-
import open from "open";
|
|
1161
|
+
var CLI_CLIENT_NAME = "ConceptCraft CLI";
|
|
1162
|
+
var CALLBACK_PORT_START = 8765;
|
|
1163
|
+
var CALLBACK_PORT_END = 8775;
|
|
1183
1164
|
function generateCodeVerifier() {
|
|
1184
1165
|
return randomBytes(32).toString("base64url");
|
|
1185
1166
|
}
|
|
@@ -1427,43 +1408,22 @@ async function runLoginFlow(options) {
|
|
|
1427
1408
|
throw err;
|
|
1428
1409
|
}
|
|
1429
1410
|
}
|
|
1430
|
-
var
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
"
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
if (hasOAuthTokens()) {
|
|
1443
|
-
warn("You are already logged in.");
|
|
1444
|
-
info("Run 'conceptcraft logout' to log out first, or continue to re-authenticate.");
|
|
1445
|
-
console.log();
|
|
1446
|
-
}
|
|
1447
|
-
try {
|
|
1448
|
-
await runLoginFlow(options);
|
|
1449
|
-
process.exit(0);
|
|
1450
|
-
} catch {
|
|
1451
|
-
process.exit(1);
|
|
1452
|
-
}
|
|
1453
|
-
});
|
|
1411
|
+
var loginCommand = new Command("login").description("Authenticate with ConceptCraft (opens browser)").option("--no-browser", "Print URL instead of opening browser").action(async (options) => {
|
|
1412
|
+
console.log();
|
|
1413
|
+
if (hasOAuthTokens()) {
|
|
1414
|
+
warn("You are already logged in.");
|
|
1415
|
+
info("Run 'conceptcraft logout' to log out first, or continue to re-authenticate.");
|
|
1416
|
+
console.log();
|
|
1417
|
+
}
|
|
1418
|
+
try {
|
|
1419
|
+
await runLoginFlow(options);
|
|
1420
|
+
process.exit(0);
|
|
1421
|
+
} catch {
|
|
1422
|
+
process.exit(1);
|
|
1454
1423
|
}
|
|
1455
1424
|
});
|
|
1456
1425
|
|
|
1457
|
-
// src/index.ts
|
|
1458
|
-
init_brand();
|
|
1459
|
-
init_login();
|
|
1460
|
-
import { Command as Command20 } from "commander";
|
|
1461
|
-
import chalk13 from "chalk";
|
|
1462
|
-
|
|
1463
1426
|
// src/commands/logout.ts
|
|
1464
|
-
init_config();
|
|
1465
|
-
init_feature_cache();
|
|
1466
|
-
init_output();
|
|
1467
1427
|
import { Command as Command2 } from "commander";
|
|
1468
1428
|
import { confirm } from "@inquirer/prompts";
|
|
1469
1429
|
var logoutCommand = new Command2("logout").description("Log out and clear authentication").option("--all", "Clear all config including API key").action(async (options) => {
|
|
@@ -1503,11 +1463,6 @@ var logoutCommand = new Command2("logout").description("Log out and clear authen
|
|
|
1503
1463
|
});
|
|
1504
1464
|
|
|
1505
1465
|
// src/commands/config.ts
|
|
1506
|
-
init_config();
|
|
1507
|
-
init_auth();
|
|
1508
|
-
init_api();
|
|
1509
|
-
init_feature_cache();
|
|
1510
|
-
init_output();
|
|
1511
1466
|
import { Command as Command3 } from "commander";
|
|
1512
1467
|
import chalk4 from "chalk";
|
|
1513
1468
|
import { input, password, confirm as confirm2, select } from "@inquirer/prompts";
|
|
@@ -1711,13 +1666,10 @@ var configCommand = new Command3("config").description("Manage CLI configuration
|
|
|
1711
1666
|
);
|
|
1712
1667
|
|
|
1713
1668
|
// src/commands/create.ts
|
|
1714
|
-
init_api();
|
|
1715
|
-
init_auth();
|
|
1716
1669
|
import { Command as Command4 } from "commander";
|
|
1717
1670
|
import chalk6 from "chalk";
|
|
1718
1671
|
|
|
1719
1672
|
// src/lib/streaming.ts
|
|
1720
|
-
init_output();
|
|
1721
1673
|
import { createParser } from "eventsource-parser";
|
|
1722
1674
|
import chalk5 from "chalk";
|
|
1723
1675
|
import ora3 from "ora";
|
|
@@ -1979,7 +1931,6 @@ async function streamTextContent(response, options = {}) {
|
|
|
1979
1931
|
}
|
|
1980
1932
|
|
|
1981
1933
|
// src/commands/create.ts
|
|
1982
|
-
init_output();
|
|
1983
1934
|
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
1984
1935
|
import { resolve } from "path";
|
|
1985
1936
|
var VALID_GOALS = [
|
|
@@ -2461,10 +2412,6 @@ function isUrl(str) {
|
|
|
2461
2412
|
}
|
|
2462
2413
|
|
|
2463
2414
|
// src/commands/list.ts
|
|
2464
|
-
init_api();
|
|
2465
|
-
init_auth();
|
|
2466
|
-
init_config();
|
|
2467
|
-
init_output();
|
|
2468
2415
|
import { Command as Command5 } from "commander";
|
|
2469
2416
|
var listCommand = new Command5("list").description("List presentations").option("-n, --limit <count>", "Number of results to return", "20").option(
|
|
2470
2417
|
"-f, --format <format>",
|
|
@@ -2528,9 +2475,6 @@ var listCommand = new Command5("list").description("List presentations").option(
|
|
|
2528
2475
|
});
|
|
2529
2476
|
|
|
2530
2477
|
// src/commands/get.ts
|
|
2531
|
-
init_api();
|
|
2532
|
-
init_auth();
|
|
2533
|
-
init_output();
|
|
2534
2478
|
import { Command as Command6 } from "commander";
|
|
2535
2479
|
import chalk7 from "chalk";
|
|
2536
2480
|
var getCommand = new Command6("get").description("Get presentation details").argument("<slug>", "Presentation slug (e.g., my-presentation-v1-abc123)").option(
|
|
@@ -2573,9 +2517,6 @@ var getCommand = new Command6("get").description("Get presentation details").arg
|
|
|
2573
2517
|
});
|
|
2574
2518
|
|
|
2575
2519
|
// src/commands/delete.ts
|
|
2576
|
-
init_api();
|
|
2577
|
-
init_auth();
|
|
2578
|
-
init_output();
|
|
2579
2520
|
import { Command as Command7 } from "commander";
|
|
2580
2521
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
2581
2522
|
var deleteCommand = new Command7("delete").description("Delete a presentation").argument("<slug>", "Presentation slug (e.g., my-presentation-v1-abc123)").option("-f, --force", "Skip confirmation prompt").option("-q, --quiet", "Suppress output").action(async (slug, options) => {
|
|
@@ -2611,9 +2552,6 @@ var deleteCommand = new Command7("delete").description("Delete a presentation").
|
|
|
2611
2552
|
});
|
|
2612
2553
|
|
|
2613
2554
|
// src/commands/export.ts
|
|
2614
|
-
init_api();
|
|
2615
|
-
init_auth();
|
|
2616
|
-
init_output();
|
|
2617
2555
|
import { Command as Command8 } from "commander";
|
|
2618
2556
|
import { writeFile } from "fs/promises";
|
|
2619
2557
|
import { resolve as resolve2 } from "path";
|
|
@@ -2655,10 +2593,6 @@ function formatBytes(bytes) {
|
|
|
2655
2593
|
}
|
|
2656
2594
|
|
|
2657
2595
|
// src/commands/import.ts
|
|
2658
|
-
init_api();
|
|
2659
|
-
init_auth();
|
|
2660
|
-
init_output();
|
|
2661
|
-
init_config();
|
|
2662
2596
|
import { Command as Command9 } from "commander";
|
|
2663
2597
|
import { readFile } from "fs/promises";
|
|
2664
2598
|
import { resolve as resolve3, basename as basename2 } from "path";
|
|
@@ -2748,10 +2682,6 @@ var importCommand = cmd.option("--dry-run", "Validate without importing").option
|
|
|
2748
2682
|
});
|
|
2749
2683
|
|
|
2750
2684
|
// src/commands/branding.ts
|
|
2751
|
-
init_api();
|
|
2752
|
-
init_auth();
|
|
2753
|
-
init_config();
|
|
2754
|
-
init_output();
|
|
2755
2685
|
import { Command as Command10 } from "commander";
|
|
2756
2686
|
import chalk8 from "chalk";
|
|
2757
2687
|
import ora6 from "ora";
|
|
@@ -2925,13 +2855,9 @@ var brandingCommand = new Command10("branding").description("Manage brand profil
|
|
|
2925
2855
|
);
|
|
2926
2856
|
|
|
2927
2857
|
// src/commands/derive.ts
|
|
2928
|
-
init_api();
|
|
2929
|
-
init_auth();
|
|
2930
|
-
init_feature_cache();
|
|
2931
2858
|
import { Command as Command11 } from "commander";
|
|
2932
2859
|
import chalk9 from "chalk";
|
|
2933
2860
|
import ora7 from "ora";
|
|
2934
|
-
init_output();
|
|
2935
2861
|
function createBlogCommand() {
|
|
2936
2862
|
return new Command11("blog").description("Generate a blog post from a presentation").argument("<slug>", "Presentation slug").option("--words <count>", "Target word count (50-2000)", "300").option(
|
|
2937
2863
|
"--tone <tone>",
|
|
@@ -3055,8 +2981,6 @@ function buildDeriveCommand() {
|
|
|
3055
2981
|
}
|
|
3056
2982
|
|
|
3057
2983
|
// src/commands/ideas.ts
|
|
3058
|
-
init_auth();
|
|
3059
|
-
init_output();
|
|
3060
2984
|
import { Command as Command12 } from "commander";
|
|
3061
2985
|
import chalk10 from "chalk";
|
|
3062
2986
|
var ideasCommand = new Command12("ideas").description("Generate presentation topic ideas").option("--context <text>", "Custom context for idea generation").option("--count <n>", "Number of ideas to generate", "5").option("-f, --format <format>", "Output format (human, json)", "human").action(async (options) => {
|
|
@@ -3080,10 +3004,6 @@ var ideasCommand = new Command12("ideas").description("Generate presentation top
|
|
|
3080
3004
|
});
|
|
3081
3005
|
|
|
3082
3006
|
// src/commands/whoami.ts
|
|
3083
|
-
init_api();
|
|
3084
|
-
init_auth();
|
|
3085
|
-
init_feature_cache();
|
|
3086
|
-
init_output();
|
|
3087
3007
|
import { Command as Command13 } from "commander";
|
|
3088
3008
|
import chalk11 from "chalk";
|
|
3089
3009
|
var whoamiCommand = new Command13("whoami").description("Show current user and team information").option("-f, --format <format>", "Output format (human, json)", "human").action(async (options) => {
|
|
@@ -3139,21 +3059,19 @@ var whoamiCommand = new Command13("whoami").description("Show current user and t
|
|
|
3139
3059
|
});
|
|
3140
3060
|
|
|
3141
3061
|
// src/commands/skill/index.ts
|
|
3142
|
-
init_output();
|
|
3143
|
-
init_brand();
|
|
3144
3062
|
import { Command as Command14 } from "commander";
|
|
3145
3063
|
import chalk12 from "chalk";
|
|
3146
3064
|
|
|
3147
3065
|
// src/commands/skill/generate-main-skill.ts
|
|
3148
3066
|
function generateMainSkillContent(context) {
|
|
3149
|
-
const { name, cmd: cmd2
|
|
3150
|
-
const envPrefix = name.toUpperCase().replace(
|
|
3067
|
+
const { name, cmd: cmd2 } = context;
|
|
3068
|
+
const envPrefix = name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
3151
3069
|
return `---
|
|
3152
3070
|
name: ${name}
|
|
3153
|
-
description: ${
|
|
3071
|
+
description: ${name} CLI for AI-powered content creation. Use when user needs to create presentations, generate video assets (voiceover, music, images, stock videos), use text-to-speech, mix audio, search stock media, or manage branding. This is the main entry point - load specialized skills (${name}-video, ${name}-presentation) for detailed workflows.
|
|
3154
3072
|
---
|
|
3155
3073
|
|
|
3156
|
-
# ${
|
|
3074
|
+
# ${name} CLI
|
|
3157
3075
|
|
|
3158
3076
|
A comprehensive CLI for AI-powered content creation. Generate presentations, video assets, voiceovers, music, and search stock media - all from your terminal.
|
|
3159
3077
|
|
|
@@ -3177,14 +3095,20 @@ A comprehensive CLI for AI-powered content creation. Generate presentations, vid
|
|
|
3177
3095
|
|
|
3178
3096
|
## Authentication
|
|
3179
3097
|
|
|
3098
|
+
**IMPORTANT:** Most commands require authentication. If a command fails with "Not authenticated", run:
|
|
3099
|
+
|
|
3180
3100
|
\`\`\`bash
|
|
3181
|
-
# Login via OAuth (recommended)
|
|
3182
3101
|
${cmd2} login
|
|
3102
|
+
\`\`\`
|
|
3183
3103
|
|
|
3184
|
-
|
|
3104
|
+
This opens a browser for OAuth login. Alternatively, set an API key:
|
|
3105
|
+
|
|
3106
|
+
\`\`\`bash
|
|
3185
3107
|
export ${envPrefix}_API_KEY="your-key-here"
|
|
3108
|
+
\`\`\`
|
|
3186
3109
|
|
|
3187
|
-
|
|
3110
|
+
**Verify authentication:**
|
|
3111
|
+
\`\`\`bash
|
|
3188
3112
|
${cmd2} whoami
|
|
3189
3113
|
\`\`\`
|
|
3190
3114
|
|
|
@@ -3560,1040 +3484,276 @@ ${cmd2} --version # Version info
|
|
|
3560
3484
|
}
|
|
3561
3485
|
|
|
3562
3486
|
// src/commands/skill/rules/video/content.ts
|
|
3563
|
-
var
|
|
3564
|
-
{
|
|
3565
|
-
filename: "video-creation-guide.md",
|
|
3566
|
-
content: `# Video Creation Guide
|
|
3567
|
-
|
|
3568
|
-
### Related Skills
|
|
3569
|
-
|
|
3570
|
-
Use these installed skills for implementation details:
|
|
3571
|
-
- \`remotion-best-practices\` \u2014 Remotion patterns and API
|
|
3572
|
-
- \`threejs-*\` skills \u2014 for R3F/WebGL (particles, 3D elements)
|
|
3487
|
+
var THUMBNAIL_RULES = `Consider creating separate thumbnail component with Remotion (can be captured with remotion still, not used in actual video).
|
|
3573
3488
|
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3489
|
+
**High-CTR principles:**
|
|
3490
|
+
- Expressive faces (emotion, not neutral) boost CTR 20-30%
|
|
3491
|
+
- High contrast, bold colors (yellow, orange stand out)
|
|
3492
|
+
- Simple: 3 main elements max (face + text + 1 visual)
|
|
3493
|
+
- Mobile-first: readable at 320px width (70% of views)
|
|
3494
|
+
- Minimal text: 3-5 words, bold legible fonts (60-80px)
|
|
3495
|
+
- Rule of thirds composition
|
|
3577
3496
|
|
|
3578
|
-
|
|
3497
|
+
**Specs:** 1280x720, <2MB, 16:9 ratio
|
|
3579
3498
|
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
2. **No static pixels** \u2014 everything breathes, drifts, pulses
|
|
3584
|
-
3. **No linear interpolation** \u2014 use \`spring()\` physics
|
|
3585
|
-
4. **Scene overlap 15-20 frames** \u2014 no hard cuts
|
|
3586
|
-
5. **60 FPS mandatory** \u2014 30fps looks choppy
|
|
3587
|
-
6. **No screenshots for UI** \u2014 rebuild in React/CSS
|
|
3499
|
+
Can capture: \`pnpm exec remotion still ThumbnailScene out/thumb.png\`
|
|
3500
|
+
`;
|
|
3501
|
+
var MOTION_DESIGN_GUIDELINES = `# Motion Design Principles
|
|
3588
3502
|
|
|
3589
|
-
|
|
3503
|
+
**Core Philosophy:** "Atomic, Kinetic Construction" - nothing is static. Elements arrive and leave via physics-based transitions.
|
|
3590
3504
|
|
|
3591
|
-
##
|
|
3505
|
+
## Design System Approach
|
|
3592
3506
|
|
|
3593
|
-
|
|
3594
|
-
-
|
|
3595
|
-
-
|
|
3507
|
+
**Separate content from logic:**
|
|
3508
|
+
- Theme object: colors (primary, accent, background, text), fonts, corner radiuses in config
|
|
3509
|
+
- Scene object: define by duration in frames and content type, not timecodes
|
|
3510
|
+
- Avoid hardcoding: colors, text, data values can be passed via props or config file
|
|
3596
3511
|
|
|
3597
|
-
|
|
3598
|
-
import { spring, interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
|
|
3512
|
+
## Animation Physics (Spring-Based)
|
|
3599
3513
|
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3514
|
+
**Spring Pop:**
|
|
3515
|
+
- UI cards, bubbles, logos "pop" with bounce
|
|
3516
|
+
- \`spring()\` function works well: low mass (0.5), moderate damping (10-12), high stiffness (100-200)
|
|
3517
|
+
- Spring value can map to scale (0 to 1) and opacity (0 to 1)
|
|
3518
|
+
- Consider \`transform-origin\` placement (center for bubbles, top for dropdowns)
|
|
3603
3519
|
|
|
3604
|
-
|
|
3520
|
+
**Kinetic Typography:**
|
|
3521
|
+
- Text entering line-by-line or word-by-word (not all at once)
|
|
3522
|
+
- Can split text into arrays, stagger with delays (index * 5 frames)
|
|
3523
|
+
- \`interpolate()\` works for opacity [0,1] and translateY [20px, 0px] - slide up
|
|
3524
|
+
- Cubic easing works well for slide-up motion
|
|
3605
3525
|
|
|
3606
|
-
|
|
3526
|
+
**Constructed UI:**
|
|
3527
|
+
- Building UI from HTML/CSS divs works better than screenshots
|
|
3528
|
+
- If user shares project: study actual UI components (buttons, cards, modals) and implement pixel-perfect recreations - match colors, fonts, shadows, border-radius
|
|
3529
|
+
- Bar charts: can animate height/width from 0% to target
|
|
3530
|
+
- Line charts: can animate SVG path \`stroke-dashoffset\`
|
|
3531
|
+
- Donut charts: can animate \`stroke-dasharray\` of SVG circle
|
|
3532
|
+
- Numbers: counter component interpolating from 0 to target over 30-60 frames
|
|
3607
3533
|
|
|
3608
|
-
|
|
3609
|
-
/* Shadows - soft, expensive */
|
|
3610
|
-
box-shadow: 0 20px 50px -12px rgba(0,0,0,0.5);
|
|
3534
|
+
## Visual Composition
|
|
3611
3535
|
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3536
|
+
**Background Ambience:**
|
|
3537
|
+
- Static backgrounds feel flat - consider faint dots/patterns
|
|
3538
|
+
- Slow oscillation works well: \`Math.sin(frame / 100)\` applied to position/rotation for "floating" effect
|
|
3539
|
+
- Parallax adds depth: background moves slower than foreground
|
|
3615
3540
|
|
|
3616
|
-
|
|
3617
|
-
-
|
|
3618
|
-
-
|
|
3541
|
+
**SVG Handling:**
|
|
3542
|
+
- Inline SVGs allow control of fill color via theme (better than img tags)
|
|
3543
|
+
- Chat bubbles can be constructed with SVG paths or heavy border-radius
|
|
3544
|
+
- Animating bubble "tail" separately adds polish
|
|
3619
3545
|
|
|
3620
|
-
|
|
3546
|
+
**Scene Transitions:**
|
|
3547
|
+
- Scenes can slide or camera can "pan" to new area
|
|
3548
|
+
- Slide-out approach: Scene A \`translateX\` 0% to -100%, Scene B 100% to 0%
|
|
3549
|
+
- Spatial pan approach: place scenes on giant canvas, animate parent container transform
|
|
3621
3550
|
|
|
3622
|
-
##
|
|
3551
|
+
## Suggested Component Architecture
|
|
3623
3552
|
|
|
3624
|
-
|
|
3625
|
-
-
|
|
3626
|
-
-
|
|
3627
|
-
-
|
|
3628
|
-
-
|
|
3629
|
-
- [ ] Something is moving on EVERY frame
|
|
3630
|
-
- [ ] Scene transitions overlap (no hard cuts)
|
|
3553
|
+
Consider these reusable patterns:
|
|
3554
|
+
- KineticText: text, delay, style props - handles word-splitting and stagger
|
|
3555
|
+
- SmartCard: container with Spring Pop entry and glassmorphism styles
|
|
3556
|
+
- AnimatedCounter: from, to, duration props - number ticking
|
|
3557
|
+
- ProgressBar/ChartElement: percentage, color props - growth animation from 0
|
|
3631
3558
|
|
|
3632
|
-
**
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
{
|
|
3636
|
-
filename: "animation-physics.md",
|
|
3637
|
-
content: `# Animation Physics
|
|
3559
|
+
**Motion Blur (optional):** Can simulate by stretching element in direction of movement on fast transitions.
|
|
3560
|
+
`;
|
|
3561
|
+
var SVG_ANIMATION_GUIDELINES = `# SVG Line Animation (Write-On Effect)
|
|
3638
3562
|
|
|
3639
|
-
|
|
3563
|
+
**Core Concept:** "Invisible Ink Rule" - lines draw in (don't fade in), as if hand-drawn in real-time.
|
|
3640
3564
|
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3565
|
+
**Animation approach:**
|
|
3566
|
+
- Setting \`pathLength="1"\` on SVG path elements normalizes length
|
|
3567
|
+
- Animating \`strokeDashoffset\` from 1 (hidden) to 0 (drawn) creates write-on effect
|
|
3568
|
+
- \`strokeDasharray: 1\` with interpolate \`[1, 0]\` over 20-30 frames works well
|
|
3569
|
+
- \`stroke-linecap="round"\` creates friendly hand-drawn look
|
|
3645
3570
|
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3571
|
+
**Draw & vanish sequence:**
|
|
3572
|
+
- Draw in: 20 frames (offset 1\u21920)
|
|
3573
|
+
- Hold: 10 frames
|
|
3574
|
+
- Draw out: fade opacity or continue offset
|
|
3650
3575
|
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3576
|
+
**Reusable component pattern:**
|
|
3577
|
+
- Props: path data (d), color, width, delay, type (underline/spark/circle/arrow)
|
|
3578
|
+
- Pre-defined path dictionaries work better than generating random coordinates
|
|
3579
|
+
- Positioning with top/left/scale/rotation props for text accents
|
|
3580
|
+
`;
|
|
3581
|
+
var ASSET_USAGE_GUIDELINES = `# Asset Usage & Optimization
|
|
3655
3582
|
|
|
3656
|
-
|
|
3583
|
+
**For project-specific videos:** Study the project first - extract logos, colors, fonts, actual UI components. Recreate components pixel-perfect in Remotion (match exact colors, shadows, border-radius, fonts). Use project's actual branding and design system for authentic look.
|
|
3657
3584
|
|
|
3658
|
-
|
|
3585
|
+
**CLI provides flexible asset search** - images and videos can be used creatively throughout compositions.
|
|
3659
3586
|
|
|
3660
|
-
**
|
|
3587
|
+
**Video assets (from CLI video search):**
|
|
3588
|
+
- Full-screen backgrounds (with overlays/text)
|
|
3589
|
+
- Embedded in UI cards or windows alongside text
|
|
3590
|
+
- Picture-in-picture style elements
|
|
3591
|
+
- Background layers with reduced opacity
|
|
3592
|
+
- Transitional footage between scenes
|
|
3661
3593
|
|
|
3662
|
-
|
|
3663
|
-
|
|
3594
|
+
**Image assets (from CLI image search):**
|
|
3595
|
+
- Scene backgrounds (static or with animation)
|
|
3596
|
+
- Embedded elements within compositions
|
|
3597
|
+
- UI component content (cards, panels)
|
|
3598
|
+
- Layered for depth and parallax effects
|
|
3664
3599
|
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
});
|
|
3600
|
+
**Dynamic backgrounds (Three.js/WebGL):**
|
|
3601
|
+
- Three.js/React Three Fiber for performance-optimized animated backgrounds
|
|
3602
|
+
- Particle systems, procedural gradients, geometric patterns
|
|
3603
|
+
- SVG animations for abstract shapes and patterns
|
|
3604
|
+
- WebGL shaders for dynamic effects
|
|
3605
|
+
- Combines well with static assets for depth
|
|
3672
3606
|
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
opacity: progress,
|
|
3676
|
-
transform: \`translateY(\${interpolate(progress, [0, 1], [20, 0])}px)\`,
|
|
3677
|
-
}}>
|
|
3678
|
-
{item}
|
|
3679
|
-
</div>
|
|
3680
|
-
);
|
|
3681
|
-
})}
|
|
3682
|
-
\`\`\`
|
|
3607
|
+
**Best approach:** Mix CLI assets (images/videos) with generated elements (Three.js, SVG) for rich, performant compositions.
|
|
3608
|
+
`;
|
|
3683
3609
|
|
|
3610
|
+
// src/commands/skill/generate-video-skill.ts
|
|
3611
|
+
function generateVideoSkillContent(context) {
|
|
3612
|
+
const { name, cmd: cmd2 } = context;
|
|
3613
|
+
return `---
|
|
3614
|
+
name: ${name}-video
|
|
3615
|
+
description: Use when user asks to create videos (product demos, explainers, social content, promos). Handles video asset generation, Remotion implementation, and thumbnail embedding.
|
|
3684
3616
|
---
|
|
3685
3617
|
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
**Cursor NEVER moves in straight lines.**
|
|
3689
|
-
|
|
3690
|
-
\`\`\`tsx
|
|
3691
|
-
const progress = spring({
|
|
3692
|
-
frame: frame - startFrame,
|
|
3693
|
-
fps,
|
|
3694
|
-
config: { damping: 20, stiffness: 80 },
|
|
3695
|
-
});
|
|
3696
|
-
|
|
3697
|
-
const linearX = interpolate(progress, [0, 1], [start.x, end.x]);
|
|
3698
|
-
const linearY = interpolate(progress, [0, 1], [start.y, end.y]);
|
|
3618
|
+
# ${name} Video Creation CLI
|
|
3699
3619
|
|
|
3700
|
-
|
|
3701
|
-
const arcHeight = 100;
|
|
3702
|
-
const arcOffset = Math.sin(progress * Math.PI) * arcHeight;
|
|
3703
|
-
const cursorY = linearY - arcOffset;
|
|
3704
|
-
\`\`\`
|
|
3620
|
+
Generate video assets (voiceover, music, images, stock videos) and render with Remotion.
|
|
3705
3621
|
|
|
3706
3622
|
---
|
|
3707
3623
|
|
|
3708
|
-
##
|
|
3624
|
+
## Prerequisites
|
|
3709
3625
|
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
const buttonScaleX = isClicking ? 1.02 : 1;
|
|
3714
|
-
const buttonScaleY = isClicking ? 0.95 : 1;
|
|
3715
|
-
// Release both with spring
|
|
3626
|
+
**Authenticate:**
|
|
3627
|
+
\`\`\`bash
|
|
3628
|
+
${cmd2} login
|
|
3716
3629
|
\`\`\`
|
|
3717
3630
|
|
|
3718
3631
|
---
|
|
3719
3632
|
|
|
3720
|
-
##
|
|
3721
|
-
|
|
3722
|
-
| Action | Frames (60fps) |
|
|
3723
|
-
|--------|----------------|
|
|
3724
|
-
| Element entrance | 15-20 |
|
|
3725
|
-
| Stagger gap | 3-5 |
|
|
3726
|
-
| Hold on key info | 45-60 |
|
|
3727
|
-
| Scene transition | 20-30 |
|
|
3728
|
-
| Fast interaction | 15-20 |
|
|
3729
|
-
`
|
|
3730
|
-
},
|
|
3731
|
-
{
|
|
3732
|
-
filename: "scene-structure.md",
|
|
3733
|
-
content: `# Scene Structure
|
|
3734
|
-
|
|
3735
|
-
## SceneWrapper
|
|
3736
|
-
|
|
3737
|
-
\`\`\`tsx
|
|
3738
|
-
<SceneWrapper
|
|
3739
|
-
durationInFrames={300}
|
|
3740
|
-
transitionType="slideLeft"
|
|
3741
|
-
cameraMotion="panRight"
|
|
3742
|
-
>
|
|
3743
|
-
<FeatureLayer />
|
|
3744
|
-
<CursorLayer />
|
|
3745
|
-
<ParticleLayer />
|
|
3746
|
-
</SceneWrapper>
|
|
3747
|
-
\`\`\`
|
|
3633
|
+
## Video Creation Workflow
|
|
3748
3634
|
|
|
3749
|
-
|
|
3635
|
+
### 1. Generate Assets
|
|
3750
3636
|
|
|
3751
|
-
|
|
3637
|
+
Generate voiceover, music, and visual assets:
|
|
3752
3638
|
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3639
|
+
\`\`\`bash
|
|
3640
|
+
cat <<SCENES | ${cmd2} video create --output ./public
|
|
3641
|
+
{
|
|
3642
|
+
"scenes": [
|
|
3643
|
+
{
|
|
3644
|
+
"name": "Hook",
|
|
3645
|
+
"script": "Watch how we transformed this complex workflow into a single click.",
|
|
3646
|
+
"imageQuery": "modern dashboard interface dark theme"
|
|
3647
|
+
},
|
|
3648
|
+
{
|
|
3649
|
+
"name": "Demo",
|
|
3650
|
+
"script": "Our AI analyzes your data in real-time, surfacing insights that matter."
|
|
3651
|
+
}
|
|
3652
|
+
],
|
|
3653
|
+
"voice": "Kore"
|
|
3654
|
+
}
|
|
3655
|
+
SCENES
|
|
3656
|
+
\`\`\`
|
|
3762
3657
|
|
|
3763
|
-
|
|
3658
|
+
**Output:**
|
|
3659
|
+
- \`public/audio/*.wav\` - scene voiceovers
|
|
3660
|
+
- \`public/audio/music.mp3\` - background music
|
|
3661
|
+
- \`public/video-manifest.json\` - timing, metadata, timeline
|
|
3764
3662
|
|
|
3765
|
-
|
|
3663
|
+
### 2. Initialize Remotion (MANDATORY)
|
|
3766
3664
|
|
|
3767
|
-
|
|
3665
|
+
Scaffold the template and copy assets:
|
|
3768
3666
|
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3667
|
+
\`\`\`bash
|
|
3668
|
+
cd .. && ${cmd2} video init my-video
|
|
3669
|
+
cd my-video
|
|
3670
|
+
# Assets are now in public/ directory
|
|
3671
|
+
\`\`\`
|
|
3772
3672
|
|
|
3773
|
-
###
|
|
3673
|
+
### 3. Render Video
|
|
3774
3674
|
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3675
|
+
**For landscape videos (16:9):**
|
|
3676
|
+
\`\`\`bash
|
|
3677
|
+
pnpm exec remotion render FullVideo out/video.mp4
|
|
3678
|
+
\`\`\`
|
|
3779
3679
|
|
|
3780
|
-
|
|
3680
|
+
**For vertical videos (9:16) with captions:**
|
|
3681
|
+
\`\`\`bash
|
|
3682
|
+
pnpm exec remotion render CaptionedVideo out/tiktok.mp4 \\
|
|
3683
|
+
--props='{"timeline":'$(cat public/video-manifest.json | jq -c .timeline)',"showCaptions":true}'
|
|
3684
|
+
\`\`\`
|
|
3781
3685
|
|
|
3782
|
-
|
|
3783
|
-
2. Other cards spread apart
|
|
3784
|
-
3. **Match Cut:** Zoom into avatar \u2192 color fills screen \u2192 becomes mobile notification background
|
|
3686
|
+
### 4. Generate & Inject Thumbnail
|
|
3785
3687
|
|
|
3786
|
-
|
|
3688
|
+
${THUMBNAIL_RULES}
|
|
3787
3689
|
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
\`\`\`tsx
|
|
3791
|
-
<AbsoluteFill>
|
|
3792
|
-
<MovingBackground />
|
|
3793
|
-
<Vignette />
|
|
3794
|
-
<CameraRig>
|
|
3795
|
-
<Sequence from={0} durationInFrames={100}>
|
|
3796
|
-
<Scene1 />
|
|
3797
|
-
</Sequence>
|
|
3798
|
-
<Sequence from={85} durationInFrames={150}> {/* 15 frame overlap! */}
|
|
3799
|
-
<Scene2 />
|
|
3800
|
-
</Sequence>
|
|
3801
|
-
</CameraRig>
|
|
3802
|
-
<Audio src={music} volume={0.3} />
|
|
3803
|
-
</AbsoluteFill>
|
|
3690
|
+
\`\`\`bash
|
|
3691
|
+
${cmd2} video thumbnail out/video.mp4 --image out/thumb.png
|
|
3804
3692
|
\`\`\`
|
|
3805
|
-
`
|
|
3806
|
-
},
|
|
3807
|
-
{
|
|
3808
|
-
filename: "scene-transitions.md",
|
|
3809
|
-
content: `# Scene Transitions
|
|
3810
|
-
|
|
3811
|
-
**No FadeIn/FadeOut.** Only contextual transitions.
|
|
3812
3693
|
|
|
3813
3694
|
---
|
|
3814
3695
|
|
|
3815
|
-
|
|
3696
|
+
${MOTION_DESIGN_GUIDELINES}
|
|
3816
3697
|
|
|
3817
|
-
|
|
3818
|
-
Same chart transforms (data, color, scale) while UI changes around it.
|
|
3819
|
-
|
|
3820
|
-
### 2. Mask Reveal
|
|
3821
|
-
Button expands to screen size via SVG \`clipPath\`.
|
|
3698
|
+
---
|
|
3822
3699
|
|
|
3823
|
-
|
|
3824
|
-
Scene A accelerates out, Scene B starts fast then slows.
|
|
3700
|
+
${SVG_ANIMATION_GUIDELINES}
|
|
3825
3701
|
|
|
3826
3702
|
---
|
|
3827
3703
|
|
|
3828
|
-
|
|
3704
|
+
${ASSET_USAGE_GUIDELINES}
|
|
3829
3705
|
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
Avatar color fills screen
|
|
3834
|
-
\u2193
|
|
3835
|
-
Scene B: That color IS the notification background
|
|
3836
|
-
\`\`\`
|
|
3706
|
+
---
|
|
3707
|
+
`;
|
|
3708
|
+
}
|
|
3837
3709
|
|
|
3710
|
+
// src/commands/skill/generate-presentation-skill.ts
|
|
3711
|
+
function generatePresentationSkillContent(context) {
|
|
3712
|
+
const { name, cmd: cmd2 } = context;
|
|
3713
|
+
return `---
|
|
3714
|
+
name: ${name}-presentation
|
|
3715
|
+
description: Use when user asks to create presentations (slides, decks, pitch decks). Generates AI-powered presentations with structured content and professional design.
|
|
3838
3716
|
---
|
|
3839
3717
|
|
|
3840
|
-
|
|
3718
|
+
# ${name} Presentation Creation CLI
|
|
3841
3719
|
|
|
3842
|
-
|
|
3843
|
-
<Sequence from={0} durationInFrames={100}>
|
|
3844
|
-
<SceneOne />
|
|
3845
|
-
</Sequence>
|
|
3846
|
-
<Sequence from={85} durationInFrames={150}> {/* 15 frames early! */}
|
|
3847
|
-
<SceneTwo />
|
|
3848
|
-
</Sequence>
|
|
3849
|
-
\`\`\`
|
|
3720
|
+
Create professional presentations (slides, decks, pitch decks) directly from your terminal. The CLI generates AI-powered slides from any context you provide - text, files, URLs, or piped content. Also supports searching for stock images and videos.
|
|
3850
3721
|
|
|
3851
3722
|
---
|
|
3852
3723
|
|
|
3853
|
-
##
|
|
3854
|
-
|
|
3855
|
-
\`\`\`tsx
|
|
3856
|
-
import { TransitionSeries, linearTiming } from '@remotion/transitions';
|
|
3857
|
-
import { slide } from '@remotion/transitions/slide';
|
|
3858
|
-
|
|
3859
|
-
<TransitionSeries>
|
|
3860
|
-
<TransitionSeries.Sequence durationInFrames={100}>
|
|
3861
|
-
<SceneOne />
|
|
3862
|
-
</TransitionSeries.Sequence>
|
|
3863
|
-
<TransitionSeries.Transition
|
|
3864
|
-
presentation={slide({ direction: 'from-bottom' })}
|
|
3865
|
-
timing={linearTiming({ durationInFrames: 20 })}
|
|
3866
|
-
/>
|
|
3867
|
-
<TransitionSeries.Sequence durationInFrames={150}>
|
|
3868
|
-
<SceneTwo />
|
|
3869
|
-
</TransitionSeries.Sequence>
|
|
3870
|
-
</TransitionSeries>
|
|
3871
|
-
\`\`\`
|
|
3872
|
-
`
|
|
3873
|
-
},
|
|
3874
|
-
{
|
|
3875
|
-
filename: "polish-effects.md",
|
|
3876
|
-
content: `# Polish Effects
|
|
3877
|
-
|
|
3878
|
-
## Reflection (Glass Glint)
|
|
3724
|
+
## Authentication
|
|
3879
3725
|
|
|
3880
|
-
|
|
3726
|
+
\`\`\`bash
|
|
3727
|
+
# Login via OAuth
|
|
3728
|
+
${cmd2} login
|
|
3881
3729
|
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
const sweepProgress = interpolate(cycleFrame, [0, 60], [-100, 200], {
|
|
3885
|
-
extrapolateRight: 'clamp',
|
|
3886
|
-
});
|
|
3730
|
+
# Or set API key
|
|
3731
|
+
export ${name.toUpperCase().replace(/-/g, "_")}_API_KEY="your-key-here"
|
|
3887
3732
|
\`\`\`
|
|
3888
3733
|
|
|
3889
3734
|
---
|
|
3890
3735
|
|
|
3891
|
-
##
|
|
3736
|
+
## Creating Presentations
|
|
3892
3737
|
|
|
3893
|
-
|
|
3738
|
+
### From Text
|
|
3894
3739
|
|
|
3895
|
-
\`\`\`
|
|
3896
|
-
|
|
3897
|
-
const orb1Y = Math.cos(frame / 80) * 100;
|
|
3740
|
+
\`\`\`bash
|
|
3741
|
+
${cmd2} create "AI-powered product analytics platform"
|
|
3898
3742
|
\`\`\`
|
|
3899
3743
|
|
|
3900
|
-
|
|
3744
|
+
### From File
|
|
3901
3745
|
|
|
3902
|
-
|
|
3746
|
+
\`\`\`bash
|
|
3747
|
+
${cmd2} create --file product-brief.md
|
|
3748
|
+
\`\`\`
|
|
3903
3749
|
|
|
3904
|
-
|
|
3905
|
-
const charIndex = Math.floor(frame / 3);
|
|
3906
|
-
const showCursor = Math.floor(frame / 15) % 2 === 0;
|
|
3750
|
+
### From URL
|
|
3907
3751
|
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
{showCursor && <span>|</span>}
|
|
3911
|
-
</span>
|
|
3752
|
+
\`\`\`bash
|
|
3753
|
+
${cmd2} create --url https://company.com/product
|
|
3912
3754
|
\`\`\`
|
|
3913
3755
|
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
## Vignette & Noise
|
|
3917
|
-
|
|
3918
|
-
\`\`\`tsx
|
|
3919
|
-
// Noise
|
|
3920
|
-
<AbsoluteFill style={{
|
|
3921
|
-
backgroundImage: 'url(/noise.png)',
|
|
3922
|
-
opacity: 0.03,
|
|
3923
|
-
mixBlendMode: 'overlay',
|
|
3924
|
-
}} />
|
|
3925
|
-
|
|
3926
|
-
// Vignette
|
|
3927
|
-
<AbsoluteFill style={{
|
|
3928
|
-
background: 'radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.8) 100%)',
|
|
3929
|
-
}} />
|
|
3930
|
-
\`\`\`
|
|
3931
|
-
`
|
|
3932
|
-
},
|
|
3933
|
-
{
|
|
3934
|
-
filename: "advanced-techniques.md",
|
|
3935
|
-
content: `# Advanced Techniques
|
|
3936
|
-
|
|
3937
|
-
## Audio-Reactive
|
|
3938
|
-
|
|
3939
|
-
- **Kick:** \`scale(1.005)\` pulse
|
|
3940
|
-
- **Snare:** Trigger scene changes
|
|
3941
|
-
- **Hi-hats:** Cursor flicker, particle shimmer
|
|
3942
|
-
|
|
3943
|
-
---
|
|
3944
|
-
|
|
3945
|
-
## Motion Blur (Fake)
|
|
3946
|
-
|
|
3947
|
-
\`\`\`tsx
|
|
3948
|
-
// Trail: Render 3-4 copies with opacity 0.3, 1-frame delay
|
|
3949
|
-
|
|
3950
|
-
// Or drop shadow for fast movement:
|
|
3951
|
-
filter: \`drop-shadow(\${velocityX * 0.5}px \${velocityY * 0.5}px 10px rgba(0,0,0,0.3))\`
|
|
3952
|
-
\`\`\`
|
|
3953
|
-
|
|
3954
|
-
---
|
|
3955
|
-
|
|
3956
|
-
## 3D Perspective
|
|
3957
|
-
|
|
3958
|
-
\`\`\`tsx
|
|
3959
|
-
<div style={{ perspective: '1000px' }}>
|
|
3960
|
-
<div style={{
|
|
3961
|
-
transform: 'rotateX(5deg) rotateY(10deg)',
|
|
3962
|
-
transformStyle: 'preserve-3d',
|
|
3963
|
-
}}>
|
|
3964
|
-
{/* Your UI */}
|
|
3965
|
-
</div>
|
|
3966
|
-
</div>
|
|
3967
|
-
\`\`\`
|
|
3968
|
-
|
|
3969
|
-
---
|
|
3970
|
-
|
|
3971
|
-
## Kinetic Typography
|
|
3972
|
-
|
|
3973
|
-
### Masked Reveal
|
|
3974
|
-
\`\`\`tsx
|
|
3975
|
-
<div style={{ overflow: 'hidden', height: 80 }}>
|
|
3976
|
-
<h1 style={{
|
|
3977
|
-
transform: \`translateY(\${interpolate(progress, [0, 1], [100, 0])}%)\`,
|
|
3978
|
-
}}>
|
|
3979
|
-
INTRODUCING
|
|
3980
|
-
</h1>
|
|
3981
|
-
</div>
|
|
3982
|
-
\`\`\`
|
|
3983
|
-
|
|
3984
|
-
### Keyword Animation
|
|
3985
|
-
Animate keywords, not whole sentences.
|
|
3986
|
-
`
|
|
3987
|
-
},
|
|
3988
|
-
{
|
|
3989
|
-
filename: "remotion-config.md",
|
|
3990
|
-
content: `# Remotion Configuration
|
|
3991
|
-
|
|
3992
|
-
## FPS & Resolution
|
|
3993
|
-
|
|
3994
|
-
- **60 FPS mandatory** \u2014 30fps looks choppy
|
|
3995
|
-
- **1920\xD71080** Full HD
|
|
3996
|
-
- **Center:** \`{x: 960, y: 540}\`
|
|
3997
|
-
|
|
3998
|
-
---
|
|
3999
|
-
|
|
4000
|
-
## Timing
|
|
4001
|
-
|
|
4002
|
-
- 1 second = 60 frames
|
|
4003
|
-
- Fast interaction = 15-20 frames
|
|
4004
|
-
- No scene > 8 seconds without action
|
|
4005
|
-
|
|
4006
|
-
---
|
|
4007
|
-
|
|
4008
|
-
## Entry-Action-Exit Structure
|
|
4009
|
-
|
|
4010
|
-
| Phase | Duration |
|
|
4011
|
-
|-------|----------|
|
|
4012
|
-
| Entry | 0.0s - 0.5s |
|
|
4013
|
-
| Action | 0.5s - (duration - 1s) |
|
|
4014
|
-
| Exit | last 1s |
|
|
4015
|
-
|
|
4016
|
-
---
|
|
4017
|
-
|
|
4018
|
-
## Font Loading
|
|
4019
|
-
|
|
4020
|
-
\`\`\`tsx
|
|
4021
|
-
const [handle] = useState(() => delayRender());
|
|
4022
|
-
|
|
4023
|
-
useEffect(() => {
|
|
4024
|
-
document.fonts.ready.then(() => {
|
|
4025
|
-
continueRender(handle);
|
|
4026
|
-
});
|
|
4027
|
-
}, [handle]);
|
|
4028
|
-
\`\`\`
|
|
4029
|
-
|
|
4030
|
-
---
|
|
4031
|
-
|
|
4032
|
-
## Zod Schema
|
|
4033
|
-
|
|
4034
|
-
\`\`\`tsx
|
|
4035
|
-
export const SceneSchema = z.object({
|
|
4036
|
-
titleText: z.string(),
|
|
4037
|
-
buttonColor: z.string(),
|
|
4038
|
-
cursorPath: z.array(z.object({ x: z.number(), y: z.number() })),
|
|
4039
|
-
});
|
|
4040
|
-
\`\`\`
|
|
4041
|
-
|
|
4042
|
-
---
|
|
4043
|
-
|
|
4044
|
-
## SaaS Video Kit Components
|
|
4045
|
-
|
|
4046
|
-
| Component | Purpose |
|
|
4047
|
-
|-----------|---------|
|
|
4048
|
-
| \`MockWindow\` | macOS window with traffic lights |
|
|
4049
|
-
| \`SmartCursor\` | Bezier curves + click physics |
|
|
4050
|
-
| \`NotificationToast\` | Slide in, wait, slide out |
|
|
4051
|
-
| \`TypingText\` | Typewriter with cursor |
|
|
4052
|
-
| \`Placeholder\` | For logos/icons |
|
|
4053
|
-
|
|
4054
|
-
---
|
|
4055
|
-
|
|
4056
|
-
## Code Rules
|
|
4057
|
-
|
|
4058
|
-
1. No \`transition: all 0.3s\` \u2014 use \`interpolate()\` or \`spring()\`
|
|
4059
|
-
2. Use \`AbsoluteFill\` for layout
|
|
4060
|
-
3. No magic numbers \u2014 extract to constants
|
|
4061
|
-
`
|
|
4062
|
-
},
|
|
4063
|
-
{
|
|
4064
|
-
filename: "elite-production.md",
|
|
4065
|
-
content: `# Elite Production
|
|
4066
|
-
|
|
4067
|
-
For Stripe/Apple/Linear quality.
|
|
4068
|
-
|
|
4069
|
-
---
|
|
4070
|
-
|
|
4071
|
-
## Global Lighting Engine
|
|
4072
|
-
|
|
4073
|
-
\`\`\`tsx
|
|
4074
|
-
const lightSource = { x: 0.2, y: -0.5 };
|
|
4075
|
-
const gradientAngle = Math.atan2(lightSource.y, lightSource.x) * (180 / Math.PI);
|
|
4076
|
-
|
|
4077
|
-
<button style={{
|
|
4078
|
-
background: \`linear-gradient(\${gradientAngle}deg, rgba(255,255,255,0.1) 0%, transparent 50%)\`,
|
|
4079
|
-
borderTop: '1px solid rgba(255,255,255,0.15)',
|
|
4080
|
-
boxShadow: \`\${-lightSource.x * 20}px \${-lightSource.y * 20}px 40px rgba(0,0,0,0.3)\`,
|
|
4081
|
-
}} />
|
|
4082
|
-
\`\`\`
|
|
4083
|
-
|
|
4084
|
-
---
|
|
4085
|
-
|
|
4086
|
-
## Noise & Dithering
|
|
4087
|
-
|
|
4088
|
-
Every background needs noise overlay (opacity 0.02-0.05). Prevents YouTube banding.
|
|
4089
|
-
|
|
4090
|
-
---
|
|
4091
|
-
|
|
4092
|
-
## React Three Fiber
|
|
4093
|
-
|
|
4094
|
-
For particles, 3D globes \u2014 use WebGL via \`@remotion/three\`, not CSS 3D.
|
|
4095
|
-
|
|
4096
|
-
\`\`\`tsx
|
|
4097
|
-
import { ThreeCanvas } from '@remotion/three';
|
|
4098
|
-
|
|
4099
|
-
<AbsoluteFill>
|
|
4100
|
-
<HtmlUI />
|
|
4101
|
-
<ThreeCanvas>
|
|
4102
|
-
<Particles />
|
|
4103
|
-
</ThreeCanvas>
|
|
4104
|
-
</AbsoluteFill>
|
|
4105
|
-
\`\`\`
|
|
4106
|
-
|
|
4107
|
-
See \`threejs-*\` skills for implementation.
|
|
4108
|
-
|
|
4109
|
-
---
|
|
4110
|
-
|
|
4111
|
-
## Virtual Camera Rig
|
|
4112
|
-
|
|
4113
|
-
Move camera, not elements:
|
|
4114
|
-
|
|
4115
|
-
\`\`\`tsx
|
|
4116
|
-
const CameraProvider = ({ children }) => {
|
|
4117
|
-
const frame = useCurrentFrame();
|
|
4118
|
-
const panX = interpolate(frame, [0, 300], [0, -100]);
|
|
4119
|
-
const zoom = interpolate(frame, [0, 300], [1, 1.05]);
|
|
4120
|
-
|
|
4121
|
-
return (
|
|
4122
|
-
<div style={{
|
|
4123
|
-
transform: \`translateX(\${panX}px) scale(\${zoom})\`,
|
|
4124
|
-
transformOrigin: 'center',
|
|
4125
|
-
}}>
|
|
4126
|
-
{children}
|
|
4127
|
-
</div>
|
|
4128
|
-
);
|
|
4129
|
-
};
|
|
4130
|
-
\`\`\`
|
|
4131
|
-
|
|
4132
|
-
---
|
|
4133
|
-
|
|
4134
|
-
## Motion Rules
|
|
4135
|
-
|
|
4136
|
-
- **Overshoot:** Modal scales to 1.02, settles to 1.0
|
|
4137
|
-
- **Overlap:** Scene B starts 15 frames before Scene A ends
|
|
4138
|
-
`
|
|
4139
|
-
},
|
|
4140
|
-
{
|
|
4141
|
-
filename: "known-issues.md",
|
|
4142
|
-
content: `# Known Issues & Fixes
|
|
4143
|
-
|
|
4144
|
-
## 1. Music Ends Before Video Finishes
|
|
4145
|
-
|
|
4146
|
-
**Problem:** Music duration is shorter than video duration, causing awkward silence at the end.
|
|
4147
|
-
|
|
4148
|
-
**Solution:** Loop music in Remotion using the \`loop\` prop:
|
|
4149
|
-
|
|
4150
|
-
\`\`\`tsx
|
|
4151
|
-
import { Audio } from 'remotion';
|
|
4152
|
-
|
|
4153
|
-
<Audio src={musicSrc} volume={0.3} loop />
|
|
4154
|
-
\`\`\`
|
|
4155
|
-
|
|
4156
|
-
**How it works:**
|
|
4157
|
-
- Music automatically loops to fill video duration
|
|
4158
|
-
- Set volume to 0.3 (30% - less loud than voice)
|
|
4159
|
-
- Add fade out at the end for smooth ending
|
|
4160
|
-
|
|
4161
|
-
---
|
|
4162
|
-
|
|
4163
|
-
## 2. Music Transitions Sound Abrupt
|
|
4164
|
-
|
|
4165
|
-
**Problem:** Music cuts harshly when scenes change or video ends.
|
|
4166
|
-
|
|
4167
|
-
**Fix in Remotion:**
|
|
4168
|
-
\`\`\`tsx
|
|
4169
|
-
import { interpolate, Audio } from 'remotion';
|
|
4170
|
-
|
|
4171
|
-
// Fade music in/out at scene boundaries
|
|
4172
|
-
const musicVolume = interpolate(
|
|
4173
|
-
frame,
|
|
4174
|
-
[0, 30, totalFrames - 60, totalFrames],
|
|
4175
|
-
[0, 0.3, 0.3, 0],
|
|
4176
|
-
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
|
|
4177
|
-
);
|
|
4178
|
-
|
|
4179
|
-
<Audio src={music} volume={musicVolume} />
|
|
4180
|
-
\`\`\`
|
|
4181
|
-
|
|
4182
|
-
---
|
|
4183
|
-
|
|
4184
|
-
## 3. Scene Transitions Too Harsh
|
|
4185
|
-
|
|
4186
|
-
**Problem:** Scenes change abruptly without smooth transitions.
|
|
4187
|
-
|
|
4188
|
-
**Fix:** Use \`@remotion/transitions\` with overlapping:
|
|
4189
|
-
\`\`\`tsx
|
|
4190
|
-
import { TransitionSeries, springTiming } from '@remotion/transitions';
|
|
4191
|
-
import { slide } from '@remotion/transitions/slide';
|
|
4192
|
-
import { fade } from '@remotion/transitions/fade';
|
|
4193
|
-
|
|
4194
|
-
<TransitionSeries>
|
|
4195
|
-
<TransitionSeries.Sequence durationInFrames={sceneA.frames}>
|
|
4196
|
-
<SceneA />
|
|
4197
|
-
</TransitionSeries.Sequence>
|
|
4198
|
-
<TransitionSeries.Transition
|
|
4199
|
-
presentation={slide({ direction: 'from-right' })}
|
|
4200
|
-
timing={springTiming({ config: { damping: 20 } })}
|
|
4201
|
-
/>
|
|
4202
|
-
<TransitionSeries.Sequence durationInFrames={sceneB.frames}>
|
|
4203
|
-
<SceneB />
|
|
4204
|
-
</TransitionSeries.Sequence>
|
|
4205
|
-
</TransitionSeries>
|
|
4206
|
-
\`\`\`
|
|
4207
|
-
|
|
4208
|
-
---
|
|
4209
|
-
|
|
4210
|
-
## 4. Voiceover Lacks Energy
|
|
4211
|
-
|
|
4212
|
-
**Problem:** Voiceover sounds flat/monotone.
|
|
4213
|
-
|
|
4214
|
-
**Fix:** Pass \`voiceSettings\` in scenes JSON:
|
|
4215
|
-
\`\`\`json
|
|
4216
|
-
{
|
|
4217
|
-
"scenes": [...],
|
|
4218
|
-
"voice": "Kore",
|
|
4219
|
-
"voiceSettings": {
|
|
4220
|
-
"style": 0.6,
|
|
4221
|
-
"stability": 0.4,
|
|
4222
|
-
"speed": 0.95
|
|
4223
|
-
}
|
|
4224
|
-
}
|
|
4225
|
-
\`\`\`
|
|
4226
|
-
|
|
4227
|
-
- \`style\`: 0.5-0.7 for more expressive delivery
|
|
4228
|
-
- \`stability\`: 0.3-0.5 for more variation
|
|
4229
|
-
- \`speed\`: 0.9-1.0 slightly slower = more impactful
|
|
4230
|
-
|
|
4231
|
-
---
|
|
4232
|
-
|
|
4233
|
-
## 5. Video Duration Mismatch
|
|
4234
|
-
|
|
4235
|
-
**Problem:** Brief says 30-45s but video is 20s (because scene duration = voiceover duration).
|
|
4236
|
-
|
|
4237
|
-
**Fixes:**
|
|
4238
|
-
1. **Slow voice:** Use \`speed: 0.85\` in voiceSettings
|
|
4239
|
-
2. **Add padding in Remotion:** Hold last frame, add breathing room
|
|
4240
|
-
\`\`\`tsx
|
|
4241
|
-
// Add 30 frames (0.5s) padding after voiceover ends
|
|
4242
|
-
const paddedDuration = voiceoverFrames + 30;
|
|
4243
|
-
\`\`\`
|
|
4244
|
-
3. **Brief should note:** "Duration based on voiceover length"
|
|
4245
|
-
|
|
4246
|
-
---
|
|
4247
|
-
|
|
4248
|
-
## 6. Not Using Project UI Components
|
|
4249
|
-
|
|
4250
|
-
**Problem:** Generic UI instead of pixel-perfect project components.
|
|
4251
|
-
|
|
4252
|
-
**Fix:** In Phase 1 Discovery:
|
|
4253
|
-
1. Find project's actual components (buttons, cards, modals, inputs)
|
|
4254
|
-
2. Copy their styles/structure into Remotion components
|
|
4255
|
-
3. Match colors, fonts, shadows, border-radius exactly
|
|
4256
|
-
|
|
4257
|
-
\`\`\`tsx
|
|
4258
|
-
// DON'T: Generic button
|
|
4259
|
-
<button style={{ background: 'blue' }}>Click</button>
|
|
4260
|
-
|
|
4261
|
-
// DO: Match project's actual button
|
|
4262
|
-
<button style={{
|
|
4263
|
-
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
|
4264
|
-
borderRadius: 8,
|
|
4265
|
-
padding: '12px 24px',
|
|
4266
|
-
boxShadow: '0 4px 14px rgba(99, 102, 241, 0.4)',
|
|
4267
|
-
border: '1px solid rgba(255,255,255,0.1)',
|
|
4268
|
-
}}>Click</button>
|
|
4269
|
-
\`\`\`
|
|
4270
|
-
|
|
4271
|
-
---
|
|
4272
|
-
|
|
4273
|
-
## 7. Missing Physics & Lighting
|
|
4274
|
-
|
|
4275
|
-
**Problem:** Video feels flat, no depth or motion.
|
|
4276
|
-
|
|
4277
|
-
**Checklist:**
|
|
4278
|
-
- [ ] Global light source defined (affects all shadows/gradients)
|
|
4279
|
-
- [ ] Camera rig with subtle drift/zoom
|
|
4280
|
-
- [ ] Spring physics on ALL entrances (no linear)
|
|
4281
|
-
- [ ] Staggered animations (never all at once)
|
|
4282
|
-
- [ ] Background orbs/particles moving
|
|
4283
|
-
- [ ] Noise overlay (opacity 0.02-0.05)
|
|
4284
|
-
- [ ] Vignette for depth
|
|
4285
|
-
`
|
|
4286
|
-
}
|
|
4287
|
-
];
|
|
4288
|
-
|
|
4289
|
-
// src/commands/skill/generate-video-skill.ts
|
|
4290
|
-
function generateVideoSkillContent(context) {
|
|
4291
|
-
const { name, cmd: cmd2, displayName } = context;
|
|
4292
|
-
return `---
|
|
4293
|
-
name: ${name}-video
|
|
4294
|
-
description: Use when user asks to create videos (product demos, explainers, social content, promos). Handles video asset generation, Remotion implementation, and thumbnail embedding.
|
|
4295
|
-
---
|
|
4296
|
-
|
|
4297
|
-
# ${displayName} Video Creation CLI
|
|
4298
|
-
|
|
4299
|
-
Create professional product videos directly from your terminal. The CLI generates AI-powered video assets (voiceover, music, images, stock videos) and provides tools for Remotion-based video production with React Three Fiber.
|
|
4300
|
-
|
|
4301
|
-
**Stack:** Remotion (React video framework) + React Three Fiber (R3F) + Three.js for 3D/WebGL, particles, shaders, lighting.
|
|
4302
|
-
|
|
4303
|
-
We create **elite product videos** (Stripe, Apple, Linear quality) using physics-based animation, dynamic lighting, and pixel-perfect UI components rebuilt from the real project \u2014 never boring screenshots or static images.
|
|
4304
|
-
|
|
4305
|
-
**Core Philosophy:** "Nothing sits still. Everything is physics-based. Every pixel breathes."
|
|
4306
|
-
|
|
4307
|
-
---
|
|
4308
|
-
|
|
4309
|
-
## CRITICAL: Professional Composition Rules
|
|
4310
|
-
|
|
4311
|
-
**These rules are MANDATORY for all marketing/product videos:**
|
|
4312
|
-
|
|
4313
|
-
### \u274C NEVER DO:
|
|
4314
|
-
1. **Walls of text** - No dense paragraphs or lists longer than 3 lines
|
|
4315
|
-
2. **Flying/floating cards** - No random floating animations across the screen
|
|
4316
|
-
3. **Stretched layouts** - No elements awkwardly stretched to fill space
|
|
4317
|
-
4. **Truncated text** - Never show "Text that gets cut off..."
|
|
4318
|
-
5. **Information overload** - Max 1-2 key points visible at once
|
|
4319
|
-
6. **Amateur motion** - No PowerPoint-style "fly in from left/right"
|
|
4320
|
-
|
|
4321
|
-
### \u2705 ALWAYS DO:
|
|
4322
|
-
1. **Hierarchy first** - One clear focal point per scene (headline OR stat OR visual, not all)
|
|
4323
|
-
2. **Breathing room** - Generous whitespace (min 100px padding around elements)
|
|
4324
|
-
3. **Purposeful motion** - Cards appear with subtle spring (0-20px translateY), not fly across screen
|
|
4325
|
-
4. **Readable text** - Max 2-3 lines per card, 24px+ font size
|
|
4326
|
-
5. **Grid alignment** - Use invisible grid (3-column or 4-column layout)
|
|
4327
|
-
6. **Professional entrance** - Elements fade + slight translate (15px max), hold for 2-3s, then exit
|
|
4328
|
-
|
|
4329
|
-
### Composition Examples:
|
|
4330
|
-
|
|
4331
|
-
**\u274C BAD - Wall of Text:**
|
|
4332
|
-
\`\`\`tsx
|
|
4333
|
-
// DON'T: 10 bullet points crammed in a card
|
|
4334
|
-
<Card>
|
|
4335
|
-
<ul>
|
|
4336
|
-
{[...10items].map(item => <li>{item.longText}...</li>)}
|
|
4337
|
-
</ul>
|
|
4338
|
-
</Card>
|
|
4339
|
-
\`\`\`
|
|
4340
|
-
|
|
4341
|
-
**\u2705 GOOD - Single Focus:**
|
|
4342
|
-
\`\`\`tsx
|
|
4343
|
-
// DO: One headline, one supporting stat
|
|
4344
|
-
<AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center' }}>
|
|
4345
|
-
<h1 style={{ fontSize: 72, marginBottom: 40 }}>12 hours wasted</h1>
|
|
4346
|
-
<p style={{ fontSize: 28, opacity: 0.7 }}>per week on manual tasks</p>
|
|
4347
|
-
</AbsoluteFill>
|
|
4348
|
-
\`\`\`
|
|
4349
|
-
|
|
4350
|
-
**\u274C BAD - Flying Cards:**
|
|
4351
|
-
\`\`\`tsx
|
|
4352
|
-
// DON'T: Cards flying from random positions
|
|
4353
|
-
<Card style={{
|
|
4354
|
-
transform: \`translateX(\${interpolate(progress, [0,1], [-500, 0])}px)\` // Flies from left
|
|
4355
|
-
}} />
|
|
4356
|
-
\`\`\`
|
|
4357
|
-
|
|
4358
|
-
**\u2705 GOOD - Subtle Entrance:**
|
|
4359
|
-
\`\`\`tsx
|
|
4360
|
-
// DO: Gentle spring entrance with minimal movement
|
|
4361
|
-
const progress = spring({ frame: frame - startFrame, fps, config: { damping: 20, stiffness: 100 }});
|
|
4362
|
-
<Card style={{
|
|
4363
|
-
opacity: progress,
|
|
4364
|
-
transform: \`translateY(\${interpolate(progress, [0,1], [15, 0])}px)\` // Subtle 15px drop
|
|
4365
|
-
}} />
|
|
4366
|
-
\`\`\`
|
|
4367
|
-
|
|
4368
|
-
### Layout Grid System:
|
|
4369
|
-
|
|
4370
|
-
**Use 12-column grid (like Bootstrap):**
|
|
4371
|
-
\`\`\`tsx
|
|
4372
|
-
const GRID = {
|
|
4373
|
-
columns: 12,
|
|
4374
|
-
gutter: 40,
|
|
4375
|
-
padding: 120 // Edge padding
|
|
4376
|
-
};
|
|
4377
|
-
|
|
4378
|
-
// Center 6 columns for main content
|
|
4379
|
-
const contentWidth = (1920 - (GRID.padding * 2) - (GRID.gutter * 5)) / 2;
|
|
4380
|
-
\`\`\`
|
|
4381
|
-
|
|
4382
|
-
**Positioning anchors:**
|
|
4383
|
-
- **Top-left:** Brand logo, context (10% from edges)
|
|
4384
|
-
- **Center:** Primary headline/stat/demo (50% transform)
|
|
4385
|
-
- **Bottom:** CTA or tagline (10% from bottom)
|
|
4386
|
-
- **Never:** Random floating between these zones
|
|
4387
|
-
|
|
4388
|
-
---
|
|
4389
|
-
|
|
4390
|
-
## Prerequisites
|
|
4391
|
-
|
|
4392
|
-
Before using this skill, ensure you have:
|
|
4393
|
-
|
|
4394
|
-
1. **Load related skills:**
|
|
4395
|
-
\`\`\`
|
|
4396
|
-
remotion-best-practices
|
|
4397
|
-
threejs-fundamentals
|
|
4398
|
-
\`\`\`
|
|
4399
|
-
|
|
4400
|
-
2. **Authenticate:**
|
|
4401
|
-
\`\`\`bash
|
|
4402
|
-
${cmd2} login
|
|
4403
|
-
\`\`\`
|
|
4404
|
-
|
|
4405
|
-
3. **Remotion installed** (if creating videos):
|
|
4406
|
-
\`\`\`bash
|
|
4407
|
-
pnpm install remotion @remotion/cli
|
|
4408
|
-
\`\`\`
|
|
4409
|
-
|
|
4410
|
-
---
|
|
4411
|
-
|
|
4412
|
-
## Video Creation Workflow
|
|
4413
|
-
|
|
4414
|
-
### Phase 0: Load Skills (MANDATORY)
|
|
4415
|
-
|
|
4416
|
-
Before ANY video work, invoke these skills:
|
|
4417
|
-
\`\`\`
|
|
4418
|
-
remotion-best-practices
|
|
4419
|
-
threejs-fundamentals
|
|
4420
|
-
\`\`\`
|
|
4421
|
-
|
|
4422
|
-
### Phase 1: Discovery
|
|
4423
|
-
|
|
4424
|
-
Explore current directory silently:
|
|
4425
|
-
- Understand what the product does (README, docs, code)
|
|
4426
|
-
- Find branding: logo, colors, fonts
|
|
4427
|
-
- Find UI components to copy into Remotion (buttons, cards, modals, etc.) \u2014 rebuild pixel-perfect, no screenshots
|
|
4428
|
-
|
|
4429
|
-
### Phase 2: Video Brief
|
|
4430
|
-
|
|
4431
|
-
Present a brief outline (scenes \u22648s each, duration, assets found) and get user approval before production.
|
|
4432
|
-
|
|
4433
|
-
### Phase 3: Production
|
|
4434
|
-
|
|
4435
|
-
1. **Generate audio assets** - \`${cmd2} video create\` with scenes JSON
|
|
4436
|
-
- IMPORTANT: Music is generated LAST after all voiceover/audio to ensure exact duration match
|
|
4437
|
-
2. **Scaffold OUTSIDE project** - \`cd .. && ${cmd2} video init my-video\`
|
|
4438
|
-
3. **Copy assets + UI components** from project into video project
|
|
4439
|
-
4. **Implement** - follow rules below
|
|
4440
|
-
|
|
4441
|
-
### Phase 4: Render & Thumbnail (REQUIRED)
|
|
4442
|
-
|
|
4443
|
-
\`\`\`bash
|
|
4444
|
-
# 1. Render the video (with voiceover and music already included)
|
|
4445
|
-
pnpm exec remotion render FullVideo
|
|
4446
|
-
|
|
4447
|
-
# 2. ALWAYS embed thumbnail before delivering
|
|
4448
|
-
${cmd2} video thumbnail out/FullVideo.mp4 --frame 60
|
|
4449
|
-
\`\`\`
|
|
4450
|
-
|
|
4451
|
-
**Note:** Remotion videos include per-scene voiceovers and background music baked in during render.
|
|
4452
|
-
|
|
4453
|
-
---
|
|
4454
|
-
|
|
4455
|
-
## Asset Generation
|
|
4456
|
-
|
|
4457
|
-
Generate voiceover, music, and visual assets for each scene:
|
|
4458
|
-
|
|
4459
|
-
\`\`\`bash
|
|
4460
|
-
cat <<SCENES | ${cmd2} video create --output ./public
|
|
4461
|
-
{
|
|
4462
|
-
"scenes": [
|
|
4463
|
-
{
|
|
4464
|
-
"name": "Hook",
|
|
4465
|
-
"script": "Watch how we transformed this complex workflow into a single click.",
|
|
4466
|
-
"imageQuery": "modern dashboard interface dark theme",
|
|
4467
|
-
"videoQuery": "abstract tech particles animation"
|
|
4468
|
-
},
|
|
4469
|
-
{
|
|
4470
|
-
"name": "Demo",
|
|
4471
|
-
"script": "Our AI analyzes your data in real-time, surfacing insights that matter.",
|
|
4472
|
-
"imageQuery": "data visualization charts analytics"
|
|
4473
|
-
},
|
|
4474
|
-
{
|
|
4475
|
-
"name": "CTA",
|
|
4476
|
-
"script": "Start your free trial today. No credit card required.",
|
|
4477
|
-
"imageQuery": "call to action button modern"
|
|
4478
|
-
}
|
|
4479
|
-
],
|
|
4480
|
-
"voice": "Kore",
|
|
4481
|
-
"voiceSettings": {
|
|
4482
|
-
"style": 0.6,
|
|
4483
|
-
"stability": 0.4,
|
|
4484
|
-
"speed": 0.95
|
|
4485
|
-
},
|
|
4486
|
-
"musicPrompt": "upbeat corporate, positive energy, modern synth"
|
|
4487
|
-
}
|
|
4488
|
-
SCENES
|
|
4489
|
-
\`\`\`
|
|
4490
|
-
|
|
4491
|
-
**Output:**
|
|
4492
|
-
- \`public/audio/Hook.mp3\` - scene voiceovers
|
|
4493
|
-
- \`public/audio/music.mp3\` - background music (30s max)
|
|
4494
|
-
- \`public/video-manifest.json\` - timing and metadata
|
|
4495
|
-
- Stock images/videos (if requested)
|
|
4496
|
-
|
|
4497
|
-
---
|
|
4498
|
-
|
|
4499
|
-
## Core Video Rules
|
|
4500
|
-
|
|
4501
|
-
${VIDEO_RULE_CONTENTS.map((rule) => rule.content).join("\n\n---\n\n")}
|
|
4502
|
-
|
|
4503
|
-
## Useful Commands
|
|
4504
|
-
|
|
4505
|
-
\`\`\`bash
|
|
4506
|
-
# Generate video assets
|
|
4507
|
-
${cmd2} video create < scenes.json
|
|
4508
|
-
cat scenes.json | ${cmd2} video create --output ./public
|
|
4509
|
-
|
|
4510
|
-
# Initialize Remotion project
|
|
4511
|
-
${cmd2} video init my-video
|
|
4512
|
-
|
|
4513
|
-
# Embed thumbnail
|
|
4514
|
-
${cmd2} video thumbnail out/video.mp4 --frame 60
|
|
4515
|
-
|
|
4516
|
-
# Search for stock assets
|
|
4517
|
-
${cmd2} images search "mountain landscape" --limit 10
|
|
4518
|
-
${cmd2} videos search "ocean waves" --limit 5
|
|
4519
|
-
|
|
4520
|
-
# Generate audio
|
|
4521
|
-
${cmd2} audio generate "Your script here" --voice Kore
|
|
4522
|
-
${cmd2} music generate "upbeat corporate" --duration 30
|
|
4523
|
-
\`\`\`
|
|
4524
|
-
|
|
4525
|
-
---
|
|
4526
|
-
|
|
4527
|
-
## Best Practices
|
|
4528
|
-
|
|
4529
|
-
1. **Keep scenes under 8 seconds** without cuts or major action
|
|
4530
|
-
2. **Use spring physics** for all animations, never linear
|
|
4531
|
-
3. **Rebuild UI components** in React/CSS, no screenshots
|
|
4532
|
-
4. **Test with thumbnail embedding** before delivering
|
|
4533
|
-
5. **Music volume at 30%** (30-40% less loud than voice)
|
|
4534
|
-
6. **Read all video rules** in Phase 0 before implementation
|
|
4535
|
-
|
|
4536
|
-
---
|
|
4537
|
-
|
|
4538
|
-
## Troubleshooting
|
|
4539
|
-
|
|
4540
|
-
If you encounter issues:
|
|
4541
|
-
- Check authentication: \`${cmd2} whoami\`
|
|
4542
|
-
- Verify asset generation: check \`video-manifest.json\`
|
|
4543
|
-
- Voiceover flat: increase style (0.5-0.7), decrease stability (0.3-0.5)
|
|
4544
|
-
- Duration mismatch: adjust \`voiceSettings.speed\` or add padding in Remotion
|
|
4545
|
-
|
|
4546
|
-
For detailed troubleshooting, see "Known Issues" section above.
|
|
4547
|
-
`;
|
|
4548
|
-
}
|
|
4549
|
-
|
|
4550
|
-
// src/commands/skill/generate-presentation-skill.ts
|
|
4551
|
-
function generatePresentationSkillContent(context) {
|
|
4552
|
-
const { name, cmd: cmd2, displayName } = context;
|
|
4553
|
-
return `---
|
|
4554
|
-
name: ${name}-presentation
|
|
4555
|
-
description: Use when user asks to create presentations (slides, decks, pitch decks). Generates AI-powered presentations with structured content and professional design.
|
|
4556
|
-
---
|
|
4557
|
-
|
|
4558
|
-
# ${displayName} Presentation CLI
|
|
4559
|
-
|
|
4560
|
-
Create professional presentations (slides, decks, pitch decks) directly from your terminal. The CLI generates AI-powered slides from any context you provide - text, files, URLs, or piped content. Also supports searching for stock images and videos.
|
|
4561
|
-
|
|
4562
|
-
---
|
|
4563
|
-
|
|
4564
|
-
## Authentication
|
|
4565
|
-
|
|
4566
|
-
\`\`\`bash
|
|
4567
|
-
# Login via OAuth
|
|
4568
|
-
${cmd2} login
|
|
4569
|
-
|
|
4570
|
-
# Or set API key
|
|
4571
|
-
export ${name.toUpperCase().replace(/-/g, "_")}_API_KEY="your-key-here"
|
|
4572
|
-
\`\`\`
|
|
4573
|
-
|
|
4574
|
-
---
|
|
4575
|
-
|
|
4576
|
-
## Creating Presentations
|
|
4577
|
-
|
|
4578
|
-
### From Text
|
|
4579
|
-
|
|
4580
|
-
\`\`\`bash
|
|
4581
|
-
${cmd2} create "AI-powered product analytics platform"
|
|
4582
|
-
\`\`\`
|
|
4583
|
-
|
|
4584
|
-
### From File
|
|
4585
|
-
|
|
4586
|
-
\`\`\`bash
|
|
4587
|
-
${cmd2} create --file product-brief.md
|
|
4588
|
-
\`\`\`
|
|
4589
|
-
|
|
4590
|
-
### From URL
|
|
4591
|
-
|
|
4592
|
-
\`\`\`bash
|
|
4593
|
-
${cmd2} create --url https://company.com/product
|
|
4594
|
-
\`\`\`
|
|
4595
|
-
|
|
4596
|
-
### From Piped Content
|
|
3756
|
+
### From Piped Content
|
|
4597
3757
|
|
|
4598
3758
|
\`\`\`bash
|
|
4599
3759
|
cat research.txt | ${cmd2} create
|
|
@@ -4822,9 +3982,10 @@ function getSupportedEditorNames() {
|
|
|
4822
3982
|
// src/commands/skill/index.ts
|
|
4823
3983
|
var SKILL_TYPES = ["main", "video", "presentation"];
|
|
4824
3984
|
var skillContext = {
|
|
4825
|
-
name: brand.name,
|
|
4826
3985
|
cmd: brand.commands[0],
|
|
4827
|
-
|
|
3986
|
+
pkg: brand.packageName,
|
|
3987
|
+
url: brand.apiUrl,
|
|
3988
|
+
name: brand.displayName
|
|
4828
3989
|
};
|
|
4829
3990
|
var skillCommand = new Command14("skill").description(`Manage ${brand.displayName} skills for AI coding assistants`).addHelpText(
|
|
4830
3991
|
"after",
|
|
@@ -4953,9 +4114,6 @@ skillCommand.command("uninstall").description(`Remove ${brand.displayName} skill
|
|
|
4953
4114
|
});
|
|
4954
4115
|
|
|
4955
4116
|
// src/commands/tts.ts
|
|
4956
|
-
init_api();
|
|
4957
|
-
init_output();
|
|
4958
|
-
init_types();
|
|
4959
4117
|
import { Command as Command15 } from "commander";
|
|
4960
4118
|
import ora8 from "ora";
|
|
4961
4119
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
@@ -5044,9 +4202,6 @@ var voicesCommand = new Command15("voices").description("List available voices")
|
|
|
5044
4202
|
var ttsCommand = new Command15("tts").description("Text-to-speech commands").addCommand(generateCommand).addCommand(voicesCommand);
|
|
5045
4203
|
|
|
5046
4204
|
// src/commands/music.ts
|
|
5047
|
-
init_api();
|
|
5048
|
-
init_output();
|
|
5049
|
-
init_types();
|
|
5050
4205
|
import { Command as Command16 } from "commander";
|
|
5051
4206
|
import ora9 from "ora";
|
|
5052
4207
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
@@ -5166,9 +4321,6 @@ var statusCommand = new Command16("status").description("Check status of a music
|
|
|
5166
4321
|
var musicCommand = new Command16("music").description("Music generation commands").addCommand(generateCommand2).addCommand(statusCommand);
|
|
5167
4322
|
|
|
5168
4323
|
// src/commands/mix.ts
|
|
5169
|
-
init_api();
|
|
5170
|
-
init_output();
|
|
5171
|
-
init_types();
|
|
5172
4324
|
import { Command as Command17 } from "commander";
|
|
5173
4325
|
import ora10 from "ora";
|
|
5174
4326
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
@@ -5292,9 +4444,6 @@ var statusCommand2 = new Command17("status").description("Check status of an aud
|
|
|
5292
4444
|
var mixAudioCommand = new Command17("mix").description("Audio mixing commands").addCommand(mixCommand).addCommand(statusCommand2);
|
|
5293
4445
|
|
|
5294
4446
|
// src/commands/image.ts
|
|
5295
|
-
init_api();
|
|
5296
|
-
init_output();
|
|
5297
|
-
init_types();
|
|
5298
4447
|
import { Command as Command18 } from "commander";
|
|
5299
4448
|
import ora11 from "ora";
|
|
5300
4449
|
var searchCommand = new Command18("search").description("Search for images").requiredOption("-q, --query <query>", "Search query").option("-n, --max-results <number>", "Maximum number of results (default: 10)").option("-s, --size <size>", "Image size: small, medium, large, any", "large").option("--safe-search", "Enable safe search (default: true)", true).option("--no-safe-search", "Disable safe search").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (options) => {
|
|
@@ -5372,9 +4521,6 @@ var searchCommand = new Command18("search").description("Search for images").req
|
|
|
5372
4521
|
var imageCommand = new Command18("image").description("Image search commands").addCommand(searchCommand);
|
|
5373
4522
|
|
|
5374
4523
|
// src/commands/video.ts
|
|
5375
|
-
init_api();
|
|
5376
|
-
init_output();
|
|
5377
|
-
init_types();
|
|
5378
4524
|
import { Command as Command19 } from "commander";
|
|
5379
4525
|
import ora12 from "ora";
|
|
5380
4526
|
import { mkdir, writeFile as writeFile5, readFile as readFile2, access, rm, cp } from "fs/promises";
|
|
@@ -5424,6 +4570,13 @@ function calculateSectionTiming(sections, totalDuration, fps = DEFAULT_FPS, time
|
|
|
5424
4570
|
const proportion = wordCount / totalWords;
|
|
5425
4571
|
const durationInSeconds = totalDuration * proportion;
|
|
5426
4572
|
const durationInFrames = Math.round(durationInSeconds * fps);
|
|
4573
|
+
const chars = text.split("");
|
|
4574
|
+
const charDuration = durationInSeconds / chars.length;
|
|
4575
|
+
const approximateTimestamps = {
|
|
4576
|
+
characters: chars,
|
|
4577
|
+
characterStartTimesSeconds: chars.map((_, i) => i * charDuration),
|
|
4578
|
+
characterEndTimesSeconds: chars.map((_, i) => (i + 1) * charDuration)
|
|
4579
|
+
};
|
|
5427
4580
|
const section = {
|
|
5428
4581
|
id: index + 1,
|
|
5429
4582
|
text,
|
|
@@ -5431,7 +4584,9 @@ function calculateSectionTiming(sections, totalDuration, fps = DEFAULT_FPS, time
|
|
|
5431
4584
|
startTime: currentTime,
|
|
5432
4585
|
endTime: currentTime + durationInSeconds,
|
|
5433
4586
|
durationInSeconds,
|
|
5434
|
-
durationInFrames
|
|
4587
|
+
durationInFrames,
|
|
4588
|
+
timestamps: approximateTimestamps
|
|
4589
|
+
// Always include timestamps (approximate if needed)
|
|
5435
4590
|
};
|
|
5436
4591
|
currentTime += durationInSeconds;
|
|
5437
4592
|
return section;
|
|
@@ -5458,6 +4613,13 @@ function calculateSectionTimingFromTimestamps(sections, timestamps, fps) {
|
|
|
5458
4613
|
const endTime = characterEndTimesSeconds[Math.min(endCharIndex, characterEndTimesSeconds.length - 1)] || startTime + 1;
|
|
5459
4614
|
const durationInSeconds = endTime - startTime;
|
|
5460
4615
|
const durationInFrames = Math.round(durationInSeconds * fps);
|
|
4616
|
+
const sectionTimestamps = {
|
|
4617
|
+
characters: characters.slice(startCharIndex, endCharIndex + 1),
|
|
4618
|
+
characterStartTimesSeconds: characterStartTimesSeconds.slice(startCharIndex, endCharIndex + 1).map((t) => t - startTime),
|
|
4619
|
+
// Make relative to section start
|
|
4620
|
+
characterEndTimesSeconds: characterEndTimesSeconds.slice(startCharIndex, endCharIndex + 1).map((t) => t - startTime)
|
|
4621
|
+
// Make relative to section start
|
|
4622
|
+
};
|
|
5461
4623
|
results.push({
|
|
5462
4624
|
id: i + 1,
|
|
5463
4625
|
text: sectionText,
|
|
@@ -5465,7 +4627,9 @@ function calculateSectionTimingFromTimestamps(sections, timestamps, fps) {
|
|
|
5465
4627
|
startTime,
|
|
5466
4628
|
endTime,
|
|
5467
4629
|
durationInSeconds,
|
|
5468
|
-
durationInFrames
|
|
4630
|
+
durationInFrames,
|
|
4631
|
+
timestamps: sectionTimestamps
|
|
4632
|
+
// Add section-specific timestamps
|
|
5469
4633
|
});
|
|
5470
4634
|
}
|
|
5471
4635
|
return results;
|
|
@@ -5490,6 +4654,64 @@ async function readStdin2() {
|
|
|
5490
4654
|
function toFilename(name) {
|
|
5491
4655
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
5492
4656
|
}
|
|
4657
|
+
function createTimelineFromScenes(scenes) {
|
|
4658
|
+
const timeline = {
|
|
4659
|
+
shortTitle: "Video",
|
|
4660
|
+
elements: [],
|
|
4661
|
+
audio: [],
|
|
4662
|
+
text: []
|
|
4663
|
+
};
|
|
4664
|
+
let zoomIn = true;
|
|
4665
|
+
scenes.forEach((scene) => {
|
|
4666
|
+
const startMs = scene.startTime * 1e3;
|
|
4667
|
+
const endMs = scene.endTime * 1e3;
|
|
4668
|
+
const durationMs = scene.durationInSeconds * 1e3;
|
|
4669
|
+
if (scene.imagePath) {
|
|
4670
|
+
timeline.elements.push({
|
|
4671
|
+
startMs,
|
|
4672
|
+
endMs,
|
|
4673
|
+
imageUrl: scene.imagePath,
|
|
4674
|
+
enterTransition: "blur",
|
|
4675
|
+
exitTransition: "blur",
|
|
4676
|
+
animations: [{
|
|
4677
|
+
type: "scale",
|
|
4678
|
+
from: zoomIn ? 1.3 : 1,
|
|
4679
|
+
to: zoomIn ? 1 : 1.3,
|
|
4680
|
+
startMs: 0,
|
|
4681
|
+
endMs: durationMs
|
|
4682
|
+
}]
|
|
4683
|
+
});
|
|
4684
|
+
zoomIn = !zoomIn;
|
|
4685
|
+
} else if (scene.videoPath) {
|
|
4686
|
+
timeline.elements.push({
|
|
4687
|
+
startMs,
|
|
4688
|
+
endMs,
|
|
4689
|
+
videoUrl: scene.videoPath,
|
|
4690
|
+
enterTransition: "blur",
|
|
4691
|
+
exitTransition: "blur",
|
|
4692
|
+
animations: []
|
|
4693
|
+
});
|
|
4694
|
+
}
|
|
4695
|
+
if (scene.audioPath) {
|
|
4696
|
+
timeline.audio.push({
|
|
4697
|
+
startMs,
|
|
4698
|
+
endMs,
|
|
4699
|
+
audioUrl: scene.audioPath
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4702
|
+
if (scene.timestamps) {
|
|
4703
|
+
timeline.text.push({
|
|
4704
|
+
startMs,
|
|
4705
|
+
endMs,
|
|
4706
|
+
text: scene.text,
|
|
4707
|
+
position: "bottom",
|
|
4708
|
+
animations: [],
|
|
4709
|
+
timestamps: scene.timestamps
|
|
4710
|
+
});
|
|
4711
|
+
}
|
|
4712
|
+
});
|
|
4713
|
+
return timeline;
|
|
4714
|
+
}
|
|
5493
4715
|
async function downloadFile3(url, outputPath) {
|
|
5494
4716
|
if (url.startsWith("data:")) {
|
|
5495
4717
|
const matches = url.match(/^data:[^;]+;base64,(.+)$/);
|
|
@@ -5519,7 +4741,7 @@ function getExtension(url) {
|
|
|
5519
4741
|
}
|
|
5520
4742
|
return "jpg";
|
|
5521
4743
|
}
|
|
5522
|
-
var
|
|
4744
|
+
var createCommand3 = new Command19("create").description("Create video assets (voiceover per scene, music, images)").option("-s, --script <text>", "Narration script (legacy single-script mode)").option("--script-file <path>", "Path to script file (legacy) or scenes JSON").option("-t, --topic <text>", "Topic for image search").option("-v, --voice <name>", "TTS voice (Kore, Puck, Rachel, alloy)", "Kore").option("-m, --music-prompt <text>", "Music description").option("-n, --num-images <number>", "Number of images to search/download", "5").option("-o, --output <dir>", "Output directory", "./public").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (options) => {
|
|
5523
4745
|
const format = options.format;
|
|
5524
4746
|
const spinner = format === "human" ? ora12("Initializing...").start() : null;
|
|
5525
4747
|
try {
|
|
@@ -5564,21 +4786,27 @@ var createCommand2 = new Command19("create").description("Create video assets (v
|
|
|
5564
4786
|
info(`Processing ${scenesInput.scenes.length} scenes...`);
|
|
5565
4787
|
spinner?.start();
|
|
5566
4788
|
}
|
|
4789
|
+
const ttsRequests = scenesInput.scenes.map((scene, i) => ({
|
|
4790
|
+
text: scene.script,
|
|
4791
|
+
id: `scene-${i}`
|
|
4792
|
+
}));
|
|
4793
|
+
if (spinner) spinner.text = "Generating speech for all scenes...";
|
|
4794
|
+
const batchResult = await generateSpeechBatch({
|
|
4795
|
+
texts: ttsRequests,
|
|
4796
|
+
options: {
|
|
4797
|
+
voice,
|
|
4798
|
+
voiceSettings: scenesInput.voiceSettings
|
|
4799
|
+
}
|
|
4800
|
+
});
|
|
4801
|
+
totalCost += batchResult.totalCost;
|
|
5567
4802
|
let currentTime = 0;
|
|
5568
4803
|
for (let i = 0; i < scenesInput.scenes.length; i++) {
|
|
5569
4804
|
const scene = scenesInput.scenes[i];
|
|
5570
4805
|
const filename = toFilename(scene.name);
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
text: scene.script,
|
|
5574
|
-
options: {
|
|
5575
|
-
voice,
|
|
5576
|
-
voiceSettings: scenesInput.voiceSettings
|
|
5577
|
-
}
|
|
5578
|
-
});
|
|
4806
|
+
const ttsResult = batchResult.results[i];
|
|
4807
|
+
if (spinner) spinner.text = `[${scene.name}] Saving audio...`;
|
|
5579
4808
|
const audioPath = join2(audioDir, `${filename}.${ttsResult.format}`);
|
|
5580
4809
|
await writeFile5(audioPath, ttsResult.audioData);
|
|
5581
|
-
totalCost += ttsResult.cost;
|
|
5582
4810
|
const durationInSeconds = ttsResult.duration;
|
|
5583
4811
|
const durationInFrames = Math.round(durationInSeconds * DEFAULT_FPS);
|
|
5584
4812
|
const sceneData = {
|
|
@@ -5590,7 +4818,9 @@ var createCommand2 = new Command19("create").description("Create video assets (v
|
|
|
5590
4818
|
endTime: currentTime + durationInSeconds,
|
|
5591
4819
|
durationInSeconds,
|
|
5592
4820
|
durationInFrames,
|
|
5593
|
-
audioPath: `audio/${filename}.${ttsResult.format}
|
|
4821
|
+
audioPath: `audio/${filename}.${ttsResult.format}`,
|
|
4822
|
+
timestamps: ttsResult.timestamps
|
|
4823
|
+
// Character-level timestamps for captions
|
|
5594
4824
|
};
|
|
5595
4825
|
if (scene.imageQuery) {
|
|
5596
4826
|
if (spinner) spinner.text = `[${scene.name}] Searching image...`;
|
|
@@ -5714,66 +4944,93 @@ var createCommand2 = new Command19("create").description("Create video assets (v
|
|
|
5714
4944
|
spinner?.start();
|
|
5715
4945
|
}
|
|
5716
4946
|
}
|
|
5717
|
-
|
|
4947
|
+
if (spinner) spinner.text = "Creating timeline...";
|
|
4948
|
+
const timeline = createTimelineFromScenes(scenes);
|
|
4949
|
+
const videoEndTimeMs = Math.max(
|
|
4950
|
+
timeline.audio.length > 0 ? Math.max(...timeline.audio.map((a) => a.endMs)) : 0,
|
|
4951
|
+
timeline.text.length > 0 ? Math.max(...timeline.text.map((t) => t.endMs)) : 0,
|
|
4952
|
+
timeline.elements.length > 0 ? Math.max(...timeline.elements.map((e) => e.endMs)) : 0
|
|
4953
|
+
);
|
|
4954
|
+
const actualVideoDuration = videoEndTimeMs / 1e3;
|
|
4955
|
+
const musicDuration = Math.min(30, Math.ceil(actualVideoDuration));
|
|
5718
4956
|
console.log(`[Music Generation] Requesting music:`, {
|
|
5719
4957
|
prompt: musicPrompt,
|
|
5720
4958
|
requestedDuration: musicDuration,
|
|
5721
|
-
totalAudioDuration: totalDuration
|
|
5722
|
-
});
|
|
5723
|
-
if (spinner) spinner.text = "Generating music...";
|
|
5724
|
-
let musicResult = await generateMusic({
|
|
5725
|
-
prompt: musicPrompt,
|
|
5726
|
-
duration: musicDuration
|
|
5727
|
-
});
|
|
5728
|
-
if (musicResult.status !== "completed" && musicResult.status !== "failed") {
|
|
5729
|
-
if (spinner) spinner.text = `Processing music (ID: ${musicResult.requestId})...`;
|
|
5730
|
-
musicResult = await pollForCompletion(
|
|
5731
|
-
() => checkMusicStatus(musicResult.requestId),
|
|
5732
|
-
60,
|
|
5733
|
-
2e3
|
|
5734
|
-
);
|
|
5735
|
-
}
|
|
5736
|
-
if (musicResult.status === "failed") {
|
|
5737
|
-
spinner?.stop();
|
|
5738
|
-
error(`Music generation failed: ${musicResult.error || "Unknown error"}`);
|
|
5739
|
-
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
5740
|
-
}
|
|
5741
|
-
const musicPath = join2(audioDir, "music.mp3");
|
|
5742
|
-
if (musicResult.audioUrl) {
|
|
5743
|
-
await downloadFile3(musicResult.audioUrl, musicPath);
|
|
5744
|
-
}
|
|
5745
|
-
totalCost += musicResult.cost || 0;
|
|
5746
|
-
const actualMusicDuration = musicResult.duration || musicDuration;
|
|
5747
|
-
console.log(`[Music Generation] Received music:`, {
|
|
5748
|
-
requestedDuration: musicDuration,
|
|
5749
|
-
returnedDuration: musicResult.duration,
|
|
5750
|
-
actualUsedDuration: actualMusicDuration,
|
|
5751
4959
|
totalAudioDuration: totalDuration,
|
|
5752
|
-
|
|
5753
|
-
|
|
4960
|
+
actualVideoDuration,
|
|
4961
|
+
timelineDurationMs: videoEndTimeMs
|
|
5754
4962
|
});
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
4963
|
+
let musicInfo;
|
|
4964
|
+
if (musicDuration < 3) {
|
|
4965
|
+
if (format === "human") {
|
|
4966
|
+
spinner?.stop();
|
|
4967
|
+
warn(`Video duration (${actualVideoDuration.toFixed(1)}s) is too short for music generation (minimum 3s).`);
|
|
4968
|
+
warn(`Skipping music generation...`);
|
|
4969
|
+
spinner?.start();
|
|
4970
|
+
}
|
|
4971
|
+
} else {
|
|
4972
|
+
try {
|
|
4973
|
+
if (spinner) spinner.text = "Generating music...";
|
|
4974
|
+
let musicResult = await generateMusic({
|
|
4975
|
+
prompt: musicPrompt,
|
|
4976
|
+
duration: musicDuration
|
|
4977
|
+
});
|
|
4978
|
+
if (musicResult.status !== "completed" && musicResult.status !== "failed") {
|
|
4979
|
+
if (spinner) spinner.text = `Processing music (ID: ${musicResult.requestId})...`;
|
|
4980
|
+
musicResult = await pollForCompletion(
|
|
4981
|
+
() => checkMusicStatus(musicResult.requestId),
|
|
4982
|
+
60,
|
|
4983
|
+
2e3
|
|
4984
|
+
);
|
|
4985
|
+
}
|
|
4986
|
+
if (musicResult.status === "failed") {
|
|
4987
|
+
throw new Error(musicResult.error || "Unknown error");
|
|
4988
|
+
}
|
|
4989
|
+
const musicPath = join2(audioDir, "music.mp3");
|
|
4990
|
+
if (musicResult.audioUrl) {
|
|
4991
|
+
await downloadFile3(musicResult.audioUrl, musicPath);
|
|
4992
|
+
}
|
|
4993
|
+
totalCost += musicResult.cost || 0;
|
|
4994
|
+
const actualMusicDuration = musicResult.duration || musicDuration;
|
|
4995
|
+
console.log(`[Music Generation] Received music:`, {
|
|
4996
|
+
requestedDuration: musicDuration,
|
|
4997
|
+
returnedDuration: musicResult.duration,
|
|
4998
|
+
actualUsedDuration: actualMusicDuration,
|
|
4999
|
+
totalAudioDuration: totalDuration,
|
|
5000
|
+
difference: actualMusicDuration - totalDuration,
|
|
5001
|
+
audioUrl: musicResult.audioUrl?.substring(0, 50) + "..."
|
|
5002
|
+
});
|
|
5003
|
+
musicInfo = {
|
|
5004
|
+
path: "audio/music.mp3",
|
|
5005
|
+
duration: actualMusicDuration,
|
|
5006
|
+
prompt: musicPrompt,
|
|
5007
|
+
cost: musicResult.cost || 0
|
|
5008
|
+
};
|
|
5009
|
+
if (format === "human") {
|
|
5010
|
+
spinner?.stop();
|
|
5011
|
+
success(`Music: ${musicPath} (${musicInfo.duration}s)`);
|
|
5012
|
+
if (actualMusicDuration < actualVideoDuration) {
|
|
5013
|
+
warn(`Music duration (${actualMusicDuration.toFixed(1)}s) is shorter than video duration (${actualVideoDuration.toFixed(1)}s).`);
|
|
5014
|
+
warn(`Consider using audio looping or extending music in Remotion.`);
|
|
5015
|
+
}
|
|
5016
|
+
spinner?.start();
|
|
5017
|
+
}
|
|
5018
|
+
} catch (musicError) {
|
|
5019
|
+
spinner?.stop();
|
|
5020
|
+
warn(`Music generation failed: ${musicError.message}`);
|
|
5021
|
+
warn(`Continuing without background music...`);
|
|
5022
|
+
if (spinner && format === "human") spinner?.start();
|
|
5767
5023
|
}
|
|
5768
|
-
spinner?.start();
|
|
5769
5024
|
}
|
|
5770
5025
|
if (spinner) spinner.text = "Writing manifest...";
|
|
5771
|
-
const totalDurationInFrames = Math.round(
|
|
5026
|
+
const totalDurationInFrames = Math.round(actualVideoDuration * DEFAULT_FPS);
|
|
5772
5027
|
const manifest = {
|
|
5773
5028
|
music: musicInfo,
|
|
5774
5029
|
images: allImages,
|
|
5775
5030
|
videos: allVideos,
|
|
5776
5031
|
scenes,
|
|
5032
|
+
timeline,
|
|
5033
|
+
// Include Remotion timeline in manifest
|
|
5777
5034
|
totalDurationInFrames,
|
|
5778
5035
|
fps: DEFAULT_FPS,
|
|
5779
5036
|
totalCost,
|
|
@@ -5802,7 +5059,7 @@ var createCommand2 = new Command19("create").description("Create video assets (v
|
|
|
5802
5059
|
].filter(Boolean).join(", ");
|
|
5803
5060
|
info(` - ${scene.name}: ${scene.durationInSeconds.toFixed(1)}s [${assets}]`);
|
|
5804
5061
|
}
|
|
5805
|
-
info(`Music: ${musicInfo
|
|
5062
|
+
info(`Music: ${musicInfo?.path} (${musicInfo?.duration}s)`);
|
|
5806
5063
|
info(`Manifest: ${manifestPath}`);
|
|
5807
5064
|
console.log();
|
|
5808
5065
|
info(`Total cost: $${totalCost.toFixed(4)}`);
|
|
@@ -6118,10 +5375,10 @@ var thumbnailCommand = new Command19("thumbnail").description("Embed a thumbnail
|
|
|
6118
5375
|
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
6119
5376
|
}
|
|
6120
5377
|
});
|
|
6121
|
-
var videoCommand = new Command19("video").description("Video asset generation commands").addCommand(initCommand).addCommand(
|
|
5378
|
+
var videoCommand = new Command19("video").description("Video asset generation commands").addCommand(initCommand).addCommand(createCommand3).addCommand(searchCommand2).addCommand(thumbnailCommand);
|
|
6122
5379
|
|
|
6123
5380
|
// src/index.ts
|
|
6124
|
-
var VERSION = "0.1.
|
|
5381
|
+
var VERSION = "0.1.11";
|
|
6125
5382
|
var program = new Command20();
|
|
6126
5383
|
var cmdName = brand.commands[0];
|
|
6127
5384
|
program.name(cmdName).description(brand.description).version(VERSION, "-v, --version", "Show version number").option("--debug", "Enable debug logging").option("--no-color", "Disable colored output").configureOutput({
|