veogent 1.0.21 → 1.0.22

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 (2) hide show
  1. package/index.js +254 -18
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -170,9 +170,9 @@ program
170
170
  try {
171
171
  const data = await api.get('/app/capabilities');
172
172
  const caps = unwrapData(data);
173
- emitJson({ data: caps?.imageModels || IMAGE_MODELS });
173
+ emitJson({ status: 'success', models: caps?.imageModels || IMAGE_MODELS });
174
174
  } catch {
175
- emitJson({ data: IMAGE_MODELS });
175
+ emitJson({ status: 'success', models: IMAGE_MODELS });
176
176
  }
177
177
  });
178
178
 
@@ -183,9 +183,9 @@ program
183
183
  try {
184
184
  const data = await api.get('/app/capabilities');
185
185
  const caps = unwrapData(data);
186
- emitJson({ data: caps?.videoModels || VIDEO_MODELS });
186
+ emitJson({ status: 'success', models: caps?.videoModels || VIDEO_MODELS });
187
187
  } catch {
188
- emitJson({ data: VIDEO_MODELS });
188
+ emitJson({ status: 'success', models: VIDEO_MODELS });
189
189
  }
190
190
  });
191
191
 
@@ -316,7 +316,7 @@ program
316
316
 
317
317
  program
318
318
  .command('create-chapter-content')
319
- .description('Generate content for a specific chapter')
319
+ .description('Generate content for a specific chapter. This is a synchronous AI generation call — use the returned chapterContent directly.')
320
320
  .requiredOption('-p, --project <project>', 'Project ID')
321
321
  .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
322
322
  .option('-s, --scenes <count>', 'Number of scenes', '1')
@@ -328,9 +328,15 @@ program
328
328
  numberScene: parseInt(options.scenes),
329
329
  };
330
330
  const data = await api.post('/app/chapter/content', payload);
331
- console.log(JSON.stringify({ status: "success", chapterContent: unwrapData(data) }, null, 2));
331
+ const raw = unwrapData(data);
332
+ // Normalize: always return chapterContent as array of strings
333
+ let chapterContent = raw;
334
+ if (raw?.chapterContent) chapterContent = raw.chapterContent;
335
+ else if (raw?.content) chapterContent = raw.content;
336
+ if (!Array.isArray(chapterContent)) chapterContent = [chapterContent];
337
+ emitJson({ status: "success", chapterContent });
332
338
  } catch (error) {
333
- console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
339
+ emitJson({ status: "error", ...formatCliError(error) });
334
340
  }
335
341
  });
336
342
 
@@ -338,13 +344,34 @@ program
338
344
  // --- Characters ---
339
345
  program
340
346
  .command('characters <projectId>')
341
- .description('Get all characters for a specific project')
347
+ .description('Get all characters for a specific project (includes readiness info)')
342
348
  .action(async (projectId) => {
343
349
  try {
344
350
  const data = await api.get(`/app/characters/${projectId}`);
345
- console.log(JSON.stringify(unwrapData(data), null, 2));
351
+ const raw = unwrapData(data);
352
+ const chars = Array.isArray(raw) ? raw : (raw?.characters || raw?.items || []);
353
+
354
+ const characters = chars.map((ch) => ({
355
+ id: ch?.id || ch?.characterId || null,
356
+ name: ch?.name || ch?.characterName || null,
357
+ imageUri: ch?.imageUri || ch?.imageUrl || ch?.image || null,
358
+ ready: !!(ch?.imageUri || ch?.imageUrl || ch?.image),
359
+ ...ch,
360
+ }));
361
+
362
+ const readyCount = characters.filter((c) => c.ready).length;
363
+ const result = {
364
+ status: 'success',
365
+ characters,
366
+ characterReadiness: {
367
+ total: characters.length,
368
+ ready: readyCount,
369
+ allReady: characters.length > 0 && readyCount === characters.length,
370
+ },
371
+ };
372
+ emitJson(result);
346
373
  } catch (error) {
347
- console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
374
+ emitJson({ status: "error", ...formatCliError(error) });
348
375
  }
349
376
  });
350
377
 
@@ -414,13 +441,66 @@ program
414
441
 
415
442
  program
416
443
  .command('scene-status')
417
- .description('Get scene status snapshot by chapter')
444
+ .description('Get scene status snapshot by chapter (with embedded asset URLs)')
418
445
  .requiredOption('-p, --project <project>', 'Project ID')
419
446
  .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
420
447
  .action(async (options) => {
421
448
  try {
422
449
  const data = await api.get(`/app/scene-status/${options.project}/${options.chapter}`);
423
- emitJson({ status: 'success', data: unwrapData(data) });
450
+ const rawData = unwrapData(data);
451
+
452
+ // Try to enrich each scene with asset URLs
453
+ const scenes = Array.isArray(rawData) ? rawData : (rawData?.scenes || rawData?.items || [rawData]);
454
+ const enriched = await Promise.all(scenes.map(async (scene) => {
455
+ const sceneId = scene?.id || scene?.sceneId;
456
+ if (!sceneId) return scene;
457
+
458
+ let imageAsset = { status: scene?.imageStatus || null, url: scene?.imageUrl || scene?.imageVerticalUri || scene?.imageHorizontalUri || null };
459
+ let videoAsset = { status: scene?.videoStatus || null, url: scene?.videoUrl || scene?.videoVerticalUri || scene?.videoHorizontalUri || null };
460
+
461
+ // Fetch assets if URLs not already present
462
+ try {
463
+ if (!imageAsset.url) {
464
+ const imgData = unwrapData(await api.get(`/app/request/assets/${options.project}/${options.chapter}/${sceneId}?type=GENERATE_IMAGES`));
465
+ const imgItems = Array.isArray(imgData) ? imgData : (imgData?.items || []);
466
+ const completed = imgItems.find((r) => String(r?.status || '').toUpperCase() === 'COMPLETED');
467
+ if (completed) {
468
+ imageAsset = {
469
+ status: 'COMPLETED',
470
+ url: completed?.imageVerticalUri || completed?.imageHorizontalUri || completed?.imageUrl || completed?.outputUrl || null,
471
+ createdAt: completed?.createdAt || null,
472
+ completedAt: completed?.completedAt || completed?.updatedAt || null,
473
+ };
474
+ }
475
+ }
476
+ } catch { /* asset fetch optional */ }
477
+
478
+ try {
479
+ if (!videoAsset.url) {
480
+ const vidData = unwrapData(await api.get(`/app/request/assets/${options.project}/${options.chapter}/${sceneId}?type=GENERATE_VIDEO`));
481
+ const vidItems = Array.isArray(vidData) ? vidData : (vidData?.items || []);
482
+ const completed = vidItems.find((r) => String(r?.status || '').toUpperCase() === 'COMPLETED');
483
+ if (completed) {
484
+ videoAsset = {
485
+ status: 'COMPLETED',
486
+ url: completed?.videoVerticalUri || completed?.videoHorizontalUri || completed?.videoUrl || completed?.outputUrl || null,
487
+ createdAt: completed?.createdAt || null,
488
+ completedAt: completed?.completedAt || completed?.updatedAt || null,
489
+ };
490
+ }
491
+ }
492
+ } catch { /* asset fetch optional */ }
493
+
494
+ return {
495
+ sceneId,
496
+ displayOrder: scene?.displayOrder ?? null,
497
+ image: imageAsset,
498
+ video: videoAsset,
499
+ raw: scene,
500
+ };
501
+ }));
502
+
503
+ emitJson({ status: 'success', data: enriched });
424
504
  } catch (error) {
425
505
  emitJson({ status: 'error', ...formatCliError(error) });
426
506
  }
@@ -428,13 +508,49 @@ program
428
508
 
429
509
  program
430
510
  .command('workflow-status')
431
- .description('Export workflow snapshot for a project/chapter (agent helper)')
511
+ .description('Export workflow snapshot for a project/chapter with embedded asset URLs (agent helper)')
432
512
  .requiredOption('-p, --project <project>', 'Project ID')
433
513
  .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
434
514
  .action(async (options) => {
435
515
  try {
436
516
  const data = await api.get(`/app/workflow-status/${options.project}/${options.chapter}`);
437
- emitJson({ status: 'success', data: unwrapData(data) });
517
+ const rawData = unwrapData(data);
518
+
519
+ // Enrich scenes with asset URLs
520
+ const scenes = Array.isArray(rawData?.scenes) ? rawData.scenes : (Array.isArray(rawData) ? rawData : []);
521
+ const enrichedScenes = await Promise.all(scenes.map(async (scene) => {
522
+ const sceneId = scene?.id || scene?.sceneId;
523
+ if (!sceneId) return scene;
524
+
525
+ let imageAsset = { status: scene?.imageStatus || null, url: scene?.imageUrl || scene?.imageVerticalUri || scene?.imageHorizontalUri || null };
526
+ let videoAsset = { status: scene?.videoStatus || null, url: scene?.videoUrl || scene?.videoVerticalUri || scene?.videoHorizontalUri || null };
527
+
528
+ try {
529
+ if (!imageAsset.url) {
530
+ const imgData = unwrapData(await api.get(`/app/request/assets/${options.project}/${options.chapter}/${sceneId}?type=GENERATE_IMAGES`));
531
+ const imgItems = Array.isArray(imgData) ? imgData : (imgData?.items || []);
532
+ const completed = imgItems.find((r) => String(r?.status || '').toUpperCase() === 'COMPLETED');
533
+ if (completed) {
534
+ imageAsset = { status: 'COMPLETED', url: completed?.imageVerticalUri || completed?.imageHorizontalUri || completed?.imageUrl || completed?.outputUrl || null };
535
+ }
536
+ }
537
+ } catch { /* optional */ }
538
+
539
+ try {
540
+ if (!videoAsset.url) {
541
+ const vidData = unwrapData(await api.get(`/app/request/assets/${options.project}/${options.chapter}/${sceneId}?type=GENERATE_VIDEO`));
542
+ const vidItems = Array.isArray(vidData) ? vidData : (vidData?.items || []);
543
+ const completed = vidItems.find((r) => String(r?.status || '').toUpperCase() === 'COMPLETED');
544
+ if (completed) {
545
+ videoAsset = { status: 'COMPLETED', url: completed?.videoVerticalUri || completed?.videoHorizontalUri || completed?.videoUrl || completed?.outputUrl || null };
546
+ }
547
+ }
548
+ } catch { /* optional */ }
549
+
550
+ return { sceneId, image: imageAsset, video: videoAsset, raw: scene };
551
+ }));
552
+
553
+ emitJson({ status: 'success', data: { ...rawData, scenes: enrichedScenes } });
438
554
  } catch (error) {
439
555
  // Backward-compatible fallback for old backend
440
556
  try {
@@ -451,13 +567,77 @@ program
451
567
  (r?.chapter_id === options.chapter || r?.chapterId === options.chapter)
452
568
  );
453
569
 
454
- emitJson({ status: 'success', data: { projectId: options.project, chapterId: options.chapter, scenes, requests: chapterRequests } });
570
+ // Enrich fallback scenes too
571
+ const enrichedFallback = await Promise.all(scenes.map(async (scene) => {
572
+ const sceneId = scene?.id || scene?.sceneId;
573
+ if (!sceneId) return scene;
574
+
575
+ let imageAsset = { status: null, url: null };
576
+ let videoAsset = { status: null, url: null };
577
+
578
+ try {
579
+ const imgData = unwrapData(await api.get(`/app/request/assets/${options.project}/${options.chapter}/${sceneId}?type=GENERATE_IMAGES`));
580
+ const imgItems = Array.isArray(imgData) ? imgData : (imgData?.items || []);
581
+ const completed = imgItems.find((r) => String(r?.status || '').toUpperCase() === 'COMPLETED');
582
+ if (completed) {
583
+ imageAsset = { status: 'COMPLETED', url: completed?.imageVerticalUri || completed?.imageHorizontalUri || completed?.imageUrl || null };
584
+ }
585
+ } catch { /* optional */ }
586
+
587
+ try {
588
+ const vidData = unwrapData(await api.get(`/app/request/assets/${options.project}/${options.chapter}/${sceneId}?type=GENERATE_VIDEO`));
589
+ const vidItems = Array.isArray(vidData) ? vidData : (vidData?.items || []);
590
+ const completed = vidItems.find((r) => String(r?.status || '').toUpperCase() === 'COMPLETED');
591
+ if (completed) {
592
+ videoAsset = { status: 'COMPLETED', url: completed?.videoVerticalUri || completed?.videoHorizontalUri || completed?.videoUrl || null };
593
+ }
594
+ } catch { /* optional */ }
595
+
596
+ return { sceneId, image: imageAsset, video: videoAsset, raw: scene };
597
+ }));
598
+
599
+ emitJson({ status: 'success', data: { projectId: options.project, chapterId: options.chapter, scenes: enrichedFallback, requests: chapterRequests } });
455
600
  } catch (fallbackError) {
456
601
  emitJson({ status: 'error', ...formatCliError(fallbackError) });
457
602
  }
458
603
  }
459
604
  });
460
605
 
606
+ program
607
+ .command('scene-materialization-status')
608
+ .description('Check how many scenes have been materialized for a chapter')
609
+ .requiredOption('-p, --project <project>', 'Project ID')
610
+ .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
611
+ .action(async (options) => {
612
+ try {
613
+ // Get chapter data to determine expected scenes
614
+ let expectedScenes = 0;
615
+ try {
616
+ const chapterData = unwrapData(await api.get(`/app/chapters/${options.project}`));
617
+ const chapters = Array.isArray(chapterData) ? chapterData : (chapterData?.chapters || chapterData?.items || []);
618
+ const chapter = chapters.find((ch) => (ch?.id || ch?.chapterId) === options.chapter);
619
+ expectedScenes = chapter?.numberScene || chapter?.sceneCount || chapter?.expectedScenes || 0;
620
+ } catch { /* fallback: expectedScenes stays 0 */ }
621
+
622
+ // Get actual scenes
623
+ const scenesRaw = unwrapData(await api.get(`/app/scenes/${options.project}/${options.chapter}`));
624
+ const scenes = Array.isArray(scenesRaw) ? scenesRaw : (scenesRaw?.scenes || scenesRaw?.items || []);
625
+ const materializedScenes = scenes.length;
626
+
627
+ // If we couldn't get expectedScenes from chapter, use materialized as fallback
628
+ if (expectedScenes === 0) expectedScenes = materializedScenes;
629
+
630
+ let status;
631
+ if (materializedScenes === 0) status = 'EMPTY';
632
+ else if (materializedScenes >= expectedScenes) status = 'READY';
633
+ else status = 'PROCESSING';
634
+
635
+ emitJson({ status: 'success', expectedScenes, materializedScenes, materialization: status });
636
+ } catch (error) {
637
+ emitJson({ status: 'error', ...formatCliError(error) });
638
+ }
639
+ });
640
+
461
641
  program
462
642
  .command('create-scene')
463
643
  .description('Create a new scene from text content')
@@ -564,7 +744,13 @@ program
564
744
  }
565
745
 
566
746
  const data = await api.post('/app/request', payload);
567
- console.log(JSON.stringify({ status: "success", request: unwrapData(data) }, null, 2));
747
+ const requestResult = unwrapData(data);
748
+ // P2-7: Persist endScene metadata when chained video generation
749
+ if (options.endscene) {
750
+ requestResult.end_scene_id = options.endscene;
751
+ requestResult.generationMode = 'CHAINED_VIDEO';
752
+ }
753
+ console.log(JSON.stringify({ status: "success", request: requestResult }, null, 2));
568
754
  } catch (error) {
569
755
  console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
570
756
  }
@@ -622,6 +808,7 @@ program
622
808
  .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
623
809
  .option('-i, --interval <sec>', 'Polling interval in seconds', '10')
624
810
  .option('-t, --timeout <sec>', 'Timeout in seconds', '1800')
811
+ .option('--require-success', 'Exit non-zero if any scene lacks a successful image asset')
625
812
  .action(async (options) => {
626
813
  const intervalMs = Math.max(1, Number(options.interval || 10)) * 1000;
627
814
  const timeoutMs = Math.max(30, Number(options.timeout || 1800)) * 1000;
@@ -640,6 +827,24 @@ program
640
827
 
641
828
  const pending = filtered.filter((r) => ['PENDING', 'PROCESSING', 'RUNNING'].includes(String(r?.status || '').toUpperCase()));
642
829
  if (pending.length === 0) {
830
+ // --require-success: verify each scene has at least one COMPLETED request with asset URL
831
+ if (options.requireSuccess) {
832
+ const sceneIds = [...new Set(filtered.map((r) => r?.scene || r?.sceneId).filter(Boolean))];
833
+ const failedScenes = [];
834
+ for (const sid of sceneIds) {
835
+ const sceneReqs = filtered.filter((r) => (r?.scene === sid || r?.sceneId === sid));
836
+ const hasSuccess = sceneReqs.some((r) => {
837
+ const st = String(r?.status || '').toUpperCase();
838
+ const hasUrl = !!(r?.imageVerticalUri || r?.imageHorizontalUri || r?.imageUrl || r?.outputUrl);
839
+ return st === 'COMPLETED' && hasUrl;
840
+ });
841
+ if (!hasSuccess) failedScenes.push(sid);
842
+ }
843
+ if (failedScenes.length > 0) {
844
+ console.log(JSON.stringify({ status: 'error', code: 'ASSETS_NOT_SUCCESS', message: 'Some scenes did not produce successful assets', failedScenes }, null, 2));
845
+ process.exit(1);
846
+ }
847
+ }
643
848
  console.log(JSON.stringify({ status: 'success', data: filtered, message: 'All image requests finished' }, null, 2));
644
849
  return;
645
850
  }
@@ -660,6 +865,7 @@ program
660
865
  .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
661
866
  .option('-i, --interval <sec>', 'Polling interval in seconds', '10')
662
867
  .option('-t, --timeout <sec>', 'Timeout in seconds', '3600')
868
+ .option('--require-success', 'Exit non-zero if any scene lacks a successful video asset')
663
869
  .action(async (options) => {
664
870
  const intervalMs = Math.max(1, Number(options.interval || 10)) * 1000;
665
871
  const timeoutMs = Math.max(30, Number(options.timeout || 3600)) * 1000;
@@ -678,6 +884,24 @@ program
678
884
 
679
885
  const pending = filtered.filter((r) => ['PENDING', 'PROCESSING', 'RUNNING'].includes(String(r?.status || '').toUpperCase()));
680
886
  if (pending.length === 0) {
887
+ // --require-success: verify each scene has at least one COMPLETED request with asset URL
888
+ if (options.requireSuccess) {
889
+ const sceneIds = [...new Set(filtered.map((r) => r?.scene || r?.sceneId).filter(Boolean))];
890
+ const failedScenes = [];
891
+ for (const sid of sceneIds) {
892
+ const sceneReqs = filtered.filter((r) => (r?.scene === sid || r?.sceneId === sid));
893
+ const hasSuccess = sceneReqs.some((r) => {
894
+ const st = String(r?.status || '').toUpperCase();
895
+ const hasUrl = !!(r?.videoVerticalUri || r?.videoHorizontalUri || r?.videoUrl || r?.outputUrl);
896
+ return st === 'COMPLETED' && hasUrl;
897
+ });
898
+ if (!hasSuccess) failedScenes.push(sid);
899
+ }
900
+ if (failedScenes.length > 0) {
901
+ console.log(JSON.stringify({ status: 'error', code: 'ASSETS_NOT_SUCCESS', message: 'Some scenes did not produce successful assets', failedScenes }, null, 2));
902
+ process.exit(1);
903
+ }
904
+ }
681
905
  console.log(JSON.stringify({ status: 'success', data: filtered, message: 'All video requests finished' }, null, 2));
682
906
  return;
683
907
  }
@@ -697,9 +921,21 @@ program
697
921
  .action(async (projectId, chapterId, sceneId, type) => {
698
922
  try {
699
923
  const data = await api.get(`/app/request/assets/${projectId}/${chapterId}/${sceneId}?type=${type}`);
700
- console.log(JSON.stringify(unwrapData(data), null, 2));
924
+ const raw = unwrapData(data);
925
+ // Enrich with lifecycle metadata
926
+ const items = Array.isArray(raw) ? raw : (raw?.items || [raw]);
927
+ const enriched = items.map((r) => ({
928
+ ...r,
929
+ createdAt: r?.createdAt || null,
930
+ startedAt: r?.startedAt || null,
931
+ updatedAt: r?.updatedAt || null,
932
+ completedAt: r?.completedAt || null,
933
+ deducted: r?.deducted ?? null,
934
+ retryable: typeof r?.retryable === 'boolean' ? r.retryable : (r?.status && ['FAILED', 'ERROR'].includes(String(r.status).toUpperCase())),
935
+ }));
936
+ emitJson({ status: 'success', data: enriched });
701
937
  } catch (error) {
702
- console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
938
+ emitJson({ status: "error", ...formatCliError(error) });
703
939
  }
704
940
  });
705
941
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veogent",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "The official CLI to interact with the VEOGENT API - AI Video and Image generation platform",
5
5
  "main": "index.js",
6
6
  "bin": {