@scenerok/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +69 -0
- package/dist/commands/render.d.ts +3 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +108 -0
- package/dist/commands/skills.d.ts +3 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +121 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +41 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/lib/api.d.ts +52 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +48 -0
- package/dist/lib/config.d.ts +11 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +37 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# SceneRok CLI
|
|
2
|
+
|
|
3
|
+
Create videos from your terminal and agent workflows.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @scenerok/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use with `npx`:
|
|
12
|
+
```bash
|
|
13
|
+
npx @scenerok/cli auth login
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Authenticate
|
|
20
|
+
scenerok auth login
|
|
21
|
+
|
|
22
|
+
# Validate a VidScript
|
|
23
|
+
scenerok validate video.vidscript
|
|
24
|
+
|
|
25
|
+
# Render a video
|
|
26
|
+
scenerok render video.vidscript --resolution 1080x1920 --watch
|
|
27
|
+
|
|
28
|
+
# Check render status
|
|
29
|
+
scenerok status 42
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
### `auth`
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
scenerok auth login # Log in via browser
|
|
38
|
+
reelforge auth status # Check auth status
|
|
39
|
+
reelforge auth logout # Remove credentials
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `skills`
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
scenerok skills install <platform> # Install skills for your agent
|
|
46
|
+
scenerok skills list # List available platforms
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Supported platforms: `opencode`, `claude`, `codex`, `cursor`, `aider`
|
|
50
|
+
|
|
51
|
+
### `validate`
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
scenerok validate <file.vidscript>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Validates VidScript syntax and returns scene count, duration, and warnings.
|
|
58
|
+
|
|
59
|
+
### `render`
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
scenerok render <file.vidscript> [options]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Options:
|
|
66
|
+
- `-r, --resolution <resolution>` - Output resolution (e.g. 1080x1920)
|
|
67
|
+
- `-w, --watch` - Watch render status until complete
|
|
68
|
+
|
|
69
|
+
### `status`
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
scenerok status <render-id>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Check the status of a render job.
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
Config is stored in `~/.scenerok/config.json`:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"apiToken": "rf_...",
|
|
84
|
+
"baseUrl": "https://scenerok.com"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## VidScript
|
|
89
|
+
|
|
90
|
+
VidScript is a declarative language for describing video compositions.
|
|
91
|
+
|
|
92
|
+
```vidscript
|
|
93
|
+
config {
|
|
94
|
+
resolution = "1080x1920";
|
|
95
|
+
fps = 30;
|
|
96
|
+
output = "video.mp4";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
scene "intro" {
|
|
100
|
+
duration = 3;
|
|
101
|
+
background { color = "#FF5733"; }
|
|
102
|
+
text "title" {
|
|
103
|
+
content = "Hello World";
|
|
104
|
+
font = "Inter Bold";
|
|
105
|
+
size = 72;
|
|
106
|
+
x = 50%; y = 50%;
|
|
107
|
+
align = "center";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Links
|
|
113
|
+
|
|
114
|
+
- Website: https://scenerok.com
|
|
115
|
+
- Docs: https://scenerok.com/agents/get-started.md
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,WAAW,SA6ErB,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { readConfig, writeConfig, isAuthenticated } from '../lib/config.js';
|
|
5
|
+
import { initiateDeviceAuth, pollDeviceAuth } from '../lib/api.js';
|
|
6
|
+
export const authCommand = new Command('auth')
|
|
7
|
+
.description('Authenticate with SceneRok')
|
|
8
|
+
.addCommand(new Command('login')
|
|
9
|
+
.description('Log in to SceneRok via browser')
|
|
10
|
+
.option('-n, --name <name>', 'Device name', 'CLI')
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
console.log(chalk.cyan('\\n🔐 SceneRok Authentication\\n'));
|
|
13
|
+
const spinner = ora('Initiating device auth...').start();
|
|
14
|
+
try {
|
|
15
|
+
const auth = await initiateDeviceAuth(options.name);
|
|
16
|
+
spinner.succeed('Device auth initiated');
|
|
17
|
+
console.log(chalk.yellow('\\n1. Open this URL in your browser:'));
|
|
18
|
+
console.log(chalk.underline(auth.verification_uri));
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log(chalk.yellow('2. Or run this command:'));
|
|
21
|
+
console.log(` open "${auth.verification_uri}"`);
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(chalk.dim(`Waiting for authentication... (expires in ${auth.expires_in}s)\\n`));
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
const expiresIn = auth.expires_in * 1000;
|
|
26
|
+
while (Date.now() - startTime < expiresIn) {
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, auth.interval * 1000));
|
|
28
|
+
const result = await pollDeviceAuth(auth.device_code);
|
|
29
|
+
if (result.status === 'complete' && result.access_token) {
|
|
30
|
+
const config = readConfig();
|
|
31
|
+
config.apiToken = result.access_token;
|
|
32
|
+
writeConfig(config);
|
|
33
|
+
console.log(chalk.green('\\n✅ Successfully authenticated with SceneRok!\\n'));
|
|
34
|
+
console.log(chalk.dim('Your API token has been saved to ~/.scenerok/config.json'));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (result.error === 'expired_token') {
|
|
38
|
+
console.log(chalk.red('\\n❌ Authentication expired. Please try again.\\n'));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
console.log(chalk.red('\\n❌ Authentication timed out. Please try again.\\n'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
spinner.fail('Authentication failed');
|
|
47
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}))
|
|
51
|
+
.addCommand(new Command('status')
|
|
52
|
+
.description('Check authentication status')
|
|
53
|
+
.action(() => {
|
|
54
|
+
if (isAuthenticated()) {
|
|
55
|
+
console.log(chalk.green('✅ Authenticated with SceneRok'));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(chalk.yellow('⚠️ Not authenticated'));
|
|
59
|
+
console.log(chalk.dim('Run: scenerok auth login'));
|
|
60
|
+
}
|
|
61
|
+
}))
|
|
62
|
+
.addCommand(new Command('logout')
|
|
63
|
+
.description('Log out and remove stored credentials')
|
|
64
|
+
.action(() => {
|
|
65
|
+
const config = readConfig();
|
|
66
|
+
delete config.apiToken;
|
|
67
|
+
writeConfig(config);
|
|
68
|
+
console.log(chalk.green('✅ Logged out successfully'));
|
|
69
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,aAAa,SAwEtB,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { submitRender, getRenderStatus } from '../lib/api.js';
|
|
6
|
+
import { isAuthenticated } from '../lib/config.js';
|
|
7
|
+
export const renderCommand = new Command('render')
|
|
8
|
+
.description('Submit a VidScript for rendering')
|
|
9
|
+
.argument('<file>', 'Path to .vidscript file')
|
|
10
|
+
.option('-r, --resolution <resolution>', 'Output resolution (e.g. 1080x1920)')
|
|
11
|
+
.option('-w, --watch', 'Watch render status until complete')
|
|
12
|
+
.action(async (file, options) => {
|
|
13
|
+
if (!isAuthenticated()) {
|
|
14
|
+
console.log(chalk.yellow('⚠️ Not authenticated'));
|
|
15
|
+
console.log(chalk.dim('Run: scenerok auth login'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
let vidscript;
|
|
19
|
+
try {
|
|
20
|
+
vidscript = readFileSync(file, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
console.log(chalk.red(`❌ Could not read file: ${file}`));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const spinner = ora('Submitting render job...').start();
|
|
27
|
+
try {
|
|
28
|
+
const result = await submitRender(vidscript, options.resolution);
|
|
29
|
+
spinner.succeed(`Render job submitted (#${result.renderId})`);
|
|
30
|
+
console.log(chalk.cyan('\\n📹 Render Job\\n'));
|
|
31
|
+
console.log(` ID: ${result.renderId}`);
|
|
32
|
+
console.log(` Status: ${result.status}`);
|
|
33
|
+
console.log(` Resolution: ${result.resolution}`);
|
|
34
|
+
console.log(` Output: ${result.outputFilename}`);
|
|
35
|
+
console.log(` Mode: ${result.dispatchMode}`);
|
|
36
|
+
console.log(` Render URL: ${result.renderUrl}`);
|
|
37
|
+
console.log(` Download: ${result.downloadUrl}`);
|
|
38
|
+
if (result.logUrl) {
|
|
39
|
+
console.log(` Log: ${result.logUrl}`);
|
|
40
|
+
}
|
|
41
|
+
console.log('');
|
|
42
|
+
if (options.watch) {
|
|
43
|
+
console.log(chalk.dim('Watching render status...\\n'));
|
|
44
|
+
while (true) {
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
46
|
+
const status = await getRenderStatus(result.renderId);
|
|
47
|
+
if (status.status === 'completed') {
|
|
48
|
+
console.log(chalk.green(`\\n✅ Render complete!\\n`));
|
|
49
|
+
console.log(` Download: ${status.downloadUrl}`);
|
|
50
|
+
console.log('');
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (status.status === 'failed') {
|
|
54
|
+
console.log(chalk.red(`\\n❌ Render failed: ${status.error}\\n`));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
process.stdout.write(`\r ${status.status}... ${status.progress}%`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log(chalk.dim(`Check status: scenerok status ${result.renderId}`));
|
|
62
|
+
console.log(chalk.dim(`Open render: ${result.renderUrl}`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
spinner.fail('Render submission failed');
|
|
67
|
+
if (error instanceof Error) {
|
|
68
|
+
console.error(chalk.red(error.message));
|
|
69
|
+
}
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
renderCommand.addCommand(new Command('status')
|
|
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
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4FpC,eAAO,MAAM,aAAa,SAqDvB,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { existsSync, copyFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
function getPlatformConfig(platform) {
|
|
7
|
+
const cwd = process.cwd();
|
|
8
|
+
const configs = {
|
|
9
|
+
opencode: {
|
|
10
|
+
name: 'OpenCode',
|
|
11
|
+
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
|
+
],
|
|
17
|
+
},
|
|
18
|
+
claude: {
|
|
19
|
+
name: 'Claude Code',
|
|
20
|
+
skillDir: join(homedir(), '.claude/skills/scenerok'),
|
|
21
|
+
files: [
|
|
22
|
+
{ source: 'skills/claude/SKILL.md', dest: 'SKILL.md' },
|
|
23
|
+
{ source: 'skills/claude/vidscript-guide.md', dest: 'vidscript-guide.md' },
|
|
24
|
+
{ source: 'skills/claude/vidscript-sample.md', dest: 'vidscript-sample.md' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
codex: {
|
|
28
|
+
name: 'Codex',
|
|
29
|
+
skillDir: join(homedir(), '.codex/skills/scenerok'),
|
|
30
|
+
files: [
|
|
31
|
+
{ source: 'skills/codex/SKILL.md', dest: 'SKILL.md' },
|
|
32
|
+
{ source: 'skills/codex/vidscript-guide.md', dest: 'vidscript-guide.md' },
|
|
33
|
+
{ source: 'skills/codex/vidscript-sample.md', dest: 'vidscript-sample.md' },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
cursor: {
|
|
37
|
+
name: 'Cursor',
|
|
38
|
+
skillDir: join(homedir(), '.cursor/skills/scenerok'),
|
|
39
|
+
files: [
|
|
40
|
+
{ source: 'skills/cursor/SKILL.md', dest: 'SKILL.md' },
|
|
41
|
+
{ source: 'skills/cursor/vidscript-guide.md', dest: 'vidscript-guide.md' },
|
|
42
|
+
{ source: 'skills/cursor/vidscript-sample.md', dest: 'vidscript-sample.md' },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
aider: {
|
|
46
|
+
name: 'Aider',
|
|
47
|
+
skillDir: join(homedir(), '.aider/skills/scenerok'),
|
|
48
|
+
files: [
|
|
49
|
+
{ source: 'skills/aider/SKILL.md', dest: 'SKILL.md' },
|
|
50
|
+
{ source: 'skills/aider/vidscript-guide.md', dest: 'vidscript-guide.md' },
|
|
51
|
+
{ source: 'skills/aider/vidscript-sample.md', dest: 'vidscript-sample.md' },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
return configs[platform] || null;
|
|
56
|
+
}
|
|
57
|
+
async function findSkillSource(sourcePath) {
|
|
58
|
+
// Check relative to node_modules/@scenerok/cli (scoped package)
|
|
59
|
+
const modulePath = join(dirname(new URL(import.meta.url).pathname), '../../../..', sourcePath);
|
|
60
|
+
if (existsSync(modulePath))
|
|
61
|
+
return modulePath;
|
|
62
|
+
// Check relative to CWD
|
|
63
|
+
const cwdPath = join(process.cwd(), sourcePath);
|
|
64
|
+
if (existsSync(cwdPath))
|
|
65
|
+
return cwdPath;
|
|
66
|
+
// Check global npm root
|
|
67
|
+
try {
|
|
68
|
+
const { execSync } = await import('node:child_process');
|
|
69
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
|
70
|
+
const globalPath = join(globalRoot, '@scenerok', 'cli', sourcePath);
|
|
71
|
+
if (existsSync(globalPath))
|
|
72
|
+
return globalPath;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// ignore
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
export const skillsCommand = new Command('skills')
|
|
80
|
+
.description('Install SceneRok skills to your agent')
|
|
81
|
+
.addCommand(new Command('install')
|
|
82
|
+
.description('Install skills for a platform')
|
|
83
|
+
.argument('<platform>', 'Agent platform: opencode, claude, codex, cursor, aider')
|
|
84
|
+
.action(async (platform) => {
|
|
85
|
+
const config = getPlatformConfig(platform);
|
|
86
|
+
if (!config) {
|
|
87
|
+
console.log(chalk.red(`❌ Unknown platform: ${platform}`));
|
|
88
|
+
console.log(chalk.dim('Supported platforms: opencode, claude, codex, cursor, aider'));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
console.log(chalk.cyan(`\\n📦 Installing SceneRok skills for ${config.name}...\\n`));
|
|
92
|
+
// Create skill directory
|
|
93
|
+
if (!existsSync(config.skillDir)) {
|
|
94
|
+
mkdirSync(config.skillDir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
for (const file of config.files) {
|
|
97
|
+
const source = await findSkillSource(file.source);
|
|
98
|
+
const dest = join(config.skillDir, file.dest);
|
|
99
|
+
if (!source) {
|
|
100
|
+
console.log(chalk.yellow(` ⚠️ Skipping ${file.dest} (source not found)`));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
copyFileSync(source, dest);
|
|
104
|
+
console.log(chalk.green(` ✓ ${file.dest}`));
|
|
105
|
+
}
|
|
106
|
+
console.log(chalk.cyan(`\\n✅ Skills installed to ${config.skillDir}\\n`));
|
|
107
|
+
console.log(chalk.dim('Your agent can now compose VidScripts and render videos!'));
|
|
108
|
+
console.log(chalk.dim('Try: Ask your agent to "create a product promo video using scenerok"'));
|
|
109
|
+
}))
|
|
110
|
+
.addCommand(new Command('list')
|
|
111
|
+
.description('List available skill platforms')
|
|
112
|
+
.action(() => {
|
|
113
|
+
console.log(chalk.cyan('\\n📦 Available SceneRok Skill Platforms\\n'));
|
|
114
|
+
console.log(' opencode - OpenCode agent (.agents/skills/)');
|
|
115
|
+
console.log(' claude - Claude Code (.claude/skills/)');
|
|
116
|
+
console.log(' codex - OpenAI Codex (.codex/skills/)');
|
|
117
|
+
console.log(' cursor - Cursor editor (.cursor/skills/)');
|
|
118
|
+
console.log(' aider - Aider (.aider/skills/)');
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(chalk.dim('Install with: scenerok skills install <platform>'));
|
|
121
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,eAAe,SAqCxB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { validateVidscript } from '../lib/api.js';
|
|
5
|
+
export const validateCommand = new Command('validate')
|
|
6
|
+
.description('Validate a VidScript file')
|
|
7
|
+
.argument('<file>', 'Path to .vidscript file')
|
|
8
|
+
.action(async (file) => {
|
|
9
|
+
let vidscript;
|
|
10
|
+
try {
|
|
11
|
+
vidscript = readFileSync(file, 'utf-8');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
console.log(chalk.red(`❌ Could not read file: ${file}`));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const result = await validateVidscript(vidscript);
|
|
19
|
+
if (result.valid) {
|
|
20
|
+
console.log(chalk.green('\\n✅ VidScript is valid\\n'));
|
|
21
|
+
console.log(` Scenes: ${result.scenes}`);
|
|
22
|
+
console.log(` Duration: ${result.duration}s`);
|
|
23
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
24
|
+
console.log(chalk.yellow('\\n Warnings:'));
|
|
25
|
+
for (const warning of result.warnings) {
|
|
26
|
+
console.log(` • ${warning}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log(chalk.red('\\n❌ VidScript is invalid\\n'));
|
|
32
|
+
console.log(` Error: ${result.error}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
console.log('');
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { authCommand } from './commands/auth.js';
|
|
4
|
+
import { skillsCommand } from './commands/skills.js';
|
|
5
|
+
import { renderCommand } from './commands/render.js';
|
|
6
|
+
import { validateCommand } from './commands/validate.js';
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name('scenerok')
|
|
10
|
+
.description('SceneRok CLI - Create videos from your terminal and agent workflows')
|
|
11
|
+
.version('1.0.0');
|
|
12
|
+
program.addCommand(authCommand);
|
|
13
|
+
program.addCommand(skillsCommand);
|
|
14
|
+
program.addCommand(renderCommand);
|
|
15
|
+
program.addCommand(validateCommand);
|
|
16
|
+
// Default help
|
|
17
|
+
program.on('--help', () => {
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log(chalk.cyan('Examples:'));
|
|
20
|
+
console.log(' $ scenerok auth login');
|
|
21
|
+
console.log(' $ scenerok skills install opencode');
|
|
22
|
+
console.log(' $ scenerok validate video.vidscript');
|
|
23
|
+
console.log(' $ scenerok render video.vidscript --resolution 1080x1920');
|
|
24
|
+
console.log(' $ scenerok status 42');
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(chalk.cyan('Get started:'));
|
|
27
|
+
console.log(' https://scenerok.com/agents');
|
|
28
|
+
});
|
|
29
|
+
program.parse();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
declare class ApiError extends Error {
|
|
2
|
+
status: number;
|
|
3
|
+
responseBody?: unknown | undefined;
|
|
4
|
+
constructor(message: string, status: number, responseBody?: unknown | undefined);
|
|
5
|
+
}
|
|
6
|
+
export declare function validateVidscript(vidscript: string): Promise<{
|
|
7
|
+
valid: boolean;
|
|
8
|
+
scenes?: number;
|
|
9
|
+
duration?: number;
|
|
10
|
+
warnings?: string[];
|
|
11
|
+
error?: string;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function submitRender(vidscript: string, resolution?: string): Promise<{
|
|
14
|
+
renderId: number;
|
|
15
|
+
status: string;
|
|
16
|
+
resolution: string;
|
|
17
|
+
outputFilename: string;
|
|
18
|
+
renderUrl: string;
|
|
19
|
+
downloadUrl: string;
|
|
20
|
+
logUrl?: string;
|
|
21
|
+
dispatchMode: string;
|
|
22
|
+
queueJobId: string | null;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function getRenderStatus(renderId: number): Promise<{
|
|
25
|
+
id: number;
|
|
26
|
+
status: string;
|
|
27
|
+
progress: number;
|
|
28
|
+
outputUrl: string | null;
|
|
29
|
+
outputFilename: string;
|
|
30
|
+
renderUrl: string;
|
|
31
|
+
downloadUrl: string;
|
|
32
|
+
downloadReady: boolean;
|
|
33
|
+
error: string | null;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
completedAt: string | null;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function initiateDeviceAuth(deviceName: string): Promise<{
|
|
38
|
+
device_code: string;
|
|
39
|
+
user_code: string;
|
|
40
|
+
verification_uri: string;
|
|
41
|
+
expires_in: number;
|
|
42
|
+
interval: number;
|
|
43
|
+
}>;
|
|
44
|
+
export declare function pollDeviceAuth(deviceCode: string): Promise<{
|
|
45
|
+
status: string;
|
|
46
|
+
access_token?: string;
|
|
47
|
+
token_type?: string;
|
|
48
|
+
expires_in?: number;
|
|
49
|
+
error?: string;
|
|
50
|
+
}>;
|
|
51
|
+
export { ApiError };
|
|
52
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +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;AAqCD,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;cAE3D,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,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"}
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { getBaseUrl, getApiToken } from './config.js';
|
|
3
|
+
class ApiError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
responseBody;
|
|
6
|
+
constructor(message, status, responseBody) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.responseBody = responseBody;
|
|
10
|
+
this.name = 'ApiError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async function apiCall(method, path, body) {
|
|
14
|
+
const baseUrl = getBaseUrl();
|
|
15
|
+
const token = getApiToken();
|
|
16
|
+
const headers = {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
};
|
|
19
|
+
if (token) {
|
|
20
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
21
|
+
}
|
|
22
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
23
|
+
method,
|
|
24
|
+
headers,
|
|
25
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
26
|
+
});
|
|
27
|
+
const data = await response.json().catch(() => ({}));
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new ApiError(data.error || `HTTP ${response.status}`, response.status, data);
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
export async function validateVidscript(vidscript) {
|
|
34
|
+
return apiCall('POST', '/api/cli/validate', { vidscript });
|
|
35
|
+
}
|
|
36
|
+
export async function submitRender(vidscript, resolution) {
|
|
37
|
+
return apiCall('POST', '/api/cli/render', { vidscript, resolution });
|
|
38
|
+
}
|
|
39
|
+
export async function getRenderStatus(renderId) {
|
|
40
|
+
return apiCall('GET', `/api/cli/status?id=${renderId}`);
|
|
41
|
+
}
|
|
42
|
+
export async function initiateDeviceAuth(deviceName) {
|
|
43
|
+
return apiCall('POST', '/api/cli/auth/device', { deviceName });
|
|
44
|
+
}
|
|
45
|
+
export async function pollDeviceAuth(deviceCode) {
|
|
46
|
+
return apiCall('GET', `/api/cli/auth/device?device_code=${deviceCode}`);
|
|
47
|
+
}
|
|
48
|
+
export { ApiError };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface CliConfig {
|
|
2
|
+
apiToken?: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
defaultResolution?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function readConfig(): CliConfig;
|
|
7
|
+
export declare function writeConfig(config: CliConfig): void;
|
|
8
|
+
export declare function getBaseUrl(): string;
|
|
9
|
+
export declare function getApiToken(): string | undefined;
|
|
10
|
+
export declare function isAuthenticated(): boolean;
|
|
11
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAQD,wBAAgB,UAAU,IAAI,SAAS,CAUtC;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,QAG5C;AAED,wBAAgB,UAAU,IAAI,MAAM,CAGnC;AAED,wBAAgB,WAAW,IAAI,MAAM,GAAG,SAAS,CAGhD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const CONFIG_DIR = join(homedir(), '.scenerok');
|
|
5
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
6
|
+
function ensureConfigDir() {
|
|
7
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
8
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function readConfig() {
|
|
12
|
+
ensureConfigDir();
|
|
13
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function writeConfig(config) {
|
|
24
|
+
ensureConfigDir();
|
|
25
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
26
|
+
}
|
|
27
|
+
export function getBaseUrl() {
|
|
28
|
+
const config = readConfig();
|
|
29
|
+
return config.baseUrl || 'https://scenerok.com';
|
|
30
|
+
}
|
|
31
|
+
export function getApiToken() {
|
|
32
|
+
const config = readConfig();
|
|
33
|
+
return config.apiToken;
|
|
34
|
+
}
|
|
35
|
+
export function isAuthenticated() {
|
|
36
|
+
return !!getApiToken();
|
|
37
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scenerok/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SceneRok CLI - Create videos from your terminal and agent workflows",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"scenerok": "dist/index.js",
|
|
8
|
+
"sr": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"scenerok",
|
|
21
|
+
"@scenerok/cli",
|
|
22
|
+
"video",
|
|
23
|
+
"cli",
|
|
24
|
+
"vidscript",
|
|
25
|
+
"agent",
|
|
26
|
+
"ai"
|
|
27
|
+
],
|
|
28
|
+
"author": "SceneRok",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"commander": "^12.0.0",
|
|
32
|
+
"chalk": "^5.3.0",
|
|
33
|
+
"ora": "^8.0.1",
|
|
34
|
+
"inquirer": "^9.2.0",
|
|
35
|
+
"node-fetch": "^3.3.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/inquirer": "^9.0.7",
|
|
39
|
+
"@types/node": "^20.11.0",
|
|
40
|
+
"typescript": "^5.3.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|