@scenerok/cli 2.2.0 → 2.4.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 +6 -6
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +31 -25
- package/dist/utils/skillsManifest.d.ts +13 -3
- package/dist/utils/skillsManifest.d.ts.map +1 -1
- package/dist/utils/skillsManifest.js +35 -12
- package/examples/system/rokmilk-chocolate-promo.vid +1 -1
- package/examples/system/voice-visual-promo.vid +1 -1
- package/package.json +3 -3
- package/skills/scenerok/SKILL.md +150 -0
- package/skills/{remote → scenerok/references}/ai-models.md +1 -2
- package/skills/{remote → scenerok/references}/animations-catalog.md +139 -2
- package/skills/{remote → scenerok/references}/animations-picker.md +12 -1
- package/skills/{remote → scenerok/references}/brand-setup.md +3 -1
- package/skills/{remote → scenerok/references}/catalog-index.json +163 -19
- package/skills/scenerok/references/editing-actions.md +560 -0
- package/skills/{remote → scenerok/references}/environments.md +23 -24
- package/skills/{remote → scenerok/references}/intent-router.md +5 -15
- package/skills/{remote → scenerok/references}/media-stdlib.md +2 -3
- package/skills/{remote → scenerok/references}/music.md +2 -21
- package/skills/scenerok/references/sdk-guide.md +301 -0
- package/skills/{remote → scenerok/references}/shaders-filters.md +9 -6
- package/skills/{remote → scenerok/references}/text-styles.md +15 -35
- package/skills/{remote → scenerok/references}/transitions-catalog.md +2 -0
- package/skills/{remote → scenerok/references}/tts.md +4 -22
- package/skills/{remote → scenerok/references}/vidscript-guide.md +250 -67
- package/skills/scenerok/references/vidscript-sample.md +44 -0
- package/skills/{remote → scenerok/references}/vidscript-strict.md +41 -26
- package/skills/{remote → scenerok/references}/workflow.md +6 -7
- package/skills/remote/sdk-guide.md +0 -201
- package/skills/remote/vidscript-sample.md +0 -28
- package/skills/shared/SKILL.md +0 -157
package/README.md
CHANGED
|
@@ -51,17 +51,17 @@ scenerok auth logout # Remove credentials
|
|
|
51
51
|
### `skills`
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
scenerok skills install <platform> [--sync] # Install
|
|
55
|
-
scenerok skills
|
|
56
|
-
scenerok skills
|
|
57
|
-
scenerok skills
|
|
58
|
-
scenerok skills list # List
|
|
54
|
+
scenerok skills install <platform> [--sync] # Install the scenerok skill (SKILL.md + references/); --sync refreshes it from scenerok.com
|
|
55
|
+
scenerok skills check [--json] # Compare the installed copy with scenerok.com
|
|
56
|
+
scenerok skills update # Download what changed
|
|
57
|
+
scenerok skills sync # Download every skill file
|
|
58
|
+
scenerok skills list # List skill files and local status
|
|
59
59
|
scenerok skills platforms # List supported agent platforms
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Supported platforms: `opencode`, `claude`, `codex`, `cursor`, `aider`
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
The skill ships inside this package (`skills/scenerok/`), so `install` works offline. scenerok.com serves the same files at https://scenerok.com/.well-known/agent-skills/ and updates them on every deploy; `check` and `update` pick up changes between CLI releases.
|
|
65
65
|
|
|
66
66
|
### `validate`
|
|
67
67
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqGpC,eAAO,MAAM,aAAa,SAqKvB,CAAC"}
|
package/dist/commands/skills.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join, dirname } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
|
-
import { checkSkills, fetchManifest, fetchSkillFile,
|
|
6
|
+
import { checkSkills, fetchManifest, fetchSkillFile, skillFiles, } from '../utils/skillsManifest.js';
|
|
7
7
|
function getPlatformConfig(platform) {
|
|
8
8
|
const configs = {
|
|
9
9
|
opencode: {
|
|
@@ -29,7 +29,8 @@ function getPlatformConfig(platform) {
|
|
|
29
29
|
};
|
|
30
30
|
return configs[platform] || null;
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
/** The skill this package ships: SKILL.md plus references/, copied from the repo's skills/scenerok/ at build. */
|
|
33
|
+
const BUNDLED_SKILL = 'skills/scenerok';
|
|
33
34
|
async function findSkillSource(sourcePath) {
|
|
34
35
|
const modulePath = join(dirname(new URL(import.meta.url).pathname), '../..', sourcePath);
|
|
35
36
|
if (existsSync(modulePath))
|
|
@@ -59,13 +60,18 @@ function printSkillDiff(skills) {
|
|
|
59
60
|
}
|
|
60
61
|
async function syncSkills(skillDir, slugs) {
|
|
61
62
|
const manifest = await fetchManifest();
|
|
62
|
-
const
|
|
63
|
+
const files = skillFiles(manifest);
|
|
64
|
+
const toSync = slugs ?? Object.keys(files);
|
|
63
65
|
let count = 0;
|
|
64
66
|
for (const slug of toSync.sort()) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
+
const path = files[slug];
|
|
68
|
+
if (!path)
|
|
69
|
+
continue;
|
|
70
|
+
const content = await fetchSkillFile(slug, undefined, path);
|
|
71
|
+
const dest = join(skillDir, path);
|
|
72
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
67
73
|
writeFileSync(dest, content, 'utf8');
|
|
68
|
-
console.log(chalk.green(` ✓ ${
|
|
74
|
+
console.log(chalk.green(` ✓ ${path}`));
|
|
69
75
|
count++;
|
|
70
76
|
}
|
|
71
77
|
return count;
|
|
@@ -73,9 +79,9 @@ async function syncSkills(skillDir, slugs) {
|
|
|
73
79
|
export const skillsCommand = new Command('skills')
|
|
74
80
|
.description('Install and sync SceneRok agent skills')
|
|
75
81
|
.addCommand(new Command('install')
|
|
76
|
-
.description('Install the
|
|
82
|
+
.description('Install the scenerok skill (SKILL.md + references) for a platform')
|
|
77
83
|
.argument('<platform>', 'Agent platform: opencode, claude, codex, cursor, aider')
|
|
78
|
-
.option('--sync', '
|
|
84
|
+
.option('--sync', 'Then refresh it from scenerok.com (newer than this CLI release)')
|
|
79
85
|
.action(async (platform, opts) => {
|
|
80
86
|
const config = getPlatformConfig(platform);
|
|
81
87
|
if (!config) {
|
|
@@ -83,28 +89,28 @@ export const skillsCommand = new Command('skills')
|
|
|
83
89
|
console.log(chalk.dim('Supported platforms: opencode, claude, codex, cursor, aider'));
|
|
84
90
|
process.exit(1);
|
|
85
91
|
}
|
|
86
|
-
console.log(chalk.cyan(`\n📦 Installing SceneRok
|
|
87
|
-
|
|
88
|
-
mkdirSync(config.skillDir, { recursive: true });
|
|
89
|
-
}
|
|
90
|
-
const source = await findSkillSource(ROUTER_SKILL.source);
|
|
91
|
-
const dest = join(config.skillDir, ROUTER_SKILL.dest);
|
|
92
|
+
console.log(chalk.cyan(`\n📦 Installing the SceneRok skill for ${config.name}...\n`));
|
|
93
|
+
const source = await findSkillSource(BUNDLED_SKILL);
|
|
92
94
|
if (!source) {
|
|
93
|
-
console.log(chalk.red(` ✗
|
|
95
|
+
console.log(chalk.red(` ✗ Bundled skill not found (${BUNDLED_SKILL})`));
|
|
94
96
|
process.exit(1);
|
|
95
97
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
// The directory is ours: replace it so files from older installs
|
|
99
|
+
// (the flat pre-references layout) don't linger.
|
|
100
|
+
rmSync(config.skillDir, { recursive: true, force: true });
|
|
101
|
+
mkdirSync(config.skillDir, { recursive: true });
|
|
102
|
+
cpSync(source, config.skillDir, { recursive: true });
|
|
103
|
+
console.log(chalk.green(' ✓ SKILL.md'));
|
|
104
|
+
console.log(chalk.green(' ✓ references/'));
|
|
98
105
|
if (opts.sync) {
|
|
99
|
-
console.log(chalk.cyan('\n⬇️
|
|
106
|
+
console.log(chalk.cyan('\n⬇️ Refreshing from scenerok.com...\n'));
|
|
100
107
|
await syncSkills(config.skillDir);
|
|
101
108
|
}
|
|
102
|
-
console.log(chalk.cyan(`\n✅
|
|
103
|
-
console.log(chalk.dim('
|
|
104
|
-
console.log(chalk.dim('Offline fallback: scenerok skills sync'));
|
|
109
|
+
console.log(chalk.cyan(`\n✅ Skill installed to ${config.skillDir}\n`));
|
|
110
|
+
console.log(chalk.dim('Check for newer docs: scenerok skills check · refresh: scenerok skills update'));
|
|
105
111
|
}))
|
|
106
112
|
.addCommand(new Command('sync')
|
|
107
|
-
.description('Download the
|
|
113
|
+
.description('Download the latest skill files from scenerok.com')
|
|
108
114
|
.option('--dir <path>', 'Skill directory (auto-detected if omitted)')
|
|
109
115
|
.option('--platform <platform>', 'Platform to resolve skill dir')
|
|
110
116
|
.action(async (opts) => {
|
|
@@ -186,8 +192,8 @@ export const skillsCommand = new Command('skills')
|
|
|
186
192
|
console.log(chalk.cyan('\n📦 SceneRok remote skills\n'));
|
|
187
193
|
printSkillDiff(result.skills);
|
|
188
194
|
console.log(chalk.dim(`\nManifest: ${Object.keys(manifest.skills).length} skills · source: ${manifest.source}`));
|
|
189
|
-
console.log(chalk.dim('Install
|
|
190
|
-
console.log(chalk.dim('Sync
|
|
195
|
+
console.log(chalk.dim('Install: scenerok skills install <platform>'));
|
|
196
|
+
console.log(chalk.dim('Sync: scenerok skills sync\n'));
|
|
191
197
|
}
|
|
192
198
|
catch (err) {
|
|
193
199
|
console.log(chalk.red(`❌ ${err.message}`));
|
|
@@ -2,6 +2,8 @@ export declare const DEFAULT_MANIFEST_URL = "https://scenerok.com/.well-known/ag
|
|
|
2
2
|
export interface SkillEntry {
|
|
3
3
|
hash: string;
|
|
4
4
|
files: number;
|
|
5
|
+
/** Location inside an installed skill directory. */
|
|
6
|
+
path?: string;
|
|
5
7
|
}
|
|
6
8
|
export interface SkillsManifest {
|
|
7
9
|
source: string;
|
|
@@ -13,6 +15,7 @@ export interface SkillDiff {
|
|
|
13
15
|
status: SkillStatus;
|
|
14
16
|
installedHash?: string;
|
|
15
17
|
latestHash?: string;
|
|
18
|
+
/** Path inside the skill directory. */
|
|
16
19
|
filename: string;
|
|
17
20
|
}
|
|
18
21
|
export interface SkillsCheckResult {
|
|
@@ -25,18 +28,25 @@ export interface SkillsCheckResult {
|
|
|
25
28
|
};
|
|
26
29
|
skills: SkillDiff[];
|
|
27
30
|
}
|
|
28
|
-
/** Map manifest slug to
|
|
31
|
+
/** Map manifest slug to its served filename. */
|
|
29
32
|
export declare function slugToFilename(slug: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The manifest entries that make up the installed skill, keyed by slug, with
|
|
35
|
+
* their path inside the skill directory. A manifest from before paths existed
|
|
36
|
+
* lists every file flat, so all of its entries count, at their flat filenames.
|
|
37
|
+
*/
|
|
38
|
+
export declare function skillFiles(manifest: SkillsManifest): Record<string, string>;
|
|
30
39
|
export declare function hashInstalledFile(filePath: string): SkillEntry | null;
|
|
31
40
|
export declare function hashInstalled(skillDir: string, manifest: SkillsManifest): Record<string, SkillEntry>;
|
|
32
41
|
export declare function diffSkills(installed: Record<string, SkillEntry>, latest: SkillsManifest): Omit<SkillsCheckResult, 'location'>;
|
|
33
42
|
export declare function fetchManifest(url?: string): Promise<SkillsManifest>;
|
|
34
|
-
|
|
43
|
+
/** Fetch one skill file. The site serves every file flat, by its basename. */
|
|
44
|
+
export declare function fetchSkillFile(slug: string, baseUrl?: string, path?: string): Promise<string>;
|
|
35
45
|
export declare function locateSkillDir(platform?: string): string | null;
|
|
36
46
|
export declare function checkSkills(opts?: {
|
|
37
47
|
dir?: string;
|
|
38
48
|
manifestUrl?: string;
|
|
39
49
|
}): Promise<SkillsCheckResult>;
|
|
40
|
-
/** List
|
|
50
|
+
/** List top-level local files the manifest doesn't know (left by older installs). */
|
|
41
51
|
export declare function listExtraLocalFiles(skillDir: string, manifest: SkillsManifest): string[];
|
|
42
52
|
//# sourceMappingURL=skillsManifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skillsManifest.d.ts","sourceRoot":"","sources":["../../src/utils/skillsManifest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skillsManifest.d.ts","sourceRoot":"","sources":["../../src/utils/skillsManifest.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,oBAAoB,gEAC8B,CAAC;AAEhE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAE7D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAWD,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAY3E;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAKrE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAOpG;AAED,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACrC,MAAM,EAAE,cAAc,GACrB,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC,CA+BrC;AAED,wBAAsB,aAAa,CAAC,GAAG,SAAuB,GAAG,OAAO,CAAC,cAAc,CAAC,CAQvF;AAED,8EAA8E;AAC9E,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnG;AAED,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiB/D;AAED,wBAAsB,WAAW,CAAC,IAAI,GAAE;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAMlC;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,EAAE,CAKxF"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Skill freshness: hash the installed skill's files against the published manifest.
|
|
3
|
+
* Manifest entries with a `path` belong to the installed skill (`SKILL.md`,
|
|
4
|
+
* `references/<file>`); entries without one are site-only docs and are skipped.
|
|
3
5
|
*/
|
|
4
6
|
import { createHash } from 'node:crypto';
|
|
5
7
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
6
8
|
import { homedir } from 'node:os';
|
|
7
|
-
import { join } from 'node:path';
|
|
9
|
+
import { basename, join } from 'node:path';
|
|
8
10
|
export const DEFAULT_MANIFEST_URL = 'https://scenerok.com/.well-known/agent-skills/manifest.json';
|
|
9
11
|
const TEXT_EXT = new Set(['.md', '.json', '.txt']);
|
|
10
12
|
function hashFileContent(content, ext) {
|
|
@@ -13,12 +15,32 @@ function hashFileContent(content, ext) {
|
|
|
13
15
|
h.update(normalized);
|
|
14
16
|
return h.digest('hex').slice(0, 16);
|
|
15
17
|
}
|
|
16
|
-
/** Map manifest slug to
|
|
18
|
+
/** Map manifest slug to its served filename. */
|
|
17
19
|
export function slugToFilename(slug) {
|
|
18
20
|
if (slug === 'catalog-index')
|
|
19
21
|
return 'catalog-index.json';
|
|
20
22
|
return `${slug}.md`;
|
|
21
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The manifest entries that make up the installed skill, keyed by slug, with
|
|
26
|
+
* their path inside the skill directory. A manifest from before paths existed
|
|
27
|
+
* lists every file flat, so all of its entries count, at their flat filenames.
|
|
28
|
+
*/
|
|
29
|
+
export function skillFiles(manifest) {
|
|
30
|
+
const entries = Object.entries(manifest.skills);
|
|
31
|
+
const hasPaths = entries.some(([, e]) => e.path);
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const [slug, entry] of entries) {
|
|
34
|
+
if (hasPaths) {
|
|
35
|
+
if (entry.path)
|
|
36
|
+
out[slug] = entry.path;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
out[slug] = slugToFilename(slug);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
22
44
|
export function hashInstalledFile(filePath) {
|
|
23
45
|
if (!existsSync(filePath))
|
|
24
46
|
return null;
|
|
@@ -28,9 +50,8 @@ export function hashInstalledFile(filePath) {
|
|
|
28
50
|
}
|
|
29
51
|
export function hashInstalled(skillDir, manifest) {
|
|
30
52
|
const out = {};
|
|
31
|
-
for (const slug of Object.
|
|
32
|
-
const
|
|
33
|
-
const entry = hashInstalledFile(join(skillDir, filename));
|
|
53
|
+
for (const [slug, path] of Object.entries(skillFiles(manifest))) {
|
|
54
|
+
const entry = hashInstalledFile(join(skillDir, path));
|
|
34
55
|
if (entry)
|
|
35
56
|
out[slug] = entry;
|
|
36
57
|
}
|
|
@@ -39,7 +60,8 @@ export function hashInstalled(skillDir, manifest) {
|
|
|
39
60
|
export function diffSkills(installed, latest) {
|
|
40
61
|
const skills = [];
|
|
41
62
|
const summary = { current: 0, outdated: 0, missing: 0 };
|
|
42
|
-
|
|
63
|
+
const files = skillFiles(latest);
|
|
64
|
+
for (const name of Object.keys(files).sort()) {
|
|
43
65
|
const latestEntry = latest.skills[name];
|
|
44
66
|
const installedEntry = installed[name];
|
|
45
67
|
let status;
|
|
@@ -60,7 +82,7 @@ export function diffSkills(installed, latest) {
|
|
|
60
82
|
status,
|
|
61
83
|
installedHash: installedEntry?.hash,
|
|
62
84
|
latestHash: latestEntry.hash,
|
|
63
|
-
filename:
|
|
85
|
+
filename: files[name],
|
|
64
86
|
});
|
|
65
87
|
}
|
|
66
88
|
return {
|
|
@@ -79,9 +101,10 @@ export async function fetchManifest(url = DEFAULT_MANIFEST_URL) {
|
|
|
79
101
|
}
|
|
80
102
|
return data;
|
|
81
103
|
}
|
|
82
|
-
|
|
104
|
+
/** Fetch one skill file. The site serves every file flat, by its basename. */
|
|
105
|
+
export async function fetchSkillFile(slug, baseUrl, path) {
|
|
83
106
|
const site = (baseUrl ?? 'https://scenerok.com').replace(/\/$/, '');
|
|
84
|
-
const filename = slugToFilename(slug);
|
|
107
|
+
const filename = path ? basename(path) : slugToFilename(slug);
|
|
85
108
|
const url = `${site}/.well-known/agent-skills/${filename}`;
|
|
86
109
|
const res = await fetch(url);
|
|
87
110
|
if (!res.ok)
|
|
@@ -112,11 +135,11 @@ export async function checkSkills(opts = {}) {
|
|
|
112
135
|
const diff = diffSkills(installed, latest);
|
|
113
136
|
return { location, ...diff };
|
|
114
137
|
}
|
|
115
|
-
/** List
|
|
138
|
+
/** List top-level local files the manifest doesn't know (left by older installs). */
|
|
116
139
|
export function listExtraLocalFiles(skillDir, manifest) {
|
|
117
140
|
if (!existsSync(skillDir))
|
|
118
141
|
return [];
|
|
119
|
-
const known = new Set(Object.
|
|
142
|
+
const known = new Set(Object.values(skillFiles(manifest)).map((p) => p.split('/')[0]));
|
|
120
143
|
known.add('SKILL.md');
|
|
121
144
|
return readdirSync(skillDir).filter((f) => !known.has(f));
|
|
122
145
|
}
|
|
@@ -20,6 +20,6 @@ import motion from "@scenerok/basic-animations"
|
|
|
20
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
21
|
|
|
22
22
|
[0s .. 15s] = filter "vignette", intensity: 0.3
|
|
23
|
-
[0s .. 15s] = filter "saturation",
|
|
23
|
+
[0s .. 15s] = filter "saturation", amount: 1.12
|
|
24
24
|
|
|
25
25
|
output to "rokmilk-chocolate-promo.mp4", resolution: "1080x1920", fps: 30, background: "#1A0F05"
|
|
@@ -16,6 +16,6 @@ main.Trim(start: 0s, end: 12s)
|
|
|
16
16
|
[7s .. 11s] = text "{{cta}}", font: "Inter", size: 56, color: "#FFFFFF", x: "50%", y: "78%", align: center, stroke: "#000000", stroke_width: 3
|
|
17
17
|
|
|
18
18
|
[0s .. 12s] = filter "vignette", intensity: 0.22
|
|
19
|
-
[0s .. 12s] = filter "saturation",
|
|
19
|
+
[0s .. 12s] = filter "saturation", amount: 1.12
|
|
20
20
|
|
|
21
21
|
output to "voice-visual-promo.mp4", resolution: "1080x1920", fps: 30
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scenerok/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "SceneRok CLI - Create videos from your terminal and agent workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
+
"prebuild": "node ../../scripts/copy-scenerok-skill.mjs .",
|
|
17
18
|
"build": "tsc",
|
|
18
19
|
"dev": "tsc --watch",
|
|
19
|
-
"
|
|
20
|
-
"prepublishOnly": "npm run sync:skills && npm run build"
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"scenerok",
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scenerok
|
|
3
|
+
description: >-
|
|
4
|
+
Writes, validates, and renders SceneRok videos from VidScript (.vid) or the
|
|
5
|
+
TypeScript @scenerok/sdk: promos, reels, explainers, social cards, motion
|
|
6
|
+
graphics, and edits of existing footage (trims, speed changes, picture-in-picture,
|
|
7
|
+
blend modes, masks, crops, color grades, LUTs, audio mixing and ducking, captions,
|
|
8
|
+
timecode). Use when the user wants to make or edit a video with SceneRok, or
|
|
9
|
+
mentions VidScript, a .vid file, @scenerok/sdk, the scenerok CLI, or the SceneRok
|
|
10
|
+
MCP tools.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# SceneRok
|
|
14
|
+
|
|
15
|
+
SceneRok turns a script into an MP4. Write the script in VidScript or in
|
|
16
|
+
TypeScript with `@scenerok/sdk`; both compile to the same timeline. Then
|
|
17
|
+
validate it, render it, and check the rendered file.
|
|
18
|
+
|
|
19
|
+
## Choose the language
|
|
20
|
+
|
|
21
|
+
| | VidScript (`.vid`) | TypeScript (`@scenerok/sdk`, `.ts`) |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Use for | Promos, reels, templates, and every editing action: masks, blends, crops, speed ramps, ducking, LUTs, timecode | Timelines built by code: loops over data, computed timing across many clips |
|
|
24
|
+
| Start with | [vidscript-strict.md](references/vidscript-strict.md) | [sdk-guide.md](references/sdk-guide.md) |
|
|
25
|
+
|
|
26
|
+
The SDK has the same values and placement (masks, blends, crops, groups,
|
|
27
|
+
ducking, timecode); LUT files, caption files, several outputs, and offline
|
|
28
|
+
media are VidScript only. A `.vid` can `import kit from "./kit.ts"` and place
|
|
29
|
+
the module's exports (`[0s .. 3s] = kit.title`), so build data-driven parts in
|
|
30
|
+
TypeScript and finish the edit in VidScript.
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
Copy this checklist into your notes and work through it:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
- [ ] 1. Identify the environment (web editor, CLI, ChatGPT, MCP, chat without tools)
|
|
38
|
+
- [ ] 2. Classify the request; read only the references it needs (table below)
|
|
39
|
+
- [ ] 3. Branded promo with unknown colors or logo: ask once
|
|
40
|
+
- [ ] 4. Probe real assets: type, width, height, duration
|
|
41
|
+
- [ ] 5. Write the script
|
|
42
|
+
- [ ] 6. Validate. Fix every error and validate again until clean
|
|
43
|
+
- [ ] 7. Uses TTS or generated media: deep-validate and fix the report (CLI only)
|
|
44
|
+
- [ ] 8. Render
|
|
45
|
+
- [ ] 9. Check the rendered MP4; fix and repeat from step 6 if it reports problems (CLI only)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With the CLI:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
scenerok validate script.vid # or scene.ts
|
|
52
|
+
scenerok validate script.vid --generate-assets --report-json # billed; VidScript only
|
|
53
|
+
scenerok render script.vid --watch
|
|
54
|
+
scenerok lint-video <render-id> --cost medium --json # billed
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`scenerok render script.vid --validate` renders and checks the MP4 in one step.
|
|
58
|
+
|
|
59
|
+
The deep validation generates the real speech and media (cached for the
|
|
60
|
+
render) and reports text that runs off-canvas or was shrunk (`offCanvas`,
|
|
61
|
+
`wasShrunk`), overlapping elements (`collisions`), and voice-over-voice
|
|
62
|
+
overlaps (`audio.overlaps`; music under voice is fine). The web editor,
|
|
63
|
+
ChatGPT, and MCP have their own validate and render calls but no deep
|
|
64
|
+
validation or MP4 check, and can't read local files:
|
|
65
|
+
[environments.md](references/environments.md).
|
|
66
|
+
|
|
67
|
+
## Rules that prevent most failures
|
|
68
|
+
|
|
69
|
+
1. **Import before calling.** `import xai from "@scenerok/xai"`, then
|
|
70
|
+
`xai.tts(…)`. The same for `@scenerok/cloudflare`, `@elevenlabs/music`,
|
|
71
|
+
`@scenerok/basic-animations`, `@scenerok/animations`,
|
|
72
|
+
`@scenerok/transitions`, and `@scenerok/media`.
|
|
73
|
+
2. **Never guess a duration.** Bind generated media and use its length:
|
|
74
|
+
`let vo = xai.tts(…)` then `[0 .. vo.duration] = audio vo`.
|
|
75
|
+
3. **Never guess text coordinates.** Use `area: "title"` (also `stage`,
|
|
76
|
+
`lower-third`, `caption`, or your own `grid`).
|
|
77
|
+
4. **No text in generated images or video.** Every prompt asks for no text,
|
|
78
|
+
letters, logos, or watermarks; put all copy in `text` lines.
|
|
79
|
+
5. **Edits return values; place the result.**
|
|
80
|
+
`[0s .. 4s] = clip.Trim(2s, 6s).Crop(420, 0, 1080, 1920)`. Put one video
|
|
81
|
+
over another with `Overlay`, `Composite`, or `blend:`.
|
|
82
|
+
6. **Use only documented syntax.** Don't invent functions or parameters. A
|
|
83
|
+
validation error names the problem; fix that line rather than rewriting the
|
|
84
|
+
script.
|
|
85
|
+
|
|
86
|
+
## References
|
|
87
|
+
|
|
88
|
+
Read the ones the task needs. Paths are relative to this file.
|
|
89
|
+
|
|
90
|
+
| Task | Read |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| Any VidScript | [vidscript-strict.md](references/vidscript-strict.md) (always) |
|
|
93
|
+
| Editing and compositing: trims, speed, reverse, freeze, picture-in-picture, blend modes, masks, crops, split screens, audio mixing and ducking, grading and LUTs, captions, timecode | [editing-actions.md](references/editing-actions.md) |
|
|
94
|
+
| Full grammar | [vidscript-guide.md](references/vidscript-guide.md) |
|
|
95
|
+
| A complete commented promo script | [vidscript-sample.md](references/vidscript-sample.md) |
|
|
96
|
+
| TypeScript SDK | [sdk-guide.md](references/sdk-guide.md) |
|
|
97
|
+
| Probing media, grabbing frames, beats | [media-stdlib.md](references/media-stdlib.md) |
|
|
98
|
+
| Generated images and video | [ai-models.md](references/ai-models.md) |
|
|
99
|
+
| Voiceover | [tts.md](references/tts.md) |
|
|
100
|
+
| Music beds | [music.md](references/music.md) |
|
|
101
|
+
| Animated blocks: lower thirds, code, social cards, text animations, custom HTML | [animations-picker.md](references/animations-picker.md), then [animations-catalog.md](references/animations-catalog.md) for every parameter |
|
|
102
|
+
| Titles animated with motion primitives | [text-styles.md](references/text-styles.md) |
|
|
103
|
+
| Scene transitions | [transitions-catalog.md](references/transitions-catalog.md) |
|
|
104
|
+
| Filters, VFX, custom shaders | [shaders-filters.md](references/shaders-filters.md) |
|
|
105
|
+
| A promo from a product URL | [workflow.md](references/workflow.md), [brand-setup.md](references/brand-setup.md) |
|
|
106
|
+
| Which workflow fits the request | [intent-router.md](references/intent-router.md) |
|
|
107
|
+
| Where you are running and what you can call | [environments.md](references/environments.md) |
|
|
108
|
+
| Every catalog item as data | [catalog-index.json](references/catalog-index.json) |
|
|
109
|
+
|
|
110
|
+
Without local files (for example, only this text was pasted into a chat),
|
|
111
|
+
fetch any reference from
|
|
112
|
+
`https://scenerok.com/.well-known/agent-skills/<filename>`. Setup guides for
|
|
113
|
+
chat apps are there too: `consumer-chat-apps.md`,
|
|
114
|
+
`chatgpt-gpt-instructions.md`, `gemini-gem-instructions.md`, `grok-skill.md`.
|
|
115
|
+
Previews of every catalog item: https://scenerok.com/catalog.
|
|
116
|
+
|
|
117
|
+
## Minimal VidScript
|
|
118
|
+
|
|
119
|
+
```vidscript
|
|
120
|
+
import motion from "@scenerok/basic-animations"
|
|
121
|
+
|
|
122
|
+
input hero = "https://cdn.example.com/clip.mp4"
|
|
123
|
+
[0s .. 4s] = hero.Trim(1s, 5s)
|
|
124
|
+
[0s .. 3s] = text "Hello", style: title, color: "#FFFFFF", area: "title", animate: motion.fadeIn(0.8s)
|
|
125
|
+
output to "video.mp4", resolution: "1080x1920", fps: 30
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Minimal SDK scene
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { timeline, text, s, fadeIn } from '@scenerok/sdk'
|
|
132
|
+
|
|
133
|
+
export default function main() {
|
|
134
|
+
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
135
|
+
t.grid({
|
|
136
|
+
rows: '16% 1fr 18% 10%',
|
|
137
|
+
areas: ['title', 'stage', 'lower-third', 'caption'],
|
|
138
|
+
})
|
|
139
|
+
t[0][s(3)] = text('SceneRok', { size: 72, area: 'title' }).animate(fadeIn(s(0.4)))
|
|
140
|
+
t[3][5] = text('Text to video', { size: 40, area: 'lower-third' })
|
|
141
|
+
return t.toIR()
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Validate and render it like a `.vid`: `scenerok validate scene.ts`.
|
|
146
|
+
|
|
147
|
+
## Keeping the skill current
|
|
148
|
+
|
|
149
|
+
`scenerok skills check` compares the installed copy with scenerok.com;
|
|
150
|
+
`scenerok skills update` downloads what changed.
|
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
**Always import:** `import xai from "@scenerok/xai"` and/or `import cf from "@scenerok/cloudflare"`.
|
|
14
14
|
|
|
15
|
-
Prefer **720p** (or the model default) for generative video. Cap spend with duration, not exotic post-steps.
|
|
16
|
-
|
|
17
15
|
Generative prompts must require: no text, no words, no letters, no captions, no logos, no watermarks, no readable UI copy.
|
|
18
16
|
|
|
19
17
|
## Examples
|
|
@@ -63,3 +61,4 @@ import xai from "@scenerok/xai"
|
|
|
63
61
|
|
|
64
62
|
[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
63
|
```
|
|
64
|
+
|