videogen-npm 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.
Files changed (3) hide show
  1. package/README.md +35 -0
  2. package/bin/videogen.js +676 -0
  3. package/package.json +31 -0
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # videogen-npm
2
+
3
+ Node/npm executable package for the VideoGen CLI.
4
+
5
+ ## Local usage (from this folder)
6
+
7
+ ```bash
8
+ cd /Users/isaaccolomercasas/Documents/videogen-npm
9
+ npm install
10
+ npm run check
11
+ ```
12
+
13
+ Run directly:
14
+
15
+ ```bash
16
+ node ./bin/videogen.js help
17
+ ```
18
+
19
+ Install as a local executable:
20
+
21
+ ```bash
22
+ npm link
23
+ videogen help
24
+ ```
25
+
26
+ ## Environment loading
27
+
28
+ On startup, the CLI attempts to load these files (if they exist):
29
+
30
+ - `.env`
31
+ - `.env.local`
32
+ - `apps/studio/.env`
33
+ - `apps/studio/.env.local`
34
+
35
+ Existing environment variables already set in the shell take precedence.
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import { access, readFile, writeFile } from "node:fs/promises";
5
+
6
+ class CliError extends Error {
7
+ constructor(message, options) {
8
+ super(message);
9
+ this.name = "CliError";
10
+ this.statusCode = options?.statusCode;
11
+ this.payload = options?.payload;
12
+ }
13
+ }
14
+
15
+ const loadEnvFiles = async () => {
16
+ const cwd = process.cwd();
17
+ const candidates = [
18
+ path.join(cwd, ".env"),
19
+ path.join(cwd, ".env.local"),
20
+ path.join(cwd, "apps/studio/.env"),
21
+ path.join(cwd, "apps/studio/.env.local"),
22
+ ];
23
+
24
+ for (const filePath of candidates) {
25
+ try {
26
+ const content = await readFile(filePath, "utf8");
27
+ applyEnv(content);
28
+ } catch (error) {
29
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
30
+ continue;
31
+ }
32
+ }
33
+ }
34
+ };
35
+
36
+ const applyEnv = (content) => {
37
+ const lines = content.split(/\r?\n/);
38
+ for (const line of lines) {
39
+ const trimmed = line.trim();
40
+ if (!trimmed || trimmed.startsWith("#")) continue;
41
+ const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
42
+ if (!match) continue;
43
+
44
+ const key = match[1];
45
+ let value = match[2] || "";
46
+ if (
47
+ (value.startsWith('"') && value.endsWith('"')) ||
48
+ (value.startsWith("'") && value.endsWith("'"))
49
+ ) {
50
+ value = value.slice(1, -1);
51
+ }
52
+
53
+ if (process.env[key] === undefined) {
54
+ process.env[key] = value;
55
+ }
56
+ }
57
+ };
58
+
59
+ const HELP_TEXT = `VideoGen CLI (agent-friendly)
60
+
61
+ Usage:
62
+ videogen <command> [options]
63
+
64
+ Global options:
65
+ --base-url <url> API base URL (default: VIDEOGEN_BASE_URL or http://localhost:3003)
66
+ --internal-service-token <t> Internal service secret (default: VIDEOGEN_INTERNAL_SERVICE_TOKEN or INTERNAL_SERVICE_SECRET)
67
+ --internal-user-id <id> Internal user id (default: VIDEOGEN_INTERNAL_USER_ID)
68
+ --cookie <cookieHeader> Raw Cookie header value (default: VIDEOGEN_COOKIE)
69
+ --agent-token <token> Legacy alias for --internal-service-token
70
+ --bearer <token> Authorization Bearer token (default: VIDEOGEN_BEARER_TOKEN)
71
+ --timeout-ms <ms> Request timeout in milliseconds (default: 60000)
72
+ --pretty Pretty-print JSON output
73
+
74
+ Commands:
75
+ help
76
+ auth:check
77
+ auth:notes
78
+ projects:list
79
+ projects:create --name <name> [--orientation horizontal|vertical]
80
+ projects:rename --project <projectId> --name <name>
81
+ media:list
82
+ media:upload --project <projectId> --file <localPath>
83
+ video-editor:get --project <projectId>
84
+ video-editor:save --project <projectId> --file <jsonFile>
85
+ video-editor:capabilities
86
+ video-editor:asset-health --project <projectId> [--repair]
87
+ render:start --project <projectId>
88
+ render:status --job <jobId> [--poll] [--poll-ms <ms>] [--max-polls <n>] [--poll-token <token>]
89
+ ai-video:create --project <projectId> --prompt <text> [--duration-seconds 4|6|8] [--target-duration-seconds <n>] [--aspect-ratio 16:9|9:16] [--out <file>]
90
+ api:request --method <GET|POST|...> --path </api/...|full_url> [--body-json '{...}']
91
+
92
+ Examples:
93
+ videogen auth:check --cookie "__session=..."
94
+ videogen auth:check --internal-service-token "$INTERNAL_SERVICE_SECRET" --internal-user-id "partner:mybot:runner1"
95
+ videogen projects:list --cookie "__session=..."
96
+ videogen render:start --project <projectId> --cookie "__session=..."
97
+ videogen render:status --job <jobId> --poll --poll-token <token>
98
+ `;
99
+
100
+ const parseArgs = (argv) => {
101
+ const flags = {};
102
+ const positionals = [];
103
+
104
+ for (let i = 0; i < argv.length; i += 1) {
105
+ const token = argv[i];
106
+ if (!token) continue;
107
+ if (!token.startsWith("--")) {
108
+ positionals.push(token);
109
+ continue;
110
+ }
111
+
112
+ const withoutPrefix = token.slice(2);
113
+ if (!withoutPrefix) continue;
114
+
115
+ const eqIndex = withoutPrefix.indexOf("=");
116
+ if (eqIndex >= 0) {
117
+ const key = withoutPrefix.slice(0, eqIndex).trim();
118
+ const value = withoutPrefix.slice(eqIndex + 1);
119
+ flags[key] = value;
120
+ continue;
121
+ }
122
+
123
+ const key = withoutPrefix.trim();
124
+ const next = argv[i + 1];
125
+ if (next && !next.startsWith("--")) {
126
+ flags[key] = next;
127
+ i += 1;
128
+ } else {
129
+ flags[key] = true;
130
+ }
131
+ }
132
+
133
+ const command = positionals.length > 0 ? positionals[0] : null;
134
+ return { command, flags, positionals };
135
+ };
136
+
137
+ const parseNumberFlag = (flags, key, fallback) => {
138
+ const raw = flags[key];
139
+ if (typeof raw !== "string") return fallback;
140
+ const parsed = Number(raw);
141
+ return Number.isFinite(parsed) ? parsed : fallback;
142
+ };
143
+
144
+ const getRequiredFlag = (flags, key) => {
145
+ const raw = flags[key];
146
+ if (typeof raw !== "string" || raw.trim().length === 0) {
147
+ throw new CliError(`Missing required --${key}`);
148
+ }
149
+ return raw.trim();
150
+ };
151
+
152
+ const normalizeBaseUrl = (raw) => {
153
+ const trimmed = raw.trim();
154
+ if (!trimmed) return "http://localhost:3003";
155
+ return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
156
+ };
157
+
158
+ const toUrl = (ctx, pathOrUrl) => {
159
+ const raw = pathOrUrl.trim();
160
+ if (raw.startsWith("http://") || raw.startsWith("https://")) return raw;
161
+ if (!raw.startsWith("/")) return `${ctx.baseUrl}/${raw}`;
162
+ return `${ctx.baseUrl}${raw}`;
163
+ };
164
+
165
+ const parseJsonSafely = async (response) => {
166
+ return await response.json().catch(() => null);
167
+ };
168
+
169
+ const readResponseBody = async (response) => {
170
+ const contentType = String(response.headers.get("content-type") || "").toLowerCase();
171
+ if (contentType.includes("application/json")) {
172
+ return await parseJsonSafely(response);
173
+ }
174
+ const text = await response.text().catch(() => "");
175
+ return text;
176
+ };
177
+
178
+ const makeHeaders = (ctx, initHeaders, extra) => {
179
+ const headers = new Headers(initHeaders || {});
180
+
181
+ if (ctx.cookie && !headers.has("cookie")) {
182
+ headers.set("cookie", ctx.cookie);
183
+ }
184
+
185
+ const bearerToken = extra?.bearerOverride ?? ctx.bearer;
186
+ if (bearerToken && !headers.has("authorization")) {
187
+ headers.set("authorization", `Bearer ${bearerToken}`);
188
+ }
189
+
190
+ if (ctx.internalServiceToken && !headers.has("x-internal-service-token")) {
191
+ headers.set("x-internal-service-token", ctx.internalServiceToken);
192
+ }
193
+ if (ctx.internalUserId && !headers.has("x-internal-user-id")) {
194
+ headers.set("x-internal-user-id", ctx.internalUserId);
195
+ }
196
+
197
+ if (!headers.has("accept")) {
198
+ headers.set("accept", "application/json");
199
+ }
200
+
201
+ return headers;
202
+ };
203
+
204
+ const request = async (ctx, pathOrUrl, init, options) => {
205
+ const controller = new AbortController();
206
+ const timeout = setTimeout(() => controller.abort(), ctx.timeoutMs);
207
+
208
+ try {
209
+ const response = await fetch(toUrl(ctx, pathOrUrl), {
210
+ ...(init || {}),
211
+ headers: makeHeaders(ctx, init?.headers, { bearerOverride: options?.bearerOverride ?? null }),
212
+ signal: controller.signal,
213
+ });
214
+ return response;
215
+ } catch (error) {
216
+ if (error instanceof Error && error.name === "AbortError") {
217
+ throw new CliError(`Request timed out after ${ctx.timeoutMs}ms`);
218
+ }
219
+ throw error;
220
+ } finally {
221
+ clearTimeout(timeout);
222
+ }
223
+ };
224
+
225
+ const requestJson = async (ctx, pathOrUrl, init, options) => {
226
+ const response = await request(ctx, pathOrUrl, init, options);
227
+ const payload = await readResponseBody(response);
228
+
229
+ if (!response.ok) {
230
+ const message =
231
+ typeof payload === "object" && payload && "details" in payload
232
+ ? String(payload.details || "")
233
+ : typeof payload === "object" && payload && "error" in payload
234
+ ? String(payload.error || "")
235
+ : `Request failed (${response.status})`;
236
+
237
+ throw new CliError(message || `Request failed (${response.status})`, {
238
+ statusCode: response.status,
239
+ payload,
240
+ });
241
+ }
242
+
243
+ return payload;
244
+ };
245
+
246
+ const guessMimeType = (filePath) => {
247
+ const ext = path.extname(filePath).toLowerCase();
248
+ if (ext === ".mp4") return "video/mp4";
249
+ if (ext === ".mov") return "video/quicktime";
250
+ if (ext === ".webm") return "video/webm";
251
+ if (ext === ".mp3") return "audio/mpeg";
252
+ if (ext === ".wav") return "audio/wav";
253
+ if (ext === ".m4a") return "audio/mp4";
254
+ if (ext === ".aac") return "audio/aac";
255
+ if (ext === ".png") return "image/png";
256
+ if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
257
+ if (ext === ".webp") return "image/webp";
258
+ if (ext === ".gif") return "image/gif";
259
+ return "application/octet-stream";
260
+ };
261
+
262
+ const fileExists = async (filePath) => {
263
+ try {
264
+ await access(filePath);
265
+ return true;
266
+ } catch {
267
+ return false;
268
+ }
269
+ };
270
+
271
+ const loadJsonFile = async (filePath) => {
272
+ if (!(await fileExists(filePath))) {
273
+ throw new CliError(`File does not exist: ${filePath}`);
274
+ }
275
+
276
+ const text = await readFile(filePath, "utf8");
277
+ try {
278
+ return JSON.parse(text);
279
+ } catch (error) {
280
+ throw new CliError(`Invalid JSON file: ${filePath} (${error instanceof Error ? error.message : "parse failed"})`);
281
+ }
282
+ };
283
+
284
+ const commandAuthCheck = async (ctx) => {
285
+ const payload = await requestJson(ctx, "/api/debug/identity", { method: "GET" });
286
+ const provider =
287
+ payload && typeof payload === "object" && "authProvider" in payload
288
+ ? String(payload.authProvider || "")
289
+ : "";
290
+
291
+ return {
292
+ ok: true,
293
+ auth: provider || "unknown",
294
+ endpoint: "/api/debug/identity",
295
+ identity: payload,
296
+ };
297
+ };
298
+
299
+ const commandAuthNotes = () => {
300
+ return {
301
+ ok: true,
302
+ authSummary: {
303
+ primary: "Clerk session via cookie (__session) OR internal service headers for agent/gateway flows",
304
+ alternate: [
305
+ "Remotion job status supports job poll token via Authorization: Bearer <pollToken> or ?pollToken=...",
306
+ ],
307
+ env: [
308
+ "For Clerk session: VIDEOGEN_COOKIE=__session=...",
309
+ "For internal service auth: INTERNAL_SERVICE_SECRET + VIDEOGEN_INTERNAL_USER_ID",
310
+ ],
311
+ noDedicatedApiLogin: false,
312
+ notes: [
313
+ "Internal auth uses X-Internal-Service-Token and X-Internal-User-Id headers.",
314
+ "Clerk remains active for frontend auth; internal service auth is additive for automation/gateway calls.",
315
+ ],
316
+ },
317
+ };
318
+ };
319
+
320
+ const commandProjectsList = async (ctx) => {
321
+ const payload = await requestJson(ctx, "/api/studio/projects", { method: "GET" });
322
+ return { ok: true, projects: payload };
323
+ };
324
+
325
+ const commandProjectsCreate = async (ctx, flags) => {
326
+ const name = getRequiredFlag(flags, "name");
327
+ const orientationRaw = typeof flags.orientation === "string" ? flags.orientation.trim().toLowerCase() : "";
328
+ const orientation =
329
+ orientationRaw === "vertical" ? "vertical" : orientationRaw === "horizontal" ? "horizontal" : undefined;
330
+ const payload = await requestJson(ctx, "/api/studio/projects", {
331
+ method: "POST",
332
+ headers: { "content-type": "application/json" },
333
+ body: JSON.stringify({ name, orientation, video_type: orientation || null }),
334
+ });
335
+ return { ok: true, project: payload };
336
+ };
337
+
338
+ const commandProjectsRename = async (ctx, flags) => {
339
+ const project = getRequiredFlag(flags, "project");
340
+ const name = getRequiredFlag(flags, "name");
341
+ const payload = await requestJson(ctx, `/api/studio/projects/${encodeURIComponent(project)}`, {
342
+ method: "PATCH",
343
+ headers: { "content-type": "application/json" },
344
+ body: JSON.stringify({ name }),
345
+ });
346
+ return { ok: true, project: payload };
347
+ };
348
+
349
+ const commandMediaList = async (ctx) => {
350
+ const payload = await requestJson(ctx, "/api/studio/db-assets?media=all", { method: "GET" });
351
+ return { ok: true, media: payload };
352
+ };
353
+
354
+ const commandMediaUpload = async (ctx, flags) => {
355
+ const project = getRequiredFlag(flags, "project");
356
+ const filePath = getRequiredFlag(flags, "file");
357
+
358
+ if (!(await fileExists(filePath))) {
359
+ throw new CliError(`File does not exist: ${filePath}`);
360
+ }
361
+
362
+ const mime = guessMimeType(filePath);
363
+ const bytes = await readFile(filePath);
364
+ const uploadBlob = new Blob([bytes], { type: mime });
365
+ const formData = new FormData();
366
+ formData.set("file", uploadBlob, path.basename(filePath));
367
+ formData.set("projectId", project);
368
+
369
+ const payload = await requestJson(
370
+ ctx,
371
+ `/api/studio/projects/${encodeURIComponent(project)}/video-editor/upload`,
372
+ {
373
+ method: "POST",
374
+ body: formData,
375
+ },
376
+ );
377
+
378
+ return { ok: true, upload: payload };
379
+ };
380
+
381
+ const commandVideoEditorGet = async (ctx, flags) => {
382
+ const project = getRequiredFlag(flags, "project");
383
+ const payload = await requestJson(ctx, `/api/studio/projects/${encodeURIComponent(project)}/video-editor`, {
384
+ method: "GET",
385
+ });
386
+ return { ok: true, state: payload };
387
+ };
388
+
389
+ const commandVideoEditorSave = async (ctx, flags) => {
390
+ const project = getRequiredFlag(flags, "project");
391
+ const filePath = getRequiredFlag(flags, "file");
392
+ const parsed = await loadJsonFile(filePath);
393
+ const videoEditor =
394
+ parsed && typeof parsed === "object" && !Array.isArray(parsed) && "videoEditor" in parsed
395
+ ? parsed.videoEditor
396
+ : parsed;
397
+
398
+ const payload = await requestJson(ctx, `/api/studio/projects/${encodeURIComponent(project)}/video-editor`, {
399
+ method: "POST",
400
+ headers: { "content-type": "application/json" },
401
+ body: JSON.stringify({ videoEditor }),
402
+ });
403
+
404
+ return { ok: true, save: payload };
405
+ };
406
+
407
+ const commandCapabilities = async (ctx) => {
408
+ const payload = await requestJson(ctx, "/api/studio/video-editor/capabilities", { method: "GET" });
409
+ return { ok: true, capabilities: payload };
410
+ };
411
+
412
+ const commandAssetHealth = async (ctx, flags) => {
413
+ const project = getRequiredFlag(flags, "project");
414
+ const repair = flags.repair === true;
415
+ const payload = await requestJson(
416
+ ctx,
417
+ `/api/studio/projects/${encodeURIComponent(project)}/video-editor/asset-health`,
418
+ { method: repair ? "POST" : "GET" },
419
+ );
420
+ return { ok: true, assetHealth: payload };
421
+ };
422
+
423
+ const commandRenderStart = async (ctx, flags) => {
424
+ const project = getRequiredFlag(flags, "project");
425
+ const payload = await requestJson(ctx, `/api/studio/projects/${encodeURIComponent(project)}/video-editor/render`, {
426
+ method: "POST",
427
+ });
428
+ return { ok: true, renderStart: payload };
429
+ };
430
+
431
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
432
+
433
+ const commandRenderStatus = async (ctx, flags) => {
434
+ const job = getRequiredFlag(flags, "job");
435
+ const pollToken = typeof flags["poll-token"] === "string" ? String(flags["poll-token"]).trim() : "";
436
+ const shouldPoll = flags.poll === true;
437
+ const pollMs = Math.max(200, parseNumberFlag(flags, "poll-ms", 2500));
438
+ const maxPolls = Math.max(1, parseNumberFlag(flags, "max-polls", 180));
439
+
440
+ let attempts = 0;
441
+ let lastPayload = null;
442
+
443
+ while (attempts < maxPolls) {
444
+ attempts += 1;
445
+
446
+ lastPayload = await requestJson(
447
+ ctx,
448
+ `/api/remotion/jobs/${encodeURIComponent(job)}`,
449
+ { method: "GET" },
450
+ { bearerOverride: pollToken || null },
451
+ );
452
+
453
+ const status =
454
+ lastPayload && typeof lastPayload === "object" && "status" in lastPayload
455
+ ? String(lastPayload.status || "").toLowerCase()
456
+ : "";
457
+
458
+ if (!shouldPoll || status === "completed" || status === "failed") {
459
+ return {
460
+ ok: true,
461
+ attempts,
462
+ jobId: job,
463
+ status: lastPayload,
464
+ };
465
+ }
466
+
467
+ await sleep(pollMs);
468
+ }
469
+
470
+ return {
471
+ ok: true,
472
+ attempts: maxPolls,
473
+ jobId: job,
474
+ timedOut: true,
475
+ status: lastPayload,
476
+ };
477
+ };
478
+
479
+ const commandAiVideoCreate = async (ctx, flags) => {
480
+ const project = getRequiredFlag(flags, "project");
481
+ const prompt = getRequiredFlag(flags, "prompt");
482
+ const aspectRatioRaw = typeof flags["aspect-ratio"] === "string" ? flags["aspect-ratio"] : "16:9";
483
+ const aspectRatio = aspectRatioRaw === "9:16" ? "9:16" : "16:9";
484
+
485
+ const durationSecondsRaw = parseNumberFlag(flags, "duration-seconds", 8);
486
+ const durationSeconds = durationSecondsRaw === 4 || durationSecondsRaw === 6 || durationSecondsRaw === 8 ? durationSecondsRaw : 8;
487
+
488
+ const targetDurationSecondsRaw = parseNumberFlag(flags, "target-duration-seconds", durationSeconds);
489
+ const targetDurationSeconds =
490
+ Number.isFinite(targetDurationSecondsRaw) && targetDurationSecondsRaw > 0
491
+ ? targetDurationSecondsRaw
492
+ : durationSeconds;
493
+
494
+ const response = await request(ctx, `/api/studio/projects/${encodeURIComponent(project)}/video-editor/create-ai-video`, {
495
+ method: "POST",
496
+ headers: { "content-type": "application/json" },
497
+ body: JSON.stringify({
498
+ prompt,
499
+ durationSeconds,
500
+ targetDurationSeconds,
501
+ aspectRatio,
502
+ }),
503
+ });
504
+
505
+ if (!response.ok) {
506
+ const payload = await readResponseBody(response);
507
+ const message =
508
+ typeof payload === "object" && payload && "details" in payload
509
+ ? String(payload.details || "")
510
+ : typeof payload === "object" && payload && "error" in payload
511
+ ? String(payload.error || "")
512
+ : `AI video generation failed (${response.status})`;
513
+ throw new CliError(message || `AI video generation failed (${response.status})`, {
514
+ statusCode: response.status,
515
+ payload,
516
+ });
517
+ }
518
+
519
+ const outputPathRaw = typeof flags.out === "string" ? flags.out.trim() : "";
520
+ const outputPath = outputPathRaw || `/tmp/videogen-ai-${Date.now()}.mp4`;
521
+
522
+ const bytes = new Uint8Array(await response.arrayBuffer());
523
+ await writeFile(outputPath, bytes);
524
+
525
+ return {
526
+ ok: true,
527
+ aiVideo: {
528
+ project,
529
+ outputPath,
530
+ sizeBytes: bytes.byteLength,
531
+ contentType: response.headers.get("content-type") || "video/mp4",
532
+ trimStatus: response.headers.get("x-ai-video-trim") || null,
533
+ trimDetails: response.headers.get("x-ai-video-trim-details") || null,
534
+ },
535
+ };
536
+ };
537
+
538
+ const commandApiRequest = async (ctx, flags) => {
539
+ const method = typeof flags.method === "string" ? flags.method.toUpperCase().trim() : "GET";
540
+ const pathOrUrl = getRequiredFlag(flags, "path");
541
+ const bodyJsonRaw = typeof flags["body-json"] === "string" ? flags["body-json"] : "";
542
+
543
+ let body;
544
+ if (bodyJsonRaw) {
545
+ try {
546
+ const parsed = JSON.parse(bodyJsonRaw);
547
+ body = JSON.stringify(parsed);
548
+ } catch (error) {
549
+ throw new CliError(`Invalid --body-json: ${error instanceof Error ? error.message : "parse failed"}`);
550
+ }
551
+ }
552
+
553
+ const payload = await requestJson(ctx, pathOrUrl, {
554
+ method,
555
+ ...(body
556
+ ? {
557
+ headers: { "content-type": "application/json" },
558
+ body,
559
+ }
560
+ : {}),
561
+ });
562
+
563
+ return { ok: true, response: payload };
564
+ };
565
+
566
+ const main = async () => {
567
+ await loadEnvFiles();
568
+ const parsed = parseArgs(process.argv.slice(2));
569
+ const command = parsed.command;
570
+
571
+ const wantsHelp = !command || command === "help" || parsed.flags.help === true;
572
+ if (wantsHelp) {
573
+ process.stdout.write(HELP_TEXT);
574
+ return;
575
+ }
576
+
577
+ const ctx = {
578
+ baseUrl: normalizeBaseUrl(
579
+ typeof parsed.flags["base-url"] === "string"
580
+ ? parsed.flags["base-url"]
581
+ : process.env.VIDEOGEN_BASE_URL || "http://localhost:3003",
582
+ ),
583
+ cookie:
584
+ typeof parsed.flags.cookie === "string"
585
+ ? parsed.flags.cookie
586
+ : process.env.VIDEOGEN_COOKIE || null,
587
+ internalServiceToken:
588
+ typeof parsed.flags["internal-service-token"] === "string"
589
+ ? parsed.flags["internal-service-token"]
590
+ : typeof parsed.flags["agent-token"] === "string"
591
+ ? parsed.flags["agent-token"]
592
+ : process.env.VIDEOGEN_INTERNAL_SERVICE_TOKEN ||
593
+ process.env.INTERNAL_SERVICE_SECRET ||
594
+ process.env.VIDEOGEN_AGENT_TOKEN ||
595
+ null,
596
+ internalUserId:
597
+ typeof parsed.flags["internal-user-id"] === "string"
598
+ ? parsed.flags["internal-user-id"]
599
+ : process.env.VIDEOGEN_INTERNAL_USER_ID ||
600
+ process.env.AGENT_INTERNAL_USER_ID ||
601
+ null,
602
+ bearer:
603
+ typeof parsed.flags.bearer === "string"
604
+ ? parsed.flags.bearer
605
+ : process.env.VIDEOGEN_BEARER_TOKEN || null,
606
+ timeoutMs: Math.max(1000, parseNumberFlag(parsed.flags, "timeout-ms", 60_000)),
607
+ pretty: parsed.flags.pretty === true,
608
+ };
609
+
610
+ let result;
611
+ switch (command) {
612
+ case "auth:check":
613
+ result = await commandAuthCheck(ctx);
614
+ break;
615
+ case "auth:notes":
616
+ result = commandAuthNotes();
617
+ break;
618
+ case "projects:list":
619
+ result = await commandProjectsList(ctx);
620
+ break;
621
+ case "projects:create":
622
+ result = await commandProjectsCreate(ctx, parsed.flags);
623
+ break;
624
+ case "projects:rename":
625
+ result = await commandProjectsRename(ctx, parsed.flags);
626
+ break;
627
+ case "media:list":
628
+ result = await commandMediaList(ctx);
629
+ break;
630
+ case "media:upload":
631
+ result = await commandMediaUpload(ctx, parsed.flags);
632
+ break;
633
+ case "video-editor:get":
634
+ result = await commandVideoEditorGet(ctx, parsed.flags);
635
+ break;
636
+ case "video-editor:save":
637
+ result = await commandVideoEditorSave(ctx, parsed.flags);
638
+ break;
639
+ case "video-editor:capabilities":
640
+ result = await commandCapabilities(ctx);
641
+ break;
642
+ case "video-editor:asset-health":
643
+ result = await commandAssetHealth(ctx, parsed.flags);
644
+ break;
645
+ case "render:start":
646
+ result = await commandRenderStart(ctx, parsed.flags);
647
+ break;
648
+ case "render:status":
649
+ result = await commandRenderStatus(ctx, parsed.flags);
650
+ break;
651
+ case "ai-video:create":
652
+ result = await commandAiVideoCreate(ctx, parsed.flags);
653
+ break;
654
+ case "api:request":
655
+ result = await commandApiRequest(ctx, parsed.flags);
656
+ break;
657
+ default:
658
+ throw new CliError(`Unknown command: ${command}`);
659
+ }
660
+
661
+ process.stdout.write(`${JSON.stringify(result, null, ctx.pretty ? 2 : 0)}\n`);
662
+ };
663
+
664
+ main().catch((error) => {
665
+ const message = error instanceof Error ? error.message : "Unknown error";
666
+ const statusCode = error instanceof CliError ? error.statusCode : undefined;
667
+ const payload = error instanceof CliError ? error.payload : undefined;
668
+ const output = {
669
+ ok: false,
670
+ error: message,
671
+ ...(statusCode ? { statusCode } : {}),
672
+ ...(payload !== undefined ? { payload } : {}),
673
+ };
674
+ process.stderr.write(`${JSON.stringify(output, null, 2)}\n`);
675
+ process.exitCode = 1;
676
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "videogen-npm",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Node/npm executable for the VideoGen CLI",
7
+ "license": "UNLICENSED",
8
+ "author": "Mindsight Ventures",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/mindsightventures/AiVideoGen2.git",
12
+ "directory": "videogen-npm"
13
+ },
14
+ "homepage": "https://github.com/mindsightventures/AiVideoGen2/tree/main/videogen-npm",
15
+ "bugs": {
16
+ "url": "https://github.com/mindsightventures/AiVideoGen2/issues"
17
+ },
18
+ "files": [
19
+ "bin",
20
+ "README.md"
21
+ ],
22
+ "bin": {
23
+ "videogen": "bin/videogen.js"
24
+ },
25
+ "scripts": {
26
+ "check": "node ./bin/videogen.js help"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.17.0"
30
+ }
31
+ }