@scenerok/cli 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +17 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +16 -2
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +14 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +13 -6
- package/dist/index.js +3 -1
- package/dist/lib/api.d.ts +12 -0
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +38 -4
- package/dist/lib/format-api-error.d.ts +2 -0
- package/dist/lib/format-api-error.d.ts.map +1 -0
- package/dist/lib/format-api-error.js +24 -0
- package/dist/lib/logger.d.ts +8 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +51 -0
- package/examples/system/rokmilk-chocolate-promo.vid +5 -4
- package/package.json +1 -1
- package/skills/aider/SKILL.md +13 -18
- package/skills/aider/vidscript-guide.md +42 -39
- package/skills/aider/vidscript-sample.md +4 -3
- package/skills/aider/vidscript-strict.md +26 -8
- package/skills/claude/SKILL.md +13 -18
- package/skills/claude/vidscript-guide.md +42 -39
- package/skills/claude/vidscript-sample.md +4 -3
- package/skills/claude/vidscript-strict.md +26 -8
- package/skills/codex/SKILL.md +13 -18
- package/skills/codex/vidscript-guide.md +42 -39
- package/skills/codex/vidscript-sample.md +4 -3
- package/skills/codex/vidscript-strict.md +26 -8
- package/skills/cursor/SKILL.md +13 -18
- package/skills/cursor/vidscript-guide.md +42 -39
- package/skills/cursor/vidscript-sample.md +4 -3
- package/skills/cursor/vidscript-strict.md +26 -8
- package/skills/opencode/SKILL.md +13 -18
- package/skills/opencode/vidscript-guide.md +42 -39
- package/skills/opencode/vidscript-sample.md +4 -3
- package/skills/opencode/vidscript-strict.md +26 -8
- package/skills/skills/aider/SKILL.md +13 -18
- package/skills/skills/aider/vidscript-guide.md +42 -39
- package/skills/skills/aider/vidscript-sample.md +4 -3
- package/skills/skills/aider/vidscript-strict.md +26 -8
- package/skills/skills/claude/SKILL.md +13 -18
- package/skills/skills/claude/vidscript-guide.md +42 -39
- package/skills/skills/claude/vidscript-sample.md +4 -3
- package/skills/skills/claude/vidscript-strict.md +26 -8
- package/skills/skills/codex/SKILL.md +13 -18
- package/skills/skills/codex/vidscript-guide.md +42 -39
- package/skills/skills/codex/vidscript-sample.md +4 -3
- package/skills/skills/codex/vidscript-strict.md +26 -8
- package/skills/skills/cursor/SKILL.md +13 -18
- package/skills/skills/cursor/vidscript-guide.md +42 -39
- package/skills/skills/cursor/vidscript-sample.md +4 -3
- package/skills/skills/cursor/vidscript-strict.md +26 -8
- package/skills/skills/opencode/SKILL.md +13 -18
- package/skills/skills/opencode/vidscript-guide.md +42 -39
- package/skills/skills/opencode/vidscript-sample.md +4 -3
- package/skills/skills/opencode/vidscript-strict.md +26 -8
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import motion from "@scenerok/basic-animations"
|
|
1
2
|
# SceneRok CLI
|
|
2
3
|
|
|
3
4
|
Create videos from your terminal and agent workflows.
|
|
@@ -102,7 +103,7 @@ scenerok project render <project-id> --watch --download ./renders
|
|
|
102
103
|
|
|
103
104
|
```bash
|
|
104
105
|
scenerok cache pull
|
|
105
|
-
scenerok cache pull --plugin xai
|
|
106
|
+
scenerok cache pull --plugin @scenerok/xai --output ~/.scenerok/cache/assets
|
|
106
107
|
```
|
|
107
108
|
|
|
108
109
|
Downloads cached generated assets and writes a `manifest.json` to the output folder.
|
|
@@ -123,15 +124,17 @@ Config is stored in `~/.scenerok/config.json`:
|
|
|
123
124
|
VidScript is a declarative language for describing video compositions.
|
|
124
125
|
|
|
125
126
|
```vidscript
|
|
127
|
+
import motion from "@scenerok/basic-animations"
|
|
128
|
+
|
|
126
129
|
input hero = "./assets/hero.mp4"
|
|
127
130
|
|
|
128
131
|
[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)]
|
|
132
|
+
[0.4s .. 3s] = text "Hello World", font: "Inter Bold", size: 72, x: "50%", y: "50%", align: center, animate: [motion.fadeIn(0.6s), motion.slideY(40, 0, 0.8s)]
|
|
130
133
|
|
|
131
134
|
output to "video.mp4", resolution: "1080x1920", fps: 30
|
|
132
135
|
```
|
|
133
136
|
|
|
134
|
-
Animation helpers: `fadeIn`, `fadeOut`, `slideX`, `slideY`, `popIn`, `riseIn`, `swingIn`, `glitchIn`, `float`, `typewriter`.
|
|
137
|
+
Animation helpers are package functions: `motion.fadeIn`, `motion.fadeOut`, `motion.slideX`, `motion.slideY`, `motion.popIn`, `motion.riseIn`, `motion.swingIn`, `motion.glitchIn`, `motion.float`, `motion.typewriter`.
|
|
135
138
|
|
|
136
139
|
## Links
|
|
137
140
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,eAAO,MAAM,WAAW,SA2GrB,CAAC"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -3,12 +3,16 @@ import chalk from 'chalk';
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { readConfig, writeConfig, isAuthenticated } from '../lib/config.js';
|
|
5
5
|
import { initiateDeviceAuth, pollDeviceAuth } from '../lib/api.js';
|
|
6
|
+
import { getBaseUrl } from '../lib/config.js';
|
|
7
|
+
import { formatApiErrorMessage } from '../lib/format-api-error.js';
|
|
8
|
+
import { getLogFilePath, isVerbose, logError } from '../lib/logger.js';
|
|
6
9
|
export const authCommand = new Command('auth')
|
|
7
10
|
.description('Authenticate with SceneRok')
|
|
8
11
|
.addCommand(new Command('login')
|
|
9
12
|
.description('Log in to SceneRok via browser')
|
|
10
13
|
.option('-n, --name <name>', 'Device name', 'CLI')
|
|
11
14
|
.option('-u, --base-url <url>', 'SceneRok API base URL (overrides ~/.scenerok/config.json and SCENEROK_BASE_URL)')
|
|
15
|
+
.option('-v, --verbose', 'Print debug output and log API calls to ~/.scenerok/logs/cli.log')
|
|
12
16
|
.action(async (options) => {
|
|
13
17
|
if (options.baseUrl) {
|
|
14
18
|
const config = readConfig();
|
|
@@ -16,6 +20,10 @@ export const authCommand = new Command('auth')
|
|
|
16
20
|
writeConfig(config);
|
|
17
21
|
}
|
|
18
22
|
console.log(chalk.cyan('\n🔐 SceneRok Authentication\n'));
|
|
23
|
+
console.log(chalk.dim(`API: ${getBaseUrl()}`));
|
|
24
|
+
if (options.verbose || isVerbose()) {
|
|
25
|
+
console.log(chalk.dim(`Log file: ${getLogFilePath()}\n`));
|
|
26
|
+
}
|
|
19
27
|
const spinner = ora('Initiating device auth...').start();
|
|
20
28
|
try {
|
|
21
29
|
const auth = await initiateDeviceAuth(options.name);
|
|
@@ -53,13 +61,21 @@ export const authCommand = new Command('auth')
|
|
|
53
61
|
}
|
|
54
62
|
catch (error) {
|
|
55
63
|
spinner.fail('Authentication failed');
|
|
56
|
-
|
|
64
|
+
logError('auth login failed', {
|
|
65
|
+
baseUrl: getBaseUrl(),
|
|
66
|
+
error: formatApiErrorMessage(error),
|
|
67
|
+
});
|
|
68
|
+
console.error(chalk.red(formatApiErrorMessage(error)));
|
|
69
|
+
console.error(chalk.dim(`\nLog: ${getLogFilePath()}`));
|
|
70
|
+
console.error(chalk.dim('Retry with: scenerok auth login --verbose (or SCENEROK_VERBOSE=1 scenerok auth login)'));
|
|
57
71
|
process.exit(1);
|
|
58
72
|
}
|
|
59
73
|
}))
|
|
60
74
|
.addCommand(new Command('status')
|
|
61
75
|
.description('Check authentication status')
|
|
62
76
|
.action(() => {
|
|
77
|
+
console.log(chalk.dim(`API: ${getBaseUrl()}`));
|
|
78
|
+
console.log(chalk.dim(`Log: ${getLogFilePath()}`));
|
|
63
79
|
if (isAuthenticated()) {
|
|
64
80
|
console.log(chalk.green('✅ Authenticated with SceneRok'));
|
|
65
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuCpC,eAAO,MAAM,cAAc,SA+JxB,CAAC"}
|
package/dist/commands/project.js
CHANGED
|
@@ -6,6 +6,14 @@ import { join, resolve } from 'node:path';
|
|
|
6
6
|
import { downloadRender, getRenderStatus, listProjects, submitRender, uploadProject } from '../lib/api.js';
|
|
7
7
|
import { isAuthenticated } from '../lib/config.js';
|
|
8
8
|
import { appendProjectToForm, prepareProject } from '../lib/project.js';
|
|
9
|
+
function formatDuration(ms) {
|
|
10
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
11
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
12
|
+
const seconds = totalSeconds % 60;
|
|
13
|
+
if (minutes === 0)
|
|
14
|
+
return `${seconds}s`;
|
|
15
|
+
return `${minutes}m ${seconds.toString().padStart(2, '0')}s`;
|
|
16
|
+
}
|
|
9
17
|
async function waitForRender(renderId) {
|
|
10
18
|
while (true) {
|
|
11
19
|
await new Promise((resolveWait) => setTimeout(resolveWait, 3000));
|
|
@@ -74,8 +82,11 @@ export const projectCommand = new Command('project')
|
|
|
74
82
|
console.log(` Download: ${result.render.downloadUrl}`);
|
|
75
83
|
if (options.watch || options.download) {
|
|
76
84
|
console.log(chalk.dim('\nWatching render status...\n'));
|
|
77
|
-
await waitForRender(result.render.renderId);
|
|
85
|
+
const status = await waitForRender(result.render.renderId);
|
|
78
86
|
console.log(chalk.green('\nRender complete'));
|
|
87
|
+
if (status.clickToDownloadMs !== null) {
|
|
88
|
+
console.log(` Time: ${formatDuration(status.clickToDownloadMs)}`);
|
|
89
|
+
}
|
|
79
90
|
}
|
|
80
91
|
if (options.download) {
|
|
81
92
|
const outputDir = typeof options.download === 'string' ? options.download : 'renders';
|
|
@@ -143,8 +154,11 @@ export const projectCommand = new Command('project')
|
|
|
143
154
|
console.log(` URL: ${result.renderUrl}`);
|
|
144
155
|
console.log(` Download: ${result.downloadUrl}`);
|
|
145
156
|
if (options.watch || options.download) {
|
|
146
|
-
await waitForRender(result.renderId);
|
|
157
|
+
const status = await waitForRender(result.renderId);
|
|
147
158
|
console.log(chalk.green('\nRender complete'));
|
|
159
|
+
if (status.clickToDownloadMs !== null) {
|
|
160
|
+
console.log(` Time: ${formatDuration(status.clickToDownloadMs)}`);
|
|
161
|
+
}
|
|
148
162
|
}
|
|
149
163
|
if (options.download) {
|
|
150
164
|
const outputDir = typeof options.download === 'string' ? options.download : 'renders';
|
|
@@ -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;AA6EpC,eAAO,MAAM,aAAa,SAAwB,CAAC;AAEnD,eAAO,MAAM,aAAa,SA0FtB,CAAC"}
|
package/dist/commands/render.js
CHANGED
|
@@ -5,6 +5,14 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
5
5
|
import { join, resolve } from 'node:path';
|
|
6
6
|
import { submitRender, getRenderStatus, downloadRender } from '../lib/api.js';
|
|
7
7
|
import { isAuthenticated } from '../lib/config.js';
|
|
8
|
+
function formatDuration(ms) {
|
|
9
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
10
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
11
|
+
const seconds = totalSeconds % 60;
|
|
12
|
+
if (minutes === 0)
|
|
13
|
+
return `${seconds}s`;
|
|
14
|
+
return `${minutes}m ${seconds.toString().padStart(2, '0')}s`;
|
|
15
|
+
}
|
|
8
16
|
function createStatusCommand() {
|
|
9
17
|
return new Command('status')
|
|
10
18
|
.description('Check render status')
|
|
@@ -27,6 +35,9 @@ function createStatusCommand() {
|
|
|
27
35
|
console.log(` ID: ${status.id}`);
|
|
28
36
|
console.log(` Status: ${status.status}`);
|
|
29
37
|
console.log(` Progress: ${status.progress}%`);
|
|
38
|
+
if (status.clickToDownloadMs !== null) {
|
|
39
|
+
console.log(` Time: ${formatDuration(status.clickToDownloadMs)}`);
|
|
40
|
+
}
|
|
30
41
|
if (status.outputUrl) {
|
|
31
42
|
console.log(` Output: ${status.outputUrl}`);
|
|
32
43
|
}
|
|
@@ -104,6 +115,9 @@ export const renderCommand = new Command('render')
|
|
|
104
115
|
const status = await getRenderStatus(result.renderId);
|
|
105
116
|
if (status.status === 'completed') {
|
|
106
117
|
console.log(chalk.green(`\\n✅ Render complete!\\n`));
|
|
118
|
+
if (status.clickToDownloadMs !== null) {
|
|
119
|
+
console.log(` Time: ${formatDuration(status.clickToDownloadMs)}`);
|
|
120
|
+
}
|
|
107
121
|
console.log(` Download: ${status.downloadUrl}`);
|
|
108
122
|
if (options.download) {
|
|
109
123
|
const outputDir = typeof options.download === 'string' ? options.download : 'renders';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,eAAO,MAAM,eAAe,SA8CxB,CAAC"}
|
|
@@ -11,6 +11,17 @@ function collectValidationErrors(result) {
|
|
|
11
11
|
}
|
|
12
12
|
return ['Validation failed (no error details returned by server)'];
|
|
13
13
|
}
|
|
14
|
+
function printAestheticWarnings(result) {
|
|
15
|
+
if (!result.warnings || result.warnings.length === 0)
|
|
16
|
+
return;
|
|
17
|
+
console.log(chalk.yellow('\n Aesthetic warnings:'));
|
|
18
|
+
if (result.aestheticScore != null) {
|
|
19
|
+
console.log(chalk.yellow(` Score: ${result.aestheticScore}/100`));
|
|
20
|
+
}
|
|
21
|
+
for (const warning of result.warnings) {
|
|
22
|
+
console.log(chalk.yellow(` • ${warning}`));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
14
25
|
export const validateCommand = new Command('validate')
|
|
15
26
|
.description('Validate a VidScript file')
|
|
16
27
|
.argument('<file>', 'Path to .vid or .vidscript file')
|
|
@@ -40,18 +51,14 @@ export const validateCommand = new Command('validate')
|
|
|
40
51
|
if (pluginCalls != null) {
|
|
41
52
|
console.log(` Plugin calls: ${pluginCalls}`);
|
|
42
53
|
}
|
|
43
|
-
|
|
44
|
-
console.log(chalk.yellow('\\n Warnings:'));
|
|
45
|
-
for (const warning of result.warnings) {
|
|
46
|
-
console.log(` • ${warning}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
54
|
+
printAestheticWarnings(result);
|
|
49
55
|
}
|
|
50
56
|
else {
|
|
51
57
|
console.log(chalk.red('\\n❌ VidScript is invalid\\n'));
|
|
52
58
|
for (const err of collectValidationErrors(result)) {
|
|
53
59
|
console.log(` • ${err}`);
|
|
54
60
|
}
|
|
61
|
+
printAestheticWarnings(result);
|
|
55
62
|
process.exit(1);
|
|
56
63
|
}
|
|
57
64
|
console.log('');
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,8 @@ const packageJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.me
|
|
|
15
15
|
program
|
|
16
16
|
.name('scenerok')
|
|
17
17
|
.description('SceneRok CLI - Create videos from your terminal and agent workflows')
|
|
18
|
-
.version(packageJson.version || '0.0.0')
|
|
18
|
+
.version(packageJson.version || '0.0.0')
|
|
19
|
+
.option('-v, --verbose', 'Log API requests to ~/.scenerok/logs/cli.log');
|
|
19
20
|
program.addCommand(authCommand);
|
|
20
21
|
program.addCommand(skillsCommand);
|
|
21
22
|
program.addCommand(renderCommand);
|
|
@@ -29,6 +30,7 @@ program.on('--help', () => {
|
|
|
29
30
|
console.log('');
|
|
30
31
|
console.log(chalk.cyan('Examples:'));
|
|
31
32
|
console.log(' $ scenerok auth login');
|
|
33
|
+
console.log(' $ scenerok auth login --verbose # debug log at ~/.scenerok/logs/cli.log');
|
|
32
34
|
console.log(' $ scenerok skills install opencode');
|
|
33
35
|
console.log(' $ scenerok validate video.vidscript');
|
|
34
36
|
console.log(' $ scenerok render video.vidscript --resolution 1080x1920');
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -9,6 +9,17 @@ export declare function validateVidscript(vidscript: string): Promise<{
|
|
|
9
9
|
scenes?: number;
|
|
10
10
|
duration?: number;
|
|
11
11
|
warnings?: string[];
|
|
12
|
+
aestheticWarnings?: Array<{
|
|
13
|
+
ruleId: string;
|
|
14
|
+
severity: "warning";
|
|
15
|
+
category: string;
|
|
16
|
+
message: string;
|
|
17
|
+
recommendation: string;
|
|
18
|
+
elementId?: string;
|
|
19
|
+
start?: number;
|
|
20
|
+
end?: number;
|
|
21
|
+
}>;
|
|
22
|
+
aestheticScore?: number;
|
|
12
23
|
errors?: string[];
|
|
13
24
|
error?: string;
|
|
14
25
|
}>;
|
|
@@ -35,6 +46,7 @@ export declare function getRenderStatus(renderId: number): Promise<{
|
|
|
35
46
|
error: string | null;
|
|
36
47
|
createdAt: string;
|
|
37
48
|
completedAt: string | null;
|
|
49
|
+
clickToDownloadMs: number | null;
|
|
38
50
|
}>;
|
|
39
51
|
export declare function downloadRender(renderId: number): Promise<{
|
|
40
52
|
buffer: Buffer;
|
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":"
|
|
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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
2
|
import { getBaseUrl, getApiToken } from './config.js';
|
|
3
|
+
import { logDebug, logError } from './logger.js';
|
|
3
4
|
class ApiError extends Error {
|
|
4
5
|
status;
|
|
5
6
|
responseBody;
|
|
@@ -23,15 +24,48 @@ function getAuthHeaders(contentType = 'application/json') {
|
|
|
23
24
|
}
|
|
24
25
|
async function apiCall(method, path, body) {
|
|
25
26
|
const baseUrl = getBaseUrl();
|
|
26
|
-
const
|
|
27
|
+
const url = `${baseUrl}${path}`;
|
|
28
|
+
const started = Date.now();
|
|
29
|
+
logDebug('API request', {
|
|
27
30
|
method,
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
url,
|
|
32
|
+
hasBody: body != null,
|
|
30
33
|
});
|
|
31
|
-
|
|
34
|
+
let response;
|
|
35
|
+
try {
|
|
36
|
+
response = await fetch(url, {
|
|
37
|
+
method,
|
|
38
|
+
headers: getAuthHeaders(),
|
|
39
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
logError('API network error', { method, url, message });
|
|
45
|
+
throw new ApiError(`Network error: ${message}`, 0, { cause: message });
|
|
46
|
+
}
|
|
47
|
+
const rawText = await response.text();
|
|
48
|
+
let data = {};
|
|
49
|
+
if (rawText) {
|
|
50
|
+
try {
|
|
51
|
+
data = JSON.parse(rawText);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
data = { raw: rawText.slice(0, 2000) };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const durationMs = Date.now() - started;
|
|
32
58
|
if (!response.ok) {
|
|
59
|
+
logError('API error response', {
|
|
60
|
+
method,
|
|
61
|
+
url,
|
|
62
|
+
status: response.status,
|
|
63
|
+
durationMs,
|
|
64
|
+
body: data,
|
|
65
|
+
});
|
|
33
66
|
throw new ApiError(data.error || `HTTP ${response.status}`, response.status, data);
|
|
34
67
|
}
|
|
68
|
+
logDebug('API success', { method, url, status: response.status, durationMs });
|
|
35
69
|
return data;
|
|
36
70
|
}
|
|
37
71
|
async function apiFormCall(path, formData) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-api-error.d.ts","sourceRoot":"","sources":["../../src/lib/format-api-error.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAwB5D"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ApiError } from './api.js';
|
|
2
|
+
export function formatApiErrorMessage(error) {
|
|
3
|
+
if (error instanceof ApiError) {
|
|
4
|
+
const parts = [`HTTP ${error.status}: ${error.message}`];
|
|
5
|
+
const body = error.responseBody;
|
|
6
|
+
if (body && typeof body === 'object') {
|
|
7
|
+
const record = body;
|
|
8
|
+
if (typeof record.hint === 'string') {
|
|
9
|
+
parts.push(`Hint: ${record.hint}`);
|
|
10
|
+
}
|
|
11
|
+
if (typeof record.detail === 'string') {
|
|
12
|
+
parts.push(`Detail: ${record.detail}`);
|
|
13
|
+
}
|
|
14
|
+
if (typeof record.code === 'string') {
|
|
15
|
+
parts.push(`Code: ${record.code}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return parts.join('\n');
|
|
19
|
+
}
|
|
20
|
+
if (error instanceof Error) {
|
|
21
|
+
return error.message;
|
|
22
|
+
}
|
|
23
|
+
return String(error);
|
|
24
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function getLogFilePath(): string;
|
|
2
|
+
export declare function isVerbose(): boolean;
|
|
3
|
+
export declare function writeLog(level: 'debug' | 'info' | 'warn' | 'error', message: string, meta?: Record<string, unknown>): void;
|
|
4
|
+
export declare function logDebug(message: string, meta?: Record<string, unknown>): void;
|
|
5
|
+
export declare function logInfo(message: string, meta?: Record<string, unknown>): void;
|
|
6
|
+
export declare function logWarn(message: string, meta?: Record<string, unknown>): void;
|
|
7
|
+
export declare function logError(message: string, meta?: Record<string, unknown>): void;
|
|
8
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAOA,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,SAAS,IAAI,OAAO,CAOnC;AAmBD,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC1C,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAIN;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAM9E;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE7E;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE7E;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE9E"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getConfigDir } from './config.js';
|
|
4
|
+
const LOG_DIR = join(getConfigDir(), 'logs');
|
|
5
|
+
const LOG_FILE = join(LOG_DIR, 'cli.log');
|
|
6
|
+
export function getLogFilePath() {
|
|
7
|
+
return LOG_FILE;
|
|
8
|
+
}
|
|
9
|
+
export function isVerbose() {
|
|
10
|
+
const verboseEnv = ['SCENEROK', '_', 'VERBOSE'].join('');
|
|
11
|
+
return (process.env[verboseEnv] === '1' ||
|
|
12
|
+
process.argv.includes('--verbose') ||
|
|
13
|
+
process.argv.includes('-v'));
|
|
14
|
+
}
|
|
15
|
+
function ensureLogDir() {
|
|
16
|
+
if (!existsSync(LOG_DIR)) {
|
|
17
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function formatMeta(meta) {
|
|
21
|
+
if (!meta || Object.keys(meta).length === 0) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return ` ${JSON.stringify(meta)}`;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return ' [meta unserializable]';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function writeLog(level, message, meta) {
|
|
32
|
+
ensureLogDir();
|
|
33
|
+
const line = `[${new Date().toISOString()}] ${level.toUpperCase()} ${message}${formatMeta(meta)}\n`;
|
|
34
|
+
appendFileSync(LOG_FILE, line, 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
export function logDebug(message, meta) {
|
|
37
|
+
writeLog('debug', message, meta);
|
|
38
|
+
if (isVerbose()) {
|
|
39
|
+
// eslint-disable-next-line no-console
|
|
40
|
+
console.error(`[debug] ${message}${formatMeta(meta)}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function logInfo(message, meta) {
|
|
44
|
+
writeLog('info', message, meta);
|
|
45
|
+
}
|
|
46
|
+
export function logWarn(message, meta) {
|
|
47
|
+
writeLog('warn', message, meta);
|
|
48
|
+
}
|
|
49
|
+
export function logError(message, meta) {
|
|
50
|
+
writeLog('error', message, meta);
|
|
51
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Requires: scenerok secrets set XAI_API_KEY=... (or configured on scenerok.com account)
|
|
3
3
|
|
|
4
4
|
import xai from "@scenerok/xai"
|
|
5
|
+
import motion from "@scenerok/basic-animations"
|
|
5
6
|
|
|
6
7
|
# Generated visuals — one clip per segment; prompts must use straight double quotes only
|
|
7
8
|
[-] = video xai.imagine("Cinematic slow-motion pour of rich creamy chocolate milk into a sleek modern 250ml carton on marble, warm studio lighting, shallow depth of field, 9:16 vertical", aspect_ratio: "9:16", duration: 5)
|
|
@@ -10,13 +11,13 @@ import xai from "@scenerok/xai"
|
|
|
10
11
|
|
|
11
12
|
[-] = video xai.imagine("Silky chocolate milk swirl in glass with RokMilk carton blurred in background, golden light, appetizing food cinematography, 9:16 vertical", aspect_ratio: "9:16", duration: 5)
|
|
12
13
|
|
|
13
|
-
[0s .. 2.5s] = text "RokMilk", font: "Bebas Neue", size: 80, color: "#FFD700", x: "50%", y: "18%", align: center, stroke: "#3E1C00", stroke_width: 4, letter_spacing: 6, animate: fadeIn(0.6s)
|
|
14
|
+
[0s .. 2.5s] = text "RokMilk", font: "Bebas Neue", size: 80, color: "#FFD700", x: "50%", y: "18%", align: center, stroke: "#3E1C00", stroke_width: 4, letter_spacing: 6, animate: motion.fadeIn(0.6s)
|
|
14
15
|
|
|
15
|
-
[2s .. 5s] = text "Chocolate Milkshake", font: "Outfit", size: 56, color: "#FFFFFF", x: "50%", y: "45%", align: center, stroke: "#3E1C00", stroke_width: 3, animate: riseIn(0.8s)
|
|
16
|
+
[2s .. 5s] = text "Chocolate Milkshake", font: "Outfit", size: 56, color: "#FFFFFF", x: "50%", y: "45%", align: center, stroke: "#3E1C00", stroke_width: 3, animate: motion.riseIn(0.8s)
|
|
16
17
|
|
|
17
|
-
[5s .. 8s] = text "250ml of Pure Indulgence", font: "Inter", size: 42, color: "#F3E5AB", x: "50%", y: "55%", align: center, stroke: "#3E1C00", stroke_width: 2, animate: slideY(40, 0, 0.8s)
|
|
18
|
+
[5s .. 8s] = text "250ml of Pure Indulgence", font: "Inter", size: 42, color: "#F3E5AB", x: "50%", y: "55%", align: center, stroke: "#3E1C00", stroke_width: 2, animate: motion.slideY(40, 0, 0.8s)
|
|
18
19
|
|
|
19
|
-
[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: fadeIn(1s)
|
|
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)
|
|
20
21
|
|
|
21
22
|
[0s .. 15s] = filter "vignette", intensity: 0.3
|
|
22
23
|
[0s .. 15s] = filter "saturation", value: 1.12
|
package/package.json
CHANGED
package/skills/aider/SKILL.md
CHANGED
|
@@ -67,37 +67,32 @@ Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignett
|
|
|
67
67
|
output to "video.mp4", resolution: "1080x1920", fps: 30
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
###
|
|
70
|
+
### Package Imports
|
|
71
71
|
|
|
72
72
|
```vidscript
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
clip.Trim(start: 0s, end: 3s)
|
|
77
|
-
[- 2s] = text headline, style: title # auto-start, 2s duration
|
|
78
|
-
}
|
|
73
|
+
import xai from "@scenerok/xai" # xAI image/video/tts
|
|
74
|
+
import eleven from "@elevenlabs/music" # ElevenLabs generative music
|
|
75
|
+
import motion from "@scenerok/basic-animations" # text/video animation helpers
|
|
79
76
|
```
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
import { intro } from "./timelines.vid"
|
|
83
|
-
import * as pack from "./effects.vid"
|
|
84
|
-
import eleven from "@elevenlabs/tts" # future scoped package # from npm registry
|
|
85
|
-
import music from "@elevenlabs/music" # generative music
|
|
86
|
-
use intro at [-] with { clip: main, headline: "Welcome" }
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Plugin Calls (required import)
|
|
78
|
+
### Plugin Calls
|
|
90
79
|
|
|
91
80
|
```vidscript
|
|
92
81
|
import xai from "@scenerok/xai"
|
|
82
|
+
import eleven from "@elevenlabs/music"
|
|
83
|
+
import motion from "@scenerok/basic-animations"
|
|
93
84
|
|
|
94
85
|
[-] = video xai.imagine("Cinematic product shot", aspect_ratio: "9:16", duration: 5)
|
|
95
86
|
[-] = audio xai.tts("Welcome to SceneRok", voice: "eve")
|
|
87
|
+
|
|
88
|
+
# ElevenLabs music package functions require an import alias.
|
|
89
|
+
let bed = eleven.music("Warm premium launch bed", duration: 15, instrumental: true)
|
|
90
|
+
[0s .. 15s] = audio bed, volume: 0.35, fade_out: 2s
|
|
96
91
|
```
|
|
97
92
|
|
|
98
|
-
**Always**
|
|
93
|
+
**Always** import the package that owns the function: `xai.*` from `@scenerok/xai`, `eleven.*` from `@elevenlabs/music`, and `motion.*` from `@scenerok/basic-animations`. Calls without import fail validation with `Unknown function 'xai.imagine'`.
|
|
99
94
|
|
|
100
|
-
|
|
95
|
+
For ElevenLabs music, import `@elevenlabs/music` and call `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)`. Use `let bed = eleven.music(...)` followed by `audio bed, volume: ...` when you need volume or fades.
|
|
101
96
|
|
|
102
97
|
Read `vidscript-strict.md` for anti-patterns and `examples/system/*.vid` for copy-paste templates (installed with `scenerok skills install`).
|
|
103
98
|
|