@vibeo/cli 0.1.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/dist/index.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bun
2
+ import { renderCommand } from "./commands/render.js";
3
+ import { previewCommand } from "./commands/preview.js";
4
+ import { listCommand } from "./commands/list.js";
5
+ import { createCommand } from "./commands/create.js";
6
+ const args = process.argv.slice(2);
7
+ const command = args[0];
8
+ function printUsage() {
9
+ console.log(`
10
+ vibeo - React video framework CLI
11
+
12
+ Usage:
13
+ vibeo <command> [options]
14
+
15
+ Commands:
16
+ create Create a new project from a template
17
+ render Render a composition to video
18
+ preview Start a dev server with live preview
19
+ list List registered compositions
20
+
21
+ Options:
22
+ --help Show help for a command
23
+
24
+ Examples:
25
+ vibeo create my-video
26
+ vibeo create music-viz --template audio-reactive
27
+ vibeo render --entry src/index.tsx --composition MyComp --output out.mp4
28
+ vibeo preview --entry src/index.tsx
29
+ vibeo list --entry src/index.tsx
30
+ `);
31
+ }
32
+ async function main() {
33
+ if (!command || command === "--help" || command === "-h") {
34
+ printUsage();
35
+ process.exit(0);
36
+ }
37
+ switch (command) {
38
+ case "create":
39
+ await createCommand(args.slice(1));
40
+ break;
41
+ case "render":
42
+ await renderCommand(args.slice(1));
43
+ break;
44
+ case "preview":
45
+ await previewCommand(args.slice(1));
46
+ break;
47
+ case "list":
48
+ await listCommand(args.slice(1));
49
+ break;
50
+ default:
51
+ console.error(`Unknown command: ${command}`);
52
+ printUsage();
53
+ process.exit(1);
54
+ }
55
+ }
56
+ main().catch((err) => {
57
+ console.error(err);
58
+ process.exit(1);
59
+ });
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,SAAS,UAAU;IACjB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;CAqBb,CAAC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACzD,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,QAAQ;YACX,MAAM,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM;QACR,KAAK,SAAS;YACZ,MAAM,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM;QACR,KAAK,MAAM;YACT,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjC,MAAM;QACR;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;YAC7C,UAAU,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@vibeo/cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "vibeo": "./dist/index.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "@vibeo/core": "0.1.0",
18
+ "@vibeo/renderer": "0.1.0",
19
+ "@vibeo/player": "0.1.0",
20
+ "@vibeo/audio": "0.1.0"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.build.json",
28
+ "clean": "rm -rf dist"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/patcito/vibeo.git",
33
+ "directory": "packages/cli"
34
+ },
35
+ "license": "MIT"
36
+ }
@@ -0,0 +1,197 @@
1
+ import { resolve, join, basename } from "node:path";
2
+ import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+
5
+ const TEMPLATES: Record<string, { description: string; example: string }> = {
6
+ basic: {
7
+ description: "Minimal composition with text animation and two scenes",
8
+ example: "basic-composition.tsx",
9
+ },
10
+ "audio-reactive": {
11
+ description: "Audio visualization with frequency bars and amplitude-driven effects",
12
+ example: "audio-reactive-viz.tsx",
13
+ },
14
+ transitions: {
15
+ description: "Scene transitions (fade, slide) between multiple scenes",
16
+ example: "transition-demo.tsx",
17
+ },
18
+ subtitles: {
19
+ description: "Video with SRT subtitle overlay",
20
+ example: "subtitle-overlay.tsx",
21
+ },
22
+ };
23
+
24
+ interface CreateArgs {
25
+ name: string;
26
+ template: string;
27
+ }
28
+
29
+ function parseArgs(args: string[]): CreateArgs {
30
+ const result: CreateArgs = { name: "", template: "basic" };
31
+
32
+ for (let i = 0; i < args.length; i++) {
33
+ const arg = args[i]!;
34
+ const next = args[i + 1];
35
+
36
+ if (arg === "--template" && next) {
37
+ result.template = next;
38
+ i++;
39
+ } else if (arg.startsWith("--template=")) {
40
+ result.template = arg.slice("--template=".length);
41
+ } else if (arg === "--help" || arg === "-h") {
42
+ printHelp();
43
+ process.exit(0);
44
+ } else if (!arg.startsWith("-") && !result.name) {
45
+ result.name = arg;
46
+ }
47
+ }
48
+
49
+ return result;
50
+ }
51
+
52
+ function printHelp(): void {
53
+ console.log(`
54
+ vibeo create - Create a new Vibeo project
55
+
56
+ Usage:
57
+ vibeo create <project-name> [options]
58
+
59
+ Options:
60
+ --template <name> Template to use (default: basic)
61
+ --help Show this help
62
+
63
+ Templates:`);
64
+
65
+ for (const [name, { description }] of Object.entries(TEMPLATES)) {
66
+ console.log(` ${name.padEnd(18)} ${description}`);
67
+ }
68
+
69
+ console.log(`
70
+ Examples:
71
+ vibeo create my-video
72
+ vibeo create music-viz --template audio-reactive
73
+ vibeo create intro --template transitions
74
+ `);
75
+ }
76
+
77
+ // Find the examples directory relative to the CLI package
78
+ function findExamplesDir(): string {
79
+ // Walk up from this file to find the repo root with examples/
80
+ let dir = import.meta.dir;
81
+ for (let i = 0; i < 6; i++) {
82
+ const candidate = join(dir, "examples");
83
+ if (existsSync(candidate)) return candidate;
84
+ dir = resolve(dir, "..");
85
+ }
86
+ throw new Error("Could not find examples directory");
87
+ }
88
+
89
+ export async function createCommand(args: string[]): Promise<void> {
90
+ const parsed = parseArgs(args);
91
+
92
+ if (!parsed.name) {
93
+ console.error("Error: project name is required\n");
94
+ printHelp();
95
+ process.exit(1);
96
+ }
97
+
98
+ const template = TEMPLATES[parsed.template];
99
+ if (!template) {
100
+ console.error(`Error: unknown template "${parsed.template}"`);
101
+ console.error(`Available: ${Object.keys(TEMPLATES).join(", ")}`);
102
+ process.exit(1);
103
+ }
104
+
105
+ const projectDir = resolve(parsed.name);
106
+ if (existsSync(projectDir)) {
107
+ console.error(`Error: directory "${parsed.name}" already exists`);
108
+ process.exit(1);
109
+ }
110
+
111
+ console.log(`\nCreating Vibeo project: ${parsed.name}`);
112
+ console.log(`Template: ${parsed.template}\n`);
113
+
114
+ // Create project structure
115
+ await mkdir(join(projectDir, "src"), { recursive: true });
116
+ await mkdir(join(projectDir, "public"), { recursive: true });
117
+
118
+ // Copy example as src/index.tsx
119
+ const examplesDir = findExamplesDir();
120
+ const exampleSrc = await readFile(join(examplesDir, template.example), "utf-8");
121
+ await writeFile(join(projectDir, "src", "index.tsx"), exampleSrc);
122
+
123
+ // Write package.json
124
+ const pkg = {
125
+ name: parsed.name,
126
+ version: "0.0.1",
127
+ private: true,
128
+ type: "module",
129
+ scripts: {
130
+ dev: "vibeo preview --entry src/index.tsx",
131
+ build: "vibeo render --entry src/index.tsx",
132
+ list: "vibeo list --entry src/index.tsx",
133
+ typecheck: "bunx tsc --noEmit",
134
+ },
135
+ dependencies: {
136
+ "@vibeo/core": "workspace:*",
137
+ "@vibeo/audio": "workspace:*",
138
+ "@vibeo/effects": "workspace:*",
139
+ "@vibeo/extras": "workspace:*",
140
+ "@vibeo/player": "workspace:*",
141
+ "@vibeo/renderer": "workspace:*",
142
+ "@vibeo/cli": "workspace:*",
143
+ react: "^19.0.0",
144
+ "react-dom": "^19.0.0",
145
+ },
146
+ devDependencies: {
147
+ "@types/react": "^19.0.0",
148
+ typescript: "^5.0.0",
149
+ },
150
+ };
151
+ await writeFile(join(projectDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
152
+
153
+ // Write tsconfig.json
154
+ const tsconfig = {
155
+ compilerOptions: {
156
+ target: "ES2022",
157
+ module: "ESNext",
158
+ moduleResolution: "bundler",
159
+ jsx: "react-jsx",
160
+ strict: true,
161
+ esModuleInterop: true,
162
+ skipLibCheck: true,
163
+ outDir: "dist",
164
+ declaration: true,
165
+ declarationMap: true,
166
+ sourceMap: true,
167
+ },
168
+ include: ["src"],
169
+ };
170
+ await writeFile(join(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
171
+
172
+ // Write .gitignore
173
+ await writeFile(
174
+ join(projectDir, ".gitignore"),
175
+ `node_modules/
176
+ dist/
177
+ out/
178
+ *.tmp
179
+ .DS_Store
180
+ `,
181
+ );
182
+
183
+ console.log(` Created ${parsed.name}/`);
184
+ console.log(` ├── src/index.tsx`);
185
+ console.log(` ├── public/`);
186
+ console.log(` ├── package.json`);
187
+ console.log(` ├── tsconfig.json`);
188
+ console.log(` └── .gitignore`);
189
+
190
+ console.log(`
191
+ Next steps:
192
+ cd ${parsed.name}
193
+ bun install
194
+ bun run dev # preview in browser
195
+ bun run build # render to video
196
+ `);
197
+ }
@@ -0,0 +1,136 @@
1
+ import { resolve } from "node:path";
2
+ import { bundle } from "@vibeo/renderer";
3
+ import { launchBrowser, createPage, closeBrowser } from "@vibeo/renderer";
4
+
5
+ interface ListArgs {
6
+ entry: string;
7
+ }
8
+
9
+ function parseArgs(args: string[]): ListArgs {
10
+ const result: ListArgs = {
11
+ entry: "",
12
+ };
13
+
14
+ for (let i = 0; i < args.length; i++) {
15
+ const arg = args[i]!;
16
+ const next = args[i + 1];
17
+
18
+ if (arg === "--entry" && next) {
19
+ result.entry = next;
20
+ i++;
21
+ } else if (arg.startsWith("--entry=")) {
22
+ result.entry = arg.slice("--entry=".length);
23
+ } else if (arg === "--help" || arg === "-h") {
24
+ printHelp();
25
+ process.exit(0);
26
+ }
27
+ }
28
+
29
+ return result;
30
+ }
31
+
32
+ function printHelp(): void {
33
+ console.log(`
34
+ vibeo list - List registered compositions
35
+
36
+ Usage:
37
+ vibeo list --entry <path>
38
+
39
+ Required:
40
+ --entry <path> Path to the root file with compositions
41
+
42
+ Options:
43
+ --help Show this help
44
+ `);
45
+ }
46
+
47
+ interface CompositionMeta {
48
+ id: string;
49
+ width: number;
50
+ height: number;
51
+ fps: number;
52
+ durationInFrames: number;
53
+ }
54
+
55
+ /**
56
+ * Bundle the entry, launch a browser to evaluate it,
57
+ * extract registered compositions, and print a table.
58
+ */
59
+ export async function listCommand(args: string[]): Promise<void> {
60
+ const parsed = parseArgs(args);
61
+
62
+ if (!parsed.entry) {
63
+ console.error("Error: --entry is required");
64
+ printHelp();
65
+ process.exit(1);
66
+ }
67
+
68
+ const entry = resolve(parsed.entry);
69
+
70
+ console.log(`Bundling ${entry}...`);
71
+ const bundleResult = await bundle(entry);
72
+
73
+ try {
74
+ const browser = await launchBrowser();
75
+ const page = await createPage(browser, 1920, 1080);
76
+
77
+ await page.goto(bundleResult.url, { waitUntil: "networkidle" });
78
+
79
+ // Wait briefly for React to register compositions
80
+ await page.waitForTimeout(2000);
81
+
82
+ // Extract composition data from the page
83
+ const compositions = await page.evaluate(() => {
84
+ const win = window as typeof window & {
85
+ vibeo_getCompositions?: () => CompositionMeta[];
86
+ };
87
+ if (typeof win.vibeo_getCompositions === "function") {
88
+ return win.vibeo_getCompositions();
89
+ }
90
+ return [];
91
+ });
92
+
93
+ await page.close();
94
+ await closeBrowser();
95
+
96
+ if (compositions.length === 0) {
97
+ console.log("\nNo compositions found.");
98
+ console.log(
99
+ "Make sure your entry file exports compositions via <Composition /> components.",
100
+ );
101
+ return;
102
+ }
103
+
104
+ // Print table
105
+ console.log("\nRegistered compositions:\n");
106
+ console.log(
107
+ padRight("ID", 25) +
108
+ padRight("Width", 8) +
109
+ padRight("Height", 8) +
110
+ padRight("FPS", 6) +
111
+ padRight("Frames", 8) +
112
+ "Duration",
113
+ );
114
+ console.log("-".repeat(70));
115
+
116
+ for (const comp of compositions) {
117
+ const duration = (comp.durationInFrames / comp.fps).toFixed(1) + "s";
118
+ console.log(
119
+ padRight(comp.id, 25) +
120
+ padRight(String(comp.width), 8) +
121
+ padRight(String(comp.height), 8) +
122
+ padRight(String(comp.fps), 6) +
123
+ padRight(String(comp.durationInFrames), 8) +
124
+ duration,
125
+ );
126
+ }
127
+
128
+ console.log();
129
+ } finally {
130
+ await bundleResult.cleanup();
131
+ }
132
+ }
133
+
134
+ function padRight(str: string, len: number): string {
135
+ return str.length >= len ? str : str + " ".repeat(len - str.length);
136
+ }
@@ -0,0 +1,88 @@
1
+ import { resolve } from "node:path";
2
+ import { bundle } from "@vibeo/renderer";
3
+
4
+ interface PreviewArgs {
5
+ entry: string;
6
+ port: number;
7
+ }
8
+
9
+ function parseArgs(args: string[]): PreviewArgs {
10
+ const result: PreviewArgs = {
11
+ entry: "",
12
+ port: 3000,
13
+ };
14
+
15
+ for (let i = 0; i < args.length; i++) {
16
+ const arg = args[i]!;
17
+ const next = args[i + 1];
18
+
19
+ if (arg === "--entry" && next) {
20
+ result.entry = next;
21
+ i++;
22
+ } else if (arg.startsWith("--entry=")) {
23
+ result.entry = arg.slice("--entry=".length);
24
+ } else if (arg === "--port" && next) {
25
+ result.port = parseInt(next, 10);
26
+ i++;
27
+ } else if (arg.startsWith("--port=")) {
28
+ result.port = parseInt(arg.slice("--port=".length), 10);
29
+ } else if (arg === "--help" || arg === "-h") {
30
+ printHelp();
31
+ process.exit(0);
32
+ }
33
+ }
34
+
35
+ return result;
36
+ }
37
+
38
+ function printHelp(): void {
39
+ console.log(`
40
+ vibeo preview - Start a dev server with live preview
41
+
42
+ Usage:
43
+ vibeo preview --entry <path> [options]
44
+
45
+ Required:
46
+ --entry <path> Path to the root file with compositions
47
+
48
+ Options:
49
+ --port <number> Port for the dev server (default: 3000)
50
+ --help Show this help
51
+ `);
52
+ }
53
+
54
+ /**
55
+ * Start a dev server hosting the Player with hot reload.
56
+ */
57
+ export async function previewCommand(args: string[]): Promise<void> {
58
+ const parsed = parseArgs(args);
59
+
60
+ if (!parsed.entry) {
61
+ console.error("Error: --entry is required");
62
+ printHelp();
63
+ process.exit(1);
64
+ }
65
+
66
+ const entry = resolve(parsed.entry);
67
+
68
+ console.log(`Starting preview server...`);
69
+ console.log(` Entry: ${entry}`);
70
+
71
+ const bundleResult = await bundle(entry);
72
+
73
+ console.log(`\n Preview running at ${bundleResult.url}`);
74
+ console.log(` Press Ctrl+C to stop\n`);
75
+
76
+ // Keep the process alive until interrupted
77
+ const shutdown = async () => {
78
+ console.log("\nShutting down preview server...");
79
+ await bundleResult.cleanup();
80
+ process.exit(0);
81
+ };
82
+
83
+ process.on("SIGINT", shutdown);
84
+ process.on("SIGTERM", shutdown);
85
+
86
+ // Block forever
87
+ await new Promise(() => {});
88
+ }