@conceptcraft/mindframes 0.1.7 → 0.1.9
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 +52 -0
- package/dist/index.js +1529 -1162
- package/dist/index.js.map +1 -1
- package/package.json +14 -3
package/dist/index.js
CHANGED
|
@@ -10,6 +10,64 @@ var __export = (target, all) => {
|
|
|
10
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// src/lib/brand.ts
|
|
14
|
+
import path from "path";
|
|
15
|
+
function detectBrand() {
|
|
16
|
+
const binaryPath = process.argv[1] || "";
|
|
17
|
+
const binaryName = path.basename(binaryPath).replace(/\.(js|ts)$/, "");
|
|
18
|
+
const brandId = COMMAND_TO_BRAND[binaryName];
|
|
19
|
+
if (brandId) {
|
|
20
|
+
return BRANDS[brandId];
|
|
21
|
+
}
|
|
22
|
+
for (const [cmd2, id] of Object.entries(COMMAND_TO_BRAND)) {
|
|
23
|
+
if (binaryPath.includes(`/${cmd2}`) || binaryPath.includes(`\\${cmd2}`)) {
|
|
24
|
+
return BRANDS[id];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return BRANDS.mindframes;
|
|
28
|
+
}
|
|
29
|
+
var BRANDS, COMMAND_TO_BRAND, brand;
|
|
30
|
+
var init_brand = __esm({
|
|
31
|
+
"src/lib/brand.ts"() {
|
|
32
|
+
"use strict";
|
|
33
|
+
BRANDS = {
|
|
34
|
+
conceptcraft: {
|
|
35
|
+
id: "conceptcraft",
|
|
36
|
+
name: "conceptcraft",
|
|
37
|
+
displayName: "ConceptCraft",
|
|
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
|
+
});
|
|
70
|
+
|
|
13
71
|
// src/lib/config.ts
|
|
14
72
|
import Conf from "conf";
|
|
15
73
|
function getConfig() {
|
|
@@ -25,7 +83,7 @@ function getConfig() {
|
|
|
25
83
|
};
|
|
26
84
|
}
|
|
27
85
|
function getApiKey() {
|
|
28
|
-
const envKey = process.env.
|
|
86
|
+
const envKey = process.env[brand.apiKeyEnvVar] ?? process.env.CC_SLIDES_API_KEY;
|
|
29
87
|
if (envKey) {
|
|
30
88
|
return envKey;
|
|
31
89
|
}
|
|
@@ -35,7 +93,7 @@ function setApiKey(key) {
|
|
|
35
93
|
config.set("apiKey", key);
|
|
36
94
|
}
|
|
37
95
|
function getApiUrl() {
|
|
38
|
-
const envUrl = process.env.
|
|
96
|
+
const envUrl = process.env[brand.apiUrlEnvVar] ?? process.env.CC_SLIDES_API_URL;
|
|
39
97
|
if (envUrl) {
|
|
40
98
|
return envUrl;
|
|
41
99
|
}
|
|
@@ -97,6 +155,7 @@ var DEFAULT_API_URL, schema, config;
|
|
|
97
155
|
var init_config = __esm({
|
|
98
156
|
"src/lib/config.ts"() {
|
|
99
157
|
"use strict";
|
|
158
|
+
init_brand();
|
|
100
159
|
DEFAULT_API_URL = "https://www.mindframes.app";
|
|
101
160
|
schema = {
|
|
102
161
|
apiKey: {
|
|
@@ -128,7 +187,7 @@ var init_config = __esm({
|
|
|
128
187
|
}
|
|
129
188
|
};
|
|
130
189
|
config = new Conf({
|
|
131
|
-
projectName:
|
|
190
|
+
projectName: brand.name,
|
|
132
191
|
schema
|
|
133
192
|
});
|
|
134
193
|
}
|
|
@@ -440,7 +499,7 @@ async function request(endpoint, options = {}) {
|
|
|
440
499
|
const apiUrl = getApiUrl();
|
|
441
500
|
if (!hasAuth()) {
|
|
442
501
|
throw new ApiError(
|
|
443
|
-
|
|
502
|
+
`Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
|
|
444
503
|
401,
|
|
445
504
|
2
|
|
446
505
|
// AUTH_ERROR
|
|
@@ -509,7 +568,7 @@ async function streamRequest(endpoint, body) {
|
|
|
509
568
|
const apiUrl = getApiUrl();
|
|
510
569
|
if (!hasAuth()) {
|
|
511
570
|
throw new ApiError(
|
|
512
|
-
|
|
571
|
+
`Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
|
|
513
572
|
401,
|
|
514
573
|
2
|
|
515
574
|
);
|
|
@@ -585,7 +644,7 @@ async function uploadFile(filePath) {
|
|
|
585
644
|
const apiUrl = getApiUrl();
|
|
586
645
|
if (!hasAuth()) {
|
|
587
646
|
throw new ApiError(
|
|
588
|
-
|
|
647
|
+
`Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
|
|
589
648
|
401,
|
|
590
649
|
2
|
|
591
650
|
);
|
|
@@ -745,7 +804,7 @@ async function exportPresentation(presentationId, options = {}) {
|
|
|
745
804
|
const apiUrl = getApiUrl();
|
|
746
805
|
if (!hasAuth()) {
|
|
747
806
|
throw new ApiError(
|
|
748
|
-
|
|
807
|
+
`Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
|
|
749
808
|
401,
|
|
750
809
|
2
|
|
751
810
|
);
|
|
@@ -779,7 +838,7 @@ async function importPresentation(fileBuffer, fileName, options = {}) {
|
|
|
779
838
|
const apiUrl = getApiUrl();
|
|
780
839
|
if (!hasAuth()) {
|
|
781
840
|
throw new ApiError(
|
|
782
|
-
|
|
841
|
+
`Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
|
|
783
842
|
401,
|
|
784
843
|
2
|
|
785
844
|
);
|
|
@@ -832,7 +891,7 @@ async function generateBlog(presentationId, documentCreatedAt, options = {}) {
|
|
|
832
891
|
const apiUrl = getApiUrl();
|
|
833
892
|
if (!hasAuth()) {
|
|
834
893
|
throw new ApiError(
|
|
835
|
-
|
|
894
|
+
`Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
|
|
836
895
|
401,
|
|
837
896
|
2
|
|
838
897
|
);
|
|
@@ -907,7 +966,7 @@ async function generateSpeech(ttsRequest) {
|
|
|
907
966
|
const apiUrl = getApiUrl();
|
|
908
967
|
if (!hasAuth()) {
|
|
909
968
|
throw new ApiError(
|
|
910
|
-
|
|
969
|
+
`Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
|
|
911
970
|
401,
|
|
912
971
|
2
|
|
913
972
|
);
|
|
@@ -1035,6 +1094,7 @@ var init_api = __esm({
|
|
|
1035
1094
|
"use strict";
|
|
1036
1095
|
init_config();
|
|
1037
1096
|
init_auth();
|
|
1097
|
+
init_brand();
|
|
1038
1098
|
ApiError = class extends Error {
|
|
1039
1099
|
constructor(message, statusCode, exitCode = 1) {
|
|
1040
1100
|
super(message);
|
|
@@ -1395,60 +1455,11 @@ var init_login = __esm({
|
|
|
1395
1455
|
});
|
|
1396
1456
|
|
|
1397
1457
|
// src/index.ts
|
|
1458
|
+
init_brand();
|
|
1459
|
+
init_login();
|
|
1398
1460
|
import { Command as Command20 } from "commander";
|
|
1399
1461
|
import chalk13 from "chalk";
|
|
1400
1462
|
|
|
1401
|
-
// src/lib/brand.ts
|
|
1402
|
-
import path from "path";
|
|
1403
|
-
var BRANDS = {
|
|
1404
|
-
conceptcraft: {
|
|
1405
|
-
id: "conceptcraft",
|
|
1406
|
-
name: "conceptcraft",
|
|
1407
|
-
displayName: "ConceptCraft",
|
|
1408
|
-
description: "CLI tool for ConceptCraft presentation generation",
|
|
1409
|
-
commands: ["cc", "conceptcraft"],
|
|
1410
|
-
apiUrl: "https://conceptcraft.ai",
|
|
1411
|
-
docsUrl: "https://docs.conceptcraft.ai",
|
|
1412
|
-
packageName: "@conceptcraft/cli",
|
|
1413
|
-
configDir: ".conceptcraft"
|
|
1414
|
-
},
|
|
1415
|
-
mindframes: {
|
|
1416
|
-
id: "mindframes",
|
|
1417
|
-
name: "mindframes",
|
|
1418
|
-
displayName: "Mindframes",
|
|
1419
|
-
description: "CLI tool for Mindframes presentation generation",
|
|
1420
|
-
commands: ["mf", "mindframes"],
|
|
1421
|
-
apiUrl: "https://mindframes.app",
|
|
1422
|
-
docsUrl: "https://docs.mindframes.app",
|
|
1423
|
-
packageName: "@mindframes/cli",
|
|
1424
|
-
configDir: ".mindframes"
|
|
1425
|
-
}
|
|
1426
|
-
};
|
|
1427
|
-
var COMMAND_TO_BRAND = {
|
|
1428
|
-
cc: "conceptcraft",
|
|
1429
|
-
conceptcraft: "conceptcraft",
|
|
1430
|
-
mf: "mindframes",
|
|
1431
|
-
mindframes: "mindframes"
|
|
1432
|
-
};
|
|
1433
|
-
function detectBrand() {
|
|
1434
|
-
const binaryPath = process.argv[1] || "";
|
|
1435
|
-
const binaryName = path.basename(binaryPath).replace(/\.(js|ts)$/, "");
|
|
1436
|
-
const brandId = COMMAND_TO_BRAND[binaryName];
|
|
1437
|
-
if (brandId) {
|
|
1438
|
-
return BRANDS[brandId];
|
|
1439
|
-
}
|
|
1440
|
-
for (const [cmd2, id] of Object.entries(COMMAND_TO_BRAND)) {
|
|
1441
|
-
if (binaryPath.includes(`/${cmd2}`) || binaryPath.includes(`\\${cmd2}`)) {
|
|
1442
|
-
return BRANDS[id];
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
return BRANDS.mindframes;
|
|
1446
|
-
}
|
|
1447
|
-
var brand = detectBrand();
|
|
1448
|
-
|
|
1449
|
-
// src/index.ts
|
|
1450
|
-
init_login();
|
|
1451
|
-
|
|
1452
1463
|
// src/commands/logout.ts
|
|
1453
1464
|
init_config();
|
|
1454
1465
|
init_feature_cache();
|
|
@@ -3129,539 +3140,527 @@ var whoamiCommand = new Command13("whoami").description("Show current user and t
|
|
|
3129
3140
|
|
|
3130
3141
|
// src/commands/skill/index.ts
|
|
3131
3142
|
init_output();
|
|
3143
|
+
init_brand();
|
|
3132
3144
|
import { Command as Command14 } from "commander";
|
|
3133
3145
|
import chalk12 from "chalk";
|
|
3134
3146
|
|
|
3135
|
-
// src/commands/skill/
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
// src/commands/skill/sections/header.ts
|
|
3145
|
-
var header3 = {
|
|
3146
|
-
title: "Header",
|
|
3147
|
-
render: (ctx) => `# ${ctx.name} CLI
|
|
3148
|
-
|
|
3149
|
-
Create professional presentations directly from your terminal. The CLI generates AI-powered slides from any context you provide - text, files, URLs, or piped content.`
|
|
3150
|
-
};
|
|
3151
|
-
|
|
3152
|
-
// src/commands/skill/sections/prerequisites.ts
|
|
3153
|
-
var prerequisites = {
|
|
3154
|
-
title: "Prerequisites",
|
|
3155
|
-
render: (ctx) => `## Prerequisites
|
|
3147
|
+
// src/commands/skill/generate-main-skill.ts
|
|
3148
|
+
function generateMainSkillContent(context) {
|
|
3149
|
+
const { name, cmd: cmd2, displayName } = context;
|
|
3150
|
+
const envPrefix = name.toUpperCase().replace(/-/g, "_");
|
|
3151
|
+
return `---
|
|
3152
|
+
name: ${name}
|
|
3153
|
+
description: ${displayName} 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
|
+
---
|
|
3156
3155
|
|
|
3157
|
-
|
|
3158
|
-
npm install -g ${ctx.pkg}
|
|
3159
|
-
${ctx.cmd} login # Authenticate (opens browser)
|
|
3160
|
-
${ctx.cmd} whoami # Verify auth
|
|
3161
|
-
\`\`\`
|
|
3156
|
+
# ${displayName} CLI
|
|
3162
3157
|
|
|
3163
|
-
|
|
3158
|
+
A comprehensive CLI for AI-powered content creation. Generate presentations, video assets, voiceovers, music, and search stock media - all from your terminal.
|
|
3164
3159
|
|
|
3165
|
-
|
|
3166
|
-
If login fails or user declines, fall back to API key: \`${ctx.cmd} config init\``
|
|
3167
|
-
};
|
|
3160
|
+
**Install:** \`npm install -g @${name}/cli\` or \`pnpm add -g @${name}/cli\`
|
|
3168
3161
|
|
|
3169
|
-
|
|
3170
|
-
var workflow = {
|
|
3171
|
-
title: "Core Workflow",
|
|
3172
|
-
render: (ctx) => `## Core Workflow
|
|
3162
|
+
---
|
|
3173
3163
|
|
|
3174
|
-
|
|
3175
|
-
2. **Create presentation** - Pass context to \`${ctx.cmd} create\`
|
|
3176
|
-
3. **Share URL** - Return the presentation link to the user`
|
|
3177
|
-
};
|
|
3164
|
+
## Quick Reference
|
|
3178
3165
|
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3166
|
+
| Task | Command |
|
|
3167
|
+
|------|---------|
|
|
3168
|
+
| Create presentation | \`${cmd2} create "Topic"\` |
|
|
3169
|
+
| Generate video assets | \`${cmd2} video create < scenes.json\` |
|
|
3170
|
+
| Text-to-speech | \`${cmd2} tts generate -t "Text" -o voice.mp3\` |
|
|
3171
|
+
| Generate music | \`${cmd2} music generate -p "upbeat corporate"\` |
|
|
3172
|
+
| Search images | \`${cmd2} image search -q "mountain landscape"\` |
|
|
3173
|
+
| Search videos | \`${cmd2} video search "ocean waves"\` |
|
|
3174
|
+
| Mix audio tracks | \`${cmd2} mix create --video v.mp4 --music m.mp3\` |
|
|
3183
3175
|
|
|
3184
|
-
|
|
3176
|
+
---
|
|
3185
3177
|
|
|
3186
|
-
|
|
3178
|
+
## Authentication
|
|
3187
3179
|
|
|
3188
3180
|
\`\`\`bash
|
|
3189
|
-
#
|
|
3190
|
-
${
|
|
3181
|
+
# Login via OAuth (recommended)
|
|
3182
|
+
${cmd2} login
|
|
3191
3183
|
|
|
3192
|
-
#
|
|
3193
|
-
${
|
|
3184
|
+
# Or set API key directly
|
|
3185
|
+
export ${envPrefix}_API_KEY="your-key-here"
|
|
3194
3186
|
|
|
3195
|
-
#
|
|
3196
|
-
${
|
|
3187
|
+
# Verify authentication
|
|
3188
|
+
${cmd2} whoami
|
|
3189
|
+
\`\`\`
|
|
3197
3190
|
|
|
3198
|
-
|
|
3199
|
-
cat README.md | ${ctx.cmd} create "Project Overview"
|
|
3191
|
+
---
|
|
3200
3192
|
|
|
3201
|
-
|
|
3202
|
-
${ctx.cmd} create "Competitor Analysis" --sources https://example.com/report
|
|
3193
|
+
## 1. Presentations
|
|
3203
3194
|
|
|
3204
|
-
|
|
3205
|
-
cat src/auth/*.ts | ${ctx.cmd} create "Auth System" \\
|
|
3206
|
-
--file ./architecture.png \\
|
|
3207
|
-
--context "Focus on security patterns"
|
|
3208
|
-
\`\`\``
|
|
3209
|
-
};
|
|
3195
|
+
Create AI-powered presentations from text, files, URLs, or piped content.
|
|
3210
3196
|
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3197
|
+
\`\`\`bash
|
|
3198
|
+
# From topic
|
|
3199
|
+
${cmd2} create "AI-powered analytics platform"
|
|
3200
|
+
|
|
3201
|
+
# From file (PDF, PPTX, DOCX, Markdown)
|
|
3202
|
+
${cmd2} create "Quarterly Report" --file report.pdf
|
|
3203
|
+
|
|
3204
|
+
# From URL
|
|
3205
|
+
${cmd2} create "Product Overview" --sources https://company.com/product
|
|
3206
|
+
|
|
3207
|
+
# From piped content
|
|
3208
|
+
cat notes.md | ${cmd2} create "Meeting Summary"
|
|
3209
|
+
|
|
3210
|
+
# With options
|
|
3211
|
+
${cmd2} create "Pitch Deck" \\
|
|
3212
|
+
--slides 10 \\
|
|
3213
|
+
--mode balanced \\
|
|
3214
|
+
--tone professional \\
|
|
3215
|
+
--audience "Investors" \\
|
|
3216
|
+
--brand my-company \\
|
|
3217
|
+
--open
|
|
3218
|
+
\`\`\`
|
|
3230
3219
|
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3220
|
+
**Key Options:**
|
|
3221
|
+
- \`--slides <1-20>\` - Number of slides (default: 10)
|
|
3222
|
+
- \`--mode <instant|ultrafast|fast|balanced|best>\` - Quality/speed tradeoff
|
|
3223
|
+
- \`--tone <creative|professional|educational|formal|casual>\`
|
|
3224
|
+
- \`--file <paths...>\` - Extract content from files
|
|
3225
|
+
- \`--sources <urls...>\` - Scrape URLs for context
|
|
3226
|
+
- \`--brand <id|url>\` - Apply branding
|
|
3227
|
+
- \`--open\` - Open in browser when done
|
|
3235
3228
|
|
|
3229
|
+
**Manage presentations:**
|
|
3236
3230
|
\`\`\`bash
|
|
3237
|
-
#
|
|
3238
|
-
${
|
|
3239
|
-
|
|
3240
|
-
#
|
|
3241
|
-
|
|
3242
|
-
${ctx.cmd} list --format json
|
|
3231
|
+
${cmd2} list # List all presentations
|
|
3232
|
+
${cmd2} get <slug> # Get details
|
|
3233
|
+
${cmd2} export <slug> -o p.zip # Export to ZIP
|
|
3234
|
+
${cmd2} delete <slug> # Delete
|
|
3235
|
+
\`\`\`
|
|
3243
3236
|
|
|
3244
|
-
|
|
3245
|
-
${ctx.cmd} get <id-or-slug>
|
|
3237
|
+
---
|
|
3246
3238
|
|
|
3247
|
-
|
|
3248
|
-
${ctx.cmd} export <id-or-slug> -o presentation.zip
|
|
3239
|
+
## 2. Video Asset Generation
|
|
3249
3240
|
|
|
3250
|
-
|
|
3251
|
-
${ctx.cmd} import ./presentation.zip
|
|
3241
|
+
Generate voiceovers, music, and find stock media for video production. Works with Remotion or any video framework.
|
|
3252
3242
|
|
|
3253
|
-
|
|
3254
|
-
${ctx.cmd} branding list
|
|
3255
|
-
${ctx.cmd} branding extract https://company.com
|
|
3243
|
+
### Generate All Assets at Once
|
|
3256
3244
|
|
|
3257
|
-
|
|
3258
|
-
${
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3245
|
+
\`\`\`bash
|
|
3246
|
+
cat <<'EOF' | ${cmd2} video create --output ./public
|
|
3247
|
+
{
|
|
3248
|
+
"scenes": [
|
|
3249
|
+
{
|
|
3250
|
+
"name": "Hook",
|
|
3251
|
+
"script": "Watch how we transformed complex workflows into a single click.",
|
|
3252
|
+
"imageQuery": "modern dashboard dark theme",
|
|
3253
|
+
"videoQuery": "abstract tech particles"
|
|
3254
|
+
},
|
|
3255
|
+
{
|
|
3256
|
+
"name": "Demo",
|
|
3257
|
+
"script": "Our AI analyzes data in real-time, surfacing insights that matter.",
|
|
3258
|
+
"imageQuery": "data visualization charts"
|
|
3259
|
+
},
|
|
3260
|
+
{
|
|
3261
|
+
"name": "CTA",
|
|
3262
|
+
"script": "Start your free trial today.",
|
|
3263
|
+
"imageQuery": "call to action button"
|
|
3264
|
+
}
|
|
3265
|
+
],
|
|
3266
|
+
"voice": "Kore",
|
|
3267
|
+
"voiceSettings": {
|
|
3268
|
+
"speed": 0.95,
|
|
3269
|
+
"stability": 0.4,
|
|
3270
|
+
"style": 0.6
|
|
3271
|
+
},
|
|
3272
|
+
"musicPrompt": "upbeat corporate, positive energy, modern synth"
|
|
3273
|
+
}
|
|
3274
|
+
EOF
|
|
3275
|
+
\`\`\`
|
|
3262
3276
|
|
|
3263
|
-
|
|
3264
|
-
var examples = {
|
|
3265
|
-
title: "Examples",
|
|
3266
|
-
render: (ctx) => `## Examples
|
|
3277
|
+
**Output:** Per-scene voiceovers, background music, stock images/videos, and \`video-manifest.json\` with timing data.
|
|
3267
3278
|
|
|
3268
|
-
###
|
|
3279
|
+
### Initialize Remotion Project
|
|
3269
3280
|
|
|
3270
3281
|
\`\`\`bash
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
--slides 8 --tone educational --audience "New developers" \\
|
|
3274
|
-
--goal train
|
|
3282
|
+
${cmd2} video init my-video # 16:9 landscape
|
|
3283
|
+
${cmd2} video init my-video --type tiktok # 9:16 vertical
|
|
3275
3284
|
\`\`\`
|
|
3276
3285
|
|
|
3277
|
-
###
|
|
3286
|
+
### Embed Thumbnail
|
|
3278
3287
|
|
|
3279
3288
|
\`\`\`bash
|
|
3280
|
-
${
|
|
3281
|
-
--file ./docs/api.md \\
|
|
3282
|
-
--file ./diagrams/architecture.png \\
|
|
3283
|
-
--mode best --amount detailed \\
|
|
3284
|
-
--goal inform
|
|
3289
|
+
${cmd2} video thumbnail video.mp4 --frame 60
|
|
3285
3290
|
\`\`\`
|
|
3286
3291
|
|
|
3287
|
-
|
|
3292
|
+
---
|
|
3288
3293
|
|
|
3289
|
-
|
|
3290
|
-
cat README.md package.json | ${ctx.cmd} create "Project Introduction" \\
|
|
3291
|
-
-m instant --slides 5
|
|
3292
|
-
\`\`\`
|
|
3294
|
+
## 3. Text-to-Speech (TTS)
|
|
3293
3295
|
|
|
3294
|
-
|
|
3296
|
+
Convert text to natural speech with multiple providers and voices.
|
|
3295
3297
|
|
|
3296
3298
|
\`\`\`bash
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3299
|
+
# Basic usage
|
|
3300
|
+
${cmd2} tts generate -t "Hello world" -o output.mp3
|
|
3301
|
+
|
|
3302
|
+
# With voice selection
|
|
3303
|
+
${cmd2} tts generate -t "Welcome to the demo" -v Rachel -o welcome.mp3
|
|
3304
|
+
|
|
3305
|
+
# With provider and settings
|
|
3306
|
+
${cmd2} tts generate \\
|
|
3307
|
+
-t "Professional narration" \\
|
|
3308
|
+
-v Kore \\
|
|
3309
|
+
-p gemini \\
|
|
3310
|
+
-s 0.9 \\
|
|
3311
|
+
-o narration.mp3
|
|
3312
|
+
|
|
3313
|
+
# List available voices
|
|
3314
|
+
${cmd2} tts voices
|
|
3315
|
+
${cmd2} tts voices --provider elevenlabs
|
|
3302
3316
|
\`\`\`
|
|
3303
3317
|
|
|
3304
|
-
|
|
3318
|
+
**Providers:** \`gemini\`, \`elevenlabs\`, \`openai\`
|
|
3319
|
+
**Popular voices:** \`Kore\`, \`Puck\`, \`Rachel\`, \`alloy\`
|
|
3320
|
+
**Speed range:** 0.25 - 4.0 (default: 1.0)
|
|
3321
|
+
|
|
3322
|
+
---
|
|
3323
|
+
|
|
3324
|
+
## 4. Music Generation
|
|
3325
|
+
|
|
3326
|
+
Generate AI music from text descriptions.
|
|
3305
3327
|
|
|
3306
3328
|
\`\`\`bash
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
--sources https://report.com/industry.pdf \\
|
|
3310
|
-
--tone formal --audience "Executive team" \\
|
|
3311
|
-
--goal report
|
|
3312
|
-
\`\`\``
|
|
3313
|
-
};
|
|
3329
|
+
# Generate music
|
|
3330
|
+
${cmd2} music generate -p "upbeat corporate, modern synth" --duration 30
|
|
3314
3331
|
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
title: "Output",
|
|
3318
|
-
render: (ctx) => `## Output
|
|
3332
|
+
# Save to file
|
|
3333
|
+
${cmd2} music generate -p "calm ambient background" -o music.mp3
|
|
3319
3334
|
|
|
3320
|
-
|
|
3335
|
+
# Check generation status (for async operations)
|
|
3336
|
+
${cmd2} music status <request-id>
|
|
3321
3337
|
\`\`\`
|
|
3322
|
-
\u2713 Presentation created successfully
|
|
3323
3338
|
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
Generated in: 45s \xB7 12,500 tokens
|
|
3339
|
+
**Duration:** 3-30 seconds
|
|
3340
|
+
**Providers:** \`elevenlabs\`, \`suno\`
|
|
3327
3341
|
|
|
3328
|
-
|
|
3329
|
-
|
|
3342
|
+
---
|
|
3343
|
+
|
|
3344
|
+
## 5. Image Search
|
|
3345
|
+
|
|
3346
|
+
Search for stock images from multiple sources.
|
|
3330
3347
|
|
|
3331
|
-
For scripting, use JSON output:
|
|
3332
3348
|
\`\`\`bash
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
};
|
|
3349
|
+
# Basic search
|
|
3350
|
+
${cmd2} image search -q "mountain landscape"
|
|
3336
3351
|
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
1. **Provide rich context** - More context = better slides. Include code, docs, data.
|
|
3343
|
-
2. **Use file uploads for binary content** - PDFs, images, PPTX files need \`--file\`.
|
|
3344
|
-
3. **Specify a goal** - Helps tailor the presentation structure and messaging.
|
|
3345
|
-
4. **Use appropriate mode** - \`instant\` for quick drafts, \`best\` for important presentations.
|
|
3346
|
-
5. **Specify audience** - Helps tailor complexity and terminology.
|
|
3347
|
-
6. **Combine sources** - Pipe multiple files for comprehensive presentations.`
|
|
3348
|
-
};
|
|
3352
|
+
# With options
|
|
3353
|
+
${cmd2} image search -q "business team meeting" \\
|
|
3354
|
+
--max-results 20 \\
|
|
3355
|
+
--size large \\
|
|
3356
|
+
--format json
|
|
3349
3357
|
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
render: (_ctx) => `## Supported File Types
|
|
3358
|
+
# Download for video project
|
|
3359
|
+
${cmd2} image search -q "tech abstract" -n 5 --format json > images.json
|
|
3360
|
+
\`\`\`
|
|
3354
3361
|
|
|
3355
|
-
|
|
3356
|
-
-
|
|
3357
|
-
-
|
|
3358
|
-
|
|
3362
|
+
**Options:**
|
|
3363
|
+
- \`--max-results <n>\` - Number of results (default: 10)
|
|
3364
|
+
- \`--size <small|medium|large|any>\` - Image size
|
|
3365
|
+
- \`--safe-search / --no-safe-search\` - Filter adult content
|
|
3359
3366
|
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3367
|
+
---
|
|
3368
|
+
|
|
3369
|
+
## 6. Video Search
|
|
3370
|
+
|
|
3371
|
+
Search for stock video clips.
|
|
3364
3372
|
|
|
3365
3373
|
\`\`\`bash
|
|
3366
|
-
#
|
|
3367
|
-
${
|
|
3374
|
+
# Basic search
|
|
3375
|
+
${cmd2} video search "ocean waves"
|
|
3376
|
+
|
|
3377
|
+
# With filters
|
|
3378
|
+
${cmd2} video search "city timelapse" \\
|
|
3379
|
+
--max-results 5 \\
|
|
3380
|
+
--orientation landscape \\
|
|
3381
|
+
--license free
|
|
3382
|
+
\`\`\`
|
|
3368
3383
|
|
|
3369
|
-
|
|
3370
|
-
|
|
3384
|
+
**Options:**
|
|
3385
|
+
- \`--orientation <landscape|portrait|square|any>\`
|
|
3386
|
+
- \`--license <free|premium|any>\`
|
|
3371
3387
|
|
|
3372
|
-
|
|
3373
|
-
${ctx.cmd} create "Test" --context "test" --debug
|
|
3374
|
-
\`\`\``
|
|
3375
|
-
};
|
|
3388
|
+
---
|
|
3376
3389
|
|
|
3377
|
-
|
|
3378
|
-
var videoCreation = {
|
|
3379
|
-
title: "Video Creation",
|
|
3380
|
-
render: (ctx) => `## Video Creation
|
|
3390
|
+
## 7. Audio Mixing
|
|
3381
3391
|
|
|
3382
|
-
|
|
3392
|
+
Mix video with voiceover and background music.
|
|
3383
3393
|
|
|
3384
|
-
|
|
3394
|
+
\`\`\`bash
|
|
3395
|
+
# Mix audio tracks
|
|
3396
|
+
${cmd2} mix create \\
|
|
3397
|
+
--video input.mp4 \\
|
|
3398
|
+
--voice voiceover.mp3 \\
|
|
3399
|
+
--music background.mp3 \\
|
|
3400
|
+
--music-volume 30 \\
|
|
3401
|
+
--voice-volume 100 \\
|
|
3402
|
+
-o final.mp4
|
|
3403
|
+
|
|
3404
|
+
# Check mixing status
|
|
3405
|
+
${cmd2} mix status <request-id>
|
|
3406
|
+
\`\`\`
|
|
3385
3407
|
|
|
3386
|
-
|
|
3408
|
+
Music automatically loops to match video duration.
|
|
3387
3409
|
|
|
3388
|
-
|
|
3410
|
+
---
|
|
3389
3411
|
|
|
3390
|
-
|
|
3391
|
-
|------|------------------|
|
|
3392
|
-
| [video-creation-guide.md](rules/video/video-creation-guide.md) | **The Motion Bible.** Camera rigs, 2.5D entrances, cursor physics, typography, backgrounds |
|
|
3393
|
-
| [failures.md](rules/video/failures.md) | **Avoid rejection.** 7 common mistakes that make videos look like slideshows |
|
|
3394
|
-
| [project-based.md](rules/video/project-based.md) | **Copy real components.** Eject actual UI code, strip logic, animate pixel-perfect |
|
|
3395
|
-
| [parameterization.md](rules/video/parameterization.md) | **Saves debugging.** Never hardcode frame numbers |
|
|
3396
|
-
| [layers.md](rules/video/layers.md) | **Prevents z-index bugs.** Background orbs \u2192 Vignette \u2192 CameraRig \u2192 Content |
|
|
3397
|
-
| [social-media.md](rules/video/social-media.md) | **Platform specs.** Resolution, aspect ratio, duration per platform |
|
|
3412
|
+
## 8. Branding
|
|
3398
3413
|
|
|
3399
|
-
|
|
3414
|
+
Manage brand profiles for consistent styling.
|
|
3400
3415
|
|
|
3401
|
-
|
|
3416
|
+
\`\`\`bash
|
|
3417
|
+
# List saved brands
|
|
3418
|
+
${cmd2} branding list
|
|
3402
3419
|
|
|
3403
|
-
|
|
3420
|
+
# Extract branding from website
|
|
3421
|
+
${cmd2} branding extract https://company.com
|
|
3404
3422
|
|
|
3405
|
-
|
|
3423
|
+
# Get brand details
|
|
3424
|
+
${cmd2} branding get <brand-id>
|
|
3406
3425
|
|
|
3407
|
-
|
|
3426
|
+
# Set default brand
|
|
3427
|
+
${cmd2} branding set-default <brand-id>
|
|
3408
3428
|
|
|
3409
|
-
|
|
3429
|
+
# Use in presentations
|
|
3430
|
+
${cmd2} create "Topic" --brand my-company
|
|
3431
|
+
\`\`\`
|
|
3410
3432
|
|
|
3411
|
-
|
|
3433
|
+
---
|
|
3412
3434
|
|
|
3413
|
-
|
|
3414
|
-
2. **Generate audio** - \`${ctx.cmd} video create\` with scenes JSON
|
|
3415
|
-
3. **Scaffold OUTSIDE project** - \`cd .. && ${ctx.cmd} video init my-video\`
|
|
3416
|
-
4. **Copy assets** - Logo, fonts, colors from project into video project
|
|
3417
|
-
5. **Implement kinetically** - Camera rig, 2.5D entrances, staggered lists, transitions
|
|
3418
|
-
6. **Verify checklist** - Is it kinetic or static?
|
|
3435
|
+
## 9. Configuration
|
|
3419
3436
|
|
|
3420
|
-
|
|
3437
|
+
\`\`\`bash
|
|
3438
|
+
# Interactive setup
|
|
3439
|
+
${cmd2} config init
|
|
3421
3440
|
|
|
3422
|
-
|
|
3441
|
+
# Show current config
|
|
3442
|
+
${cmd2} config show
|
|
3443
|
+
${cmd2} config show --verify # Verify API key
|
|
3423
3444
|
|
|
3424
|
-
|
|
3445
|
+
# Set values
|
|
3446
|
+
${cmd2} config set api-key <key>
|
|
3447
|
+
${cmd2} config set team-id <id>
|
|
3425
3448
|
|
|
3426
|
-
|
|
3449
|
+
# Clear config
|
|
3450
|
+
${cmd2} config clear
|
|
3427
3451
|
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
- [ ] Lists/grids stagger (never appear all at once)
|
|
3432
|
-
- [ ] Text uses masked reveal or keyword animation
|
|
3433
|
-
- [ ] Background has moving orbs + vignette + noise
|
|
3434
|
-
- [ ] Something is moving on EVERY frame
|
|
3435
|
-
- [ ] No static resting states longer than 30 frames
|
|
3452
|
+
# Show config file location
|
|
3453
|
+
${cmd2} config path
|
|
3454
|
+
\`\`\`
|
|
3436
3455
|
|
|
3437
|
-
|
|
3456
|
+
---
|
|
3438
3457
|
|
|
3439
|
-
|
|
3440
|
-
cat <<EOF | ${ctx.cmd} video create --output ./public
|
|
3441
|
-
{
|
|
3442
|
-
"scenes": [
|
|
3443
|
-
{ "name": "Hook", "script": "..." },
|
|
3444
|
-
{ "name": "Demo", "script": "..." },
|
|
3445
|
-
{ "name": "CTA", "script": "..." }
|
|
3446
|
-
],
|
|
3447
|
-
"voice": "Kore",
|
|
3448
|
-
"musicPrompt": "upbeat corporate"
|
|
3449
|
-
}
|
|
3450
|
-
EOF
|
|
3451
|
-
\`\`\``
|
|
3452
|
-
};
|
|
3458
|
+
## Output Formats
|
|
3453
3459
|
|
|
3454
|
-
|
|
3455
|
-
var DEFAULT_SECTIONS = [
|
|
3456
|
-
frontmatter,
|
|
3457
|
-
header3,
|
|
3458
|
-
prerequisites,
|
|
3459
|
-
workflow,
|
|
3460
|
-
createCommand2,
|
|
3461
|
-
createOptions,
|
|
3462
|
-
otherCommands,
|
|
3463
|
-
examples,
|
|
3464
|
-
output,
|
|
3465
|
-
videoCreation,
|
|
3466
|
-
bestPractices,
|
|
3467
|
-
fileTypes,
|
|
3468
|
-
troubleshooting
|
|
3469
|
-
];
|
|
3470
|
-
function createSkillContext(brand2) {
|
|
3471
|
-
return {
|
|
3472
|
-
cmd: brand2.name,
|
|
3473
|
-
pkg: brand2.packageName,
|
|
3474
|
-
url: brand2.apiUrl,
|
|
3475
|
-
name: brand2.displayName
|
|
3476
|
-
};
|
|
3477
|
-
}
|
|
3478
|
-
function generateSkillContent(brand2, sections = DEFAULT_SECTIONS) {
|
|
3479
|
-
const ctx = createSkillContext(brand2);
|
|
3480
|
-
return sections.map((section) => section.render(ctx)).join("\n\n");
|
|
3481
|
-
}
|
|
3460
|
+
All commands support multiple output formats:
|
|
3482
3461
|
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3462
|
+
\`\`\`bash
|
|
3463
|
+
${cmd2} <command> --format human # Default, colored terminal output
|
|
3464
|
+
${cmd2} <command> --format json # Machine-readable JSON
|
|
3465
|
+
${cmd2} <command> --format quiet # Minimal output, errors only
|
|
3466
|
+
\`\`\`
|
|
3487
3467
|
|
|
3488
|
-
|
|
3489
|
-
var SUPPORTED_EDITORS = [
|
|
3490
|
-
{ name: "Claude Code", dir: ".claude" },
|
|
3491
|
-
{ name: "Cursor", dir: ".cursor" },
|
|
3492
|
-
{ name: "Codex", dir: ".codex" },
|
|
3493
|
-
{ name: "OpenCode", dir: ".opencode" },
|
|
3494
|
-
{ name: "Windsurf", dir: ".windsurf" },
|
|
3495
|
-
{ name: "Agent", dir: ".agent" }
|
|
3496
|
-
];
|
|
3468
|
+
---
|
|
3497
3469
|
|
|
3498
|
-
|
|
3499
|
-
var VIDEO_RULE_CONTENTS = [
|
|
3500
|
-
{
|
|
3501
|
-
filename: "video-creation-guide.md",
|
|
3502
|
-
content: `# Video Creation Guide
|
|
3470
|
+
## Environment Variables
|
|
3503
3471
|
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3472
|
+
\`\`\`bash
|
|
3473
|
+
${envPrefix}_API_KEY # API key for authentication
|
|
3474
|
+
${envPrefix}_API_URL # Custom API URL (optional)
|
|
3475
|
+
\`\`\`
|
|
3507
3476
|
|
|
3508
3477
|
---
|
|
3509
3478
|
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
**Objective:** Replicate the high-energy, fluid feel of premium tech product videos (Stripe, Apple, Linear, Affable.ai).
|
|
3479
|
+
## Specialized Skills
|
|
3513
3480
|
|
|
3514
|
-
|
|
3481
|
+
For detailed workflows, load these skills:
|
|
3515
3482
|
|
|
3516
|
-
|
|
3483
|
+
- **\`${name}-video\`** - Detailed video creation workflow, Remotion integration, composition rules, R3F/Three.js patterns
|
|
3484
|
+
- **\`${name}-presentation\`** - Detailed presentation creation, styling options, export formats
|
|
3517
3485
|
|
|
3518
3486
|
---
|
|
3519
3487
|
|
|
3520
|
-
##
|
|
3521
|
-
|
|
3522
|
-
Even when reading text, the screen is slowly zooming or panning.
|
|
3488
|
+
## Common Workflows
|
|
3523
3489
|
|
|
3524
|
-
###
|
|
3490
|
+
### Video Production Pipeline
|
|
3525
3491
|
|
|
3526
|
-
|
|
3492
|
+
\`\`\`bash
|
|
3493
|
+
# 1. Initialize project
|
|
3494
|
+
${cmd2} video init product-demo
|
|
3527
3495
|
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
const frame = useCurrentFrame();
|
|
3531
|
-
const { durationInFrames } = useVideoConfig();
|
|
3496
|
+
# 2. Generate assets
|
|
3497
|
+
cat scenes.json | ${cmd2} video create -o product-demo/public
|
|
3532
3498
|
|
|
3533
|
-
|
|
3534
|
-
const scale = interpolate(frame, [0, durationInFrames], [1.0, 1.05]);
|
|
3535
|
-
const rotation = interpolate(frame, [0, durationInFrames], [0, 0.5]);
|
|
3499
|
+
# 3. Implement in Remotion (see ${name}-video skill)
|
|
3536
3500
|
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
{children}
|
|
3542
|
-
</AbsoluteFill>
|
|
3543
|
-
);
|
|
3544
|
-
};
|
|
3501
|
+
# 4. Render and add thumbnail
|
|
3502
|
+
cd product-demo
|
|
3503
|
+
pnpm exec remotion render FullVideo
|
|
3504
|
+
${cmd2} video thumbnail out/FullVideo.mp4 --frame 60
|
|
3545
3505
|
\`\`\`
|
|
3546
3506
|
|
|
3547
|
-
###
|
|
3507
|
+
### Presentation from Research
|
|
3508
|
+
|
|
3509
|
+
\`\`\`bash
|
|
3510
|
+
# Gather content from multiple sources
|
|
3511
|
+
${cmd2} create "Market Analysis" \\
|
|
3512
|
+
--file research.pdf \\
|
|
3513
|
+
--sources https://industry-report.com \\
|
|
3514
|
+
--context "Focus on Q4 trends" \\
|
|
3515
|
+
--slides 15 \\
|
|
3516
|
+
--tone professional \\
|
|
3517
|
+
--brand company \\
|
|
3518
|
+
--open
|
|
3519
|
+
\`\`\`
|
|
3548
3520
|
|
|
3549
|
-
|
|
3521
|
+
### Batch Image Collection
|
|
3550
3522
|
|
|
3551
|
-
\`\`\`
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3523
|
+
\`\`\`bash
|
|
3524
|
+
# Get images for video scenes
|
|
3525
|
+
for query in "hero shot" "team photo" "product showcase"; do
|
|
3526
|
+
${cmd2} image search -q "$query" -n 3 --format json
|
|
3527
|
+
done
|
|
3555
3528
|
\`\`\`
|
|
3556
3529
|
|
|
3557
3530
|
---
|
|
3558
3531
|
|
|
3559
|
-
##
|
|
3532
|
+
## Troubleshooting
|
|
3533
|
+
|
|
3534
|
+
**Authentication issues:**
|
|
3535
|
+
\`\`\`bash
|
|
3536
|
+
${cmd2} whoami # Check current user
|
|
3537
|
+
${cmd2} logout && ${cmd2} login # Re-authenticate
|
|
3538
|
+
\`\`\`
|
|
3539
|
+
|
|
3540
|
+
**Check API status:**
|
|
3541
|
+
\`\`\`bash
|
|
3542
|
+
${cmd2} config show --verify
|
|
3543
|
+
\`\`\`
|
|
3560
3544
|
|
|
3561
|
-
|
|
3545
|
+
**Debug mode:**
|
|
3546
|
+
\`\`\`bash
|
|
3547
|
+
${cmd2} <command> --debug
|
|
3548
|
+
\`\`\`
|
|
3562
3549
|
|
|
3563
|
-
|
|
3550
|
+
---
|
|
3564
3551
|
|
|
3565
|
-
|
|
3552
|
+
## Help
|
|
3566
3553
|
|
|
3567
3554
|
\`\`\`bash
|
|
3568
|
-
#
|
|
3569
|
-
|
|
3570
|
-
|
|
3555
|
+
${cmd2} --help # General help
|
|
3556
|
+
${cmd2} <command> --help # Command-specific help
|
|
3557
|
+
${cmd2} --version # Version info
|
|
3571
3558
|
\`\`\`
|
|
3559
|
+
`;
|
|
3560
|
+
}
|
|
3572
3561
|
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
5. **Apply kinetic animation** using the rules below
|
|
3562
|
+
// src/commands/skill/rules/video/content.ts
|
|
3563
|
+
var VIDEO_RULE_CONTENTS = [
|
|
3564
|
+
{
|
|
3565
|
+
filename: "video-creation-guide.md",
|
|
3566
|
+
content: `# Video Creation Guide
|
|
3579
3567
|
|
|
3580
|
-
|
|
3568
|
+
### Related Skills
|
|
3581
3569
|
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
3. \u274C Generic mockups or stock images (ONLY for non-UI scenes like hooks)
|
|
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)
|
|
3586
3573
|
|
|
3587
|
-
|
|
3574
|
+
---
|
|
3588
3575
|
|
|
3589
|
-
|
|
3590
|
-
// Heavy spring for weighty feel
|
|
3591
|
-
const progress = spring({
|
|
3592
|
-
frame,
|
|
3593
|
-
fps,
|
|
3594
|
-
config: { mass: 2, damping: 20, stiffness: 100 },
|
|
3595
|
-
});
|
|
3576
|
+
## Core Rules
|
|
3596
3577
|
|
|
3597
|
-
|
|
3598
|
-
const rotateX = interpolate(progress, [0, 1], [20, 0]);
|
|
3599
|
-
const y = interpolate(progress, [0, 1], [100, 0]);
|
|
3600
|
-
const scale = interpolate(progress, [0, 1], [0.8, 1.0]);
|
|
3578
|
+
Your task is not "making slideshows" \u2014 you are **simulating a real interface** that obeys cinematic physics.
|
|
3601
3579
|
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3580
|
+
### Hard Constraints
|
|
3581
|
+
|
|
3582
|
+
1. **No scene > 8 seconds** without cut or major action
|
|
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
|
|
3588
|
+
|
|
3589
|
+
---
|
|
3608
3590
|
|
|
3609
|
-
|
|
3591
|
+
## Code Organization
|
|
3610
3592
|
|
|
3611
|
-
|
|
3593
|
+
- Create separate files: \`Button.tsx\`, \`Window.tsx\`, \`Cursor.tsx\`
|
|
3594
|
+
- Use Zod schemas for props validation
|
|
3595
|
+
- Extract animation configs to constants
|
|
3612
3596
|
|
|
3613
3597
|
\`\`\`tsx
|
|
3614
|
-
|
|
3615
|
-
const progress = spring({
|
|
3616
|
-
frame: frame - startFrame,
|
|
3617
|
-
fps,
|
|
3618
|
-
config: { damping: 20, stiffness: 80 }, // Slower, deliberate
|
|
3619
|
-
});
|
|
3598
|
+
import { spring, interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
|
|
3620
3599
|
|
|
3621
|
-
//
|
|
3622
|
-
|
|
3623
|
-
|
|
3600
|
+
// ALWAYS use spring for element entrances
|
|
3601
|
+
// NEVER use magic numbers
|
|
3602
|
+
\`\`\`
|
|
3624
3603
|
|
|
3625
|
-
|
|
3626
|
-
const arcHeight = 100; // How much the cursor "loops"
|
|
3627
|
-
const arcOffset = Math.sin(progress * Math.PI) * arcHeight;
|
|
3604
|
+
---
|
|
3628
3605
|
|
|
3629
|
-
|
|
3630
|
-
|
|
3606
|
+
## Aesthetics (Linear/Stripe Style)
|
|
3607
|
+
|
|
3608
|
+
\`\`\`css
|
|
3609
|
+
/* Shadows - soft, expensive */
|
|
3610
|
+
box-shadow: 0 20px 50px -12px rgba(0,0,0,0.5);
|
|
3611
|
+
|
|
3612
|
+
/* Borders - thin, barely visible */
|
|
3613
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
3631
3614
|
\`\`\`
|
|
3632
3615
|
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
// 1. Cursor scales down
|
|
3637
|
-
const cursorScale = isClicking ? 0.8 : 1;
|
|
3616
|
+
- Fonts: Inter or SF Pro
|
|
3617
|
+
- Never pure \`#000000\` \u2014 use \`#050505\`
|
|
3618
|
+
- Never pure \`#FFFFFF\` \u2014 use \`#F0F0F0\`
|
|
3638
3619
|
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3620
|
+
---
|
|
3621
|
+
|
|
3622
|
+
## Self-Check Before Render
|
|
3623
|
+
|
|
3624
|
+
- [ ] Camera rig wraps entire scene with drift/zoom
|
|
3625
|
+
- [ ] Every UI element uses 2.5D rotation entrance
|
|
3626
|
+
- [ ] Cursor moves in curves with overshoot
|
|
3627
|
+
- [ ] Lists/grids stagger (never appear all at once)
|
|
3628
|
+
- [ ] Background has moving orbs + vignette + noise
|
|
3629
|
+
- [ ] Something is moving on EVERY frame
|
|
3630
|
+
- [ ] Scene transitions overlap (no hard cuts)
|
|
3631
|
+
|
|
3632
|
+
**If your video looks like PowerPoint with voiceover \u2014 START OVER.**
|
|
3633
|
+
`
|
|
3634
|
+
},
|
|
3635
|
+
{
|
|
3636
|
+
filename: "animation-physics.md",
|
|
3637
|
+
content: `# Animation Physics
|
|
3638
|
+
|
|
3639
|
+
## Spring Configurations
|
|
3642
3640
|
|
|
3643
|
-
|
|
3641
|
+
### Heavy UI (Modals, Sidebars)
|
|
3642
|
+
\`\`\`tsx
|
|
3643
|
+
config: { mass: 1, stiffness: 100, damping: 15 }
|
|
3644
3644
|
\`\`\`
|
|
3645
3645
|
|
|
3646
|
-
|
|
3646
|
+
### Light UI (Tooltips, Badges)
|
|
3647
3647
|
\`\`\`tsx
|
|
3648
|
-
|
|
3649
|
-
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
3650
|
-
<path
|
|
3651
|
-
d="M4 4L11.5 26L16 17.5L25 25L27 23L18.5 15L28 11.5L4 4Z"
|
|
3652
|
-
fill="black"
|
|
3653
|
-
stroke="white"
|
|
3654
|
-
strokeWidth="2"
|
|
3655
|
-
/>
|
|
3656
|
-
</svg>
|
|
3648
|
+
config: { mass: 0.6, stiffness: 180, damping: 12 }
|
|
3657
3649
|
\`\`\`
|
|
3658
3650
|
|
|
3659
|
-
###
|
|
3651
|
+
### Standard (Snappy)
|
|
3652
|
+
\`\`\`tsx
|
|
3653
|
+
config: { mass: 1, damping: 15, stiffness: 120 }
|
|
3654
|
+
\`\`\`
|
|
3655
|
+
|
|
3656
|
+
---
|
|
3660
3657
|
|
|
3661
|
-
|
|
3658
|
+
## Staggering
|
|
3659
|
+
|
|
3660
|
+
**NEVER show a list all at once.**
|
|
3662
3661
|
|
|
3663
3662
|
\`\`\`tsx
|
|
3664
|
-
const STAGGER_FRAMES =
|
|
3663
|
+
const STAGGER_FRAMES = 3; // 3-5 frames between items
|
|
3665
3664
|
|
|
3666
3665
|
{items.map((item, i) => {
|
|
3667
3666
|
const delay = i * STAGGER_FRAMES;
|
|
@@ -3684,904 +3683,1064 @@ const STAGGER_FRAMES = 4; // ~0.05s at 60fps
|
|
|
3684
3683
|
|
|
3685
3684
|
---
|
|
3686
3685
|
|
|
3687
|
-
##
|
|
3686
|
+
## Cursor Movement
|
|
3688
3687
|
|
|
3689
|
-
|
|
3688
|
+
**Cursor NEVER moves in straight lines.**
|
|
3690
3689
|
|
|
3691
|
-
|
|
3690
|
+
\`\`\`tsx
|
|
3691
|
+
const progress = spring({
|
|
3692
|
+
frame: frame - startFrame,
|
|
3693
|
+
fps,
|
|
3694
|
+
config: { damping: 20, stiffness: 80 },
|
|
3695
|
+
});
|
|
3692
3696
|
|
|
3693
|
-
|
|
3697
|
+
const linearX = interpolate(progress, [0, 1], [start.x, end.x]);
|
|
3698
|
+
const linearY = interpolate(progress, [0, 1], [start.y, end.y]);
|
|
3694
3699
|
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
}}>
|
|
3700
|
-
INTRODUCING
|
|
3701
|
-
</h1>
|
|
3702
|
-
</div>
|
|
3700
|
+
// THE ARC: Parabola that peaks mid-travel
|
|
3701
|
+
const arcHeight = 100;
|
|
3702
|
+
const arcOffset = Math.sin(progress * Math.PI) * arcHeight;
|
|
3703
|
+
const cursorY = linearY - arcOffset;
|
|
3703
3704
|
\`\`\`
|
|
3704
3705
|
|
|
3705
|
-
|
|
3706
|
+
---
|
|
3706
3707
|
|
|
3707
|
-
|
|
3708
|
+
## Click Interaction
|
|
3708
3709
|
|
|
3709
3710
|
\`\`\`tsx
|
|
3710
|
-
//
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
color: interpolateColors(keywordProgress, [0, 1], ['#F0F0F0', '#FF6B6B']),
|
|
3716
|
-
textShadow: \`0 0 \${glowProgress * 20}px rgba(255,107,107,0.5)\`,
|
|
3717
|
-
}}>
|
|
3718
|
-
HOURS
|
|
3719
|
-
</span>
|
|
3720
|
-
</p>
|
|
3711
|
+
// On click:
|
|
3712
|
+
const cursorScale = isClicking ? 0.95 : 1;
|
|
3713
|
+
const buttonScaleX = isClicking ? 1.02 : 1;
|
|
3714
|
+
const buttonScaleY = isClicking ? 0.95 : 1;
|
|
3715
|
+
// Release both with spring
|
|
3721
3716
|
\`\`\`
|
|
3722
3717
|
|
|
3723
|
-
|
|
3718
|
+
---
|
|
3719
|
+
|
|
3720
|
+
## Timing Reference
|
|
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
|
|
3724
3734
|
|
|
3725
|
-
|
|
3735
|
+
## SceneWrapper
|
|
3726
3736
|
|
|
3727
3737
|
\`\`\`tsx
|
|
3728
|
-
|
|
3729
|
-
{
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3738
|
+
<SceneWrapper
|
|
3739
|
+
durationInFrames={300}
|
|
3740
|
+
transitionType="slideLeft"
|
|
3741
|
+
cameraMotion="panRight"
|
|
3742
|
+
>
|
|
3743
|
+
<FeatureLayer />
|
|
3744
|
+
<CursorLayer />
|
|
3745
|
+
<ParticleLayer />
|
|
3746
|
+
</SceneWrapper>
|
|
3735
3747
|
\`\`\`
|
|
3736
3748
|
|
|
3737
|
-
|
|
3749
|
+
---
|
|
3750
|
+
|
|
3751
|
+
## Layer Structure (Z-Index)
|
|
3738
3752
|
|
|
3739
|
-
|
|
3753
|
+
| Layer | Z-Index |
|
|
3754
|
+
|-------|---------|
|
|
3755
|
+
| Background orbs | 0 |
|
|
3756
|
+
| Vignette | 1 |
|
|
3757
|
+
| UI Base | 10 |
|
|
3758
|
+
| UI Elements | 20 |
|
|
3759
|
+
| Overlays | 30 |
|
|
3760
|
+
| Text/Captions | 40 |
|
|
3761
|
+
| Cursor | 50 |
|
|
3740
3762
|
|
|
3741
3763
|
---
|
|
3742
3764
|
|
|
3743
|
-
##
|
|
3765
|
+
## Case Study: SaaS Task Tracker
|
|
3744
3766
|
|
|
3745
|
-
|
|
3767
|
+
### Scene 1: "The Hook" (~5s)
|
|
3746
3768
|
|
|
3747
|
-
|
|
3769
|
+
1. Dark background (\`#0B0C10\`), grid drifting
|
|
3770
|
+
2. Scattered circles magnetically attract \u2192 morph into logo
|
|
3771
|
+
3. Logo expands \u2192 becomes sidebar navigation
|
|
3748
3772
|
|
|
3749
|
-
|
|
3750
|
-
const MovingBackground: React.FC = () => {
|
|
3751
|
-
const frame = useCurrentFrame();
|
|
3773
|
+
### Scene 2: "Micro-Interaction" (~6s)
|
|
3752
3774
|
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
const orb2Y = Math.cos(frame / 90 + Math.PI) * 120;
|
|
3775
|
+
1. Modal "Create Issue" appears
|
|
3776
|
+
2. Text types character by character (non-uniform speed)
|
|
3777
|
+
3. \`CMD + K\` hint glows, keys animate
|
|
3778
|
+
4. Cursor flies to "Save" in arc, slows on approach
|
|
3758
3779
|
|
|
3759
|
-
|
|
3760
|
-
<AbsoluteFill style={{ backgroundColor: '#0a0a0a' }}>
|
|
3761
|
-
{/* Orb 1 - Teal */}
|
|
3762
|
-
<div style={{
|
|
3763
|
-
position: 'absolute',
|
|
3764
|
-
width: 600,
|
|
3765
|
-
height: 600,
|
|
3766
|
-
borderRadius: '50%',
|
|
3767
|
-
background: 'radial-gradient(circle, rgba(20,184,166,0.3) 0%, transparent 70%)',
|
|
3768
|
-
filter: 'blur(100px)',
|
|
3769
|
-
transform: \`translate(\${orb1X}px, \${orb1Y}px)\`,
|
|
3770
|
-
left: '20%',
|
|
3771
|
-
top: '30%',
|
|
3772
|
-
}} />
|
|
3773
|
-
|
|
3774
|
-
{/* Orb 2 - Purple */}
|
|
3775
|
-
<div style={{
|
|
3776
|
-
position: 'absolute',
|
|
3777
|
-
width: 500,
|
|
3778
|
-
height: 500,
|
|
3779
|
-
borderRadius: '50%',
|
|
3780
|
-
background: 'radial-gradient(circle, rgba(168,85,247,0.3) 0%, transparent 70%)',
|
|
3781
|
-
filter: 'blur(100px)',
|
|
3782
|
-
transform: \`translate(\${orb2X}px, \${orb2Y}px)\`,
|
|
3783
|
-
right: '20%',
|
|
3784
|
-
bottom: '20%',
|
|
3785
|
-
}} />
|
|
3786
|
-
</AbsoluteFill>
|
|
3787
|
-
);
|
|
3788
|
-
};
|
|
3789
|
-
\`\`\`
|
|
3780
|
+
### Scene 3: "The Connection" (~5s)
|
|
3790
3781
|
|
|
3791
|
-
|
|
3782
|
+
1. Task card grabbed, scales 1.05, shadow deepens
|
|
3783
|
+
2. Other cards spread apart
|
|
3784
|
+
3. **Match Cut:** Zoom into avatar \u2192 color fills screen \u2192 becomes mobile notification background
|
|
3792
3785
|
|
|
3793
|
-
|
|
3794
|
-
// Noise texture overlay (5% opacity)
|
|
3795
|
-
<AbsoluteFill style={{
|
|
3796
|
-
backgroundImage: 'url(/noise.png)',
|
|
3797
|
-
opacity: 0.05,
|
|
3798
|
-
mixBlendMode: 'overlay',
|
|
3799
|
-
}} />
|
|
3786
|
+
---
|
|
3800
3787
|
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3788
|
+
## Composition
|
|
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>
|
|
3805
3804
|
\`\`\`
|
|
3805
|
+
`
|
|
3806
|
+
},
|
|
3807
|
+
{
|
|
3808
|
+
filename: "scene-transitions.md",
|
|
3809
|
+
content: `# Scene Transitions
|
|
3810
|
+
|
|
3811
|
+
**No FadeIn/FadeOut.** Only contextual transitions.
|
|
3806
3812
|
|
|
3807
3813
|
---
|
|
3808
3814
|
|
|
3809
|
-
##
|
|
3815
|
+
## Types
|
|
3810
3816
|
|
|
3811
|
-
###
|
|
3817
|
+
### 1. Object Persistence
|
|
3818
|
+
Same chart transforms (data, color, scale) while UI changes around it.
|
|
3812
3819
|
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
| Standard (snappy) | \`{ mass: 1, damping: 15, stiffness: 120 }\` | Smooth, professional |
|
|
3816
|
-
| Heavy (UI mockups) | \`{ mass: 2, damping: 20, stiffness: 100 }\` | Weighty, premium |
|
|
3817
|
-
| Bouncy (attention) | \`{ mass: 1, damping: 10, stiffness: 150 }\` | Playful overshoot |
|
|
3820
|
+
### 2. Mask Reveal
|
|
3821
|
+
Button expands to screen size via SVG \`clipPath\`.
|
|
3818
3822
|
|
|
3819
|
-
###
|
|
3823
|
+
### 3. Speed Ramps
|
|
3824
|
+
Scene A accelerates out, Scene B starts fast then slows.
|
|
3820
3825
|
|
|
3821
|
-
|
|
3822
|
-
|--------|--------|-------|
|
|
3823
|
-
| Element entrance | 15-20 | Spring to rest |
|
|
3824
|
-
| Stagger gap | 3-5 | Between list items |
|
|
3825
|
-
| Hold on key info | 45-60 | Minimum read time |
|
|
3826
|
-
| Scene transition | 20-30 | Zoom-through |
|
|
3826
|
+
---
|
|
3827
3827
|
|
|
3828
|
-
|
|
3828
|
+
## Match Cut Example
|
|
3829
3829
|
|
|
3830
|
-
|
|
3830
|
+
\`\`\`
|
|
3831
|
+
Scene A: Zoom into avatar
|
|
3832
|
+
\u2193
|
|
3833
|
+
Avatar color fills screen
|
|
3834
|
+
\u2193
|
|
3835
|
+
Scene B: That color IS the notification background
|
|
3836
|
+
\`\`\`
|
|
3831
3837
|
|
|
3832
3838
|
---
|
|
3833
3839
|
|
|
3834
|
-
##
|
|
3840
|
+
## Overlapping Sequences (CRITICAL)
|
|
3835
3841
|
|
|
3836
|
-
|
|
3842
|
+
\`\`\`tsx
|
|
3843
|
+
<Sequence from={0} durationInFrames={100}>
|
|
3844
|
+
<SceneOne />
|
|
3845
|
+
</Sequence>
|
|
3846
|
+
<Sequence from={85} durationInFrames={150}> {/* 15 frames early! */}
|
|
3847
|
+
<SceneTwo />
|
|
3848
|
+
</Sequence>
|
|
3849
|
+
\`\`\`
|
|
3850
|
+
|
|
3851
|
+
---
|
|
3837
3852
|
|
|
3838
|
-
|
|
3853
|
+
## TransitionSeries
|
|
3839
3854
|
|
|
3840
3855
|
\`\`\`tsx
|
|
3841
|
-
|
|
3842
|
-
|
|
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>
|
|
3843
3871
|
\`\`\`
|
|
3872
|
+
`
|
|
3873
|
+
},
|
|
3874
|
+
{
|
|
3875
|
+
filename: "polish-effects.md",
|
|
3876
|
+
content: `# Polish Effects
|
|
3844
3877
|
|
|
3845
|
-
|
|
3878
|
+
## Reflection (Glass Glint)
|
|
3846
3879
|
|
|
3847
|
-
|
|
3880
|
+
Diagonal gradient sweeps every 5 seconds.
|
|
3848
3881
|
|
|
3849
3882
|
\`\`\`tsx
|
|
3850
|
-
const
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3883
|
+
const cycleFrame = frame % 300;
|
|
3884
|
+
const sweepProgress = interpolate(cycleFrame, [0, 60], [-100, 200], {
|
|
3885
|
+
extrapolateRight: 'clamp',
|
|
3886
|
+
});
|
|
3887
|
+
\`\`\`
|
|
3854
3888
|
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3889
|
+
---
|
|
3890
|
+
|
|
3891
|
+
## Background Breathing
|
|
3892
|
+
|
|
3893
|
+
Background is NEVER static.
|
|
3894
|
+
|
|
3895
|
+
\`\`\`tsx
|
|
3896
|
+
const orb1X = Math.sin(frame / 60) * 200;
|
|
3897
|
+
const orb1Y = Math.cos(frame / 80) * 100;
|
|
3862
3898
|
\`\`\`
|
|
3863
3899
|
|
|
3864
|
-
|
|
3900
|
+
---
|
|
3865
3901
|
|
|
3866
|
-
|
|
3902
|
+
## Typewriter Effect
|
|
3867
3903
|
|
|
3868
3904
|
\`\`\`tsx
|
|
3869
|
-
const
|
|
3870
|
-
|
|
3871
|
-
{ rotation: 0, x: 0 },
|
|
3872
|
-
{ rotation: 5, x: 20 },
|
|
3873
|
-
];
|
|
3905
|
+
const charIndex = Math.floor(frame / 3);
|
|
3906
|
+
const showCursor = Math.floor(frame / 15) % 2 === 0;
|
|
3874
3907
|
|
|
3875
|
-
|
|
3876
|
-
|
|
3908
|
+
<span>
|
|
3909
|
+
{text.slice(0, charIndex)}
|
|
3910
|
+
{showCursor && <span>|</span>}
|
|
3911
|
+
</span>
|
|
3912
|
+
\`\`\`
|
|
3877
3913
|
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
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
|
+
}} />
|
|
3888
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
|
|
3889
3942
|
|
|
3890
3943
|
---
|
|
3891
3944
|
|
|
3892
|
-
##
|
|
3945
|
+
## Motion Blur (Fake)
|
|
3893
3946
|
|
|
3894
|
-
|
|
3895
|
-
|
|
3947
|
+
\`\`\`tsx
|
|
3948
|
+
// Trail: Render 3-4 copies with opacity 0.3, 1-frame delay
|
|
3896
3949
|
|
|
3897
|
-
|
|
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
|
+
\`\`\`
|
|
3898
3953
|
|
|
3899
|
-
|
|
3900
|
-
- *Standard Spring:* \`{ mass: 1, damping: 15, stiffness: 120 }\` (Snappy but smooth)
|
|
3901
|
-
- *Heavy Spring (Mockups):* \`{ mass: 2, damping: 20, stiffness: 100 }\` (Feels weighty)
|
|
3902
|
-
- **Continuous Flow:** Use \`interpolate(frame)\` to ensure \`scale\` or \`rotation\` is changing slightly on EVERY frame. No static resting states.
|
|
3954
|
+
---
|
|
3903
3955
|
|
|
3904
|
-
|
|
3956
|
+
## 3D Perspective
|
|
3905
3957
|
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
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
|
+
\`\`\`
|
|
3914
3968
|
|
|
3915
|
-
|
|
3969
|
+
---
|
|
3916
3970
|
|
|
3917
|
-
|
|
3918
|
-
- **Pattern B (Keywords):** "Scale & Glow". Keywords scale to \`1.1\` and emit a \`text-shadow\`.
|
|
3919
|
-
- **Colors:** Text is never pure white (\`#FFF\`). Use \`#F0F0F0\` with a subtle gradient or shadow to add depth.
|
|
3971
|
+
## Kinetic Typography
|
|
3920
3972
|
|
|
3921
|
-
###
|
|
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
|
+
\`\`\`
|
|
3922
3983
|
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
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}\`
|
|
3926
3997
|
|
|
3927
3998
|
---
|
|
3928
3999
|
|
|
3929
|
-
##
|
|
4000
|
+
## Timing
|
|
4001
|
+
|
|
4002
|
+
- 1 second = 60 frames
|
|
4003
|
+
- Fast interaction = 15-20 frames
|
|
4004
|
+
- No scene > 8 seconds without action
|
|
3930
4005
|
|
|
3931
|
-
|
|
4006
|
+
---
|
|
3932
4007
|
|
|
3933
|
-
|
|
4008
|
+
## Entry-Action-Exit Structure
|
|
3934
4009
|
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
4010
|
+
| Phase | Duration |
|
|
4011
|
+
|-------|----------|
|
|
4012
|
+
| Entry | 0.0s - 0.5s |
|
|
4013
|
+
| Action | 0.5s - (duration - 1s) |
|
|
4014
|
+
| Exit | last 1s |
|
|
3938
4015
|
|
|
3939
|
-
|
|
4016
|
+
---
|
|
3940
4017
|
|
|
3941
|
-
|
|
4018
|
+
## Font Loading
|
|
3942
4019
|
|
|
3943
4020
|
\`\`\`tsx
|
|
3944
|
-
|
|
3945
|
-
const scaleA = interpolate(frame, [duration - 20, duration], [1.0, 5.0]);
|
|
4021
|
+
const [handle] = useState(() => delayRender());
|
|
3946
4022
|
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
4023
|
+
useEffect(() => {
|
|
4024
|
+
document.fonts.ready.then(() => {
|
|
4025
|
+
continueRender(handle);
|
|
4026
|
+
});
|
|
4027
|
+
}, [handle]);
|
|
3950
4028
|
\`\`\`
|
|
3951
4029
|
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
### The "Color Swipe" (Safety Net)
|
|
4030
|
+
---
|
|
3955
4031
|
|
|
3956
|
-
|
|
4032
|
+
## Zod Schema
|
|
3957
4033
|
|
|
3958
4034
|
\`\`\`tsx
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
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
|
+
});
|
|
3962
4040
|
\`\`\`
|
|
3963
4041
|
|
|
3964
|
-
|
|
4042
|
+
---
|
|
3965
4043
|
|
|
3966
|
-
|
|
4044
|
+
## SaaS Video Kit Components
|
|
3967
4045
|
|
|
3968
|
-
|
|
3969
|
-
|
|
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 |
|
|
3970
4053
|
|
|
3971
|
-
|
|
3972
|
-
// enterFrom: 'left' | 'right' | 'bottom'
|
|
4054
|
+
---
|
|
3973
4055
|
|
|
3974
|
-
|
|
3975
|
-
<YourScene />
|
|
3976
|
-
</TransitionWrapper>
|
|
3977
|
-
\`\`\`
|
|
4056
|
+
## Code Rules
|
|
3978
4057
|
|
|
3979
|
-
|
|
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
|
+
---
|
|
3980
4070
|
|
|
3981
|
-
|
|
4071
|
+
## Global Lighting Engine
|
|
3982
4072
|
|
|
3983
4073
|
\`\`\`tsx
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
{/* SCENE 2: Starts at frame 85 (15 frames early!) */}
|
|
3993
|
-
{/* TransitionWrapper slides it ON TOP of Scene 1 */}
|
|
3994
|
-
<Sequence from={85} durationInFrames={150}>
|
|
3995
|
-
<TransitionWrapper type="slide" enterFrom="bottom">
|
|
3996
|
-
<SceneTwo />
|
|
3997
|
-
</TransitionWrapper>
|
|
3998
|
-
</Sequence>
|
|
3999
|
-
|
|
4000
|
-
{/* SCENE 3: Starts early again */}
|
|
4001
|
-
<Sequence from={220} durationInFrames={150}>
|
|
4002
|
-
<TransitionWrapper type="zoom">
|
|
4003
|
-
<SceneThree />
|
|
4004
|
-
</TransitionWrapper>
|
|
4005
|
-
</Sequence>
|
|
4006
|
-
</AbsoluteFill>
|
|
4007
|
-
);
|
|
4008
|
-
};
|
|
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
|
+
}} />
|
|
4009
4082
|
\`\`\`
|
|
4010
4083
|
|
|
4011
|
-
|
|
4084
|
+
---
|
|
4085
|
+
|
|
4086
|
+
## Noise & Dithering
|
|
4087
|
+
|
|
4088
|
+
Every background needs noise overlay (opacity 0.02-0.05). Prevents YouTube banding.
|
|
4012
4089
|
|
|
4013
4090
|
---
|
|
4014
4091
|
|
|
4015
|
-
##
|
|
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
|
+
\`\`\`
|
|
4016
4106
|
|
|
4017
|
-
|
|
4018
|
-
- **Overlap delays.** If Title starts at frame 10 and Card starts at frame 30, the title hasn't finished moving when the card appears. This overlap is crucial for fluidity.
|
|
4019
|
-
- **High FPS.** Ensure your \`remotion.config.ts\` is set to 60fps for springs to look smooth.
|
|
4107
|
+
See \`threejs-*\` skills for implementation.
|
|
4020
4108
|
|
|
4021
4109
|
---
|
|
4022
4110
|
|
|
4023
|
-
##
|
|
4111
|
+
## Virtual Camera Rig
|
|
4024
4112
|
|
|
4025
|
-
|
|
4113
|
+
Move camera, not elements:
|
|
4026
4114
|
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
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
|
+
---
|
|
4037
4133
|
|
|
4038
|
-
|
|
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
|
|
4039
4138
|
`
|
|
4040
4139
|
},
|
|
4041
4140
|
{
|
|
4042
|
-
filename: "
|
|
4043
|
-
content: `#
|
|
4141
|
+
filename: "known-issues.md",
|
|
4142
|
+
content: `# Known Issues & Fixes
|
|
4044
4143
|
|
|
4045
|
-
|
|
4144
|
+
## 1. Music Ends Before Video Finishes
|
|
4046
4145
|
|
|
4047
|
-
|
|
4146
|
+
**Problem:** Music duration is shorter than video duration, causing awkward silence at the end.
|
|
4048
4147
|
|
|
4049
|
-
|
|
4050
|
-
What you made:
|
|
4051
|
-
- Background image
|
|
4052
|
-
- Text appears on top
|
|
4053
|
-
- Text disappears
|
|
4054
|
-
- New background image
|
|
4055
|
-
- More text
|
|
4056
|
-
|
|
4057
|
-
This is PowerPoint, not a video. REJECTED.
|
|
4058
|
-
\`\`\`
|
|
4148
|
+
**Solution:** Loop music in Remotion using the \`loop\` prop:
|
|
4059
4149
|
|
|
4060
|
-
|
|
4150
|
+
\`\`\`tsx
|
|
4151
|
+
import { Audio } from 'remotion';
|
|
4061
4152
|
|
|
4153
|
+
<Audio src={musicSrc} volume={0.3} loop />
|
|
4062
4154
|
\`\`\`
|
|
4063
|
-
What you made:
|
|
4064
|
-
- "Lorem ipsum dolor sit amet..."
|
|
4065
|
-
- "Sample text here"
|
|
4066
|
-
- Generic placeholder content
|
|
4067
4155
|
|
|
4068
|
-
|
|
4069
|
-
|
|
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
|
|
4070
4160
|
|
|
4071
|
-
|
|
4161
|
+
---
|
|
4072
4162
|
|
|
4073
|
-
|
|
4074
|
-
What you made:
|
|
4075
|
-
- Screenshot of the app
|
|
4076
|
-
- Text overlay saying "Our Dashboard"
|
|
4077
|
-
- No motion, no interaction
|
|
4163
|
+
## 2. Music Transitions Sound Abrupt
|
|
4078
4164
|
|
|
4079
|
-
|
|
4080
|
-
\`\`\`
|
|
4165
|
+
**Problem:** Music cuts harshly when scenes change or video ends.
|
|
4081
4166
|
|
|
4082
|
-
|
|
4167
|
+
**Fix in Remotion:**
|
|
4168
|
+
\`\`\`tsx
|
|
4169
|
+
import { interpolate, Audio } from 'remotion';
|
|
4083
4170
|
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
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
|
+
);
|
|
4089
4178
|
|
|
4090
|
-
|
|
4179
|
+
<Audio src={music} volume={musicVolume} />
|
|
4091
4180
|
\`\`\`
|
|
4092
4181
|
|
|
4093
|
-
|
|
4182
|
+
---
|
|
4094
4183
|
|
|
4095
|
-
|
|
4096
|
-
What you made:
|
|
4097
|
-
- Scene 1 ends at frame 100
|
|
4098
|
-
- Scene 2 starts at frame 101
|
|
4099
|
-
- No overlap, no slide, no zoom-through
|
|
4100
|
-
- Feels like a light switching on/off
|
|
4101
|
-
|
|
4102
|
-
FIX: Overlap sequences by 15-20 frames. Scene 2 must slide/zoom
|
|
4103
|
-
ON TOP of Scene 1 while Scene 1 is still visible. Use TransitionWrapper.
|
|
4104
|
-
REJECTED.
|
|
4105
|
-
\`\`\`
|
|
4184
|
+
## 3. Scene Transitions Too Harsh
|
|
4106
4185
|
|
|
4107
|
-
|
|
4186
|
+
**Problem:** Scenes change abruptly without smooth transitions.
|
|
4108
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>
|
|
4109
4206
|
\`\`\`
|
|
4110
|
-
What you made:
|
|
4111
|
-
- Camera never moves
|
|
4112
|
-
- No drift, no zoom, no rotation
|
|
4113
|
-
- Feels like watching a screenshot
|
|
4114
4207
|
|
|
4115
|
-
|
|
4116
|
-
\`\`\`
|
|
4208
|
+
---
|
|
4117
4209
|
|
|
4118
|
-
##
|
|
4210
|
+
## 4. Voiceover Lacks Energy
|
|
4119
4211
|
|
|
4120
|
-
|
|
4121
|
-
What you made:
|
|
4122
|
-
- Cursor moves in straight lines
|
|
4123
|
-
- No overshoot on stops
|
|
4124
|
-
- Click has no feedback
|
|
4212
|
+
**Problem:** Voiceover sounds flat/monotone.
|
|
4125
4213
|
|
|
4126
|
-
|
|
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
|
+
}
|
|
4127
4225
|
\`\`\`
|
|
4128
|
-
`
|
|
4129
|
-
},
|
|
4130
|
-
{
|
|
4131
|
-
filename: "parameterization.md",
|
|
4132
|
-
content: `# Parameterization (Critical)
|
|
4133
|
-
|
|
4134
|
-
Never hardcode frame numbers. Use variables for all keyframes.
|
|
4135
4226
|
|
|
4136
|
-
|
|
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
|
|
4137
4230
|
|
|
4138
|
-
|
|
4231
|
+
---
|
|
4139
4232
|
|
|
4140
|
-
##
|
|
4233
|
+
## 5. Video Duration Mismatch
|
|
4141
4234
|
|
|
4142
|
-
|
|
4143
|
-
// Define keyframes as variables
|
|
4144
|
-
const SCENE_START = 0;
|
|
4145
|
-
const TITLE_IN = SCENE_START + 15;
|
|
4146
|
-
const TITLE_HOLD = TITLE_IN + 60;
|
|
4147
|
-
const TITLE_OUT = TITLE_HOLD + 15;
|
|
4148
|
-
const SUBTITLE_IN = TITLE_IN + 20; // Relative to title
|
|
4149
|
-
const SCENE_END = TITLE_OUT + 30;
|
|
4235
|
+
**Problem:** Brief says 30-45s but video is 20s (because scene duration = voiceover duration).
|
|
4150
4236
|
|
|
4151
|
-
|
|
4152
|
-
|
|
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;
|
|
4153
4243
|
\`\`\`
|
|
4244
|
+
3. **Brief should note:** "Duration based on voiceover length"
|
|
4154
4245
|
|
|
4155
|
-
|
|
4246
|
+
---
|
|
4156
4247
|
|
|
4157
|
-
|
|
4158
|
-
// Calculate from keyframes, don't hardcode
|
|
4159
|
-
const sceneDuration = SCENE_END - SCENE_START;
|
|
4160
|
-
\`\`\`
|
|
4248
|
+
## 6. Not Using Project UI Components
|
|
4161
4249
|
|
|
4162
|
-
|
|
4250
|
+
**Problem:** Generic UI instead of pixel-perfect project components.
|
|
4163
4251
|
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
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>
|
|
4168
4269
|
\`\`\`
|
|
4169
4270
|
|
|
4170
|
-
|
|
4271
|
+
---
|
|
4171
4272
|
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
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
|
|
4178
4285
|
`
|
|
4179
|
-
}
|
|
4180
|
-
|
|
4181
|
-
filename: "layers.md",
|
|
4182
|
-
content: `# Layers & Composition
|
|
4286
|
+
}
|
|
4287
|
+
];
|
|
4183
4288
|
|
|
4184
|
-
|
|
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
|
+
---
|
|
4185
4296
|
|
|
4186
|
-
|
|
4297
|
+
# ${displayName} Video Creation CLI
|
|
4187
4298
|
|
|
4188
|
-
|
|
4189
|
-
|-------|---------|----------|
|
|
4190
|
-
| Background orbs | 0 | Moving gradients, noise |
|
|
4191
|
-
| Vignette | 1 | Dark corners overlay |
|
|
4192
|
-
| UI Base | 10 | Main UI container |
|
|
4193
|
-
| UI Elements | 20 | Buttons, cards, inputs |
|
|
4194
|
-
| Overlays | 30 | Modals, tooltips, highlights |
|
|
4195
|
-
| Text/Captions | 40 | Titles, labels |
|
|
4196
|
-
| Cursor | 50 | Mouse pointer |
|
|
4197
|
-
| Debug | 100 | Frame counter (remove in final) |
|
|
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.
|
|
4198
4300
|
|
|
4199
|
-
|
|
4301
|
+
**Stack:** Remotion (React video framework) + React Three Fiber (R3F) + Three.js for 3D/WebGL, particles, shaders, lighting.
|
|
4200
4302
|
|
|
4201
|
-
|
|
4202
|
-
<AbsoluteFill>
|
|
4203
|
-
{/* Background layer */}
|
|
4204
|
-
<MovingBackground />
|
|
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.
|
|
4205
4304
|
|
|
4206
|
-
|
|
4207
|
-
<Vignette />
|
|
4305
|
+
**Core Philosophy:** "Nothing sits still. Everything is physics-based. Every pixel breathes."
|
|
4208
4306
|
|
|
4209
|
-
|
|
4210
|
-
<CameraRig>
|
|
4211
|
-
<Sequence from={0}>
|
|
4212
|
-
<Scene1 />
|
|
4213
|
-
</Sequence>
|
|
4214
|
-
<Sequence from={SCENE_2_START}>
|
|
4215
|
-
<Scene2 />
|
|
4216
|
-
</Sequence>
|
|
4217
|
-
</CameraRig>
|
|
4307
|
+
---
|
|
4218
4308
|
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
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"
|
|
4224
4320
|
|
|
4225
|
-
|
|
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
|
|
4226
4328
|
|
|
4227
|
-
|
|
4329
|
+
### Composition Examples:
|
|
4228
4330
|
|
|
4331
|
+
**\u274C BAD - Wall of Text:**
|
|
4229
4332
|
\`\`\`tsx
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
</
|
|
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>
|
|
4236
4339
|
\`\`\`
|
|
4237
|
-
`
|
|
4238
|
-
},
|
|
4239
|
-
{
|
|
4240
|
-
filename: "project-based.md",
|
|
4241
|
-
content: `# Component Extraction (Critical for Product Videos)
|
|
4242
4340
|
|
|
4243
|
-
|
|
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
|
+
\`\`\`
|
|
4244
4349
|
|
|
4245
|
-
|
|
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
|
+
\`\`\`
|
|
4246
4357
|
|
|
4247
|
-
|
|
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
|
+
\`\`\`
|
|
4248
4367
|
|
|
4249
|
-
|
|
4368
|
+
### Layout Grid System:
|
|
4250
4369
|
|
|
4251
|
-
|
|
4370
|
+
**Use 12-column grid (like Bootstrap):**
|
|
4371
|
+
\`\`\`tsx
|
|
4372
|
+
const GRID = {
|
|
4373
|
+
columns: 12,
|
|
4374
|
+
gutter: 40,
|
|
4375
|
+
padding: 120 // Edge padding
|
|
4376
|
+
};
|
|
4252
4377
|
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
ls src/components/
|
|
4256
|
-
ls src/app/
|
|
4257
|
-
# Find the key UI: dashboards, forms, cards, modals, etc.
|
|
4378
|
+
// Center 6 columns for main content
|
|
4379
|
+
const contentWidth = (1920 - (GRID.padding * 2) - (GRID.gutter * 5)) / 2;
|
|
4258
4380
|
\`\`\`
|
|
4259
4381
|
|
|
4260
|
-
|
|
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
|
|
4261
4387
|
|
|
4262
|
-
|
|
4388
|
+
---
|
|
4263
4389
|
|
|
4264
|
-
|
|
4390
|
+
## Prerequisites
|
|
4265
4391
|
|
|
4266
|
-
|
|
4267
|
-
// BEFORE: Original component from project
|
|
4268
|
-
export function PricingCard({ plan, onSelect }) {
|
|
4269
|
-
const [loading, setLoading] = useState(false);
|
|
4270
|
-
const handleClick = async () => { /* API calls */ };
|
|
4271
|
-
return <div>...</div>;
|
|
4272
|
-
}
|
|
4392
|
+
Before using this skill, ensure you have:
|
|
4273
4393
|
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4394
|
+
1. **Load related skills:**
|
|
4395
|
+
\`\`\`
|
|
4396
|
+
remotion-best-practices
|
|
4397
|
+
threejs-fundamentals
|
|
4398
|
+
\`\`\`
|
|
4278
4399
|
|
|
4279
|
-
|
|
4280
|
-
|
|
4400
|
+
2. **Authenticate:**
|
|
4401
|
+
\`\`\`bash
|
|
4402
|
+
${cmd2} login
|
|
4403
|
+
\`\`\`
|
|
4281
4404
|
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
}}>
|
|
4287
|
-
...
|
|
4288
|
-
</div>
|
|
4289
|
-
);
|
|
4290
|
-
}
|
|
4291
|
-
\`\`\`
|
|
4405
|
+
3. **Remotion installed** (if creating videos):
|
|
4406
|
+
\`\`\`bash
|
|
4407
|
+
pnpm install remotion @remotion/cli
|
|
4408
|
+
\`\`\`
|
|
4292
4409
|
|
|
4293
|
-
|
|
4410
|
+
---
|
|
4294
4411
|
|
|
4295
|
-
|
|
4296
|
-
- 2.5D rotation entrance
|
|
4297
|
-
- Spring physics
|
|
4298
|
-
- Staggered children
|
|
4299
|
-
- Cursor interaction states
|
|
4412
|
+
## Video Creation Workflow
|
|
4300
4413
|
|
|
4301
|
-
###
|
|
4414
|
+
### Phase 0: Load Skills (MANDATORY)
|
|
4302
4415
|
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
{ name: 'Pro', price: 29, features: ['Unlimited projects', '100GB storage', 'Priority support'] },
|
|
4308
|
-
];
|
|
4309
|
-
// NOT: "Plan A", "$XX/mo", "Feature 1"
|
|
4416
|
+
Before ANY video work, invoke these skills:
|
|
4417
|
+
\`\`\`
|
|
4418
|
+
remotion-best-practices
|
|
4419
|
+
threejs-fundamentals
|
|
4310
4420
|
\`\`\`
|
|
4311
4421
|
|
|
4312
|
-
|
|
4422
|
+
### Phase 1: Discovery
|
|
4313
4423
|
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
| \`/public/logo.svg\` | Actual logo file |
|
|
4319
|
-
| \`/src/components/*.tsx\` | Component structure & styles |
|
|
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
|
|
4320
4428
|
|
|
4321
|
-
|
|
4429
|
+
### Phase 2: Video Brief
|
|
4322
4430
|
|
|
4323
|
-
\
|
|
4324
|
-
\u2705 Using exact project colors from tailwind config
|
|
4431
|
+
Present a brief outline (scenes \u22648s each, duration, assets found) and get user approval before production.
|
|
4325
4432
|
|
|
4326
|
-
|
|
4327
|
-
\u2705 Real content: "Unlimited projects", "Priority support"
|
|
4433
|
+
### Phase 3: Production
|
|
4328
4434
|
|
|
4329
|
-
|
|
4330
|
-
|
|
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
|
|
4331
4440
|
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
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.
|
|
4339
4452
|
|
|
4340
|
-
|
|
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
|
+
\`\`\`
|
|
4341
4490
|
|
|
4342
|
-
**
|
|
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)
|
|
4343
4496
|
|
|
4344
4497
|
---
|
|
4345
4498
|
|
|
4346
|
-
##
|
|
4499
|
+
## Core Video Rules
|
|
4347
4500
|
|
|
4348
|
-
|
|
4501
|
+
${VIDEO_RULE_CONTENTS.map((rule) => rule.content).join("\n\n---\n\n")}
|
|
4349
4502
|
|
|
4350
|
-
|
|
4503
|
+
## Useful Commands
|
|
4351
4504
|
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4505
|
+
\`\`\`bash
|
|
4506
|
+
# Generate video assets
|
|
4507
|
+
${cmd2} video create < scenes.json
|
|
4508
|
+
cat scenes.json | ${cmd2} video create --output ./public
|
|
4355
4509
|
|
|
4356
|
-
|
|
4510
|
+
# Initialize Remotion project
|
|
4511
|
+
${cmd2} video init my-video
|
|
4357
4512
|
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
| Face Close-Up | Tight on face, zoom out | Personal/story |
|
|
4361
|
-
| Green Screen | Speaker + background tweet/article | Commentary |
|
|
4362
|
-
| Text Slam | Bold text hits screen with sound | Listicles, tips |
|
|
4363
|
-
| Movement | Object thrown, quick zoom | Product demos |
|
|
4513
|
+
# Embed thumbnail
|
|
4514
|
+
${cmd2} video thumbnail out/video.mp4 --frame 60
|
|
4364
4515
|
|
|
4365
|
-
|
|
4516
|
+
# Search for stock assets
|
|
4517
|
+
${cmd2} images search "mountain landscape" --limit 10
|
|
4518
|
+
${cmd2} videos search "ocean waves" --limit 5
|
|
4366
4519
|
|
|
4367
|
-
|
|
4368
|
-
|
|
4520
|
+
# Generate audio
|
|
4521
|
+
${cmd2} audio generate "Your script here" --voice Kore
|
|
4522
|
+
${cmd2} music generate "upbeat corporate" --duration 30
|
|
4523
|
+
\`\`\`
|
|
4369
4524
|
|
|
4370
4525
|
---
|
|
4371
4526
|
|
|
4372
|
-
##
|
|
4527
|
+
## Best Practices
|
|
4373
4528
|
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
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
|
|
4380
4535
|
|
|
4381
4536
|
---
|
|
4382
4537
|
|
|
4383
|
-
##
|
|
4538
|
+
## Troubleshooting
|
|
4384
4539
|
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
| Color graded | Raw, slightly oversaturated |
|
|
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
|
|
4391
4545
|
|
|
4392
|
-
|
|
4546
|
+
For detailed troubleshooting, see "Known Issues" section above.
|
|
4547
|
+
`;
|
|
4548
|
+
}
|
|
4393
4549
|
|
|
4394
|
-
|
|
4395
|
-
|
|
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"
|
|
4396
4572
|
\`\`\`
|
|
4397
4573
|
|
|
4398
4574
|
---
|
|
4399
4575
|
|
|
4400
|
-
##
|
|
4576
|
+
## Creating Presentations
|
|
4401
4577
|
|
|
4402
|
-
|
|
4578
|
+
### From Text
|
|
4403
4579
|
|
|
4580
|
+
\`\`\`bash
|
|
4581
|
+
${cmd2} create "AI-powered product analytics platform"
|
|
4404
4582
|
\`\`\`
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
| CONTENT SAFE ZONE |
|
|
4411
|
-
| (60px left, 120px right margins) |
|
|
4412
|
-
| |
|
|
4413
|
-
| ----------------------------------------|
|
|
4414
|
-
| BOTTOM SAFE ZONE (384px / 20%) | <- Captions, username
|
|
4415
|
-
| [Icon strip on right: 120px] | <- Like, Comment, Share
|
|
4416
|
-
+------------------------------------------+
|
|
4583
|
+
|
|
4584
|
+
### From File
|
|
4585
|
+
|
|
4586
|
+
\`\`\`bash
|
|
4587
|
+
${cmd2} create --file product-brief.md
|
|
4417
4588
|
\`\`\`
|
|
4418
4589
|
|
|
4419
|
-
###
|
|
4590
|
+
### From URL
|
|
4420
4591
|
|
|
4421
|
-
\`\`\`
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
bottom: 384, // 20% of 1920 - captions/username
|
|
4425
|
-
left: 60, // Edge safety
|
|
4426
|
-
right: 120, // Like/Comment/Share icons
|
|
4427
|
-
};
|
|
4592
|
+
\`\`\`bash
|
|
4593
|
+
${cmd2} create --url https://company.com/product
|
|
4594
|
+
\`\`\`
|
|
4428
4595
|
|
|
4429
|
-
|
|
4430
|
-
|
|
4596
|
+
### From Piped Content
|
|
4597
|
+
|
|
4598
|
+
\`\`\`bash
|
|
4599
|
+
cat research.txt | ${cmd2} create
|
|
4600
|
+
pbpaste | ${cmd2} create
|
|
4431
4601
|
\`\`\`
|
|
4432
4602
|
|
|
4433
|
-
###
|
|
4603
|
+
### Advanced Options
|
|
4434
4604
|
|
|
4435
|
-
\`\`\`
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
}} />
|
|
4443
|
-
{/* Bottom danger zone */}
|
|
4444
|
-
<div style={{
|
|
4445
|
-
position: "absolute", bottom: 0, left: 0, right: 0, height: 384,
|
|
4446
|
-
backgroundColor: "rgba(255,0,0,0.2)", borderTop: "2px dashed red",
|
|
4447
|
-
}} />
|
|
4448
|
-
{/* Right icon strip */}
|
|
4449
|
-
<div style={{
|
|
4450
|
-
position: "absolute", top: 150, right: 0, bottom: 384, width: 120,
|
|
4451
|
-
backgroundColor: "rgba(255,165,0,0.2)", borderLeft: "2px dashed orange",
|
|
4452
|
-
}} />
|
|
4453
|
-
</AbsoluteFill>
|
|
4454
|
-
);
|
|
4605
|
+
\`\`\`bash
|
|
4606
|
+
${cmd2} create "Topic" \\
|
|
4607
|
+
--slides 10 \\
|
|
4608
|
+
--style professional \\
|
|
4609
|
+
--branding my-brand \\
|
|
4610
|
+
--template minimal \\
|
|
4611
|
+
--output presentation.zip
|
|
4455
4612
|
\`\`\`
|
|
4456
4613
|
|
|
4457
4614
|
---
|
|
4458
4615
|
|
|
4459
|
-
##
|
|
4616
|
+
## Presentation Options
|
|
4460
4617
|
|
|
4461
|
-
|
|
4618
|
+
- **\`--slides <count>\`** - Number of slides (default: 8-12 based on content)
|
|
4619
|
+
- **\`--style <style>\`** - Presentation style: professional, creative, minimal, corporate
|
|
4620
|
+
- **\`--branding <name>\`** - Use saved branding profile
|
|
4621
|
+
- **\`--template <name>\`** - Design template to use
|
|
4622
|
+
- **\`--output <file>\`** - Export to file (.zip, .pptx, .pdf)
|
|
4623
|
+
- **\`--format <format>\`** - Output format: human, json, quiet
|
|
4462
4624
|
|
|
4463
|
-
|
|
4464
|
-
<AbsoluteFill>
|
|
4465
|
-
<Img src={backgroundImage} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
|
|
4466
|
-
<div style={{
|
|
4467
|
-
position: "absolute", bottom: 400, left: 40,
|
|
4468
|
-
width: 300, height: 400, borderRadius: 20, overflow: "hidden",
|
|
4469
|
-
}}>
|
|
4470
|
-
<Video src={speakerVideo} />
|
|
4471
|
-
</div>
|
|
4472
|
-
</AbsoluteFill>
|
|
4473
|
-
\`\`\`
|
|
4625
|
+
---
|
|
4474
4626
|
|
|
4475
|
-
|
|
4627
|
+
## Managing Presentations
|
|
4476
4628
|
|
|
4477
|
-
\`\`\`
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
\`\`\`
|
|
4629
|
+
\`\`\`bash
|
|
4630
|
+
# List all presentations
|
|
4631
|
+
${cmd2} list
|
|
4632
|
+
${cmd2} list --format json
|
|
4482
4633
|
|
|
4483
|
-
|
|
4634
|
+
# Get presentation details
|
|
4635
|
+
${cmd2} get <id-or-slug>
|
|
4484
4636
|
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
- Satisfying click feedback
|
|
4488
|
-
- Ken Burns zoom on details
|
|
4637
|
+
# Export presentation
|
|
4638
|
+
${cmd2} export <id-or-slug> -o presentation.zip
|
|
4489
4639
|
|
|
4490
|
-
|
|
4640
|
+
# Import presentation
|
|
4641
|
+
${cmd2} import ./presentation.zip
|
|
4491
4642
|
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
POV: {setup}
|
|
4495
|
-
</div>
|
|
4643
|
+
# Delete presentation
|
|
4644
|
+
${cmd2} delete <id-or-slug>
|
|
4496
4645
|
\`\`\`
|
|
4497
4646
|
|
|
4498
4647
|
---
|
|
4499
4648
|
|
|
4500
|
-
##
|
|
4649
|
+
## Branding Management
|
|
4501
4650
|
|
|
4502
|
-
|
|
4651
|
+
\`\`\`bash
|
|
4652
|
+
# List saved brands
|
|
4653
|
+
${cmd2} branding list
|
|
4503
4654
|
|
|
4504
|
-
|
|
4505
|
-
|
|
4655
|
+
# Extract branding from website
|
|
4656
|
+
${cmd2} branding extract https://company.com
|
|
4506
4657
|
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
combineTokensWithinMilliseconds: 1200, // Higher = more words per page
|
|
4510
|
-
});
|
|
4658
|
+
# Use branding in presentation
|
|
4659
|
+
${cmd2} create "Topic" --branding company-brand
|
|
4511
4660
|
\`\`\`
|
|
4512
4661
|
|
|
4513
|
-
|
|
4662
|
+
---
|
|
4514
4663
|
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
{token.text}
|
|
4526
|
-
</span>
|
|
4527
|
-
);
|
|
4528
|
-
})}
|
|
4664
|
+
## Stock Asset Search
|
|
4665
|
+
|
|
4666
|
+
\`\`\`bash
|
|
4667
|
+
# Search for images
|
|
4668
|
+
${cmd2} images search "mountain landscape" --limit 10
|
|
4669
|
+
${cmd2} images search "business team" --format json
|
|
4670
|
+
|
|
4671
|
+
# Search for videos
|
|
4672
|
+
${cmd2} videos search "ocean waves" --limit 5
|
|
4673
|
+
${cmd2} videos search "city timelapse" --orientation landscape
|
|
4529
4674
|
\`\`\`
|
|
4530
4675
|
|
|
4531
4676
|
---
|
|
4532
4677
|
|
|
4533
|
-
##
|
|
4678
|
+
## Best Practices
|
|
4534
4679
|
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
| 0:40-0:55 | **PROOF** | Show, don't tell |
|
|
4541
|
-
| 0:55-0:60 | **CTA** | Soft ask (Follow/Link in bio) |
|
|
4680
|
+
1. **Provide context** - More input = better presentations
|
|
4681
|
+
2. **Use branding** - Extract and apply brand consistency
|
|
4682
|
+
3. **Review content** - AI-generated content should be reviewed
|
|
4683
|
+
4. **Export for sharing** - Use \`--output\` to create shareable files
|
|
4684
|
+
5. **Iterate** - Regenerate specific slides if needed
|
|
4542
4685
|
|
|
4543
|
-
|
|
4686
|
+
---
|
|
4544
4687
|
|
|
4545
|
-
|
|
4546
|
-
|----|-------|
|
|
4547
|
-
| "Follow for more tips" | "SMASH that like button" |
|
|
4548
|
-
| "Link in bio" | "Click the link below" |
|
|
4549
|
-
| "Save this for later" | "Subscribe to my channel" |
|
|
4688
|
+
## Troubleshooting
|
|
4550
4689
|
|
|
4551
|
-
|
|
4690
|
+
**Authentication Issues:**
|
|
4691
|
+
\`\`\`bash
|
|
4692
|
+
# Check current user
|
|
4693
|
+
${cmd2} whoami
|
|
4552
4694
|
|
|
4553
|
-
|
|
4695
|
+
# Re-authenticate
|
|
4696
|
+
${cmd2} logout
|
|
4697
|
+
${cmd2} login
|
|
4698
|
+
\`\`\`
|
|
4554
4699
|
|
|
4555
|
-
|
|
4700
|
+
**Generation Failures:**
|
|
4701
|
+
- Ensure input is clear and has enough context
|
|
4702
|
+
- Try different styles or templates
|
|
4703
|
+
- Check API status and quotas
|
|
4556
4704
|
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
| Shorts | 9:16 | 1080x1920 | 15-60s | 30 |
|
|
4562
|
-
| YouTube | 16:9 | 1920x1080 | any | 30/60 |
|
|
4705
|
+
**Export Issues:**
|
|
4706
|
+
- Verify output format is supported
|
|
4707
|
+
- Check file permissions in output directory
|
|
4708
|
+
- Ensure presentation ID is correct
|
|
4563
4709
|
|
|
4564
|
-
|
|
4710
|
+
---
|
|
4565
4711
|
|
|
4566
|
-
|
|
4567
|
-
|--------|--------|---------|
|
|
4568
|
-
| Hook impact | 0-3 | 0-0.1s |
|
|
4569
|
-
| Text hold min | 45 | 1.5s |
|
|
4570
|
-
| Scene change max | 90 | 3s |
|
|
4571
|
-
| Ideal scene | 60 | 2s |
|
|
4712
|
+
## Examples
|
|
4572
4713
|
|
|
4573
|
-
|
|
4714
|
+
**Quick pitch deck:**
|
|
4715
|
+
\`\`\`bash
|
|
4716
|
+
${cmd2} create "SaaS analytics platform for e-commerce" --slides 8 --style professional
|
|
4717
|
+
\`\`\`
|
|
4574
4718
|
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4719
|
+
**From product brief:**
|
|
4720
|
+
\`\`\`bash
|
|
4721
|
+
${cmd2} create --file brief.md --branding acme --output pitch.zip
|
|
4722
|
+
\`\`\`
|
|
4723
|
+
|
|
4724
|
+
**Research presentation:**
|
|
4725
|
+
\`\`\`bash
|
|
4726
|
+
cat research-notes.txt | ${cmd2} create --slides 15 --style minimal
|
|
4727
|
+
\`\`\`
|
|
4728
|
+
`;
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4731
|
+
// src/commands/skill/installer.ts
|
|
4732
|
+
import { mkdirSync, writeFileSync, existsSync as existsSync2, rmSync } from "fs";
|
|
4733
|
+
import { join, resolve as resolve4, relative } from "path";
|
|
4734
|
+
import { homedir } from "os";
|
|
4735
|
+
|
|
4736
|
+
// src/commands/skill/editors.ts
|
|
4737
|
+
var SUPPORTED_EDITORS = [
|
|
4738
|
+
{ name: "Claude Code", dir: ".claude" },
|
|
4739
|
+
{ name: "Cursor", dir: ".cursor" },
|
|
4740
|
+
{ name: "Codex", dir: ".codex" },
|
|
4741
|
+
{ name: "OpenCode", dir: ".opencode" },
|
|
4742
|
+
{ name: "Windsurf", dir: ".windsurf" },
|
|
4743
|
+
{ name: "Agent", dir: ".agent" }
|
|
4585
4744
|
];
|
|
4586
4745
|
|
|
4587
4746
|
// src/commands/skill/installer.ts
|
|
@@ -4594,27 +4753,10 @@ function validatePath(basePath, targetPath) {
|
|
|
4594
4753
|
}
|
|
4595
4754
|
return resolvedTarget;
|
|
4596
4755
|
}
|
|
4597
|
-
function getRuleFiles() {
|
|
4598
|
-
const rules = [];
|
|
4599
|
-
for (const rule of VIDEO_RULE_CONTENTS) {
|
|
4600
|
-
rules.push({
|
|
4601
|
-
path: `rules/video/${rule.filename}`,
|
|
4602
|
-
content: rule.content
|
|
4603
|
-
});
|
|
4604
|
-
}
|
|
4605
|
-
return rules;
|
|
4606
|
-
}
|
|
4607
4756
|
function installSkillToPath(skillPath, content) {
|
|
4608
4757
|
const skillFile = join(skillPath, "SKILL.md");
|
|
4609
4758
|
mkdirSync(skillPath, { recursive: true });
|
|
4610
4759
|
writeFileSync(skillFile, content, "utf-8");
|
|
4611
|
-
const rules = getRuleFiles();
|
|
4612
|
-
for (const rule of rules) {
|
|
4613
|
-
const rulePath = join(skillPath, rule.path);
|
|
4614
|
-
const ruleDir = join(skillPath, rule.path.split("/").slice(0, -1).join("/"));
|
|
4615
|
-
mkdirSync(ruleDir, { recursive: true });
|
|
4616
|
-
writeFileSync(rulePath, rule.content, "utf-8");
|
|
4617
|
-
}
|
|
4618
4760
|
}
|
|
4619
4761
|
function installSkill(skillName, content, options = {}) {
|
|
4620
4762
|
const result = {
|
|
@@ -4678,67 +4820,133 @@ function getSupportedEditorNames() {
|
|
|
4678
4820
|
}
|
|
4679
4821
|
|
|
4680
4822
|
// src/commands/skill/index.ts
|
|
4681
|
-
var
|
|
4823
|
+
var SKILL_TYPES = ["main", "video", "presentation"];
|
|
4824
|
+
var skillContext = {
|
|
4825
|
+
name: brand.name,
|
|
4826
|
+
cmd: brand.commands[0],
|
|
4827
|
+
displayName: brand.displayName
|
|
4828
|
+
};
|
|
4829
|
+
var skillCommand = new Command14("skill").description(`Manage ${brand.displayName} skills for AI coding assistants`).addHelpText(
|
|
4682
4830
|
"after",
|
|
4683
4831
|
`
|
|
4832
|
+
${chalk12.bold("Skill Types:")}
|
|
4833
|
+
${chalk12.cyan("main")} Main CLI skill with all capabilities (TTS, music, images, videos, presentations)
|
|
4834
|
+
${chalk12.cyan("video")} Detailed video creation workflow with Remotion/R3F patterns
|
|
4835
|
+
${chalk12.cyan("presentation")} Detailed presentation creation workflow
|
|
4836
|
+
|
|
4684
4837
|
${chalk12.bold("Examples:")}
|
|
4685
|
-
${chalk12.gray("# Install
|
|
4686
|
-
$ ${brand.
|
|
4838
|
+
${chalk12.gray("# Install main CLI skill (comprehensive overview)")}
|
|
4839
|
+
$ ${brand.commands[0]} skill install main
|
|
4840
|
+
|
|
4841
|
+
${chalk12.gray("# Install video skill")}
|
|
4842
|
+
$ ${brand.commands[0]} skill install video
|
|
4843
|
+
|
|
4844
|
+
${chalk12.gray("# Install presentation skill")}
|
|
4845
|
+
$ ${brand.commands[0]} skill install presentation
|
|
4846
|
+
|
|
4847
|
+
${chalk12.gray("# Install all skills")}
|
|
4848
|
+
$ ${brand.commands[0]} skill install
|
|
4687
4849
|
|
|
4688
4850
|
${chalk12.gray("# Install to specific directory")}
|
|
4689
|
-
$ ${brand.
|
|
4851
|
+
$ ${brand.commands[0]} skill install main --dir ~/.claude
|
|
4690
4852
|
|
|
4691
4853
|
${chalk12.gray("# Show skill content")}
|
|
4692
|
-
$ ${brand.
|
|
4854
|
+
$ ${brand.commands[0]} skill show main
|
|
4693
4855
|
`
|
|
4694
4856
|
);
|
|
4695
|
-
skillCommand.command("install").description(`Install
|
|
4696
|
-
const
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
console.log();
|
|
4703
|
-
if (result.installed.length > 0) {
|
|
4704
|
-
success("Skill installed successfully");
|
|
4705
|
-
console.log();
|
|
4706
|
-
keyValue("Installed to", result.installed.join(", "));
|
|
4857
|
+
skillCommand.command("install").description(`Install ${brand.displayName} skills for AI coding assistants`).argument("[type]", "Skill type: main, video, presentation, or omit for all").option("-d, --dir <path>", "Install to specific directory").option("-g, --global", "Install globally (to home directory)", true).option("-l, --local", "Install locally (to current directory)").option("-f, --force", "Overwrite existing skill files").action(async (type, options) => {
|
|
4858
|
+
const skillsToInstall = [];
|
|
4859
|
+
if (!type || type === "main") {
|
|
4860
|
+
skillsToInstall.push({
|
|
4861
|
+
name: brand.name,
|
|
4862
|
+
content: generateMainSkillContent(skillContext)
|
|
4863
|
+
});
|
|
4707
4864
|
}
|
|
4708
|
-
if (
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4865
|
+
if (!type || type === "video") {
|
|
4866
|
+
skillsToInstall.push({
|
|
4867
|
+
name: `${brand.name}-video`,
|
|
4868
|
+
content: generateVideoSkillContent(skillContext)
|
|
4869
|
+
});
|
|
4712
4870
|
}
|
|
4713
|
-
if (
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
}
|
|
4871
|
+
if (!type || type === "presentation") {
|
|
4872
|
+
skillsToInstall.push({
|
|
4873
|
+
name: `${brand.name}-presentation`,
|
|
4874
|
+
content: generatePresentationSkillContent(skillContext)
|
|
4875
|
+
});
|
|
4718
4876
|
}
|
|
4719
|
-
if (
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
console.log(chalk12.gray("Supported editors: " + getSupportedEditorNames().join(", ")));
|
|
4723
|
-
console.log(chalk12.gray("Use --dir <path> to install to a specific directory"));
|
|
4877
|
+
if (type && !SKILL_TYPES.includes(type)) {
|
|
4878
|
+
error(`Invalid skill type: ${type}. Must be one of: ${SKILL_TYPES.join(", ")}`);
|
|
4879
|
+
process.exit(1);
|
|
4724
4880
|
}
|
|
4725
4881
|
console.log();
|
|
4882
|
+
for (const skill of skillsToInstall) {
|
|
4883
|
+
info(`Installing ${skill.name}...`);
|
|
4884
|
+
const result = installSkill(skill.name, skill.content, {
|
|
4885
|
+
dir: options.dir,
|
|
4886
|
+
local: options.local,
|
|
4887
|
+
force: options.force
|
|
4888
|
+
});
|
|
4889
|
+
if (result.installed.length > 0) {
|
|
4890
|
+
success(`${skill.name} installed successfully`);
|
|
4891
|
+
keyValue(" Installed to", result.installed.join(", "));
|
|
4892
|
+
}
|
|
4893
|
+
if (result.skipped.length > 0) {
|
|
4894
|
+
info(` Skipped (already exists): ${result.skipped.join(", ")}`);
|
|
4895
|
+
console.log(chalk12.gray(" Use --force to overwrite"));
|
|
4896
|
+
}
|
|
4897
|
+
if (result.errors.length > 0) {
|
|
4898
|
+
for (const err of result.errors) {
|
|
4899
|
+
error(` ${err}`);
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
if (result.installed.length === 0 && result.skipped.length === 0 && result.errors.length === 0) {
|
|
4903
|
+
info(" No supported AI coding assistants detected");
|
|
4904
|
+
console.log(chalk12.gray(" Supported editors: " + getSupportedEditorNames().join(", ")));
|
|
4905
|
+
console.log(chalk12.gray(" Use --dir <path> to install to a specific directory"));
|
|
4906
|
+
}
|
|
4907
|
+
console.log();
|
|
4908
|
+
}
|
|
4726
4909
|
});
|
|
4727
|
-
skillCommand.command("show").description("Display
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
success("Skill uninstalled");
|
|
4735
|
-
keyValue("Removed from", result.removed.join(", "));
|
|
4910
|
+
skillCommand.command("show").description("Display skill content").argument("[type]", "Skill type: main, video, or presentation (default: main)").action((type = "main") => {
|
|
4911
|
+
if (type === "main") {
|
|
4912
|
+
console.log(generateMainSkillContent(skillContext));
|
|
4913
|
+
} else if (type === "video") {
|
|
4914
|
+
console.log(generateVideoSkillContent(skillContext));
|
|
4915
|
+
} else if (type === "presentation") {
|
|
4916
|
+
console.log(generatePresentationSkillContent(skillContext));
|
|
4736
4917
|
} else {
|
|
4737
|
-
|
|
4918
|
+
error(`Invalid skill type: ${type}. Must be one of: ${SKILL_TYPES.join(", ")}`);
|
|
4919
|
+
process.exit(1);
|
|
4920
|
+
}
|
|
4921
|
+
});
|
|
4922
|
+
skillCommand.command("uninstall").description(`Remove ${brand.displayName} skills from AI coding assistants`).argument("[type]", "Skill type: main, video, presentation, or omit for all").option("-g, --global", "Uninstall globally (from home directory)", true).option("-l, --local", "Uninstall locally (from current directory)").action(async (type, options) => {
|
|
4923
|
+
const skillsToRemove = [];
|
|
4924
|
+
if (!type || type === "main") {
|
|
4925
|
+
skillsToRemove.push(brand.name);
|
|
4926
|
+
}
|
|
4927
|
+
if (!type || type === "video") {
|
|
4928
|
+
skillsToRemove.push(`${brand.name}-video`);
|
|
4929
|
+
}
|
|
4930
|
+
if (!type || type === "presentation") {
|
|
4931
|
+
skillsToRemove.push(`${brand.name}-presentation`);
|
|
4932
|
+
}
|
|
4933
|
+
if (type && !SKILL_TYPES.includes(type)) {
|
|
4934
|
+
error(`Invalid skill type: ${type}. Must be one of: ${SKILL_TYPES.join(", ")}`);
|
|
4935
|
+
process.exit(1);
|
|
4738
4936
|
}
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4937
|
+
console.log();
|
|
4938
|
+
for (const skillName of skillsToRemove) {
|
|
4939
|
+
const result = uninstallSkill(skillName, { local: options.local });
|
|
4940
|
+
if (result.removed.length > 0) {
|
|
4941
|
+
success(`${skillName} uninstalled`);
|
|
4942
|
+
keyValue(" Removed from", result.removed.join(", "));
|
|
4943
|
+
} else {
|
|
4944
|
+
info(` ${skillName} not found`);
|
|
4945
|
+
}
|
|
4946
|
+
if (result.errors.length > 0) {
|
|
4947
|
+
for (const err of result.errors) {
|
|
4948
|
+
warn(` Failed to remove: ${err}`);
|
|
4949
|
+
}
|
|
4742
4950
|
}
|
|
4743
4951
|
}
|
|
4744
4952
|
console.log();
|
|
@@ -5000,7 +5208,7 @@ async function downloadFile2(url, outputPath) {
|
|
|
5000
5208
|
const buffer = await response.arrayBuffer();
|
|
5001
5209
|
await writeFile4(outputPath, Buffer.from(buffer));
|
|
5002
5210
|
}
|
|
5003
|
-
var mixCommand = new Command17("create").description("Mix audio tracks into a video").requiredOption("--video <url>", "Input video file/URL").option("--music <url>", "Background music file/URL").option("--voice <url>", "Voiceover file/URL").option("--music-volume <percent>", "Music volume 0-100", "
|
|
5211
|
+
var mixCommand = new Command17("create").description("Mix audio tracks into a video (music will loop to match video duration)").requiredOption("--video <url>", "Input video file/URL").option("--music <url>", "Background music file/URL (will loop if shorter than video)").option("--voice <url>", "Voiceover file/URL").option("--music-volume <percent>", "Music volume 0-100 (default: 30, recommended for mix with voice)", "30").option("--voice-volume <percent>", "Voice volume 0-100", "100").option("-o, --output <path>", "Output file path").option("--no-wait", "Do not wait for completion").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (options) => {
|
|
5004
5212
|
if (!options.music && !options.voice) {
|
|
5005
5213
|
error("At least one of --music or --voice must be provided");
|
|
5006
5214
|
process.exit(EXIT_CODES.INVALID_INPUT);
|
|
@@ -5169,9 +5377,10 @@ init_output();
|
|
|
5169
5377
|
init_types();
|
|
5170
5378
|
import { Command as Command19 } from "commander";
|
|
5171
5379
|
import ora12 from "ora";
|
|
5172
|
-
import { mkdir, writeFile as writeFile5, readFile as readFile2, access, rm } from "fs/promises";
|
|
5380
|
+
import { mkdir, writeFile as writeFile5, readFile as readFile2, access, rm, cp } from "fs/promises";
|
|
5173
5381
|
import { join as join2, resolve as resolve5 } from "path";
|
|
5174
5382
|
import { execSync, spawn } from "child_process";
|
|
5383
|
+
import ffmpegPath from "ffmpeg-static";
|
|
5175
5384
|
var DEFAULT_TEMPLATE = "inizio-inc/remotion-composition";
|
|
5176
5385
|
var DEFAULT_FPS = 30;
|
|
5177
5386
|
function parseScriptIntoSections(script) {
|
|
@@ -5310,7 +5519,7 @@ function getExtension(url) {
|
|
|
5310
5519
|
}
|
|
5311
5520
|
return "jpg";
|
|
5312
5521
|
}
|
|
5313
|
-
var
|
|
5522
|
+
var createCommand2 = 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) => {
|
|
5314
5523
|
const format = options.format;
|
|
5315
5524
|
const spinner = format === "human" ? ora12("Initializing...").start() : null;
|
|
5316
5525
|
try {
|
|
@@ -5362,7 +5571,10 @@ var createCommand3 = new Command19("create").description("Create video assets (v
|
|
|
5362
5571
|
if (spinner) spinner.text = `[${scene.name}] Generating speech...`;
|
|
5363
5572
|
const ttsResult = await generateSpeech({
|
|
5364
5573
|
text: scene.script,
|
|
5365
|
-
options: {
|
|
5574
|
+
options: {
|
|
5575
|
+
voice,
|
|
5576
|
+
voiceSettings: scenesInput.voiceSettings
|
|
5577
|
+
}
|
|
5366
5578
|
});
|
|
5367
5579
|
const audioPath = join2(audioDir, `${filename}.${ttsResult.format}`);
|
|
5368
5580
|
await writeFile5(audioPath, ttsResult.audioData);
|
|
@@ -5417,7 +5629,7 @@ var createCommand3 = new Command19("create").description("Create video assets (v
|
|
|
5417
5629
|
try {
|
|
5418
5630
|
const videoResults = await searchVideos({
|
|
5419
5631
|
query: scene.videoQuery,
|
|
5420
|
-
options: { maxResults: 1 }
|
|
5632
|
+
options: { maxResults: 1, license: "free" }
|
|
5421
5633
|
});
|
|
5422
5634
|
const vids = videoResults.data.results.flatMap((r) => r.results);
|
|
5423
5635
|
totalCost += videoResults.data.totalCost;
|
|
@@ -5502,7 +5714,12 @@ var createCommand3 = new Command19("create").description("Create video assets (v
|
|
|
5502
5714
|
spinner?.start();
|
|
5503
5715
|
}
|
|
5504
5716
|
}
|
|
5505
|
-
const musicDuration = Math.min(30, Math.ceil(totalDuration)
|
|
5717
|
+
const musicDuration = Math.min(30, Math.ceil(totalDuration));
|
|
5718
|
+
console.log(`[Music Generation] Requesting music:`, {
|
|
5719
|
+
prompt: musicPrompt,
|
|
5720
|
+
requestedDuration: musicDuration,
|
|
5721
|
+
totalAudioDuration: totalDuration
|
|
5722
|
+
});
|
|
5506
5723
|
if (spinner) spinner.text = "Generating music...";
|
|
5507
5724
|
let musicResult = await generateMusic({
|
|
5508
5725
|
prompt: musicPrompt,
|
|
@@ -5526,15 +5743,28 @@ var createCommand3 = new Command19("create").description("Create video assets (v
|
|
|
5526
5743
|
await downloadFile3(musicResult.audioUrl, musicPath);
|
|
5527
5744
|
}
|
|
5528
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
|
+
totalAudioDuration: totalDuration,
|
|
5752
|
+
difference: actualMusicDuration - totalDuration,
|
|
5753
|
+
audioUrl: musicResult.audioUrl?.substring(0, 50) + "..."
|
|
5754
|
+
});
|
|
5529
5755
|
const musicInfo = {
|
|
5530
5756
|
path: "audio/music.mp3",
|
|
5531
|
-
duration:
|
|
5757
|
+
duration: actualMusicDuration,
|
|
5532
5758
|
prompt: musicPrompt,
|
|
5533
5759
|
cost: musicResult.cost || 0
|
|
5534
5760
|
};
|
|
5535
5761
|
if (format === "human") {
|
|
5536
5762
|
spinner?.stop();
|
|
5537
5763
|
success(`Music: ${musicPath} (${musicInfo.duration}s)`);
|
|
5764
|
+
if (actualMusicDuration < totalDuration) {
|
|
5765
|
+
warn(`Music duration (${actualMusicDuration.toFixed(1)}s) is shorter than video duration (${totalDuration.toFixed(1)}s).`);
|
|
5766
|
+
warn(`Consider using audio looping or extending music in Remotion.`);
|
|
5767
|
+
}
|
|
5538
5768
|
spinner?.start();
|
|
5539
5769
|
}
|
|
5540
5770
|
if (spinner) spinner.text = "Writing manifest...";
|
|
@@ -5582,7 +5812,7 @@ var createCommand3 = new Command19("create").description("Create video assets (v
|
|
|
5582
5812
|
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
5583
5813
|
}
|
|
5584
5814
|
});
|
|
5585
|
-
var searchCommand2 = new Command19("search").description("Search for stock videos").argument("<query>", "Search query").option("-n, --max-results <count>", "Maximum number of results", "10").option("-o, --orientation <type>", "Video orientation: landscape, portrait, square, any", "any").option("-l, --license <type>", "License type: free, premium, any", "
|
|
5815
|
+
var searchCommand2 = new Command19("search").description("Search for stock videos").argument("<query>", "Search query").option("-n, --max-results <count>", "Maximum number of results", "10").option("-o, --orientation <type>", "Video orientation: landscape, portrait, square, any", "any").option("-l, --license <type>", "License type: free, premium, any", "free").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (query, options) => {
|
|
5586
5816
|
const { maxResults, orientation, license, format } = options;
|
|
5587
5817
|
const spinner = format === "human" ? ora12("Searching for videos...").start() : null;
|
|
5588
5818
|
try {
|
|
@@ -5751,10 +5981,147 @@ var initCommand = new Command19("init").description("Create a new Remotion video
|
|
|
5751
5981
|
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
5752
5982
|
}
|
|
5753
5983
|
});
|
|
5754
|
-
|
|
5984
|
+
function getFfmpegPath() {
|
|
5985
|
+
if (!ffmpegPath) {
|
|
5986
|
+
throw new Error("ffmpeg-static binary not found. Try reinstalling the CLI.");
|
|
5987
|
+
}
|
|
5988
|
+
return ffmpegPath;
|
|
5989
|
+
}
|
|
5990
|
+
var thumbnailCommand = new Command19("thumbnail").description("Embed a thumbnail/poster image into a video file for preview in Slack, Twitter, etc.").argument("<video>", "Video file to add thumbnail to (e.g., out/video.mp4)").option("-i, --image <path>", "Thumbnail image to embed (if not provided, extracts from video)").option("-f, --frame <number>", "Frame number to extract as thumbnail (default: 30)", "30").option("-c, --composition <id>", "Remotion composition to extract frame from (uses Remotion still)").option("-o, --output <path>", "Output video path (default: overwrites input)").option("--json", "Output as JSON").option("-q, --quiet", "Only output the file path").action(async (videoPath, options) => {
|
|
5991
|
+
const format = options.json ? "json" : options.quiet ? "quiet" : "human";
|
|
5992
|
+
const spinner = format === "human" ? ora12("Processing...").start() : null;
|
|
5993
|
+
try {
|
|
5994
|
+
let ffmpeg;
|
|
5995
|
+
try {
|
|
5996
|
+
ffmpeg = getFfmpegPath();
|
|
5997
|
+
} catch (err) {
|
|
5998
|
+
spinner?.stop();
|
|
5999
|
+
error(err instanceof Error ? err.message : "ffmpeg not available");
|
|
6000
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
6001
|
+
}
|
|
6002
|
+
const videoFullPath = resolve5(process.cwd(), videoPath);
|
|
6003
|
+
try {
|
|
6004
|
+
await access(videoFullPath);
|
|
6005
|
+
} catch {
|
|
6006
|
+
spinner?.stop();
|
|
6007
|
+
error(`Video file not found: ${videoPath}`);
|
|
6008
|
+
process.exit(EXIT_CODES.INVALID_INPUT);
|
|
6009
|
+
}
|
|
6010
|
+
const frameNum = parseInt(options.frame, 10);
|
|
6011
|
+
if (isNaN(frameNum) || frameNum < 0) {
|
|
6012
|
+
spinner?.stop();
|
|
6013
|
+
error("Invalid frame number. Must be a non-negative integer.");
|
|
6014
|
+
process.exit(EXIT_CODES.INVALID_INPUT);
|
|
6015
|
+
}
|
|
6016
|
+
let thumbnailPath = options.image;
|
|
6017
|
+
let tempThumbnail = false;
|
|
6018
|
+
if (!thumbnailPath) {
|
|
6019
|
+
const tempDir = join2(process.cwd(), ".tmp-thumbnail");
|
|
6020
|
+
await mkdir(tempDir, { recursive: true });
|
|
6021
|
+
thumbnailPath = join2(tempDir, "thumb.png");
|
|
6022
|
+
tempThumbnail = true;
|
|
6023
|
+
if (options.composition) {
|
|
6024
|
+
if (spinner) spinner.text = `Extracting frame ${frameNum} from ${options.composition}...`;
|
|
6025
|
+
const args = [
|
|
6026
|
+
"exec",
|
|
6027
|
+
"remotion",
|
|
6028
|
+
"still",
|
|
6029
|
+
options.composition,
|
|
6030
|
+
thumbnailPath,
|
|
6031
|
+
`--frame=${frameNum}`
|
|
6032
|
+
];
|
|
6033
|
+
try {
|
|
6034
|
+
execSync(`pnpm ${args.join(" ")}`, {
|
|
6035
|
+
stdio: "pipe",
|
|
6036
|
+
cwd: process.cwd()
|
|
6037
|
+
});
|
|
6038
|
+
} catch (err) {
|
|
6039
|
+
spinner?.stop();
|
|
6040
|
+
error(`Failed to extract frame from composition: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
6041
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
6042
|
+
}
|
|
6043
|
+
} else {
|
|
6044
|
+
if (spinner) spinner.text = `Extracting frame ${frameNum} from video...`;
|
|
6045
|
+
try {
|
|
6046
|
+
execSync(
|
|
6047
|
+
`"${ffmpeg}" -y -i "${videoFullPath}" -vf "select=eq(n\\,${frameNum})" -vframes 1 "${thumbnailPath}"`,
|
|
6048
|
+
{ stdio: "pipe" }
|
|
6049
|
+
);
|
|
6050
|
+
} catch (err) {
|
|
6051
|
+
spinner?.stop();
|
|
6052
|
+
error(`Failed to extract frame from video: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
6053
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
6054
|
+
}
|
|
6055
|
+
}
|
|
6056
|
+
} else {
|
|
6057
|
+
thumbnailPath = resolve5(process.cwd(), thumbnailPath);
|
|
6058
|
+
try {
|
|
6059
|
+
await access(thumbnailPath);
|
|
6060
|
+
} catch {
|
|
6061
|
+
spinner?.stop();
|
|
6062
|
+
error(`Thumbnail image not found: ${options.image}`);
|
|
6063
|
+
process.exit(EXIT_CODES.INVALID_INPUT);
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
const outputPath = options.output ? resolve5(process.cwd(), options.output) : videoFullPath;
|
|
6067
|
+
const needsTempOutput = outputPath === videoFullPath;
|
|
6068
|
+
const tempOutput = needsTempOutput ? videoFullPath.replace(/\.mp4$/, ".thumb-temp.mp4") : outputPath;
|
|
6069
|
+
if (spinner) spinner.text = "Embedding thumbnail into video...";
|
|
6070
|
+
try {
|
|
6071
|
+
execSync(
|
|
6072
|
+
`"${ffmpeg}" -y -i "${videoFullPath}" -i "${thumbnailPath}" -map 0 -map 1 -c copy -disposition:v:1 attached_pic "${tempOutput}"`,
|
|
6073
|
+
{ stdio: "pipe" }
|
|
6074
|
+
);
|
|
6075
|
+
if (needsTempOutput) {
|
|
6076
|
+
await rm(videoFullPath);
|
|
6077
|
+
await cp(tempOutput, videoFullPath);
|
|
6078
|
+
await rm(tempOutput);
|
|
6079
|
+
}
|
|
6080
|
+
} catch (err) {
|
|
6081
|
+
spinner?.stop();
|
|
6082
|
+
error(`Failed to embed thumbnail: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
6083
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
6084
|
+
}
|
|
6085
|
+
if (tempThumbnail) {
|
|
6086
|
+
try {
|
|
6087
|
+
await rm(join2(process.cwd(), ".tmp-thumbnail"), { recursive: true });
|
|
6088
|
+
} catch {
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
spinner?.stop();
|
|
6092
|
+
const finalOutput = options.output || videoPath;
|
|
6093
|
+
if (format === "json") {
|
|
6094
|
+
printJson({
|
|
6095
|
+
video: finalOutput,
|
|
6096
|
+
thumbnail: options.image || `frame ${frameNum}`,
|
|
6097
|
+
composition: options.composition || null
|
|
6098
|
+
});
|
|
6099
|
+
return;
|
|
6100
|
+
}
|
|
6101
|
+
if (format === "quiet") {
|
|
6102
|
+
console.log(resolve5(process.cwd(), finalOutput));
|
|
6103
|
+
return;
|
|
6104
|
+
}
|
|
6105
|
+
console.log();
|
|
6106
|
+
success(`Thumbnail embedded: ${finalOutput}`);
|
|
6107
|
+
if (options.image) {
|
|
6108
|
+
keyValue("Thumbnail", options.image);
|
|
6109
|
+
} else if (options.composition) {
|
|
6110
|
+
keyValue("Source", `${options.composition} frame ${frameNum}`);
|
|
6111
|
+
} else {
|
|
6112
|
+
keyValue("Source", `Video frame ${frameNum}`);
|
|
6113
|
+
}
|
|
6114
|
+
console.log();
|
|
6115
|
+
} catch (err) {
|
|
6116
|
+
spinner?.stop();
|
|
6117
|
+
error(err instanceof Error ? err.message : "Failed to process thumbnail");
|
|
6118
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
6119
|
+
}
|
|
6120
|
+
});
|
|
6121
|
+
var videoCommand = new Command19("video").description("Video asset generation commands").addCommand(initCommand).addCommand(createCommand2).addCommand(searchCommand2).addCommand(thumbnailCommand);
|
|
5755
6122
|
|
|
5756
6123
|
// src/index.ts
|
|
5757
|
-
var VERSION = "0.1.
|
|
6124
|
+
var VERSION = "0.1.9";
|
|
5758
6125
|
var program = new Command20();
|
|
5759
6126
|
var cmdName = brand.commands[0];
|
|
5760
6127
|
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({
|