veogent 1.3.0 → 1.4.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 (4) hide show
  1. package/README.md +155 -324
  2. package/index.js +123 -165
  3. package/package.json +1 -1
  4. package/skills/SKILL.md +314 -734
package/index.js CHANGED
@@ -201,13 +201,12 @@ program
201
201
  }
202
202
  });
203
203
 
204
- // [REMOVED v1.3.0] create-project-description — use docs or compound commands instead
205
204
  program
206
205
  .command('create-project')
207
- .description('Create a new project')
206
+ .description('Create a new project (auto-generates rich description from idea)')
208
207
  .requiredOption('--name <name>', 'Project name')
209
- .requiredOption('--keyword <keyword>', 'Keyword')
210
- .requiredOption('--desc <desc>', 'Description')
208
+ .requiredOption('--idea <idea>', 'Story idea')
209
+ .option('--desc <desc>', 'Description (if omitted, auto-generated from idea via AI)')
211
210
  .requiredOption('--lang <lang>', 'Story language')
212
211
  .option('--sound <sound>', 'Sound effects (true/false)', 'true')
213
212
  .requiredOption('--material <material>', 'Image material')
@@ -216,19 +215,32 @@ program
216
215
  .option('--objects <objects...>', 'List of specific objects for the project as JSON strings (optional)')
217
216
  .action(async (options) => {
218
217
  try {
218
+ // Step 1: Generate rich description from keyword if not provided
219
+ let description = options.desc;
220
+ if (!description) {
221
+ humanLog('📝 Generating description from idea...');
222
+ const descData = unwrapData(await api.post('/app/description', {
223
+ keyword: options.idea,
224
+ language: options.lang,
225
+ }));
226
+ description = descData?.description || descData?.text || String(descData);
227
+ humanLog(`✅ Description generated (${description.length} chars)`);
228
+ }
229
+
230
+ // Step 2: Create project with rich description
219
231
  let parsedObjects = [];
220
232
  if (options.objects && options.objects.length > 0) {
221
233
  try {
222
234
  parsedObjects = options.objects.map(objStr => typeof objStr === 'string' ? JSON.parse(objStr) : objStr);
223
235
  } catch (e) {
224
- throw new Error("Invalid JSON format for --objects. Example: -O '{\"name\":\"Hero\",\"entityType\":\"character\",\"description\":\"desc\",\"file\":\"data:image/jpeg;base64,...\"}'");
236
+ throw new Error("Invalid JSON format for --objects.");
225
237
  }
226
238
  }
227
239
 
228
240
  const payload = {
229
241
  projectName: options.name,
230
- keyword: options.keyword,
231
- description: options.desc,
242
+ keyword: options.idea,
243
+ description,
232
244
  storyLanguage: options.lang,
233
245
  soundEffects: options.sound === 'true' || options.sound === true,
234
246
  imageMaterial: options.material,
@@ -238,149 +250,12 @@ program
238
250
  if (options.customPromptId) payload.customPromptId = options.customPromptId;
239
251
 
240
252
  const data = await api.post('/app/project', payload);
241
- console.log(JSON.stringify({ status: "success", project: unwrapData(data) }, null, 2));
253
+ console.log(JSON.stringify({ status: "success", description, project: unwrapData(data) }, null, 2));
242
254
  } catch (error) {
243
255
  console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
244
256
  }
245
257
  });
246
258
 
247
- // --- Quick Project (compound: create project + chapter content + scenes + wait) ---
248
- program
249
- .command('quick-project')
250
- .description('Create a full project with scenes in one command (create-project → create-chapter-content → create-scene → wait materialization)')
251
- .requiredOption('--keyword <keyword>', 'Story keyword/brief')
252
- .requiredOption('--lang <lang>', 'Story language (e.g. "Vietnamese", "English")')
253
- .requiredOption('--material <material>', 'Image material (e.g. CINEMATIC, PIXAR_3D)')
254
- .option('--name <name>', 'Project name (auto-generated from keyword if omitted)')
255
- .option('--scenes <count>', 'Number of scenes per chapter', '5')
256
- .option('--chapters <count>', 'Number of chapters', '1')
257
- .option('--customPromptId <customPromptId>', 'Custom Prompt ID')
258
- .option('--objects <objects...>', 'Character objects as JSON strings')
259
- .option('--wait', 'Wait for materialization + characters to be ready')
260
- .action(async (options) => {
261
- try {
262
- const results = { steps: [] };
263
-
264
- // Step 1: Generate description
265
- humanLog('📝 Step 1/4: Generating project description...');
266
- const descData = unwrapData(await api.post('/app/description', {
267
- keyword: options.keyword,
268
- language: options.lang,
269
- }));
270
- const description = descData?.description || descData?.text || String(descData);
271
- results.steps.push({ step: 'description', status: 'success' });
272
-
273
- // Step 2: Create project
274
- humanLog('🎬 Step 2/4: Creating project...');
275
- let parsedObjects = [];
276
- if (options.objects?.length > 0) {
277
- parsedObjects = options.objects.map(o => typeof o === 'string' ? JSON.parse(o) : o);
278
- }
279
- const projectPayload = {
280
- projectName: options.name || options.keyword.slice(0, 50),
281
- keyword: options.keyword,
282
- description,
283
- storyLanguage: options.lang,
284
- soundEffects: true,
285
- imageMaterial: options.material,
286
- numberChapters: parseInt(options.chapters),
287
- objects: parsedObjects,
288
- };
289
- if (options.customPromptId) projectPayload.customPromptId = options.customPromptId;
290
-
291
- const projectData = unwrapData(await api.post('/app/project', projectPayload));
292
- const projectId = projectData?.id || projectData?.projectId;
293
- if (!projectId) throw new Error('Failed to create project — no ID returned');
294
- results.projectId = projectId;
295
- results.steps.push({ step: 'create-project', status: 'success', projectId });
296
-
297
- // Get chapter ID
298
- const chaptersData = unwrapData(await api.get(`/app/chapters/${projectId}`));
299
- const chapters = Array.isArray(chaptersData) ? chaptersData : (chaptersData?.chapters || []);
300
- const chapterId = chapters[0]?.id || chapters[0]?.chapterId;
301
- if (!chapterId) throw new Error('No chapter found after project creation');
302
- results.chapterId = chapterId;
303
-
304
- // Step 3: Create chapter content + scenes
305
- humanLog('✍️ Step 3/4: Generating chapter content + creating scenes...');
306
- const contentData = unwrapData(await api.post('/app/chapter/content', {
307
- project: projectId,
308
- chapter: chapterId,
309
- numberScene: parseInt(options.scenes),
310
- }));
311
- let chapterContent = contentData?.chapterContent || contentData?.content || contentData;
312
- if (!Array.isArray(chapterContent)) chapterContent = [chapterContent];
313
- results.steps.push({ step: 'chapter-content', status: 'success', scenes: chapterContent.length });
314
-
315
- // Create scenes
316
- const scenePayload = {
317
- project: projectId,
318
- chapter: chapterId,
319
- chapterContent,
320
- useFlowKey: true,
321
- };
322
- const sceneData = unwrapData(await api.post('/app/scene', scenePayload));
323
- results.steps.push({ step: 'create-scenes', status: 'success' });
324
-
325
- // Step 4: Wait for materialization + characters (optional)
326
- if (options.wait) {
327
- humanLog('⏳ Step 4/4: Waiting for materialization + characters...');
328
-
329
- // Wait materialization
330
- const matStart = Date.now();
331
- const matTimeout = 600000; // 10 min
332
- while (Date.now() - matStart < matTimeout) {
333
- try {
334
- const matData = unwrapData(await api.get(
335
- `/app/scene/materialization-status?projectId=${projectId}&chapterId=${chapterId}`
336
- ));
337
- if (matData?.materialization === 'READY') {
338
- results.steps.push({ step: 'materialization', status: 'success' });
339
- break;
340
- }
341
- } catch { /* retry */ }
342
- await new Promise(r => setTimeout(r, 30000));
343
- }
344
-
345
- // Wait characters
346
- const charStart = Date.now();
347
- const charTimeout = 900000; // 15 min
348
- while (Date.now() - charStart < charTimeout) {
349
- try {
350
- const charRaw = unwrapData(await api.get(`/app/characters/${projectId}`));
351
- const chars = Array.isArray(charRaw) ? charRaw : (charRaw?.characters || []);
352
- const allReady = chars.length > 0 && chars.every(c => c?.imageUri || c?.imageUrl || c?.image);
353
- if (allReady) {
354
- results.characters = chars.map(c => ({
355
- id: c?.id, name: c?.name,
356
- imageUri: c?.imageUri || c?.imageUrl || c?.image,
357
- }));
358
- results.steps.push({ step: 'characters', status: 'success', count: chars.length });
359
- break;
360
- }
361
- } catch { /* retry */ }
362
- await new Promise(r => setTimeout(r, 30000));
363
- }
364
- }
365
-
366
- // Fetch final scene list
367
- const finalScenes = unwrapData(await api.get(`/app/scenes/${projectId}/${chapterId}`));
368
- const sceneList = (Array.isArray(finalScenes) ? finalScenes : (finalScenes?.scenes || finalScenes?.items || []))
369
- .map((s, i) => ({
370
- id: s?.id || s?.sceneId,
371
- displayOrder: s?.displayOrder ?? i,
372
- scriptSegment: s?.scriptSegment || s?.content,
373
- characterNames: (s?.imagePromptJson?.character_names) || [],
374
- }));
375
- results.scenes = sceneList;
376
- results.status = 'success';
377
-
378
- emitJson(results);
379
- } catch (error) {
380
- emitJson({ status: "error", ...formatCliError(error) });
381
- }
382
- });
383
-
384
259
  // --- Chapters ---
385
260
  program
386
261
  .command('chapters <projectId>')
@@ -468,20 +343,57 @@ program
468
343
  }
469
344
  });
470
345
 
346
+ program
347
+ .command('add-character')
348
+ .description('Add a new character to a project (does NOT generate image — use generate-character after)')
349
+ .requiredOption('--project <project>', 'Project ID')
350
+ .requiredOption('--name <name>', 'Character name')
351
+ .option('--type <type>', 'Entity type: character, creature, location, visual_asset, faction, generic_troop', 'character')
352
+ .action(async (options) => {
353
+ try {
354
+ const payload = {
355
+ name: options.name,
356
+ entityType: options.type,
357
+ };
358
+ const data = await api.post(`/app/character/${options.project}`, payload);
359
+ console.log(JSON.stringify({ status: "success", character: unwrapData(data) }, null, 2));
360
+ } catch (error) {
361
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
362
+ }
363
+ });
364
+
365
+ program
366
+ .command('generate-character')
367
+ .description('Generate reference image for a character (first time — no existing image)')
368
+ .requiredOption('--project <project>', 'Project ID')
369
+ .requiredOption('--character <character>', 'Character ID')
370
+ .requiredOption('--prompt <prompt>', 'Description of the character for image generation')
371
+ .action(async (options) => {
372
+ try {
373
+ const payload = {
374
+ userPrompt: options.prompt,
375
+ useFlowKey: true,
376
+ editImageMode: false, // false = generate new image (no existing image)
377
+ };
378
+ const data = await api.post(`/app/character/${options.project}/${options.character}/update-by-prompt`, payload);
379
+ console.log(JSON.stringify({ status: "success", character: unwrapData(data) }, null, 2));
380
+ } catch (error) {
381
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
382
+ }
383
+ });
384
+
471
385
  program
472
386
  .command('edit-character')
473
- .description('Update a character\'s description or edit their generated image directly via AI prompt')
387
+ .description('Edit an existing character reference image directly via AI prompt')
474
388
  .requiredOption('--project <project>', 'Project ID')
475
- .requiredOption('--character <character>', 'Character ID (e.g., drelenavance)')
476
- .requiredOption('--userprompt <userprompt>', 'User instruction to modify the character')
477
- .option('--editimage', 'Enable direct Image Editing Mode (true). Default is Regenerate Profile Mode (false)', false)
478
- .option('--flowkey', 'Enable useFlowKey to sync context via FireBase', true)
389
+ .requiredOption('--character <character>', 'Character ID')
390
+ .requiredOption('--prompt <prompt>', 'Edit instruction (what to change in the existing image)')
479
391
  .action(async (options) => {
480
392
  try {
481
393
  const payload = {
482
- userPrompt: options.userprompt,
483
- useFlowKey: options.flowkey === true || options.flowkey === 'true',
484
- editImageMode: options.editimage === true,
394
+ userPrompt: options.prompt,
395
+ useFlowKey: true,
396
+ editImageMode: true, // true = edit existing image directly
485
397
  };
486
398
  const data = await api.post(`/app/character/${options.project}/${options.character}/update-by-prompt`, payload);
487
399
  console.log(JSON.stringify({ status: "success", character: unwrapData(data) }, null, 2));
@@ -740,33 +652,80 @@ program
740
652
  }
741
653
  });
742
654
 
743
- // [REMOVED v1.3.0] add-scene — use docs or compound commands instead
655
+ program
656
+ .command('delete-project')
657
+ .description('⚠️ DANGER: Permanently delete a project and all its data. Requires explicit confirmation.')
658
+ .requiredOption('--project <project>', 'Project ID to delete')
659
+ .option('--confirm', 'Skip confirmation prompt (required for agent-safe mode)')
660
+ .action(async (options) => {
661
+ try {
662
+ if (!options.confirm) {
663
+ // In agent-safe mode, require --confirm flag
664
+ console.log(JSON.stringify({
665
+ status: "error",
666
+ code: "CONFIRMATION_REQUIRED",
667
+ message: "⚠️ This will PERMANENTLY DELETE the project and all its scenes, characters, images, and videos. Re-run with --confirm to proceed."
668
+ }));
669
+ return;
670
+ }
671
+ const data = await api.delete(`/app/project/${options.project}`);
672
+ console.log(JSON.stringify({ status: "success", deleted: options.project, data: unwrapData(data) }, null, 2));
673
+ } catch (error) {
674
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
675
+ }
676
+ });
677
+
678
+ program
679
+ .command('insert-scene')
680
+ .description('Insert a new scene into an existing chapter (after a specific scene)')
681
+ .requiredOption('--project <project>', 'Project ID')
682
+ .requiredOption('--chapter <chapter>', 'Chapter ID')
683
+ .requiredOption('--prompt <prompt>', 'Scene narrative/script text')
684
+ .option('--after <sceneId>', 'Insert after this scene ID (appends to end if omitted)')
685
+ .action(async (options) => {
686
+ try {
687
+ const payload = {
688
+ projectId: options.project,
689
+ chapterId: options.chapter,
690
+ userPrompt: options.prompt,
691
+ };
692
+ if (options.after) {
693
+ payload.afterSceneId = options.after;
694
+ }
695
+ const data = await api.post('/app/scene', payload);
696
+ console.log(JSON.stringify({ status: "success", scene: unwrapData(data) }, null, 2));
697
+ } catch (error) {
698
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
699
+ }
700
+ });
701
+
744
702
  program
745
703
  .command('edit-scene')
746
- .description('Edit an existing scene (image prompt) via AI assistance')
704
+ .description('Edit scene storyboard/image prompt, or regenerate image with a request ID')
747
705
  .requiredOption('--project <project>', 'Project ID')
748
706
  .requiredOption('--chapter <chapter>', 'Chapter ID')
749
707
  .requiredOption('--scene <scene>', 'Scene ID')
750
- .requiredOption('--userprompt <userprompt>', 'User narrative prompt to modify the scene')
751
- .option('--no-regenerate', 'Tells backend to NOT automatically trigger regenerating the image')
752
- .option('--request <request>', 'Target a specific past Request ID to edit its output')
753
- .option('--wait', 'Wait for image regeneration to complete (only if regenerate is enabled)')
708
+ .requiredOption('--prompt <prompt>', 'Edit instruction or new scene prompt')
709
+ .option('--request <requestId>', 'Target a request ID VEOGENT AI regenerates the scene image')
710
+ .option('--wait', 'Wait for image regeneration to complete (only with --request)')
754
711
  .action(async (options) => {
755
712
  try {
713
+ // Auto-derive regenerate: with --request → regenerate (direct image edit), without → no regenerate (prompt update only)
714
+ const hasRequest = !!options.request;
756
715
  const payload = {
757
- userPrompt: options.userprompt,
758
- regenerateImage: options.regenerate !== false,
716
+ userPrompt: options.prompt,
717
+ regenerateImage: hasRequest,
759
718
  };
760
719
 
761
- if (options.request) {
720
+ if (hasRequest) {
762
721
  payload.requestId = options.request;
763
722
  }
764
723
 
765
724
  const data = await api.patch(`/app/scene/script-segment/${options.project}/${options.chapter}/${options.scene}`, payload);
766
725
  const result = { status: "success", scene: unwrapData(data) };
767
726
 
768
- // Wait for image regen if requested
769
- if (options.wait && options.regenerate !== false) {
727
+ // Wait for image regen if requested (only when regenerating via --request)
728
+ if (options.wait && hasRequest) {
770
729
  humanLog('⏳ Waiting for image regeneration...');
771
730
  const waitResult = await waitForAssets(api, options.project, options.chapter, 'GENERATE_IMAGES', [options.scene]);
772
731
  result.waitResult = waitResult;
@@ -1296,7 +1255,6 @@ program
1296
1255
  // --- System ---
1297
1256
  // NOTE: Standalone commands (gen-image, gen-video, standalone-requests, standalone-request)
1298
1257
  // have been removed in v1.3.0. Use project-scoped workflows instead.
1299
- // For quick generation, use: quick-project --keyword "..." --wait
1300
1258
 
1301
1259
  // --- System ---
1302
1260
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veogent",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
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": {