@scenerok/cli 2.2.0 → 2.3.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.
Files changed (31) hide show
  1. package/README.md +6 -6
  2. package/dist/commands/skills.d.ts.map +1 -1
  3. package/dist/commands/skills.js +31 -25
  4. package/dist/utils/skillsManifest.d.ts +13 -3
  5. package/dist/utils/skillsManifest.d.ts.map +1 -1
  6. package/dist/utils/skillsManifest.js +35 -12
  7. package/examples/system/rokmilk-chocolate-promo.vid +1 -1
  8. package/examples/system/voice-visual-promo.vid +1 -1
  9. package/package.json +3 -3
  10. package/skills/scenerok/SKILL.md +147 -0
  11. package/skills/{remote → scenerok/references}/ai-models.md +1 -2
  12. package/skills/{remote → scenerok/references}/animations-catalog.md +139 -2
  13. package/skills/{remote → scenerok/references}/animations-picker.md +12 -1
  14. package/skills/{remote → scenerok/references}/brand-setup.md +3 -1
  15. package/skills/{remote → scenerok/references}/catalog-index.json +163 -19
  16. package/skills/scenerok/references/editing-actions.md +518 -0
  17. package/skills/{remote → scenerok/references}/environments.md +23 -24
  18. package/skills/{remote → scenerok/references}/intent-router.md +5 -15
  19. package/skills/{remote → scenerok/references}/media-stdlib.md +2 -3
  20. package/skills/{remote → scenerok/references}/music.md +2 -21
  21. package/skills/{remote → scenerok/references}/sdk-guide.md +2 -2
  22. package/skills/{remote → scenerok/references}/shaders-filters.md +9 -6
  23. package/skills/{remote → scenerok/references}/text-styles.md +15 -35
  24. package/skills/{remote → scenerok/references}/transitions-catalog.md +2 -0
  25. package/skills/{remote → scenerok/references}/tts.md +4 -22
  26. package/skills/{remote → scenerok/references}/vidscript-guide.md +202 -55
  27. package/skills/scenerok/references/vidscript-sample.md +44 -0
  28. package/skills/{remote → scenerok/references}/vidscript-strict.md +41 -26
  29. package/skills/{remote → scenerok/references}/workflow.md +6 -7
  30. package/skills/remote/vidscript-sample.md +0 -28
  31. 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 router skill (+ optional full sync)
55
- scenerok skills sync # Download remote skill bundle
56
- scenerok skills check [--json] # Compare local vs remote manifest
57
- scenerok skills update # Refresh outdated skills
58
- scenerok skills list # List remote skills + local status
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
- Remote skills are served from https://scenerok.com/.well-known/agent-skills/ and updated on every deploy — no CLI release needed for skill content changes.
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;AAgGpC,eAAO,MAAM,aAAa,SAuKvB,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"}
@@ -1,9 +1,9 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import { existsSync, copyFileSync, mkdirSync, writeFileSync } from 'node:fs';
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, slugToFilename, } from '../utils/skillsManifest.js';
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
- const ROUTER_SKILL = { source: 'skills/shared/SKILL.md', dest: 'SKILL.md' };
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 toSync = slugs ?? Object.keys(manifest.skills);
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 content = await fetchSkillFile(slug);
66
- const dest = join(skillDir, slugToFilename(slug));
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(` ✓ ${slugToFilename(slug)}`));
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 router skill for a platform')
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', 'Also download the full remote skill bundle')
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 router skill for ${config.name}...\n`));
87
- if (!existsSync(config.skillDir)) {
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(` ✗ Router skill not found (${ROUTER_SKILL.source})`));
95
+ console.log(chalk.red(` ✗ Bundled skill not found (${BUNDLED_SKILL})`));
94
96
  process.exit(1);
95
97
  }
96
- copyFileSync(source, dest);
97
- console.log(chalk.green(` ✓ ${ROUTER_SKILL.dest} (router)`));
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⬇️ Syncing remote skill bundle...\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✅ Router skill installed to ${config.skillDir}\n`));
103
- console.log(chalk.dim('Agents fetch latest docs from https://scenerok.com/.well-known/agent-skills/'));
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 full remote skill bundle locally')
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 router: scenerok skills install <platform>'));
190
- console.log(chalk.dim('Sync all: scenerok skills sync\n'));
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 on-disk filename. */
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
- export declare function fetchSkillFile(slug: string, baseUrl?: string): Promise<string>;
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 extra local files not in manifest (legacy bundled files). */
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":"AAQA,eAAO,MAAM,oBAAoB,gEAC8B,CAAC;AAEhE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,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,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,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;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,CAQpG;AAED,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACrC,MAAM,EAAE,cAAc,GACrB,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC,CA8BrC;AAED,wBAAsB,aAAa,CAAC,GAAG,SAAuB,GAAG,OAAO,CAAC,cAAc,CAAC,CAQvF;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOpF;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,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,EAAE,CAKxF"}
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
- * Remote skill freshness: hash flat markdown/json files against the published manifest.
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 on-disk filename. */
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.keys(manifest.skills)) {
32
- const filename = slugToFilename(slug);
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
- for (const name of Object.keys(latest.skills).sort()) {
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: slugToFilename(name),
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
- export async function fetchSkillFile(slug, baseUrl) {
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 extra local files not in manifest (legacy bundled files). */
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.keys(manifest.skills).map(slugToFilename));
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", value: 1.12
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", value: 1.12
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.2.0",
3
+ "version": "2.3.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
- "sync:skills": "node ../../scripts/sync-scenerok-skills.mjs",
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,147 @@
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 no masks, blends, crops, ducking, or delivery settings. When a
27
+ scene needs both, write it in VidScript and `import` the SDK scene into it.
28
+
29
+ ## Workflow
30
+
31
+ Copy this checklist into your notes and work through it:
32
+
33
+ ```text
34
+ - [ ] 1. Identify the environment (web editor, CLI, ChatGPT, MCP, chat without tools)
35
+ - [ ] 2. Classify the request; read only the references it needs (table below)
36
+ - [ ] 3. Branded promo with unknown colors or logo: ask once
37
+ - [ ] 4. Probe real assets: type, width, height, duration
38
+ - [ ] 5. Write the script
39
+ - [ ] 6. Validate. Fix every error and validate again until clean
40
+ - [ ] 7. Uses TTS or generated media: deep-validate and fix the report (CLI only)
41
+ - [ ] 8. Render
42
+ - [ ] 9. Check the rendered MP4; fix and repeat from step 6 if it reports problems (CLI only)
43
+ ```
44
+
45
+ With the CLI:
46
+
47
+ ```bash
48
+ scenerok validate script.vid # or scene.ts
49
+ scenerok validate script.vid --generate-assets --report-json # billed; VidScript only
50
+ scenerok render script.vid --watch
51
+ scenerok lint-video <render-id> --cost medium --json # billed
52
+ ```
53
+
54
+ `scenerok render script.vid --validate` renders and checks the MP4 in one step.
55
+
56
+ The deep validation generates the real speech and media (cached for the
57
+ render) and reports text that runs off-canvas or was shrunk (`offCanvas`,
58
+ `wasShrunk`), overlapping elements (`collisions`), and voice-over-voice
59
+ overlaps (`audio.overlaps`; music under voice is fine). The web editor,
60
+ ChatGPT, and MCP have their own validate and render calls but no deep
61
+ validation or MP4 check, and can't read local files:
62
+ [environments.md](references/environments.md).
63
+
64
+ ## Rules that prevent most failures
65
+
66
+ 1. **Import before calling.** `import xai from "@scenerok/xai"`, then
67
+ `xai.tts(…)`. The same for `@scenerok/cloudflare`, `@elevenlabs/music`,
68
+ `@scenerok/basic-animations`, `@scenerok/animations`,
69
+ `@scenerok/transitions`, and `@scenerok/media`.
70
+ 2. **Never guess a duration.** Bind generated media and use its length:
71
+ `let vo = xai.tts(…)` then `[0 .. vo.duration] = audio vo`.
72
+ 3. **Never guess text coordinates.** Use `area: "title"` (also `stage`,
73
+ `lower-third`, `caption`, or your own `grid`).
74
+ 4. **No text in generated images or video.** Every prompt asks for no text,
75
+ letters, logos, or watermarks; put all copy in `text` lines.
76
+ 5. **Edits return values; place the result.**
77
+ `[0s .. 4s] = clip.Trim(2s, 6s).Crop(420, 0, 1080, 1920)`. Put one video
78
+ over another with `Overlay`, `Composite`, or `blend:`.
79
+ 6. **Use only documented syntax.** Don't invent functions or parameters. A
80
+ validation error names the problem; fix that line rather than rewriting the
81
+ script.
82
+
83
+ ## References
84
+
85
+ Read the ones the task needs. Paths are relative to this file.
86
+
87
+ | Task | Read |
88
+ | --- | --- |
89
+ | Any VidScript | [vidscript-strict.md](references/vidscript-strict.md) (always) |
90
+ | 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) |
91
+ | Full grammar | [vidscript-guide.md](references/vidscript-guide.md) |
92
+ | A complete commented promo script | [vidscript-sample.md](references/vidscript-sample.md) |
93
+ | TypeScript SDK | [sdk-guide.md](references/sdk-guide.md) |
94
+ | Probing media, grabbing frames, beats | [media-stdlib.md](references/media-stdlib.md) |
95
+ | Generated images and video | [ai-models.md](references/ai-models.md) |
96
+ | Voiceover | [tts.md](references/tts.md) |
97
+ | Music beds | [music.md](references/music.md) |
98
+ | 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 |
99
+ | Titles animated with motion primitives | [text-styles.md](references/text-styles.md) |
100
+ | Scene transitions | [transitions-catalog.md](references/transitions-catalog.md) |
101
+ | Filters, VFX, custom shaders | [shaders-filters.md](references/shaders-filters.md) |
102
+ | A promo from a product URL | [workflow.md](references/workflow.md), [brand-setup.md](references/brand-setup.md) |
103
+ | Which workflow fits the request | [intent-router.md](references/intent-router.md) |
104
+ | Where you are running and what you can call | [environments.md](references/environments.md) |
105
+ | Every catalog item as data | [catalog-index.json](references/catalog-index.json) |
106
+
107
+ Without local files (for example, only this text was pasted into a chat),
108
+ fetch any reference from
109
+ `https://scenerok.com/.well-known/agent-skills/<filename>`. Setup guides for
110
+ chat apps are there too: `consumer-chat-apps.md`,
111
+ `chatgpt-gpt-instructions.md`, `gemini-gem-instructions.md`, `grok-skill.md`.
112
+ Previews of every catalog item: https://scenerok.com/catalog.
113
+
114
+ ## Minimal VidScript
115
+
116
+ ```vidscript
117
+ import motion from "@scenerok/basic-animations"
118
+
119
+ input hero = "https://cdn.example.com/clip.mp4"
120
+ [0s .. 4s] = hero.Trim(1s, 5s)
121
+ [0s .. 3s] = text "Hello", style: title, color: "#FFFFFF", area: "title", animate: motion.fadeIn(0.8s)
122
+ output to "video.mp4", resolution: "1080x1920", fps: 30
123
+ ```
124
+
125
+ ## Minimal SDK scene
126
+
127
+ ```ts
128
+ import { timeline, text, s, fadeIn } from '@scenerok/sdk'
129
+
130
+ export default function main() {
131
+ const t = timeline({ width: 1080, height: 1920, fps: 30 })
132
+ t.grid({
133
+ rows: '16% 1fr 18% 10%',
134
+ areas: ['title', 'stage', 'lower-third', 'caption'],
135
+ })
136
+ t[0][s(3)] = text('SceneRok', { size: 72, area: 'title' }).animate(fadeIn(s(0.4)))
137
+ t[3][5] = text('Text to video', { size: 40, area: 'lower-third' })
138
+ return t.toIR()
139
+ }
140
+ ```
141
+
142
+ Validate and render it like a `.vid`: `scenerok validate scene.ts`.
143
+
144
+ ## Keeping the skill current
145
+
146
+ `scenerok skills check` compares the installed copy with scenerok.com;
147
+ `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
+