@scenerok/cli 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/commands/render.d.ts.map +1 -1
  2. package/dist/commands/render.js +48 -4
  3. package/dist/commands/skills.d.ts.map +1 -1
  4. package/dist/commands/skills.js +9 -34
  5. package/dist/lib/api.d.ts +11 -0
  6. package/dist/lib/api.d.ts.map +1 -1
  7. package/dist/lib/api.js +3 -0
  8. package/dist/lib/project.d.ts +15 -0
  9. package/dist/lib/project.d.ts.map +1 -1
  10. package/dist/lib/project.js +62 -3
  11. package/package.json +3 -2
  12. package/skills/{skills/aider → shared}/SKILL.md +40 -10
  13. package/skills/{claude → shared}/vidscript-guide.md +75 -41
  14. package/skills/{claude → shared}/vidscript-sample.md +1 -1
  15. package/skills/{codex → shared}/vidscript-strict.md +53 -8
  16. package/skills/aider/SKILL.md +0 -176
  17. package/skills/aider/vidscript-guide.md +0 -378
  18. package/skills/aider/vidscript-sample.md +0 -30
  19. package/skills/aider/vidscript-strict.md +0 -113
  20. package/skills/claude/SKILL.md +0 -176
  21. package/skills/claude/vidscript-strict.md +0 -113
  22. package/skills/codex/SKILL.md +0 -176
  23. package/skills/codex/vidscript-guide.md +0 -378
  24. package/skills/codex/vidscript-sample.md +0 -30
  25. package/skills/cursor/SKILL.md +0 -176
  26. package/skills/cursor/vidscript-guide.md +0 -378
  27. package/skills/cursor/vidscript-sample.md +0 -30
  28. package/skills/cursor/vidscript-strict.md +0 -113
  29. package/skills/opencode/SKILL.md +0 -176
  30. package/skills/opencode/vidscript-guide.md +0 -378
  31. package/skills/opencode/vidscript-sample.md +0 -30
  32. package/skills/opencode/vidscript-strict.md +0 -113
  33. package/skills/skills/aider/vidscript-guide.md +0 -378
  34. package/skills/skills/aider/vidscript-sample.md +0 -30
  35. package/skills/skills/aider/vidscript-strict.md +0 -113
  36. package/skills/skills/claude/SKILL.md +0 -185
  37. package/skills/skills/claude/vidscript-guide.md +0 -378
  38. package/skills/skills/claude/vidscript-sample.md +0 -30
  39. package/skills/skills/claude/vidscript-strict.md +0 -113
  40. package/skills/skills/codex/SKILL.md +0 -185
  41. package/skills/skills/codex/vidscript-guide.md +0 -378
  42. package/skills/skills/codex/vidscript-sample.md +0 -30
  43. package/skills/skills/codex/vidscript-strict.md +0 -113
  44. package/skills/skills/cursor/SKILL.md +0 -185
  45. package/skills/skills/cursor/vidscript-guide.md +0 -378
  46. package/skills/skills/cursor/vidscript-sample.md +0 -30
  47. package/skills/skills/cursor/vidscript-strict.md +0 -113
  48. package/skills/skills/opencode/SKILL.md +0 -185
  49. package/skills/skills/opencode/vidscript-guide.md +0 -378
  50. package/skills/skills/opencode/vidscript-sample.md +0 -30
  51. package/skills/skills/opencode/vidscript-strict.md +0 -113
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6EpC,eAAO,MAAM,aAAa,SAAwB,CAAC;AAEnD,eAAO,MAAM,aAAa,SA0FtB,CAAC"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoFpC,eAAO,MAAM,aAAa,SAAwB,CAAC;AAEnD,eAAO,MAAM,aAAa,SAqItB,CAAC"}
@@ -2,9 +2,10 @@ import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import ora from 'ora';
4
4
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
- import { join, resolve } from 'node:path';
6
- import { submitRender, getRenderStatus, downloadRender } from '../lib/api.js';
5
+ import { basename, join, resolve } from 'node:path';
6
+ import { submitRender, getRenderStatus, downloadRender, uploadCliAssets } from '../lib/api.js';
7
7
  import { isAuthenticated } from '../lib/config.js';
8
+ import { prepareProject, getMimeType, rewritePreparedVidscriptWithAssetUrls, writeAssetManifest, } from '../lib/project.js';
8
9
  function formatDuration(ms) {
9
10
  const totalSeconds = Math.max(0, Math.round(ms / 1000));
10
11
  const minutes = Math.floor(totalSeconds / 60);
@@ -81,11 +82,54 @@ export const renderCommand = new Command('render')
81
82
  process.exit(1);
82
83
  }
83
84
  let vidscript;
85
+ let preparedProject = null;
84
86
  try {
85
- vidscript = readFileSync(file, 'utf-8');
87
+ preparedProject = prepareProject(file);
88
+ vidscript = preparedProject.rewrittenVidscript;
86
89
  }
87
90
  catch {
88
- console.log(chalk.red(`❌ Could not read file: ${file}`));
91
+ try {
92
+ vidscript = readFileSync(file, 'utf-8');
93
+ }
94
+ catch {
95
+ console.log(chalk.red(`❌ Could not read file: ${file}`));
96
+ process.exit(1);
97
+ }
98
+ }
99
+ if (preparedProject && preparedProject.assets.length > 0) {
100
+ const assetSpinner = ora(`Uploading ${preparedProject.assets.length} local asset${preparedProject.assets.length === 1 ? '' : 's'}...`).start();
101
+ try {
102
+ const formData = new FormData();
103
+ for (const asset of preparedProject.assets) {
104
+ const bytes = readFileSync(asset.absolutePath);
105
+ const blob = new Blob([new Uint8Array(bytes)], { type: getMimeType(asset.absolutePath) });
106
+ formData.append('asset', blob, basename(asset.absolutePath));
107
+ formData.append('assetPath', asset.localPath);
108
+ }
109
+ const uploaded = await uploadCliAssets(formData);
110
+ const mappings = uploaded.assets.map((asset, index) => ({
111
+ localPath: preparedProject.assets[index].localPath,
112
+ id: asset.id,
113
+ url: asset.url,
114
+ mimeType: asset.mimeType,
115
+ fileSize: asset.fileSize,
116
+ }));
117
+ vidscript = rewritePreparedVidscriptWithAssetUrls(vidscript, mappings);
118
+ const manifestPath = writeAssetManifest(preparedProject, mappings);
119
+ assetSpinner.succeed(`Uploaded ${uploaded.assets.length} asset${uploaded.assets.length === 1 ? '' : 's'}`);
120
+ console.log(chalk.dim(` Manifest: ${manifestPath}`));
121
+ }
122
+ catch (error) {
123
+ assetSpinner.fail('Asset upload failed');
124
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
125
+ process.exit(1);
126
+ }
127
+ }
128
+ else if (preparedProject) {
129
+ vidscript = preparedProject.originalVidscript;
130
+ }
131
+ if (vidscript.includes('__SCENEROK_ASSET_')) {
132
+ console.log(chalk.red('❌ Some local assets could not be uploaded or mapped.'));
89
133
  process.exit(1);
90
134
  }
91
135
  const spinner = ora('Submitting render job...').start();
@@ -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;AAkIpC,eAAO,MAAM,aAAa,SA4DvB,CAAC"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwGpC,eAAO,MAAM,aAAa,SA4DvB,CAAC"}
@@ -4,64 +4,39 @@ import { existsSync, copyFileSync, mkdirSync, readdirSync } from 'node:fs';
4
4
  import { join, dirname } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  function getPlatformConfig(platform) {
7
- const cwd = process.cwd();
8
7
  const configs = {
9
8
  opencode: {
10
9
  name: 'OpenCode',
11
10
  skillDir: join(homedir(), '.agents/skills/scenerok'),
12
- files: [
13
- { source: 'skills/opencode/SKILL.md', dest: 'SKILL.md' },
14
- { source: 'skills/opencode/vidscript-guide.md', dest: 'vidscript-guide.md' },
15
- { source: 'skills/opencode/vidscript-sample.md', dest: 'vidscript-sample.md' },
16
- { source: 'skills/opencode/vidscript-strict.md', dest: 'vidscript-strict.md' },
17
- ],
18
11
  },
19
12
  claude: {
20
13
  name: 'Claude Code',
21
14
  skillDir: join(homedir(), '.claude/skills/scenerok'),
22
- files: [
23
- { source: 'skills/claude/SKILL.md', dest: 'SKILL.md' },
24
- { source: 'skills/claude/vidscript-guide.md', dest: 'vidscript-guide.md' },
25
- { source: 'skills/claude/vidscript-sample.md', dest: 'vidscript-sample.md' },
26
- { source: 'skills/claude/vidscript-strict.md', dest: 'vidscript-strict.md' },
27
- ],
28
15
  },
29
16
  codex: {
30
17
  name: 'Codex',
31
18
  skillDir: join(homedir(), '.codex/skills/scenerok'),
32
- files: [
33
- { source: 'skills/codex/SKILL.md', dest: 'SKILL.md' },
34
- { source: 'skills/codex/vidscript-guide.md', dest: 'vidscript-guide.md' },
35
- { source: 'skills/codex/vidscript-sample.md', dest: 'vidscript-sample.md' },
36
- { source: 'skills/codex/vidscript-strict.md', dest: 'vidscript-strict.md' },
37
- ],
38
19
  },
39
20
  cursor: {
40
21
  name: 'Cursor',
41
22
  skillDir: join(homedir(), '.cursor/skills/scenerok'),
42
- files: [
43
- { source: 'skills/cursor/SKILL.md', dest: 'SKILL.md' },
44
- { source: 'skills/cursor/vidscript-guide.md', dest: 'vidscript-guide.md' },
45
- { source: 'skills/cursor/vidscript-sample.md', dest: 'vidscript-sample.md' },
46
- { source: 'skills/cursor/vidscript-strict.md', dest: 'vidscript-strict.md' },
47
- ],
48
23
  },
49
24
  aider: {
50
25
  name: 'Aider',
51
26
  skillDir: join(homedir(), '.aider/skills/scenerok'),
52
- files: [
53
- { source: 'skills/aider/SKILL.md', dest: 'SKILL.md' },
54
- { source: 'skills/aider/vidscript-guide.md', dest: 'vidscript-guide.md' },
55
- { source: 'skills/aider/vidscript-sample.md', dest: 'vidscript-sample.md' },
56
- { source: 'skills/aider/vidscript-strict.md', dest: 'vidscript-strict.md' },
57
- ],
58
27
  },
59
28
  };
60
29
  return configs[platform] || null;
61
30
  }
31
+ const sharedSkillFiles = [
32
+ 'SKILL.md',
33
+ 'vidscript-guide.md',
34
+ 'vidscript-sample.md',
35
+ 'vidscript-strict.md',
36
+ ].map((name) => ({ source: `skills/shared/${name}`, dest: name }));
62
37
  async function findSkillSource(sourcePath) {
63
- // Check relative to node_modules/@scenerok/cli (scoped package)
64
- const modulePath = join(dirname(new URL(import.meta.url).pathname), '../../../..', sourcePath);
38
+ // Check relative to the installed @scenerok/cli package.
39
+ const modulePath = join(dirname(new URL(import.meta.url).pathname), '../..', sourcePath);
65
40
  if (existsSync(modulePath))
66
41
  return modulePath;
67
42
  // Check relative to CWD
@@ -127,7 +102,7 @@ export const skillsCommand = new Command('skills')
127
102
  if (!existsSync(config.skillDir)) {
128
103
  mkdirSync(config.skillDir, { recursive: true });
129
104
  }
130
- for (const file of config.files) {
105
+ for (const file of sharedSkillFiles) {
131
106
  const source = await findSkillSource(file.source);
132
107
  const dest = join(config.skillDir, file.dest);
133
108
  if (!source) {
package/dist/lib/api.d.ts CHANGED
@@ -90,6 +90,17 @@ export declare function uploadProject(formData: FormData): Promise<{
90
90
  }>;
91
91
  render?: Awaited<ReturnType<typeof submitRender>> | null;
92
92
  }>;
93
+ export declare function uploadCliAssets(formData: FormData): Promise<{
94
+ assets: Array<{
95
+ id: number;
96
+ filename: string;
97
+ url: string;
98
+ mimeType: string | null;
99
+ fileType: string;
100
+ fileSize: number;
101
+ externalId: string | null;
102
+ }>;
103
+ }>;
93
104
  export declare function listProjects(): Promise<{
94
105
  projects: Array<{
95
106
  id: number;
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
package/dist/lib/api.js CHANGED
@@ -112,6 +112,9 @@ export async function downloadRender(renderId) {
112
112
  export async function uploadProject(formData) {
113
113
  return apiFormCall('/api/cli/projects', formData);
114
114
  }
115
+ export async function uploadCliAssets(formData) {
116
+ return apiFormCall('/api/cli/assets', formData);
117
+ }
115
118
  export async function listProjects() {
116
119
  return apiCall('GET', '/api/cli/projects');
117
120
  }
@@ -6,9 +6,24 @@ export interface PreparedProject {
6
6
  assets: Array<{
7
7
  localPath: string;
8
8
  absolutePath: string;
9
+ size: number;
10
+ sha256: string;
9
11
  }>;
10
12
  }
13
+ export declare function getMimeType(filePath: string): string;
11
14
  export declare function listAssetFiles(assetDir: string): string[];
12
15
  export declare function prepareProject(entryFile: string, assetDirs?: string[]): PreparedProject;
13
16
  export declare function appendProjectToForm(formData: FormData, project: PreparedProject): Promise<void>;
17
+ export declare function buildAssetToken(localPath: string): string;
18
+ export declare function rewritePreparedVidscriptWithAssetUrls(vidscript: string, mappings: Array<{
19
+ localPath: string;
20
+ url: string;
21
+ }>): string;
22
+ export declare function writeAssetManifest(project: PreparedProject, mappings: Array<{
23
+ localPath: string;
24
+ url: string;
25
+ id?: number;
26
+ mimeType?: string | null;
27
+ fileSize?: number;
28
+ }>): string;
14
29
  //# sourceMappingURL=project.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/lib/project.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,KAAK,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;CACJ;AAuBD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBzD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,GAAG,eAAe,CAgD3F;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrG"}
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/lib/project.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,KAAK,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ;AAcD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAepD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBzD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,GAAG,eAAe,CAiE3F;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrG;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,qCAAqC,CACnD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAClD,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5G,MAAM,CAwBR"}
@@ -1,11 +1,21 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
3
  import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
3
4
  const INPUT_RE = /^(\s*input\s+[A-Za-z_][A-Za-z0-9_]*\s*=\s*)(["'])([^"']+)(\2)/gm;
5
+ const ASSET_IMPORT_RE = /^(\s*)import\s+([A-Za-z_][A-Za-z0-9_]*)\s+from\s+(["'])([^"']+)\3\s*;?\s*$/gm;
4
6
  const HTTP_RE = /^https?:\/\//i;
7
+ const MEDIA_EXT_RE = /\.(mp4|mov|m4v|webm|mp3|wav|m4a|aac|ogg|png|jpe?g|webp|gif|avif|svg)(?:[?#].*)?$/i;
5
8
  function isRenderableLocalPath(value) {
6
- return Boolean(value) && !HTTP_RE.test(value) && !value.startsWith('/uploads/') && !value.startsWith('data:');
9
+ return Boolean(value)
10
+ && !HTTP_RE.test(value)
11
+ && !value.startsWith('scenerok://')
12
+ && !value.startsWith('/uploads/')
13
+ && !value.startsWith('data:');
7
14
  }
8
- function getMimeType(filePath) {
15
+ function isAssetImportPath(value) {
16
+ return isRenderableLocalPath(value) && MEDIA_EXT_RE.test(value);
17
+ }
18
+ export function getMimeType(filePath) {
9
19
  const ext = filePath.toLowerCase().split('.').pop();
10
20
  switch (ext) {
11
21
  case 'mp4': return 'video/mp4';
@@ -60,6 +70,18 @@ export function prepareProject(entryFile, assetDirs = []) {
60
70
  assetsByPath.set(localPath, absolutePath);
61
71
  return `${prefix}${quote}{{asset:${localPath}}}${suffix}`;
62
72
  });
73
+ rewrittenVidscript = rewrittenVidscript.replace(ASSET_IMPORT_RE, (match, indent, localName, _quote, rawPath) => {
74
+ if (!isAssetImportPath(rawPath)) {
75
+ return match;
76
+ }
77
+ const absolutePath = isAbsolute(rawPath) ? rawPath : resolve(rootDir, rawPath);
78
+ if (!existsSync(absolutePath)) {
79
+ return match;
80
+ }
81
+ const localPath = relative(rootDir, absolutePath).replace(/\\/g, '/');
82
+ assetsByPath.set(localPath, absolutePath);
83
+ return `${indent}input ${localName} = "{{asset:${localPath}}}"`;
84
+ });
63
85
  for (const dir of assetDirs) {
64
86
  const absoluteDir = resolve(dir);
65
87
  for (const absolutePath of listAssetFiles(absoluteDir)) {
@@ -79,6 +101,8 @@ export function prepareProject(entryFile, assetDirs = []) {
79
101
  assets: Array.from(assetsByPath.entries()).map(([localPath, absolutePath]) => ({
80
102
  localPath,
81
103
  absolutePath,
104
+ size: statSync(absolutePath).size,
105
+ sha256: createHash('sha256').update(readFileSync(absolutePath)).digest('hex'),
82
106
  })),
83
107
  };
84
108
  }
@@ -95,3 +119,38 @@ export async function appendProjectToForm(formData, project) {
95
119
  }
96
120
  formData.set('vidscript', serverVidscript);
97
121
  }
122
+ export function buildAssetToken(localPath) {
123
+ return `__SCENEROK_ASSET_${Buffer.from(localPath).toString('base64url')}__`;
124
+ }
125
+ export function rewritePreparedVidscriptWithAssetUrls(vidscript, mappings) {
126
+ let next = vidscript;
127
+ for (const mapping of mappings) {
128
+ next = next.replaceAll(buildAssetToken(mapping.localPath), mapping.url);
129
+ }
130
+ return next;
131
+ }
132
+ export function writeAssetManifest(project, mappings) {
133
+ const manifestDir = join(project.rootDir, '.scenerok');
134
+ mkdirSync(manifestDir, { recursive: true });
135
+ const manifestPath = join(manifestDir, 'assets.manifest.json');
136
+ const byLocalPath = new Map(project.assets.map((asset) => [asset.localPath, asset]));
137
+ const manifest = {
138
+ version: 1,
139
+ entryFile: project.entryFile,
140
+ generatedAt: new Date().toISOString(),
141
+ assets: mappings.map((mapping) => {
142
+ const localAsset = byLocalPath.get(mapping.localPath);
143
+ return {
144
+ localPath: mapping.localPath,
145
+ absolutePath: localAsset?.absolutePath,
146
+ size: localAsset?.size ?? mapping.fileSize,
147
+ sha256: localAsset?.sha256,
148
+ assetId: mapping.id,
149
+ mimeType: mapping.mimeType,
150
+ url: mapping.url,
151
+ };
152
+ }),
153
+ };
154
+ writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
155
+ return manifestPath;
156
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scenerok/cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "SceneRok CLI - Create videos from your terminal and agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,8 @@
16
16
  "scripts": {
17
17
  "build": "tsc",
18
18
  "dev": "tsc --watch",
19
- "prepublishOnly": "npm run build"
19
+ "sync:skills": "node ../../scripts/sync-scenerok-skills.mjs",
20
+ "prepublishOnly": "npm run sync:skills && npm run build"
20
21
  },
21
22
  "keywords": [
22
23
  "scenerok",
@@ -7,11 +7,11 @@ description: >-
7
7
  SceneRok, scenerok CLI, or terminal/agent video generation.
8
8
  ---
9
9
 
10
- # SceneRok Skill for Aider
10
+ # SceneRok Agent Skill
11
11
 
12
12
  ## Overview
13
13
 
14
- You are a VidScript composer and video generation expert integrated with Aider. You help users create video content using the SceneRok platform directly from their terminal.
14
+ You are a VidScript composer and video generation expert integrated with your agent. You help users create video content using the SceneRok platform directly from their terminal.
15
15
 
16
16
  ## Capabilities
17
17
 
@@ -21,6 +21,7 @@ You are a VidScript composer and video generation expert integrated with Aider.
21
21
  - Check render status and retrieve output
22
22
  - Guide users through video creation workflows
23
23
  - Fill template placeholders and customize system templates
24
+ - When the user gives a product or website URL, use available browser or screenshot tools to capture real site visuals for VidScript assets
24
25
 
25
26
  ## VidScript Language
26
27
 
@@ -28,7 +29,7 @@ VidScript is a declarative language for describing video compositions using time
28
29
 
29
30
  ### Core Concepts
30
31
 
31
- **Inputs** - Declare video sources:
32
+ **Inputs** - Declare video/image sources:
32
33
  ```vidscript
33
34
  input hero = "https://cdn.example.com/hero.mp4"
34
35
  input logo = "/uploads/logo.png"
@@ -91,7 +92,7 @@ import xai from "@scenerok/xai"
91
92
  import eleven from "@elevenlabs/music"
92
93
  import motion from "@scenerok/basic-animations"
93
94
 
94
- [-] = video xai.imagine("Cinematic product shot", aspect_ratio: "9:16", duration: 5)
95
+ [-] = video xai.imagine("Cinematic product shot, premium lighting, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", duration: 5)
95
96
  [-] = audio xai.tts("Welcome to SceneRok", voice: "eve")
96
97
 
97
98
  # ElevenLabs music package functions require an import alias.
@@ -117,17 +118,37 @@ input hero = "{{hero_clip}}"
117
118
 
118
119
  1. **Understand the goal** — What video does the user want? (promo, testimonial, meme, etc.)
119
120
  2. **Plan the structure** — Time blocks, durations, inputs
120
- 3. **Gather assets** — Video URLs or local paths
121
- 4. **Compose VidScript** — Write the full script
122
- 5. **Validate** — Run `scenerok validate script.vid`
123
- 6. **Render** — Run `scenerok render script.vid --watch`
124
- 7. **Deliver** — Share the download URL when complete
121
+ 3. **Gather assets** — Video URLs, local paths, or captured website screenshots/images; when the user provides a URL, use browser screenshot capability if available
122
+ 4. **Probe asset dimensions** — Inspect every local or downloaded image/video before placing it. Use `ffprobe` for video/audio, `sips -g pixelWidth -g pixelHeight file` on macOS images, or `identify file` when ImageMagick is available.
123
+ 5. **Compose VidScript** — Write the full script using measured dimensions, aspect-ratio-preserving scale, centered/safe-area placement, and entry/exit animation beats for each visual asset
124
+ 6. **Validate** — Run `scenerok validate script.vid`
125
+ 7. **Render** — Run `scenerok render script.vid --watch`
126
+ 8. **Deliver** — Share the download URL when complete
127
+
128
+ ## Website URL Asset Workflow
129
+
130
+ When the user provides a product, company, landing page, app store listing, or ecommerce URL, treat the site as the primary visual source for the video.
131
+
132
+ 1. Visit the URL with an available browser tool before composing the final VidScript.
133
+ 2. Capture screenshots of useful page states: hero section, product detail, pricing or offer section, testimonials, checkout/app preview, and any visual proof points.
134
+ 3. Detect and save usable product images, app screenshots, brand marks, and logos from the page when available. Prefer direct image assets only when they are accessible and clearly match the product; otherwise use browser screenshots.
135
+ 4. Probe saved asset dimensions before composing. Record width, height, aspect ratio, and duration if video; never guess dimensions from filenames or screenshots.
136
+ 5. Use those screenshots or images as VidScript input assets and build the ad from real product visuals. Crop, resize, overlay text, add filters, and sequence them into a promo.
137
+ 6. Scale assets from their measured aspect ratio to fit the output frame and safe areas. For 1080x1920 ads, keep primary visuals inside roughly 80-88% of frame width and reserve enough top/bottom room for text.
138
+ 7. Animate visual assets intentionally: use entry animations such as `motion.popIn`, `motion.riseIn`, or `motion.slideY`; use short exit fade/slide segments when a clean transition is needed. For static plates, split the asset into a main segment and a final 0.4-0.7s segment with `motion.fadeOut(...)` to create a real exit.
139
+ 8. Use generative visuals such as `xai.imagine` only as a fallback, background texture, transition, or supplemental shot when real website assets are missing, low quality, or the user explicitly asks for generated footage.
140
+ 9. For every generated visual prompt, explicitly require a clean scene with no text, no words, no letters, no captions, no logos, no watermarks, and no readable UI copy. AI video generation often corrupts text; all final titles, offers, captions, CTAs, prices, and labels must be created with VidScript `text` primitives.
141
+ 10. Do not invent product claims. Extract copy, pricing, feature names, social proof, and CTA language from the site or ask the user.
125
142
 
126
143
  ## Best Practices
127
144
 
128
145
  - **Use dynamic timeblocks** — `[-]` auto-advances the cursor, reducing calculation errors
129
146
  - **Use `prev` for offsets** — `[prev + 0.5s .. prev + 2s]` for gaps between content
130
147
  - **Named arguments for clarity** — `hero.Trim(start: 0s, end: 5s)` over `hero.Trim(0s, 5s)`
148
+ - **Prefer real website assets for URL-based ads** — browser screenshots, product images, app screenshots, and logos should come before fully generative clips
149
+ - **Probe before placing** — get exact dimensions for every real asset, calculate scale from aspect ratio, and set `width`, `height`, `x`, and `y` explicitly
150
+ - **Animate assets, not only text** — give product screenshots/logos/cards a clear entrance and a clean exit; split static visuals into main and fade-out segments if needed
151
+ - **Keep generated media text-free** — prompts for AI-generated images/videos must explicitly say no text, no words, no letters, no captions, no logos, no watermarks, and no readable UI copy; add all final copy with VidScript `text` primitives
131
152
  - Use 1080x1920 for vertical content (TikTok/Instagram)
132
153
  - Use 1920x1080 for horizontal content (YouTube)
133
154
  - Hook viewers in the first 3 seconds
@@ -167,7 +188,7 @@ If a render fails:
167
188
  Always ask clarifying questions about:
168
189
  - Target platform (TikTok, Instagram, YouTube, etc.)
169
190
  - Brand colors and fonts
170
- - Existing assets (video clips, logo, etc.)
191
+ - Existing assets, product URLs, browser-captured screenshots, logos, and page images
171
192
  - Desired duration and style
172
193
  - Whether they want to start from a template or from scratch
173
194
 
@@ -180,6 +201,15 @@ Always ask clarifying questions about:
180
201
  | `vidscript-sample.md` | Minimal promo with xAI import |
181
202
  | `examples/system/*.vid` | Official SceneRok templates (text-only, product launch, xAI reels) |
182
203
 
204
+ ## SceneRok codebase development
205
+
206
+ This skill covers **VidScript composition and rendering videos**. For engineering on the SceneRok platform (parser, render subsystem, API, billing, GPU workers), load the **`scenerok-development`** skill from `.agents/skills/scenerok-development/` in the SceneRok repository.
207
+
208
+ | Doc | Purpose |
209
+ |-----|---------|
210
+ | `plan-v1/35-render-subsystem-architecture.md` | Render registries, IR flow, extension points |
211
+ | `AGENTS.md` | Project context, env vars, dev commands |
212
+
183
213
  ## Full Reference
184
214
 
185
215
  https://scenerok.com/docs/vidscript
@@ -8,7 +8,11 @@ VidScript is a declarative DSL for composing short-form videos. Write a script,
8
8
  2. **Validate early** — `scenerok validate file.vid` (errors show as `Line N:C — message`).
9
9
  3. **Music package syntax** — `import eleven from "@elevenlabs/music"`, then call `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)`.
10
10
  4. **Copy working examples** — see `examples/system/` in this skill folder (e.g. `minimal-text-reel.vid`, `ecom-product-launch.vid`, `rokmilk-chocolate-promo.vid`).
11
- 5. **Strict rules** — see `vidscript-strict.md` for invalid patterns (bare `xai.imagine`, bare `eleven.music(...)` without import, trailing params after direct plugin calls, nested quotes in prompts).
11
+ 5. **URL assets first** — if the user gives a website or product URL and browser screenshot tools are available, capture real page screenshots, product images, app screenshots, and logos for VidScript `input` assets before using generated visuals.
12
+ 6. **Probe asset dimensions** — before placing real assets, inspect width, height, and duration with `ffprobe`, `sips`, or `identify`; then scale from the measured aspect ratio and set explicit `width`, `height`, `x`, and `y`.
13
+ 7. **Animate real assets** — visual inputs should have entry motion and, where transitions matter, an exit beat. For static plates, split into a main segment and a short final `motion.fadeOut(...)` segment.
14
+ 8. **Generated media has no text** — prompts for AI-generated images/videos must explicitly ask for no text, no words, no letters, no captions, no logos, no watermarks, and no readable UI copy. Use VidScript `text` primitives for all final copy.
15
+ 9. **Strict rules** — see `vidscript-strict.md` for invalid patterns (bare `xai.imagine`, bare `eleven.music(...)` without import, trailing params after direct plugin calls, nested quotes in prompts).
12
16
 
13
17
  ### Common validation failures
14
18
 
@@ -39,7 +43,28 @@ input hero = "https://cdn.example.com/hero.mp4"
39
43
  input logo = "./assets/logo.png"
40
44
  ```
41
45
 
42
- Supports HTTP(S) URLs, `/uploads/` paths, and local paths.
46
+ Supports HTTP(S) URLs, `/uploads/` paths, and local paths for video/image assets.
47
+
48
+ For ads based on a supplied URL, use browser tools to capture real website screenshots and save product images or logos when available. Declare those files as inputs and use them in the composition before reaching for generated clips.
49
+
50
+ After declaring or downloading assets, probe dimensions before placing them:
51
+
52
+ ```bash
53
+ ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration -of csv=p=0 asset.mp4
54
+ sips -g pixelWidth -g pixelHeight asset.png
55
+ identify asset.webp
56
+ ```
57
+
58
+ Use the measured width and height to preserve aspect ratio. For a 1080x1920 vertical ad, a common centered fit is:
59
+
60
+ ```text
61
+ scaledWidth = min(assetWidth * scale, 900)
62
+ scaledHeight = scaledWidth / (assetWidth / assetHeight)
63
+ x = (1080 - scaledWidth) / 2
64
+ y = chosen safe-area top offset
65
+ ```
66
+
67
+ Do not guess asset size. Tiny logos, wide screenshots, and tall app screenshots need different scale and placement.
43
68
 
44
69
  ### Output Declaration
45
70
 
@@ -152,7 +177,7 @@ hero.Speed(factor: 1.5)
152
177
  | `x` | number \| string | Horizontal position. Number = px from left. `"50%"` = percent of width. Overrides `position`. |
153
178
  | `y` | number \| string | Vertical position. Number = px from top. `"50%"` = percent of height. Overrides `position`. |
154
179
  | `color` | string | Hex color |
155
- | `size` | number | Font size in pixels |
180
+ | `size` | number \| string | Font size in output canvas pixels. `size: 72` is shorthand for `size: "72px"`. |
156
181
  | `font` | string | Font family. System fonts or Google Fonts: `"Inter"`, `"Bebas Neue"`, `"Space Grotesk"`, `"Outfit"`, etc. |
157
182
  | `align` | string | `left`, `center`, `right`. Default: `center` |
158
183
  | `line_height` | number | Line height multiplier for multi-line text. Default: 1.2 |
@@ -233,7 +258,7 @@ import xai from "@scenerok/xai"
233
258
  import eleven from "@elevenlabs/music"
234
259
  import motion from "@scenerok/basic-animations"
235
260
 
236
- [-] = video xai.imagine("Cinematic product shot", aspect_ratio: "9:16", duration: 5)
261
+ [-] = video xai.imagine("Cinematic product shot, premium lighting, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", duration: 5)
237
262
  [-] = audio xai.tts("Welcome to SceneRok", voice: "eve")
238
263
 
239
264
  # ElevenLabs music package functions require an import alias.
@@ -254,68 +279,77 @@ let bed = eleven.music("Warm premium launch bed", duration: 15, instrumental: tr
254
279
  [0s .. 15s] = audio eleven.music("..."), volume: 0.5 # trailing params after direct plugin call
255
280
  ```
256
281
 
257
- ## Animations, Effects & Plugins (v2 Extensibility)
282
+ ## Animations, Effects & Rendering (v2)
258
283
 
259
- VidScript v2 uses a **minimal-grammar, plugin-first** architecture for animations and effects.
284
+ VidScript v2 uses a **minimal-grammar, plugin-first** architecture. The compiler lowers VidScript into `IRTimeline` — the single contract shared by browser preview and final render.
260
285
 
261
- Instead of adding dozens of animation keywords to the language, we use two powerful parameters:
286
+ User-facing extension uses two parameters on any text or video surface:
262
287
 
263
- - `animate:`
264
- - `effects:`
265
-
266
- These accept either plugin function calls or plain object descriptors.
288
+ - `animate:` — motion over time (plugin calls or object descriptors)
289
+ - `effects:` — post-processing filters with optional animated params
267
290
 
268
291
  ### animate: parameter
269
292
 
270
- Attach animations to **any** text or video surface.
271
-
272
293
  ```vidscript
273
294
  import motion from "@scenerok/basic-animations"
274
295
 
275
- text "Hello", animate: motion.fadeIn(0.8s)
296
+ [0s .. 3s] = text "Hello", x: "50%", y: "50%", animate: motion.fadeIn(0.8s)
276
297
 
277
- video hero, animate: [motion.fadeIn(0.5s), motion.slideY(-40, 0, 1.2s)]
298
+ [-] = video hero, animate: [motion.fadeIn(0.5s), motion.slideY(-40, 0, 1.2s)]
278
299
 
279
- text "Title", animate: {
300
+ # Static asset with measured 4:3-ish aspect ratio, centered in a vertical 1080x1920 frame.
301
+ [0s .. 3.4s] = video app_screen, width: 760, height: 615, x: 160, y: 300, animate: [motion.riseIn(0.6s, 70), motion.float(2.4s, 8)]
302
+ [3.4s .. 4s] = video app_screen, width: 760, height: 615, x: 160, y: 300, animate: motion.fadeOut(0.6s)
303
+
304
+ [0s .. 2s] = text "Title", animate: {
280
305
  type: "fade",
281
306
  from: { opacity: 0 },
282
307
  to: { opacity: 1 },
283
308
  start: 0,
284
- end: 1.5
309
+ end: 1.5,
310
+ easing: "easeOutQuad"
285
311
  }
286
312
  ```
287
313
 
288
- ### effects: parameter
314
+ Compiler lowers `animate:` into `IRMotionTrack[]` (property-path keyframes). Legacy `IRAnimation` descriptors are adapted at runtime for backward compatibility.
289
315
 
290
- Attach post-processing effects, with support for animated parameters.
316
+ ### effects: parameter
291
317
 
292
318
  ```vidscript
293
- text "Dramatic", effects: [grayscale(intensity: 0.8)]
319
+ [0s .. 5s] = filter "vignette", intensity: 0.4
294
320
 
295
- video hero, effects: [{
296
- name: "glitch",
297
- strength: animate: { type: "fade", from: { strength: 0 }, to: { strength: 1 } }
298
- }]
321
+ [0s .. 5s] = filter "glitch", intensity: 0.5, animate: motion.fadeIn(1s)
299
322
  ```
300
323
 
301
- ### Currently Available Animation Functions (`basic-animations` plugin)
302
-
303
- | Function | Description |
304
- |---------------------------|------------------------------|
305
- | `motion.fadeIn(duration?)` | Opacity 0 → 1 |
306
- | `motion.fadeOut(duration?)` | Opacity 1 → 0 |
307
- | `motion.slideX(from, to, dur?)` | Horizontal slide |
308
- | `motion.slideY(from, to, dur?)` | Vertical slide |
309
- | `motion.popIn(duration?)` | Scale + fade entrance |
310
- | `motion.riseIn(duration?, dist?)` | Upward fade entrance |
311
- | `motion.swingIn(duration?)` | Rotating slide/fade |
312
- | `motion.glitchIn(duration?)` | Jitter + flash entrance |
313
- | `motion.float(duration?, amp?)` | Gentle vertical bob |
314
- | `motion.typewriter(duration?)` | Character reveal |
315
-
316
- More plugins (advanced text animations, pixel effects, 3D, etc.) are planned.
324
+ Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`.
317
325
 
318
- The full architecture and IR details live in `plan-v1/29-surface-animation-effect-plugin-architecture.md`.
326
+ ### Animation functions (`@scenerok/basic-animations`)
327
+
328
+ | Function | Description |
329
+ |----------|-------------|
330
+ | `motion.fadeIn(duration?)` | Opacity 0 → 1 |
331
+ | `motion.fadeOut(duration?)` | Opacity 1 → 0 |
332
+ | `motion.slideX(from, to, dur?)` | Horizontal slide |
333
+ | `motion.slideY(from, to, dur?)` | Vertical slide |
334
+ | `motion.popIn(duration?)` | Scale + fade entrance |
335
+ | `motion.riseIn(duration?, dist?)` | Upward fade entrance |
336
+ | `motion.swingIn(duration?)` | Rotating slide/fade |
337
+ | `motion.glitchIn(duration?)` | Jitter + flash entrance |
338
+ | `motion.float(duration?, amp?)` | Gentle vertical bob |
339
+ | `motion.typewriter(duration?)` | Character reveal |
340
+
341
+ ### Render architecture (contributors)
342
+
343
+ Implemented registry-based rendering. See `plan-v1/35-render-subsystem-architecture.md`.
344
+
345
+ | Registry | Path | Add via |
346
+ |----------|------|---------|
347
+ | Motion | `src/lib/motion/registry.ts` | `registerEasing`, `registerMotionBehavior` |
348
+ | Surface | `src/render/surfaces/registry.ts` | `registerSurfaceRenderer` |
349
+ | Effect | `src/render/effects/registry.ts` | `registerEffect` or `src/shaders/library.ts` |
350
+ | Engine | `src/render/engine/index.ts` | `createRenderEngine` backends |
351
+
352
+ For SceneRok platform engineering, load the **`scenerok-development`** skill (`.agents/skills/scenerok-development/`).
319
353
 
320
354
  ## Expressions
321
355