@scenerok/cli 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +94 -0
- package/dist/commands/cache.d.ts +3 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +65 -0
- package/dist/commands/lint-video.d.ts +21 -0
- package/dist/commands/lint-video.d.ts.map +1 -0
- package/dist/commands/lint-video.js +122 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +173 -0
- package/dist/commands/render.d.ts +4 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +242 -0
- package/dist/commands/skills.d.ts +3 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +208 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +254 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/lib/api.d.ts +268 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +160 -0
- package/dist/lib/config.d.ts +13 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +53 -0
- package/dist/lib/format-api-error.d.ts +2 -0
- package/dist/lib/format-api-error.d.ts.map +1 -0
- package/dist/lib/format-api-error.js +24 -0
- package/dist/lib/language.d.ts +3 -0
- package/dist/lib/language.d.ts.map +1 -0
- package/dist/lib/language.js +9 -0
- package/dist/lib/logger.d.ts +8 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +51 -0
- package/dist/lib/project.d.ts +29 -0
- package/dist/lib/project.d.ts.map +1 -0
- package/dist/lib/project.js +156 -0
- package/dist/lib/sdk-scene-modules.d.ts +7 -0
- package/dist/lib/sdk-scene-modules.d.ts.map +1 -0
- package/dist/lib/sdk-scene-modules.js +35 -0
- package/dist/utils/skillsManifest.d.ts +42 -0
- package/dist/utils/skillsManifest.d.ts.map +1 -0
- package/dist/utils/skillsManifest.js +122 -0
- package/examples/system/3-tips-fitness.vid +45 -0
- package/examples/system/ecom-product-launch.vid +31 -0
- package/examples/system/glitch-title.vid +17 -0
- package/examples/system/meme-remix.vid +15 -0
- package/examples/system/minimal-text-reel.vid +11 -0
- package/examples/system/product-launch.vid +18 -0
- package/examples/system/rokmilk-chocolate-promo.vid +25 -0
- package/examples/system/testimonial-cut.vid +16 -0
- package/examples/system/ugc-testimonial-voiceover.vid +33 -0
- package/examples/system/voice-visual-promo.vid +21 -0
- package/package.json +51 -0
- package/skills/remote/ai-models.md +65 -0
- package/skills/remote/animations-catalog.md +89 -0
- package/skills/remote/animations-picker.md +77 -0
- package/skills/remote/brand-setup.md +69 -0
- package/skills/remote/catalog-index.json +1810 -0
- package/skills/remote/environments.md +73 -0
- package/skills/remote/intent-router.md +81 -0
- package/skills/remote/media-stdlib.md +66 -0
- package/skills/remote/music.md +69 -0
- package/skills/remote/sdk-guide.md +201 -0
- package/skills/remote/shaders-filters.md +97 -0
- package/skills/remote/text-styles.md +108 -0
- package/skills/remote/transitions-catalog.md +55 -0
- package/skills/remote/tts.md +82 -0
- package/skills/remote/vidscript-guide.md +574 -0
- package/skills/remote/vidscript-sample.md +28 -0
- package/skills/remote/vidscript-strict.md +251 -0
- package/skills/remote/workflow.md +72 -0
- package/skills/shared/SKILL.md +157 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote skill freshness: hash flat markdown/json files against the published manifest.
|
|
3
|
+
*/
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
export const DEFAULT_MANIFEST_URL = 'https://scenerok.com/.well-known/agent-skills/manifest.json';
|
|
9
|
+
const TEXT_EXT = new Set(['.md', '.json', '.txt']);
|
|
10
|
+
function hashFileContent(content, ext) {
|
|
11
|
+
const h = createHash('sha256');
|
|
12
|
+
const normalized = TEXT_EXT.has(ext) ? content.replace(/\r\n/g, '\n') : content;
|
|
13
|
+
h.update(normalized);
|
|
14
|
+
return h.digest('hex').slice(0, 16);
|
|
15
|
+
}
|
|
16
|
+
/** Map manifest slug to on-disk filename. */
|
|
17
|
+
export function slugToFilename(slug) {
|
|
18
|
+
if (slug === 'catalog-index')
|
|
19
|
+
return 'catalog-index.json';
|
|
20
|
+
return `${slug}.md`;
|
|
21
|
+
}
|
|
22
|
+
export function hashInstalledFile(filePath) {
|
|
23
|
+
if (!existsSync(filePath))
|
|
24
|
+
return null;
|
|
25
|
+
const content = readFileSync(filePath, 'utf8');
|
|
26
|
+
const ext = filePath.slice(filePath.lastIndexOf('.'));
|
|
27
|
+
return { hash: hashFileContent(content, ext), files: 1 };
|
|
28
|
+
}
|
|
29
|
+
export function hashInstalled(skillDir, manifest) {
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const slug of Object.keys(manifest.skills)) {
|
|
32
|
+
const filename = slugToFilename(slug);
|
|
33
|
+
const entry = hashInstalledFile(join(skillDir, filename));
|
|
34
|
+
if (entry)
|
|
35
|
+
out[slug] = entry;
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
export function diffSkills(installed, latest) {
|
|
40
|
+
const skills = [];
|
|
41
|
+
const summary = { current: 0, outdated: 0, missing: 0 };
|
|
42
|
+
for (const name of Object.keys(latest.skills).sort()) {
|
|
43
|
+
const latestEntry = latest.skills[name];
|
|
44
|
+
const installedEntry = installed[name];
|
|
45
|
+
let status;
|
|
46
|
+
if (!installedEntry)
|
|
47
|
+
status = 'missing';
|
|
48
|
+
else if (installedEntry.hash === latestEntry.hash)
|
|
49
|
+
status = 'current';
|
|
50
|
+
else
|
|
51
|
+
status = 'outdated';
|
|
52
|
+
if (status === 'current')
|
|
53
|
+
summary.current++;
|
|
54
|
+
else if (status === 'outdated')
|
|
55
|
+
summary.outdated++;
|
|
56
|
+
else
|
|
57
|
+
summary.missing++;
|
|
58
|
+
skills.push({
|
|
59
|
+
name,
|
|
60
|
+
status,
|
|
61
|
+
installedHash: installedEntry?.hash,
|
|
62
|
+
latestHash: latestEntry.hash,
|
|
63
|
+
filename: slugToFilename(name),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
updateAvailable: summary.outdated > 0 || summary.missing > 0,
|
|
68
|
+
summary,
|
|
69
|
+
skills,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export async function fetchManifest(url = DEFAULT_MANIFEST_URL) {
|
|
73
|
+
const res = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
74
|
+
if (!res.ok)
|
|
75
|
+
throw new Error(`HTTP ${res.status} fetching ${url}`);
|
|
76
|
+
const data = (await res.json());
|
|
77
|
+
if (!data?.skills || typeof data.skills !== 'object') {
|
|
78
|
+
throw new Error(`Malformed skills manifest from ${url}`);
|
|
79
|
+
}
|
|
80
|
+
return data;
|
|
81
|
+
}
|
|
82
|
+
export async function fetchSkillFile(slug, baseUrl) {
|
|
83
|
+
const site = (baseUrl ?? 'https://scenerok.com').replace(/\/$/, '');
|
|
84
|
+
const filename = slugToFilename(slug);
|
|
85
|
+
const url = `${site}/.well-known/agent-skills/${filename}`;
|
|
86
|
+
const res = await fetch(url);
|
|
87
|
+
if (!res.ok)
|
|
88
|
+
throw new Error(`HTTP ${res.status} fetching ${url}`);
|
|
89
|
+
return res.text();
|
|
90
|
+
}
|
|
91
|
+
export function locateSkillDir(platform) {
|
|
92
|
+
const dirs = {
|
|
93
|
+
opencode: join(homedir(), '.agents/skills/scenerok'),
|
|
94
|
+
claude: join(homedir(), '.claude/skills/scenerok'),
|
|
95
|
+
codex: join(homedir(), '.codex/skills/scenerok'),
|
|
96
|
+
cursor: join(homedir(), '.cursor/skills/scenerok'),
|
|
97
|
+
aider: join(homedir(), '.aider/skills/scenerok'),
|
|
98
|
+
};
|
|
99
|
+
if (platform && dirs[platform] && existsSync(dirs[platform])) {
|
|
100
|
+
return dirs[platform];
|
|
101
|
+
}
|
|
102
|
+
for (const dir of Object.values(dirs)) {
|
|
103
|
+
if (existsSync(join(dir, 'SKILL.md')))
|
|
104
|
+
return dir;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
export async function checkSkills(opts = {}) {
|
|
109
|
+
const latest = await fetchManifest(opts.manifestUrl);
|
|
110
|
+
const location = opts.dir ?? locateSkillDir() ?? null;
|
|
111
|
+
const installed = location ? hashInstalled(location, latest) : {};
|
|
112
|
+
const diff = diffSkills(installed, latest);
|
|
113
|
+
return { location, ...diff };
|
|
114
|
+
}
|
|
115
|
+
/** List extra local files not in manifest (legacy bundled files). */
|
|
116
|
+
export function listExtraLocalFiles(skillDir, manifest) {
|
|
117
|
+
if (!existsSync(skillDir))
|
|
118
|
+
return [];
|
|
119
|
+
const known = new Set(Object.keys(manifest.skills).map(slugToFilename));
|
|
120
|
+
known.add('SKILL.md');
|
|
121
|
+
return readdirSync(skillDir).filter((f) => !known.has(f));
|
|
122
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 3 Tips Fitness Reel - Reusable Template
|
|
2
|
+
# Demonstrates dynamic timing [-] blocks + xAI video generation per tip (explicit import form)
|
|
3
|
+
|
|
4
|
+
import xai from "@scenerok/xai"
|
|
5
|
+
|
|
6
|
+
# === INPUTS (user provides these) ===
|
|
7
|
+
input tip1_text = "{{tip1_text | Tip 1 goes here}}"
|
|
8
|
+
input tip2_text = "{{tip2_text | Tip 2 goes here}}"
|
|
9
|
+
input tip3_text = "{{tip3_text | Tip 3 goes here}}"
|
|
10
|
+
input cta_text = "{{cta_text | Save this for later}}"
|
|
11
|
+
|
|
12
|
+
# === DYNAMIC TIP SECTIONS ===
|
|
13
|
+
# Each [-] block auto-calculates duration based on content
|
|
14
|
+
[-] = text tip1_text, style: title, position: center, color: "#FFFFFF", size: 64, align: center
|
|
15
|
+
|
|
16
|
+
# xAI generates a fresh visual for this specific tip
|
|
17
|
+
[-] = video xai.imagine(
|
|
18
|
+
"Cinematic fitness shot: " + tip1_text,
|
|
19
|
+
aspect_ratio: "9:16",
|
|
20
|
+
resolution: "1k"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
[-] = text tip2_text, style: title, position: center, color: "#FFFFFF", size: 64, align: center
|
|
24
|
+
|
|
25
|
+
[-] = video xai.imagine(
|
|
26
|
+
"Cinematic fitness shot: " + tip2_text,
|
|
27
|
+
aspect_ratio: "9:16",
|
|
28
|
+
resolution: "1k"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
[-] = text tip3_text, style: title, position: center, color: "#FFFFFF", size: 64, align: center
|
|
32
|
+
|
|
33
|
+
[-] = video xai.imagine(
|
|
34
|
+
"Cinematic fitness shot: " + tip3_text,
|
|
35
|
+
aspect_ratio: "9:16",
|
|
36
|
+
resolution: "1k"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# === CTA + BRANDING ===
|
|
40
|
+
[- 2s] = text cta_text, style: cta, position: bottom, color: "#FF0055", size: 48
|
|
41
|
+
|
|
42
|
+
# Subtle brand watermark
|
|
43
|
+
[-] = text "SceneRok", style: watermark, position: "85%, 92%", color: "#888888", size: 28, opacity: 0.6
|
|
44
|
+
|
|
45
|
+
output to "3-tips-fitness.mp4", resolution: "1080x1920", fps: 30
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Ecom Product Launch Reel - Reusable Template
|
|
2
|
+
# Shows product + lifestyle shot generated by xAI (@scenerok/xai), with strong CTA
|
|
3
|
+
|
|
4
|
+
import xai from "@scenerok/xai"
|
|
5
|
+
|
|
6
|
+
# === INPUTS ===
|
|
7
|
+
input product_name = "{{product_name | Your Product}}"
|
|
8
|
+
input hook_text = "{{hook_text | The one thing that actually works}}"
|
|
9
|
+
input price = "{{price | $49}}"
|
|
10
|
+
input cta_text = "{{cta_text | Shop Now}}"
|
|
11
|
+
|
|
12
|
+
# === HOOK ===
|
|
13
|
+
[0s .. 3s] = text hook_text, style: hook, position: center, color: "#FFFFFF", size: 72, align: center
|
|
14
|
+
|
|
15
|
+
# === PRODUCT VISUAL (xAI generated lifestyle video) ===
|
|
16
|
+
[3s .. 10s] = video xai.imagine(
|
|
17
|
+
"Premium lifestyle product shot of " + product_name + ", cinematic lighting, modern aesthetic, 9:16 vertical",
|
|
18
|
+
aspect_ratio: "9:16",
|
|
19
|
+
resolution: "1k"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# === PRICE + URGENCY ===
|
|
23
|
+
[8s .. 12s] = text "Only " + price, style: price, position: "center, 70%", color: "#FF0055", size: 56
|
|
24
|
+
|
|
25
|
+
# === STRONG CTA ===
|
|
26
|
+
[- 3s] = text cta_text, style: cta, position: bottom, color: "#FFFFFF", size: 64, stroke: "#000000", stroke_width: 4
|
|
27
|
+
|
|
28
|
+
# Subtle brand element
|
|
29
|
+
[-] = text "SceneRok", style: watermark, position: "85%, 92%", color: "#888888", size: 26, opacity: 0.5
|
|
30
|
+
|
|
31
|
+
output to "product-launch.mp4", resolution: "1080x1920", fps: 30
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Glitch Title
|
|
2
|
+
# A stylized intro with glitch effects and bold typography.
|
|
3
|
+
# Great for gaming, tech, or edgy brand content.
|
|
4
|
+
|
|
5
|
+
input background = "{{background_clip}}"
|
|
6
|
+
|
|
7
|
+
[0s .. 5s] = background
|
|
8
|
+
background.Trim(start: 0s, end: 5s)
|
|
9
|
+
background.Opacity(value: 0.8, duration: 5s)
|
|
10
|
+
|
|
11
|
+
[0.5s .. 3s] = text "{{title}}", font: "Bebas Neue", position: center, color: "#00FF88", size: 72, stroke: "#000000", stroke_width: 4, x: "50%", y: "45%", align: center, letter_spacing: 4
|
|
12
|
+
|
|
13
|
+
[3.2s .. 4.5s] = text "{{subtitle}}", font: "Oswald", position: center, color: "#FF0055", size: 96, stroke: "#000000", stroke_width: 4, x: "50%", y: "60%", align: center, rotation: 2, shadow_color: "#00FF88", shadow_blur: 8
|
|
14
|
+
|
|
15
|
+
[0s .. 5s] = filter "glitch", intensity: 0.4
|
|
16
|
+
|
|
17
|
+
output to "glitch-title.mp4", resolution: "1080x1920", fps: 30
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Meme Remix
|
|
2
|
+
# Fast-cut format with top and bottom text overlays.
|
|
3
|
+
# Perfect for reaction clips, punchline edits, and viral-style content.
|
|
4
|
+
|
|
5
|
+
input clip = "{{reaction_clip}}"
|
|
6
|
+
|
|
7
|
+
[0s .. 4s] = clip
|
|
8
|
+
clip.Trim(start: 0s, end: 4s)
|
|
9
|
+
clip.Speed(factor: 1.2)
|
|
10
|
+
|
|
11
|
+
[0.1s .. 1.5s] = text "{{hook_text}}", style: title, position: top, color: "#FFFFFF", size: 56, stroke: "#000000", stroke_width: 3
|
|
12
|
+
|
|
13
|
+
[2.5s .. 3.8s] = text "{{punchline}}", style: title, position: bottom, color: "#EF4444", size: 64, stroke: "#000000", stroke_width: 4
|
|
14
|
+
|
|
15
|
+
output to "meme-remix.mp4", resolution: "1080x1920", fps: 30
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Minimal Text Reel v2
|
|
2
|
+
# A clean text-only reel demonstrating advanced typography:
|
|
3
|
+
# dynamic placement, rotation, Google Fonts, shadows, letter spacing.
|
|
4
|
+
|
|
5
|
+
[0s .. 2.5s] = text "{{headline}}", font: "Bebas Neue", size: 96, color: "#FFFFFF", x: "50%", y: "30%", align: center, letter_spacing: 8, shadow_color: "#FF0055", shadow_blur: 10, shadow_offset_x: 4, shadow_offset_y: 4
|
|
6
|
+
|
|
7
|
+
[2s .. 4s] = text "{{subtitle}}", font: "Inter", size: 48, color: "#00FF88", x: "50%", y: "55%", align: center, rotation: -3, opacity: 0.9
|
|
8
|
+
|
|
9
|
+
[3.5s .. 5.5s] = text "{{cta}}", font: "Space Grotesk", size: 44, color: "#6366F1", x: "50%", y: "75%", align: center, shadow_color: "#000000", shadow_blur: 16, shadow_offset_y: 6
|
|
10
|
+
|
|
11
|
+
output to "minimal-text-reel.mp4", resolution: "1080x1920", fps: 30
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Product Launch
|
|
2
|
+
# Showcase a product with bold headlines and a clear call-to-action.
|
|
3
|
+
# Uses a subtle vignette filter for cinematic depth.
|
|
4
|
+
|
|
5
|
+
input hero = "{{hero_clip}}"
|
|
6
|
+
|
|
7
|
+
[0s .. 6s] = hero
|
|
8
|
+
hero.Trim(start: 0s, end: 6s)
|
|
9
|
+
|
|
10
|
+
[0.3s .. 2.3s] = text "{{headline}}", style: title, position: top, color: "#FFFFFF", size: 64, stroke: "#000000", stroke_width: 3
|
|
11
|
+
|
|
12
|
+
[2.5s .. 4.5s] = text "{{description}}", style: caption, position: center, color: "#F3F4F6", size: 36, stroke: "#000000", stroke_width: 2
|
|
13
|
+
|
|
14
|
+
[4.5s .. 5.8s] = text "{{cta}}", style: title, position: bottom, color: "#F59E0B", size: 52, stroke: "#000000", stroke_width: 3
|
|
15
|
+
|
|
16
|
+
[0s .. 6s] = filter "vignette", intensity: 0.35
|
|
17
|
+
|
|
18
|
+
output to "product-launch.mp4", resolution: "1080x1920", fps: 30
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# RokMilk Chocolate Milkshake — 15s promo (correct VidScript v2 patterns)
|
|
2
|
+
# Requires: scenerok secrets set XAI_API_KEY=... (or configured on scenerok.com account)
|
|
3
|
+
|
|
4
|
+
import xai from "@scenerok/xai"
|
|
5
|
+
import motion from "@scenerok/basic-animations"
|
|
6
|
+
|
|
7
|
+
# Generated visuals — one clip per segment; prompts must use straight double quotes only
|
|
8
|
+
[-] = video xai.imagine("Cinematic slow-motion pour of rich creamy chocolate milk into a sleek modern 250ml carton on marble, warm studio lighting, shallow depth of field, 9:16 vertical", aspect_ratio: "9:16", duration: 5)
|
|
9
|
+
|
|
10
|
+
[-] = video xai.imagine("Elegant rotating product shot of premium chocolate milk carton labeled RokMilk, brown and gold palette, soft bokeh, advertising visual, 9:16 vertical", aspect_ratio: "9:16", duration: 5)
|
|
11
|
+
|
|
12
|
+
[-] = video xai.imagine("Silky chocolate milk swirl in glass with RokMilk carton blurred in background, golden light, appetizing food cinematography, 9:16 vertical", aspect_ratio: "9:16", duration: 5)
|
|
13
|
+
|
|
14
|
+
[0s .. 2.5s] = text "RokMilk", font: "Bebas Neue", size: 80, color: "#FFD700", x: "50%", y: "18%", align: center, stroke: "#3E1C00", stroke_width: 4, letter_spacing: 6, animate: motion.fadeIn(0.6s)
|
|
15
|
+
|
|
16
|
+
[2s .. 5s] = text "Chocolate Milkshake", font: "Outfit", size: 56, color: "#FFFFFF", x: "50%", y: "45%", align: center, stroke: "#3E1C00", stroke_width: 3, animate: motion.riseIn(0.8s)
|
|
17
|
+
|
|
18
|
+
[5s .. 8s] = text "250ml of Pure Indulgence", font: "Inter", size: 42, color: "#F3E5AB", x: "50%", y: "55%", align: center, stroke: "#3E1C00", stroke_width: 2, animate: motion.slideY(40, 0, 0.8s)
|
|
19
|
+
|
|
20
|
+
[11s .. 14s] = text "visit your nearest grocery store", font: "Bebas Neue", size: 48, color: "#FFD700", x: "50%", y: "82%", align: center, stroke: "#3E1C00", stroke_width: 4, letter_spacing: 3, animate: motion.fadeIn(1s)
|
|
21
|
+
|
|
22
|
+
[0s .. 15s] = filter "vignette", intensity: 0.3
|
|
23
|
+
[0s .. 15s] = filter "saturation", value: 1.12
|
|
24
|
+
|
|
25
|
+
output to "rokmilk-chocolate-promo.mp4", resolution: "1080x1920", fps: 30, background: "#1A0F05"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Testimonial Cut
|
|
2
|
+
# A quote-focused format with a subtle vintage feel.
|
|
3
|
+
# Great for customer stories, reviews, and social proof clips.
|
|
4
|
+
|
|
5
|
+
input speaker = "{{speaker_clip}}"
|
|
6
|
+
|
|
7
|
+
[0s .. 5s] = speaker
|
|
8
|
+
speaker.Trim(start: 0s, end: 5s)
|
|
9
|
+
|
|
10
|
+
[0.5s .. 3s] = text "{{quote}}", style: subtitle, position: center, color: "#FFFFFF", size: 42, stroke: "#000000", stroke_width: 2
|
|
11
|
+
|
|
12
|
+
[3.2s .. 4.8s] = text "{{attribution}}", style: caption, position: bottom, color: "#D1D5DB", size: 32, stroke: "#000000", stroke_width: 2
|
|
13
|
+
|
|
14
|
+
[0s .. 5s] = filter "sepia", intensity: 0.3
|
|
15
|
+
|
|
16
|
+
output to "testimonial.mp4", resolution: "1080x1920", fps: 30
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# UGC Testimonial with Voiceover - Reusable Template
|
|
2
|
+
# Uses xAI (via explicit import) for TTS voiceover + generated visuals
|
|
3
|
+
|
|
4
|
+
import xai from "@scenerok/xai"
|
|
5
|
+
|
|
6
|
+
# === INPUTS ===
|
|
7
|
+
input quote = "{{quote | This product completely changed how I work}}"
|
|
8
|
+
input customer_name = "{{customer_name | Sarah K.}}"
|
|
9
|
+
input product_name = "{{product_name | SceneRok}}"
|
|
10
|
+
|
|
11
|
+
# === VOICEOVER + VISUAL ===
|
|
12
|
+
[-] = audio xai.tts(
|
|
13
|
+
quote + ". " + customer_name + " from " + product_name,
|
|
14
|
+
voice: "eve",
|
|
15
|
+
speed: 1.05
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
[-] = video xai.imagine(
|
|
19
|
+
"Cinematic lifestyle shot representing: " + quote + ", premium modern aesthetic, 9:16 vertical",
|
|
20
|
+
aspect_ratio: "9:16",
|
|
21
|
+
resolution: "1k"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# === QUOTE TEXT (appears while voiceover plays) ===
|
|
25
|
+
[-] = text "\"" + quote + "\"", style: quote, position: center, color: "#FFFFFF", size: 52, align: center
|
|
26
|
+
|
|
27
|
+
# === ATTRIBUTION ===
|
|
28
|
+
[- 2s] = text "— " + customer_name, style: attribution, position: "center, 75%", color: "#CCCCCC", size: 32
|
|
29
|
+
|
|
30
|
+
# === BRANDING ===
|
|
31
|
+
[-] = text "Made with SceneRok", style: watermark, position: "85%, 92%", color: "#888888", size: 24, opacity: 0.6
|
|
32
|
+
|
|
33
|
+
output to "ugc-testimonial.mp4", resolution: "1080x1920", fps: 30
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Voice + Visual Promo
|
|
2
|
+
# Combines main footage with voiceover or music and layered titles.
|
|
3
|
+
|
|
4
|
+
input main = "{{main_video}}"
|
|
5
|
+
input voice = "{{voiceover_audio}}"
|
|
6
|
+
|
|
7
|
+
[0s .. 12s] = main
|
|
8
|
+
main.Trim(start: 0s, end: 12s)
|
|
9
|
+
|
|
10
|
+
[0s .. 12s] = audio voice, volume: 0.9
|
|
11
|
+
|
|
12
|
+
[0.5s .. 3s] = text "{{title}}", font: "Inter", size: 68, color: "{{brand_color}}", x: "50%", y: "22%", align: center, stroke: "#111827", stroke_width: 3
|
|
13
|
+
|
|
14
|
+
[3s .. 7s] = text "{{subtitle}}", font: "Inter", size: 34, color: "#F3F4F6", x: "50%", y: "50%", align: center, stroke: "#0F172A", stroke_width: 2
|
|
15
|
+
|
|
16
|
+
[7s .. 11s] = text "{{cta}}", font: "Inter", size: 56, color: "#FFFFFF", x: "50%", y: "78%", align: center, stroke: "#000000", stroke_width: 3
|
|
17
|
+
|
|
18
|
+
[0s .. 12s] = filter "vignette", intensity: 0.22
|
|
19
|
+
[0s .. 12s] = filter "saturation", value: 1.12
|
|
20
|
+
|
|
21
|
+
output to "voice-visual-promo.mp4", resolution: "1080x1920", fps: 30
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scenerok/cli",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "SceneRok CLI - Create videos from your terminal and agent workflows",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"scenerok": "dist/index.js",
|
|
8
|
+
"sr": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"skills",
|
|
13
|
+
"examples",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"sync:skills": "node ../../scripts/sync-scenerok-skills.mjs",
|
|
20
|
+
"prepublishOnly": "npm run sync:skills && npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"scenerok",
|
|
24
|
+
"@scenerok/cli",
|
|
25
|
+
"video",
|
|
26
|
+
"cli",
|
|
27
|
+
"vidscript",
|
|
28
|
+
"agent",
|
|
29
|
+
"ai"
|
|
30
|
+
],
|
|
31
|
+
"author": "SceneRok",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"commander": "^12.0.0",
|
|
35
|
+
"chalk": "^5.3.0",
|
|
36
|
+
"ora": "^8.0.1",
|
|
37
|
+
"inquirer": "^9.2.0",
|
|
38
|
+
"node-fetch": "^3.3.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/inquirer": "^9.0.7",
|
|
42
|
+
"@types/node": "^20.11.0",
|
|
43
|
+
"typescript": "^5.3.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# AI Models — Video and Image Generation
|
|
2
|
+
|
|
3
|
+
## Modality picker
|
|
4
|
+
|
|
5
|
+
| Intent | API | When |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| Clean still / key visual | `xai.image(...)` · `cf.image(...)` | Product shot, poster frame |
|
|
8
|
+
| Text-to-video | `xai.imagine(...)` · `xai.genVideo(...)` · `cf.video(...)` | B-roll, lifestyle, cinematic |
|
|
9
|
+
| Animate one image | `xai.imageToVideo(img, prompt, ...)` · `cf.imageToVideo(...)` | Product photo → motion |
|
|
10
|
+
| Multi-reference scene | `xai.referenceToVideo([imgs], prompt, ...)` | Brand elements guide scene |
|
|
11
|
+
| Extend existing clip | `xai.extendVideo(...)` · `cf.extendVideo(...)` | Continue generated clip |
|
|
12
|
+
|
|
13
|
+
**Always import:** `import xai from "@scenerok/xai"` and/or `import cf from "@scenerok/cloudflare"`.
|
|
14
|
+
|
|
15
|
+
Prefer **720p** (or the model default) for generative video. Cap spend with duration, not exotic post-steps.
|
|
16
|
+
|
|
17
|
+
Generative prompts must require: no text, no words, no letters, no captions, no logos, no watermarks, no readable UI copy.
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Grok genVideo()
|
|
22
|
+
|
|
23
|
+
Explicit duration, resolution, and aspect ratio for text-to-video.
|
|
24
|
+
|
|
25
|
+
**Landscape 8 s**
|
|
26
|
+
|
|
27
|
+
```vidscript
|
|
28
|
+
import xai from "@scenerok/xai"
|
|
29
|
+
|
|
30
|
+
[- 3s] = video xai.genVideo("Timelapse of clouds over desert mesas, wide cinematic, anamorphic flare", duration: 3, aspect_ratio: "16:9", resolution: "720p")
|
|
31
|
+
|
|
32
|
+
[prev .. prev + 0.8s] = text "genVideo()", font: "Inter Bold", size: 44, color: "#FFFFFF", shadow_blur: 10, align: "center", y: "88%"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Vertical 6 s reel**
|
|
36
|
+
|
|
37
|
+
```vidscript
|
|
38
|
+
import xai from "@scenerok/xai"
|
|
39
|
+
|
|
40
|
+
[- 3s] = video xai.genVideo("Drone shot over a futuristic city at sunset, lens flare, cinematic", duration: 3, aspect_ratio: "9:16", resolution: "720p")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Grok imagine()
|
|
44
|
+
|
|
45
|
+
One-call text-to-video. Default 5 s, 720p, 9:16.
|
|
46
|
+
|
|
47
|
+
**Ocean drone**
|
|
48
|
+
|
|
49
|
+
```vidscript
|
|
50
|
+
import xai from "@scenerok/xai"
|
|
51
|
+
|
|
52
|
+
[- 3s] = video xai.imagine("Slow aerial drone over turquoise ocean waves, golden hour, cinematic", aspect_ratio: "16:9", resolution: "720p")
|
|
53
|
+
|
|
54
|
+
[prev .. prev + 0.8s] = text "imagine()", font: "Inter Bold", size: 48, color: "#FFFFFF", align: "center", y: "85%"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Street market**
|
|
58
|
+
|
|
59
|
+
```vidscript
|
|
60
|
+
import xai from "@scenerok/xai"
|
|
61
|
+
|
|
62
|
+
[- 3s] = video xai.imagine("A neon-lit street market at night, cinematic, volumetric light", aspect_ratio: "9:16", resolution: "720p")
|
|
63
|
+
|
|
64
|
+
[prev .. prev + 0.8s] = text "Grok imagine()", font: "Inter Bold", size: 56, color: "#FFFFFF", shadow_color: "#000000", shadow_blur: 12, position: "bottom", align: "center"
|
|
65
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Animations Catalog
|
|
2
|
+
|
|
3
|
+
All `animations.<fn>(...)` blocks from `@scenerok/animations`. Total: **67** entries.
|
|
4
|
+
|
|
5
|
+
```vidscript
|
|
6
|
+
import animations from "@scenerok/animations"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
| fn | family | verified | defaultDuration | params |
|
|
10
|
+
| --- | --- | --- | --- | --- |
|
|
11
|
+
| `app_showcase` | other | yes | 6s | positional: `imageUrl` · named: `imageUrl, userName, userRole, tagline, accentColor` |
|
|
12
|
+
| `apple_money_count` | other | yes | 5s | positional: `amount` · named: `amount, currency, accentColor` |
|
|
13
|
+
| `blend_caption` | lower-third | yes | 5s | positional: `text` · named: `text, color` |
|
|
14
|
+
| `blue_sweater_intro_video` | other | yes | 12s | positional: `creatorName` · named: `headline, creatorName, creatorHandle, creatorBio, avatarUrl` |
|
|
15
|
+
| `code_3d_extrude` | code-anim | yes | 8s | positional: `code` · named: `code, lang, theme, filename` |
|
|
16
|
+
| `code_diff` | code-anim | yes | 6s | positional: `from, to` · named: `from, to, lang, theme` |
|
|
17
|
+
| `code_highlight` | code-anim | yes | 5s | positional: `code` · named: `code, lang, theme, filename` |
|
|
18
|
+
| `code_morph` | code-anim | yes | 7s | positional: `from, to` · named: `from, to, lang, theme` |
|
|
19
|
+
| `code_particle_assemble` | code-anim | yes | 8s | positional: `code` · named: `code, lang, theme, filename` |
|
|
20
|
+
| `code_scroll` | code-anim | yes | 6s | positional: `code` · named: `code, lang, theme, filename` |
|
|
21
|
+
| `code_shader_dissolve` | code-anim | yes | 7s | positional: `code` · named: `code, lang, theme, filename` |
|
|
22
|
+
| `code_snippet_apple_terminal_basic` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
23
|
+
| `code_snippet_apple_terminal_clear_dark` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
24
|
+
| `code_snippet_apple_terminal_clear_light` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
25
|
+
| `code_snippet_apple_terminal_grass` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
26
|
+
| `code_snippet_apple_terminal_homebrew` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
27
|
+
| `code_snippet_apple_terminal_man_page` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
28
|
+
| `code_snippet_apple_terminal_novel` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
29
|
+
| `code_snippet_apple_terminal_ocean` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
30
|
+
| `code_snippet_apple_terminal_pro` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
31
|
+
| `code_snippet_apple_terminal_red_sands` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
32
|
+
| `code_snippet_apple_terminal_silver_aerogel` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
33
|
+
| `code_snippet_apple_terminal_solid_colors` | code-snippet | yes | 12s | positional: `command` · named: `command, output, prompt, title` |
|
|
34
|
+
| `code_snippet_dark_2026` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
35
|
+
| `code_snippet_dark_modern` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
36
|
+
| `code_snippet_dark_plus` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
37
|
+
| `code_snippet_flight` | code-snippet | yes | 6s | positional: `code` · named: `code, lang, theme, filename` |
|
|
38
|
+
| `code_snippet_high_contrast` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
39
|
+
| `code_snippet_high_contrast_light` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
40
|
+
| `code_snippet_light_2026` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
41
|
+
| `code_snippet_light_modern` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
42
|
+
| `code_snippet_light_plus` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
43
|
+
| `code_snippet_monokai` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
44
|
+
| `code_snippet_solarized_light` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
45
|
+
| `code_snippet_visual_studio_dark` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
46
|
+
| `code_snippet_visual_studio_light` | code-snippet | yes | 11s | positional: `code` · named: `code, filename, lang` |
|
|
47
|
+
| `code_typing` | code-anim | yes | 5s | positional: `code` · named: `code, lang, theme, filename` |
|
|
48
|
+
| `instagram_follow` | social-card | yes | 5s | positional: `displayName, handle` · named: `displayName, handle, followers, avatarUrl, url` |
|
|
49
|
+
| `ios26_liquid_glass` | other | yes | 15s | positional: `(none)` · named: `(none)` |
|
|
50
|
+
| `liquid_glass_context_menu` | other | yes | 8s | positional: `fileName` · named: `fileName, fileMeta` |
|
|
51
|
+
| `liquid_glass_media_controls` | other | yes | 8s | positional: `trackTitle` · named: `trackTitle, artist` |
|
|
52
|
+
| `liquid_glass_notification` | other | yes | 8s | positional: `title` · named: `title, body, messageText` |
|
|
53
|
+
| `liquid_glass_widgets` | other | yes | 8s | positional: `showcaseTitle` · named: `showcaseTitle, showcaseDescription` |
|
|
54
|
+
| `logo_outro` | brand | yes | 6s | positional: `tagline` · named: `tagline, url` |
|
|
55
|
+
| `lower_third` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle` |
|
|
56
|
+
| `lower_third_bild` | lower-third | yes | 8s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
57
|
+
| `lt_accent_underline` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
58
|
+
| `lt_bold_block` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
59
|
+
| `lt_clean_bar` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
60
|
+
| `lt_color_block` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
61
|
+
| `lt_dark_card` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
62
|
+
| `lt_kicker_name` | lower-third | yes | 5s | positional: `kicker, title` · named: `kicker, title, accent` |
|
|
63
|
+
| `lt_mask_reveal` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
64
|
+
| `lt_side_rule` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
65
|
+
| `lt_soft_pill` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
66
|
+
| `lt_stack_bars` | lower-third | yes | 5s | positional: `title, subtitle` · named: `title, subtitle, accent, bg` |
|
|
67
|
+
| `macos_notification` | social-card | yes | 5s | positional: `appName, title` · named: `appName, timeLabel, title, body` |
|
|
68
|
+
| `macos_tahoe_liquid_glass` | other | yes | 15s | positional: `windowTitle` · named: `wallpaperUrl, windowTitle, toastBody, weatherLocation` |
|
|
69
|
+
| `news_ticker` | social-card | yes | 7s | positional: `headline` · named: `desk, time, label, headline, headlineAccent, ticker` |
|
|
70
|
+
| `reddit_post` | social-card | yes | 5s | positional: `subreddit, title` · named: `subreddit, author, title, body, votes` |
|
|
71
|
+
| `spotify_card` | social-card | yes | 5s | positional: `track, artist` · named: `track, artist` |
|
|
72
|
+
| `tiktok_follow` | social-card | yes | 5s | positional: `displayName, handle` · named: `displayName, handle, followers, avatarUrl, url` |
|
|
73
|
+
| `ui_3d_reveal` | other | yes | 13s | positional: `heroTitle` · named: `heroTitle, outroUrl` |
|
|
74
|
+
| `vfx_iphone_device` | other | yes | 15s | positional: `imageUrl` · named: `imageUrl` |
|
|
75
|
+
| `vpn_youtube_spot` | other | yes | 7s | positional: `headlineLine1` · named: `headlineLine1, headlineLine2, subtext, appTitle, appSubtitle` |
|
|
76
|
+
| `x_post` | social-card | yes | 5s | positional: `displayName, handle` · named: `displayName, handle, body, hashtag, timestamp` |
|
|
77
|
+
| `yt_lower_third` | lower-third | yes | 5s | positional: `channel, subscribers` · named: `channel, subscribers, avatarUrl` |
|
|
78
|
+
|
|
79
|
+
## Unverified blocks
|
|
80
|
+
|
|
81
|
+
Blocks with `verified: no` vendor upstream demo content and may ignore user params. `scenerok validate` warns but does not error.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
```vidscript
|
|
86
|
+
[0s .. 6s] = animations.code_typing("const x = 1;")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
See `animations-picker.md` for intent-based selection. Preview samples: https://scenerok.com/catalog#animations
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Animation Block Picker
|
|
2
|
+
|
|
3
|
+
`import animations from "@scenerok/animations"` surfaces the vendored HyperFrames block catalog as positionable, time-windowed VidScript surfaces. Each `animations.<fn>(...)` call resolves to a `hyperframes` IR surface; no LLM runs at render time.
|
|
4
|
+
|
|
5
|
+
**Reuse-first:** prefer `animations.<fn>(...)` over hand-authoring composition HTML. Full per-fn param shapes: `animations-catalog.md`.
|
|
6
|
+
|
|
7
|
+
## Intent → `animations.<fn>(...)`
|
|
8
|
+
|
|
9
|
+
| Intent | Family | `animations.<fn>(…)` | Positional args (in order) |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| typing / live code walk | code-anim | `code_typing` | `code`, `lang`, `theme`, `filename` |
|
|
12
|
+
| scroll through a file | code-anim | `code_scroll` | `code`, `lang`, `theme` |
|
|
13
|
+
| spotlight a line | code-anim | `code_highlight` | `code`, `lang`, `theme` |
|
|
14
|
+
| 3D extrude / shader dissolve / particle title | code-anim | `code_3d_extrude` · `code_shader_dissolve` · `code_particle_assemble` | `code`, `theme` |
|
|
15
|
+
| assemble snippets in flight | code-anim | `code_snippet_flight` | `code`, `theme` |
|
|
16
|
+
| before/after code change | code-anim | `code_morph` · `code_diff` | `from`, `to`, `lang`, `theme` |
|
|
17
|
+
| themed VS Code window | code-snippet | 24 `code_snippet_*` themes | `code`, `filename` |
|
|
18
|
+
| Apple Terminal command + output | code-snippet | 12 `code_snippet_apple_terminal_*` | `command`, `output`, `prompt`, `title` |
|
|
19
|
+
| broadcast name / title bar | lower-third | `lt_clean_bar` · `lt_accent_underline` · `lt_kicker_name` · `lt_mask_reveal` · `lt_stack_bars` | `title`, `subtitle`, `accent?` |
|
|
20
|
+
| lower-third with kicker | lower-third | `lt_kicker_name` · `lt_side_rule` | `kicker`, `title`, `accent?` |
|
|
21
|
+
| YouTube channel card | lower-third | `yt_lower_third` | `channel`, `subscribers`, `avatarUrl?` |
|
|
22
|
+
| Bild-style brand accent | lower-third | `lower_third_bild` | `title`, `subtitle`, `accent?` |
|
|
23
|
+
| inline text over footage | text-overlay | `lower_third` · `blend_caption` | inline `{{title}}` / `{{subtitle}}` tokens |
|
|
24
|
+
| X / Twitter post | social-card | `x_post` | `displayName`, `handle`, `body?`, `hashtag?`, `timestamp?` |
|
|
25
|
+
| Reddit thread | social-card | `reddit_post` | `subreddit`, `title`, `body?`, `author?`, `votes?` |
|
|
26
|
+
| TikTok / Instagram follow | social-card | `tiktok_follow` · `instagram_follow` | `displayName`, `handle`, `avatarUrl?`, `url?` |
|
|
27
|
+
| Spotify now-playing | social-card | `spotify_card` | `track`, `artist` |
|
|
28
|
+
| macOS notification | social-card | `macos_notification` | `appName`, `title`, `body?`, `timeLabel?` |
|
|
29
|
+
| news ticker / headline crawl | social-card | `news_ticker` | `headline`, `headlineAccent?`, `ticker?`, `desk?`, `time?` |
|
|
30
|
+
| logo outro / brand sting | brand | `logo_outro` | `tagline`, `url?` |
|
|
31
|
+
| punchy kinetic title (no block) | kinetic-type | (use `text` + `motion.*` — see `text-styles.md`) | — |
|
|
32
|
+
|
|
33
|
+
Param resolution: **named > positional > default**.
|
|
34
|
+
|
|
35
|
+
## Gotchas
|
|
36
|
+
|
|
37
|
+
| Block / family | Issue | Fix |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `code_diff` | Line indices may be 0-based in adapter | Match sample in `animations-catalog.md` |
|
|
40
|
+
| Unverified blocks | `scenerok validate` warns `verified:false` | Accept demo render or switch to verified family |
|
|
41
|
+
| Maps / liquid-glass / app-showcase | Ignore user params | Use for demo aesthetic only, or hand-author |
|
|
42
|
+
| Adapter blocks | No inline audio/text siblings | Put `audio`, `text`, `filter` on separate time blocks |
|
|
43
|
+
|
|
44
|
+
## Two-phase compose recipe
|
|
45
|
+
|
|
46
|
+
**Part 1 — classify (before probing assets):**
|
|
47
|
+
|
|
48
|
+
1. Decide intent family from the table above.
|
|
49
|
+
2. Pick `animations.<fn>` (or `text` + `motion.*` for kinetic-type).
|
|
50
|
+
3. Note param values from the user request.
|
|
51
|
+
4. Draft time blocks: `[<start>s .. <end>s] = animations.<fn>(...)`.
|
|
52
|
+
|
|
53
|
+
**Part 2 — compose (after probing assets):**
|
|
54
|
+
|
|
55
|
+
1. Probe assets (logo, avatar, code) — never guess dimensions.
|
|
56
|
+
2. Resolve params: named override positional; defaults fill the rest.
|
|
57
|
+
3. Emit VidScript call.
|
|
58
|
+
4. Adjacent `audio`, `text`, `filter` on **separate** time blocks.
|
|
59
|
+
5. Run `scenerok validate script.vid`.
|
|
60
|
+
|
|
61
|
+
## Demo script
|
|
62
|
+
|
|
63
|
+
```vidscript
|
|
64
|
+
import animations from "@scenerok/animations"
|
|
65
|
+
import tr from "@scenerok/transitions"
|
|
66
|
+
output to "demo.mp4", resolution: "1920x1080", fps: 30
|
|
67
|
+
|
|
68
|
+
[0s .. 6s] = animations.code_typing("let abc = 23; abc += 10; console.log(abc);")
|
|
69
|
+
[6s .. 6.6s] = tr.dissolve()
|
|
70
|
+
[6.6s .. 13s] = animations.code_morph("const a = 1", "const a = 2")
|
|
71
|
+
[13s .. 19s] = animations.code_typing(code: "const z = 5;", duration: 6, filename: "app.ts", theme: "monokai")
|
|
72
|
+
[19s .. 24s] = animations.lt_clean_bar("Ada Lovelace", "Engineer", accent: "#00ff00")
|
|
73
|
+
[24s .. 29s] = animations.x_post("SceneRok", "@scenerok", body: "Shipped adapters.", hashtag: "#v1")
|
|
74
|
+
[29s .. 34s] = animations.news_ticker("Tech Desk", time: "15:30 UTC",
|
|
75
|
+
headline: "SceneRok ships", headlineAccent: "53 verified blocks",
|
|
76
|
+
ticker: "Verified count: 53/78\nBeta opens July")
|
|
77
|
+
```
|