@scenerok/cli 1.0.0 → 1.0.1
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 +42 -18
- package/dist/commands/cache.d.ts +3 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +65 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +159 -0
- package/dist/commands/render.d.ts +1 -0
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +71 -40
- package/dist/index.js +8 -1
- package/dist/lib/api.d.ts +43 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +59 -8
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +11 -0
- package/dist/lib/project.d.ts +14 -0
- package/dist/lib/project.d.ts.map +1 -0
- package/dist/lib/project.js +97 -0
- package/package.json +2 -1
- package/skills/aider/SKILL.md +171 -0
- package/skills/aider/vidscript-guide.md +355 -0
- package/skills/aider/vidscript-sample.md +21 -0
- package/skills/claude/SKILL.md +171 -0
- package/skills/claude/vidscript-guide.md +356 -0
- package/skills/claude/vidscript-sample.md +21 -0
- package/skills/codex/SKILL.md +171 -0
- package/skills/codex/vidscript-guide.md +355 -0
- package/skills/codex/vidscript-sample.md +21 -0
- package/skills/cursor/SKILL.md +171 -0
- package/skills/cursor/vidscript-guide.md +355 -0
- package/skills/cursor/vidscript-sample.md +21 -0
- package/skills/opencode/SKILL.md +171 -0
- package/skills/opencode/vidscript-guide.md +355 -0
- package/skills/opencode/vidscript-sample.md +21 -0
package/README.md
CHANGED
|
@@ -25,6 +25,15 @@ scenerok validate video.vidscript
|
|
|
25
25
|
# Render a video
|
|
26
26
|
scenerok render video.vidscript --resolution 1080x1920 --watch
|
|
27
27
|
|
|
28
|
+
# Upload a local project and assets
|
|
29
|
+
scenerok project upload video.vidscript --assets ./assets
|
|
30
|
+
|
|
31
|
+
# Upload, render, watch, and download
|
|
32
|
+
scenerok project upload video.vidscript --assets ./assets --render --watch --download ./renders
|
|
33
|
+
|
|
34
|
+
# Pull generated asset cache
|
|
35
|
+
scenerok cache pull
|
|
36
|
+
|
|
28
37
|
# Check render status
|
|
29
38
|
scenerok status 42
|
|
30
39
|
```
|
|
@@ -35,8 +44,8 @@ scenerok status 42
|
|
|
35
44
|
|
|
36
45
|
```bash
|
|
37
46
|
scenerok auth login # Log in via browser
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
scenerok auth status # Check auth status
|
|
48
|
+
scenerok auth logout # Remove credentials
|
|
40
49
|
```
|
|
41
50
|
|
|
42
51
|
### `skills`
|
|
@@ -64,7 +73,9 @@ scenerok render <file.vidscript> [options]
|
|
|
64
73
|
|
|
65
74
|
Options:
|
|
66
75
|
- `-r, --resolution <resolution>` - Output resolution (e.g. 1080x1920)
|
|
76
|
+
- `-p, --project-id <id>` - Attach the render to an uploaded project
|
|
67
77
|
- `-w, --watch` - Watch render status until complete
|
|
78
|
+
- `--download [dir]` - Download the completed MP4
|
|
68
79
|
|
|
69
80
|
### `status`
|
|
70
81
|
|
|
@@ -74,6 +85,28 @@ scenerok status <render-id>
|
|
|
74
85
|
|
|
75
86
|
Check the status of a render job.
|
|
76
87
|
|
|
88
|
+
Use `scenerok status <render-id> --download ./renders` to download a completed render.
|
|
89
|
+
|
|
90
|
+
### `project`
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
scenerok project upload <file.vidscript> --assets ./assets
|
|
94
|
+
scenerok project upload <file.vidscript> --assets ./assets --render --watch --download ./renders
|
|
95
|
+
scenerok project list
|
|
96
|
+
scenerok project render <project-id> --watch --download ./renders
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`project upload` creates a private SceneRok project on the main server, uploads referenced local assets, rewrites local `input` paths to server asset URLs, and returns a project id. Project renders are visible on the website.
|
|
100
|
+
|
|
101
|
+
### `cache`
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
scenerok cache pull
|
|
105
|
+
scenerok cache pull --plugin xai-media-video --output ~/.scenerok/cache/assets
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Downloads cached generated assets and writes a `manifest.json` to the output folder.
|
|
109
|
+
|
|
77
110
|
## Configuration
|
|
78
111
|
|
|
79
112
|
Config is stored in `~/.scenerok/config.json`:
|
|
@@ -90,25 +123,16 @@ Config is stored in `~/.scenerok/config.json`:
|
|
|
90
123
|
VidScript is a declarative language for describing video compositions.
|
|
91
124
|
|
|
92
125
|
```vidscript
|
|
93
|
-
|
|
94
|
-
resolution = "1080x1920";
|
|
95
|
-
fps = 30;
|
|
96
|
-
output = "video.mp4";
|
|
97
|
-
}
|
|
126
|
+
input hero = "./assets/hero.mp4"
|
|
98
127
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
content = "Hello World";
|
|
104
|
-
font = "Inter Bold";
|
|
105
|
-
size = 72;
|
|
106
|
-
x = 50%; y = 50%;
|
|
107
|
-
align = "center";
|
|
108
|
-
}
|
|
109
|
-
}
|
|
128
|
+
[0s .. 5s] = hero
|
|
129
|
+
[0.4s .. 3s] = text "Hello World", font: "Inter Bold", size: 72, x: "50%", y: "50%", align: center, animate: [fadeIn(0.6s), slideY(40, 0, 0.8s)]
|
|
130
|
+
|
|
131
|
+
output to "video.mp4", resolution: "1080x1920", fps: 30
|
|
110
132
|
```
|
|
111
133
|
|
|
134
|
+
Animation helpers: `fadeIn`, `fadeOut`, `slideX`, `slideY`, `popIn`, `riseIn`, `swingIn`, `glitchIn`, `float`, `typewriter`.
|
|
135
|
+
|
|
112
136
|
## Links
|
|
113
137
|
|
|
114
138
|
- Website: https://scenerok.com
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/commands/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,eAAO,MAAM,YAAY,SA6CtB,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { basename, extname, join, resolve } from 'node:path';
|
|
6
|
+
import fetch from 'node-fetch';
|
|
7
|
+
import { getAssetCache } from '../lib/api.js';
|
|
8
|
+
import { getCacheDir, isAuthenticated } from '../lib/config.js';
|
|
9
|
+
function safeName(value) {
|
|
10
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 120);
|
|
11
|
+
}
|
|
12
|
+
function filenameFor(url, cacheKey) {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = new URL(url);
|
|
15
|
+
const fromUrl = basename(parsed.pathname);
|
|
16
|
+
if (fromUrl && extname(fromUrl))
|
|
17
|
+
return safeName(fromUrl);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// fall through
|
|
21
|
+
}
|
|
22
|
+
return `${safeName(cacheKey)}.bin`;
|
|
23
|
+
}
|
|
24
|
+
export const cacheCommand = new Command('cache')
|
|
25
|
+
.description('Manage local SceneRok asset cache')
|
|
26
|
+
.addCommand(new Command('pull')
|
|
27
|
+
.description('Download cached generated assets to a local folder')
|
|
28
|
+
.option('-o, --output <dir>', 'Output directory', join(getCacheDir(), 'assets'))
|
|
29
|
+
.option('--plugin <name>', 'Filter by plugin name')
|
|
30
|
+
.option('--limit <count>', 'Maximum assets to pull', '100')
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
if (!isAuthenticated()) {
|
|
33
|
+
console.log(chalk.yellow('Not authenticated'));
|
|
34
|
+
console.log(chalk.dim('Run: scenerok auth login'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const outputDir = resolve(options.output);
|
|
38
|
+
mkdirSync(outputDir, { recursive: true });
|
|
39
|
+
const spinner = ora('Fetching asset cache manifest...').start();
|
|
40
|
+
try {
|
|
41
|
+
const manifest = await getAssetCache({
|
|
42
|
+
plugin: options.plugin,
|
|
43
|
+
limit: Number.parseInt(options.limit, 10) || 100,
|
|
44
|
+
});
|
|
45
|
+
spinner.text = `Downloading ${manifest.entries.length} cached asset${manifest.entries.length === 1 ? '' : 's'}...`;
|
|
46
|
+
const downloaded = [];
|
|
47
|
+
for (const entry of manifest.entries) {
|
|
48
|
+
const response = await fetch(entry.r2Url);
|
|
49
|
+
if (!response.ok)
|
|
50
|
+
continue;
|
|
51
|
+
const filename = filenameFor(entry.r2Url, entry.cacheKey);
|
|
52
|
+
const filePath = join(outputDir, filename);
|
|
53
|
+
writeFileSync(filePath, Buffer.from(await response.arrayBuffer()));
|
|
54
|
+
downloaded.push({ ...entry, filePath });
|
|
55
|
+
}
|
|
56
|
+
writeFileSync(join(outputDir, 'manifest.json'), JSON.stringify(downloaded, null, 2));
|
|
57
|
+
spinner.succeed(`Downloaded ${downloaded.length} cached asset${downloaded.length === 1 ? '' : 's'}`);
|
|
58
|
+
console.log(chalk.dim(`Manifest: ${join(outputDir, 'manifest.json')}`));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
spinner.fail('Asset cache pull failed');
|
|
62
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BpC,eAAO,MAAM,cAAc,SAyJxB,CAAC"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { downloadRender, getRenderStatus, listProjects, submitRender, uploadProject } from '../lib/api.js';
|
|
7
|
+
import { isAuthenticated } from '../lib/config.js';
|
|
8
|
+
import { appendProjectToForm, prepareProject } from '../lib/project.js';
|
|
9
|
+
async function waitForRender(renderId) {
|
|
10
|
+
while (true) {
|
|
11
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 3000));
|
|
12
|
+
const status = await getRenderStatus(renderId);
|
|
13
|
+
if (status.status === 'completed')
|
|
14
|
+
return status;
|
|
15
|
+
if (status.status === 'failed') {
|
|
16
|
+
throw new Error(status.error || 'Render failed');
|
|
17
|
+
}
|
|
18
|
+
process.stdout.write(`\r ${status.status}... ${status.progress}%`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function downloadRenderTo(renderId, outputDir) {
|
|
22
|
+
const result = await downloadRender(renderId);
|
|
23
|
+
mkdirSync(outputDir, { recursive: true });
|
|
24
|
+
const outputPath = join(outputDir, result.filename);
|
|
25
|
+
writeFileSync(outputPath, result.buffer);
|
|
26
|
+
return outputPath;
|
|
27
|
+
}
|
|
28
|
+
export const projectCommand = new Command('project')
|
|
29
|
+
.description('Upload and render SceneRok projects')
|
|
30
|
+
.addCommand(new Command('upload')
|
|
31
|
+
.description('Upload a local VidScript project and its assets')
|
|
32
|
+
.argument('<file>', 'Path to the project .vidscript file')
|
|
33
|
+
.option('-t, --title <title>', 'Project title')
|
|
34
|
+
.option('-d, --description <description>', 'Project description')
|
|
35
|
+
.option('-a, --assets <dir...>', 'Additional asset directories to upload')
|
|
36
|
+
.option('--tags <tags>', 'Comma-separated project tags')
|
|
37
|
+
.option('-r, --render', 'Create a render after uploading')
|
|
38
|
+
.option('--resolution <resolution>', 'Render resolution if --render is used')
|
|
39
|
+
.option('-w, --watch', 'Watch the render until complete')
|
|
40
|
+
.option('--download [dir]', 'Download completed render to a directory', false)
|
|
41
|
+
.action(async (file, options) => {
|
|
42
|
+
if (!isAuthenticated()) {
|
|
43
|
+
console.log(chalk.yellow('Not authenticated'));
|
|
44
|
+
console.log(chalk.dim('Run: scenerok auth login'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const spinner = ora('Preparing project...').start();
|
|
48
|
+
try {
|
|
49
|
+
const project = prepareProject(file, options.assets || []);
|
|
50
|
+
const formData = new FormData();
|
|
51
|
+
formData.set('title', options.title || project.entryFile.replace(/\.(vid|vidscript)$/i, ''));
|
|
52
|
+
if (options.description)
|
|
53
|
+
formData.set('description', options.description);
|
|
54
|
+
if (options.tags)
|
|
55
|
+
formData.set('tags', options.tags);
|
|
56
|
+
if (options.render)
|
|
57
|
+
formData.set('render', 'true');
|
|
58
|
+
if (options.resolution)
|
|
59
|
+
formData.set('resolution', options.resolution);
|
|
60
|
+
await appendProjectToForm(formData, project);
|
|
61
|
+
spinner.text = `Uploading project (${project.assets.length} asset${project.assets.length === 1 ? '' : 's'})...`;
|
|
62
|
+
const result = await uploadProject(formData);
|
|
63
|
+
spinner.succeed(`Project uploaded (#${result.project.id})`);
|
|
64
|
+
console.log(chalk.cyan('\nProject\n'));
|
|
65
|
+
console.log(` ID: ${result.project.id}`);
|
|
66
|
+
console.log(` Title: ${result.project.title}`);
|
|
67
|
+
console.log(` URL: ${result.project.projectUrl}`);
|
|
68
|
+
console.log(` Assets: ${result.assets.length}`);
|
|
69
|
+
if (result.render) {
|
|
70
|
+
console.log(chalk.cyan('\nRender\n'));
|
|
71
|
+
console.log(` ID: ${result.render.renderId}`);
|
|
72
|
+
console.log(` Status: ${result.render.status}`);
|
|
73
|
+
console.log(` URL: ${result.render.renderUrl}`);
|
|
74
|
+
console.log(` Download: ${result.render.downloadUrl}`);
|
|
75
|
+
if (options.watch || options.download) {
|
|
76
|
+
console.log(chalk.dim('\nWatching render status...\n'));
|
|
77
|
+
await waitForRender(result.render.renderId);
|
|
78
|
+
console.log(chalk.green('\nRender complete'));
|
|
79
|
+
}
|
|
80
|
+
if (options.download) {
|
|
81
|
+
const outputDir = typeof options.download === 'string' ? options.download : 'renders';
|
|
82
|
+
const outputPath = await downloadRenderTo(result.render.renderId, resolve(outputDir));
|
|
83
|
+
console.log(chalk.green(`Downloaded: ${outputPath}`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
console.log('');
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
spinner.fail('Project upload failed');
|
|
90
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}))
|
|
94
|
+
.addCommand(new Command('list')
|
|
95
|
+
.description('List projects uploaded from CLI/agents')
|
|
96
|
+
.action(async () => {
|
|
97
|
+
if (!isAuthenticated()) {
|
|
98
|
+
console.log(chalk.yellow('Not authenticated'));
|
|
99
|
+
console.log(chalk.dim('Run: scenerok auth login'));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const result = await listProjects();
|
|
104
|
+
console.log(chalk.cyan('\nProjects\n'));
|
|
105
|
+
for (const project of result.projects) {
|
|
106
|
+
console.log(` #${project.id} ${project.title} (${project.status})`);
|
|
107
|
+
}
|
|
108
|
+
if (result.projects.length === 0) {
|
|
109
|
+
console.log(chalk.dim(' No CLI projects yet.'));
|
|
110
|
+
}
|
|
111
|
+
console.log('');
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}))
|
|
118
|
+
.addCommand(new Command('render')
|
|
119
|
+
.description('Render an uploaded project')
|
|
120
|
+
.argument('<project-id>', 'Project ID')
|
|
121
|
+
.option('-r, --resolution <resolution>', 'Output resolution')
|
|
122
|
+
.option('-w, --watch', 'Watch render status until complete')
|
|
123
|
+
.option('--download [dir]', 'Download completed render to a directory', false)
|
|
124
|
+
.action(async (projectId, options) => {
|
|
125
|
+
if (!isAuthenticated()) {
|
|
126
|
+
console.log(chalk.yellow('Not authenticated'));
|
|
127
|
+
console.log(chalk.dim('Run: scenerok auth login'));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
const id = Number.parseInt(projectId, 10);
|
|
131
|
+
if (Number.isNaN(id)) {
|
|
132
|
+
console.log(chalk.red('Invalid project ID'));
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const projects = await listProjects();
|
|
137
|
+
const project = projects.projects.find((item) => item.id === id);
|
|
138
|
+
if (!project) {
|
|
139
|
+
throw new Error(`Project not found: ${id}`);
|
|
140
|
+
}
|
|
141
|
+
const result = await submitRender('', options.resolution, id);
|
|
142
|
+
console.log(chalk.green(`Render submitted (#${result.renderId})`));
|
|
143
|
+
console.log(` URL: ${result.renderUrl}`);
|
|
144
|
+
console.log(` Download: ${result.downloadUrl}`);
|
|
145
|
+
if (options.watch || options.download) {
|
|
146
|
+
await waitForRender(result.renderId);
|
|
147
|
+
console.log(chalk.green('\nRender complete'));
|
|
148
|
+
}
|
|
149
|
+
if (options.download) {
|
|
150
|
+
const outputDir = typeof options.download === 'string' ? options.download : 'renders';
|
|
151
|
+
const outputPath = await downloadRenderTo(result.renderId, resolve(outputDir));
|
|
152
|
+
console.log(chalk.green(`Downloaded: ${outputPath}`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
}));
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkEpC,eAAO,MAAM,aAAa,SAAwB,CAAC;AAEnD,eAAO,MAAM,aAAa,SAuFtB,CAAC"}
|
package/dist/commands/render.js
CHANGED
|
@@ -1,14 +1,68 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
-
import { readFileSync } from 'node:fs';
|
|
5
|
-
import {
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { submitRender, getRenderStatus, downloadRender } from '../lib/api.js';
|
|
6
7
|
import { isAuthenticated } from '../lib/config.js';
|
|
8
|
+
function createStatusCommand() {
|
|
9
|
+
return new Command('status')
|
|
10
|
+
.description('Check render status')
|
|
11
|
+
.argument('<id>', 'Render job ID')
|
|
12
|
+
.option('--download [dir]', 'Download completed render to a directory', false)
|
|
13
|
+
.action(async (id, options) => {
|
|
14
|
+
if (!isAuthenticated()) {
|
|
15
|
+
console.log(chalk.yellow('⚠️ Not authenticated'));
|
|
16
|
+
console.log(chalk.dim('Run: scenerok auth login'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const renderId = Number.parseInt(id, 10);
|
|
20
|
+
if (Number.isNaN(renderId)) {
|
|
21
|
+
console.log(chalk.red('❌ Invalid render ID'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const status = await getRenderStatus(renderId);
|
|
26
|
+
console.log(chalk.cyan('\\n📹 Render Status\\n'));
|
|
27
|
+
console.log(` ID: ${status.id}`);
|
|
28
|
+
console.log(` Status: ${status.status}`);
|
|
29
|
+
console.log(` Progress: ${status.progress}%`);
|
|
30
|
+
if (status.outputUrl) {
|
|
31
|
+
console.log(` Output: ${status.outputUrl}`);
|
|
32
|
+
}
|
|
33
|
+
if (status.downloadUrl) {
|
|
34
|
+
console.log(` Download: ${status.downloadUrl}`);
|
|
35
|
+
}
|
|
36
|
+
if (options.download) {
|
|
37
|
+
if (status.status !== 'completed') {
|
|
38
|
+
throw new Error('Render is not completed yet');
|
|
39
|
+
}
|
|
40
|
+
const outputDir = typeof options.download === 'string' ? options.download : 'renders';
|
|
41
|
+
const downloaded = await downloadRender(renderId);
|
|
42
|
+
mkdirSync(resolve(outputDir), { recursive: true });
|
|
43
|
+
const outputPath = join(resolve(outputDir), downloaded.filename);
|
|
44
|
+
writeFileSync(outputPath, downloaded.buffer);
|
|
45
|
+
console.log(` Saved: ${outputPath}`);
|
|
46
|
+
}
|
|
47
|
+
if (status.error) {
|
|
48
|
+
console.log(chalk.red(` Error: ${status.error}`));
|
|
49
|
+
}
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export const statusCommand = createStatusCommand();
|
|
7
59
|
export const renderCommand = new Command('render')
|
|
8
60
|
.description('Submit a VidScript for rendering')
|
|
9
61
|
.argument('<file>', 'Path to .vidscript file')
|
|
10
62
|
.option('-r, --resolution <resolution>', 'Output resolution (e.g. 1080x1920)')
|
|
63
|
+
.option('-p, --project-id <id>', 'Attach render to an uploaded project ID')
|
|
11
64
|
.option('-w, --watch', 'Watch render status until complete')
|
|
65
|
+
.option('--download [dir]', 'Download completed render to a directory', false)
|
|
12
66
|
.action(async (file, options) => {
|
|
13
67
|
if (!isAuthenticated()) {
|
|
14
68
|
console.log(chalk.yellow('⚠️ Not authenticated'));
|
|
@@ -25,7 +79,11 @@ export const renderCommand = new Command('render')
|
|
|
25
79
|
}
|
|
26
80
|
const spinner = ora('Submitting render job...').start();
|
|
27
81
|
try {
|
|
28
|
-
const
|
|
82
|
+
const projectId = options.projectId ? Number.parseInt(options.projectId, 10) : undefined;
|
|
83
|
+
if (options.projectId && Number.isNaN(projectId)) {
|
|
84
|
+
throw new Error('Invalid project ID');
|
|
85
|
+
}
|
|
86
|
+
const result = await submitRender(vidscript, options.resolution, projectId);
|
|
29
87
|
spinner.succeed(`Render job submitted (#${result.renderId})`);
|
|
30
88
|
console.log(chalk.cyan('\\n📹 Render Job\\n'));
|
|
31
89
|
console.log(` ID: ${result.renderId}`);
|
|
@@ -39,7 +97,7 @@ export const renderCommand = new Command('render')
|
|
|
39
97
|
console.log(` Log: ${result.logUrl}`);
|
|
40
98
|
}
|
|
41
99
|
console.log('');
|
|
42
|
-
if (options.watch) {
|
|
100
|
+
if (options.watch || options.download) {
|
|
43
101
|
console.log(chalk.dim('Watching render status...\\n'));
|
|
44
102
|
while (true) {
|
|
45
103
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -47,6 +105,14 @@ export const renderCommand = new Command('render')
|
|
|
47
105
|
if (status.status === 'completed') {
|
|
48
106
|
console.log(chalk.green(`\\n✅ Render complete!\\n`));
|
|
49
107
|
console.log(` Download: ${status.downloadUrl}`);
|
|
108
|
+
if (options.download) {
|
|
109
|
+
const outputDir = typeof options.download === 'string' ? options.download : 'renders';
|
|
110
|
+
const downloaded = await downloadRender(result.renderId);
|
|
111
|
+
mkdirSync(resolve(outputDir), { recursive: true });
|
|
112
|
+
const outputPath = join(resolve(outputDir), downloaded.filename);
|
|
113
|
+
writeFileSync(outputPath, downloaded.buffer);
|
|
114
|
+
console.log(` Saved: ${outputPath}`);
|
|
115
|
+
}
|
|
50
116
|
console.log('');
|
|
51
117
|
break;
|
|
52
118
|
}
|
|
@@ -70,39 +136,4 @@ export const renderCommand = new Command('render')
|
|
|
70
136
|
process.exit(1);
|
|
71
137
|
}
|
|
72
138
|
});
|
|
73
|
-
renderCommand.addCommand(
|
|
74
|
-
.description('Check render status')
|
|
75
|
-
.argument('<id>', 'Render job ID')
|
|
76
|
-
.action(async (id) => {
|
|
77
|
-
if (!isAuthenticated()) {
|
|
78
|
-
console.log(chalk.yellow('⚠️ Not authenticated'));
|
|
79
|
-
console.log(chalk.dim('Run: scenerok auth login'));
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
const renderId = Number.parseInt(id, 10);
|
|
83
|
-
if (Number.isNaN(renderId)) {
|
|
84
|
-
console.log(chalk.red('❌ Invalid render ID'));
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
const status = await getRenderStatus(renderId);
|
|
89
|
-
console.log(chalk.cyan('\\n📹 Render Status\\n'));
|
|
90
|
-
console.log(` ID: ${status.id}`);
|
|
91
|
-
console.log(` Status: ${status.status}`);
|
|
92
|
-
console.log(` Progress: ${status.progress}%`);
|
|
93
|
-
if (status.outputUrl) {
|
|
94
|
-
console.log(` Output: ${status.outputUrl}`);
|
|
95
|
-
}
|
|
96
|
-
if (status.downloadUrl) {
|
|
97
|
-
console.log(` Download: ${status.downloadUrl}`);
|
|
98
|
-
}
|
|
99
|
-
if (status.error) {
|
|
100
|
-
console.log(chalk.red(` Error: ${status.error}`));
|
|
101
|
-
}
|
|
102
|
-
console.log('');
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
}));
|
|
139
|
+
renderCommand.addCommand(createStatusCommand());
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,10 @@ import { Command } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { authCommand } from './commands/auth.js';
|
|
4
4
|
import { skillsCommand } from './commands/skills.js';
|
|
5
|
-
import { renderCommand } from './commands/render.js';
|
|
5
|
+
import { renderCommand, statusCommand } from './commands/render.js';
|
|
6
6
|
import { validateCommand } from './commands/validate.js';
|
|
7
|
+
import { projectCommand } from './commands/project.js';
|
|
8
|
+
import { cacheCommand } from './commands/cache.js';
|
|
7
9
|
const program = new Command();
|
|
8
10
|
program
|
|
9
11
|
.name('scenerok')
|
|
@@ -12,7 +14,10 @@ program
|
|
|
12
14
|
program.addCommand(authCommand);
|
|
13
15
|
program.addCommand(skillsCommand);
|
|
14
16
|
program.addCommand(renderCommand);
|
|
17
|
+
program.addCommand(projectCommand);
|
|
18
|
+
program.addCommand(cacheCommand);
|
|
15
19
|
program.addCommand(validateCommand);
|
|
20
|
+
program.addCommand(statusCommand);
|
|
16
21
|
// Default help
|
|
17
22
|
program.on('--help', () => {
|
|
18
23
|
console.log('');
|
|
@@ -21,6 +26,8 @@ program.on('--help', () => {
|
|
|
21
26
|
console.log(' $ scenerok skills install opencode');
|
|
22
27
|
console.log(' $ scenerok validate video.vidscript');
|
|
23
28
|
console.log(' $ scenerok render video.vidscript --resolution 1080x1920');
|
|
29
|
+
console.log(' $ scenerok project upload video.vidscript --assets ./assets --render --watch --download');
|
|
30
|
+
console.log(' $ scenerok cache pull');
|
|
24
31
|
console.log(' $ scenerok status 42');
|
|
25
32
|
console.log('');
|
|
26
33
|
console.log(chalk.cyan('Get started:'));
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare function validateVidscript(vidscript: string): Promise<{
|
|
|
10
10
|
warnings?: string[];
|
|
11
11
|
error?: string;
|
|
12
12
|
}>;
|
|
13
|
-
export declare function submitRender(vidscript: string, resolution?: string): Promise<{
|
|
13
|
+
export declare function submitRender(vidscript: string, resolution?: string, templateId?: number): Promise<{
|
|
14
14
|
renderId: number;
|
|
15
15
|
status: string;
|
|
16
16
|
resolution: string;
|
|
@@ -34,6 +34,48 @@ export declare function getRenderStatus(renderId: number): Promise<{
|
|
|
34
34
|
createdAt: string;
|
|
35
35
|
completedAt: string | null;
|
|
36
36
|
}>;
|
|
37
|
+
export declare function downloadRender(renderId: number): Promise<{
|
|
38
|
+
buffer: Buffer;
|
|
39
|
+
filename: string;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function uploadProject(formData: FormData): Promise<{
|
|
42
|
+
project: {
|
|
43
|
+
id: number;
|
|
44
|
+
title: string;
|
|
45
|
+
vidscript: string;
|
|
46
|
+
projectUrl: string;
|
|
47
|
+
};
|
|
48
|
+
assets: Array<{
|
|
49
|
+
id: number;
|
|
50
|
+
filename: string;
|
|
51
|
+
url: string;
|
|
52
|
+
mimeType: string | null;
|
|
53
|
+
fileSize: number;
|
|
54
|
+
}>;
|
|
55
|
+
render?: Awaited<ReturnType<typeof submitRender>> | null;
|
|
56
|
+
}>;
|
|
57
|
+
export declare function listProjects(): Promise<{
|
|
58
|
+
projects: Array<{
|
|
59
|
+
id: number;
|
|
60
|
+
title: string;
|
|
61
|
+
status: string;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
metadata?: unknown;
|
|
64
|
+
}>;
|
|
65
|
+
}>;
|
|
66
|
+
export declare function getAssetCache(options?: {
|
|
67
|
+
plugin?: string;
|
|
68
|
+
limit?: number;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
entries: Array<{
|
|
71
|
+
id: string;
|
|
72
|
+
cacheKey: string;
|
|
73
|
+
r2Url: string;
|
|
74
|
+
pluginName: string | null;
|
|
75
|
+
metadata: unknown;
|
|
76
|
+
updatedAt: string;
|
|
77
|
+
}>;
|
|
78
|
+
}>;
|
|
37
79
|
export declare function initiateDeviceAuth(deviceName: string): Promise<{
|
|
38
80
|
device_code: string;
|
|
39
81
|
user_code: string;
|
package/dist/lib/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AASA,cAAM,QAAS,SAAQ,KAAK;IAGjB,MAAM,EAAE,MAAM;IACd,YAAY,CAAC,EAAE,OAAO;gBAF7B,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,OAAO,YAAA;CAKhC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AASA,cAAM,QAAS,SAAQ,KAAK;IAGjB,MAAM,EAAE,MAAM;IACd,YAAY,CAAC,EAAE,OAAO;gBAF7B,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,OAAO,YAAA;CAKhC;AAkED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM;WAE9C,OAAO;aACL,MAAM;eACJ,MAAM;eACN,MAAM,EAAE;YACX,MAAM;GAEjB;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;cAEhF,MAAM;YACR,MAAM;gBACF,MAAM;oBACF,MAAM;eACX,MAAM;iBACJ,MAAM;aACV,MAAM;kBACD,MAAM;gBACR,MAAM,GAAG,IAAI;GAE5B;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM;QAE9C,MAAM;YACF,MAAM;cACJ,MAAM;eACL,MAAM,GAAG,IAAI;oBACR,MAAM;eACX,MAAM;iBACJ,MAAM;mBACJ,OAAO;WACf,MAAM,GAAG,IAAI;eACT,MAAM;iBACJ,MAAM,GAAG,IAAI;GAE7B;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBpG;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ;aAEzC;QACP,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB;YACO,KAAK,CAAC;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;aACO,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,GAAG,IAAI;GAE3D;AAED,wBAAsB,YAAY;cAEpB,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;GAEL;AAED,wBAAsB,aAAa,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO;aAMxE,KAAK,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;GAEL;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM;iBAE1C,MAAM;eACR,MAAM;sBACC,MAAM;gBACZ,MAAM;cACR,MAAM;GAEnB;AAED,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM;YAE3C,MAAM;mBACC,MAAM;iBACR,MAAM;iBACN,MAAM;YACX,MAAM;GAEjB;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|