veogent 1.1.3 → 1.1.4
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/index.js +127 -91
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -135,8 +135,8 @@ program
|
|
|
135
135
|
program
|
|
136
136
|
.command('setup-flow')
|
|
137
137
|
.description('Setup Google Flow Key and User Payment Tier (Requires authentication)')
|
|
138
|
-
.requiredOption('
|
|
139
|
-
.option('
|
|
138
|
+
.requiredOption('--flowkey <key>', 'The Google access token (ya29...)')
|
|
139
|
+
.option('--tier <tier>', 'User payment tier', 'PAYGATE_TIER_TWO')
|
|
140
140
|
.action(async (options) => {
|
|
141
141
|
try {
|
|
142
142
|
const payload = {
|
|
@@ -239,10 +239,10 @@ program
|
|
|
239
239
|
program
|
|
240
240
|
.command('create-project-description')
|
|
241
241
|
.description('Generate AI description for a new project based on keywords')
|
|
242
|
-
.requiredOption('
|
|
243
|
-
.requiredOption('
|
|
244
|
-
.option('
|
|
245
|
-
.option('
|
|
242
|
+
.requiredOption('--keyword <keyword>', 'Keywords for the project')
|
|
243
|
+
.requiredOption('--lang <lang>', 'Story language')
|
|
244
|
+
.option('--promptId <promptId>', 'Custom Prompt ID from custom-prompts (optional)')
|
|
245
|
+
.option('--objects <objects...>', 'List of specific objects to include as JSON strings (optional)')
|
|
246
246
|
.action(async (options) => {
|
|
247
247
|
try {
|
|
248
248
|
let parsedObjects = [];
|
|
@@ -270,15 +270,15 @@ program
|
|
|
270
270
|
program
|
|
271
271
|
.command('create-project')
|
|
272
272
|
.description('Create a new project')
|
|
273
|
-
.requiredOption('
|
|
274
|
-
.requiredOption('
|
|
275
|
-
.requiredOption('
|
|
276
|
-
.requiredOption('
|
|
277
|
-
.option('
|
|
278
|
-
.requiredOption('
|
|
279
|
-
.option('
|
|
280
|
-
.option('
|
|
281
|
-
.option('
|
|
273
|
+
.requiredOption('--name <name>', 'Project name')
|
|
274
|
+
.requiredOption('--keyword <keyword>', 'Keyword')
|
|
275
|
+
.requiredOption('--desc <desc>', 'Description')
|
|
276
|
+
.requiredOption('--lang <lang>', 'Story language')
|
|
277
|
+
.option('--sound <sound>', 'Sound effects (true/false)', 'true')
|
|
278
|
+
.requiredOption('--material <material>', 'Image material')
|
|
279
|
+
.option('--chapters <count>', 'Number of chapters', '1')
|
|
280
|
+
.option('--customPromptId <customPromptId>', 'Custom Prompt ID')
|
|
281
|
+
.option('--objects <objects...>', 'List of specific objects for the project as JSON strings (optional)')
|
|
282
282
|
.action(async (options) => {
|
|
283
283
|
try {
|
|
284
284
|
let parsedObjects = [];
|
|
@@ -325,7 +325,7 @@ program
|
|
|
325
325
|
program
|
|
326
326
|
.command('recent-chapters')
|
|
327
327
|
.description('Get recently created or updated chapters')
|
|
328
|
-
.option('
|
|
328
|
+
.option('--limit <limit>', 'Maximum number of recent chapters to return', '10')
|
|
329
329
|
.action(async (options) => {
|
|
330
330
|
try {
|
|
331
331
|
const data = await api.get(`/app/chapter/recent-chapters?limit=${options.limit}`);
|
|
@@ -338,9 +338,9 @@ program
|
|
|
338
338
|
program
|
|
339
339
|
.command('create-chapter-content')
|
|
340
340
|
.description('Generate content for a specific chapter. This is a synchronous AI generation call — use the returned chapterContent directly.')
|
|
341
|
-
.requiredOption('
|
|
342
|
-
.requiredOption('
|
|
343
|
-
.option('
|
|
341
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
342
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
343
|
+
.option('--scenes <count>', 'Number of scenes', '1')
|
|
344
344
|
.action(async (options) => {
|
|
345
345
|
try {
|
|
346
346
|
const payload = {
|
|
@@ -399,10 +399,10 @@ program
|
|
|
399
399
|
program
|
|
400
400
|
.command('edit-character')
|
|
401
401
|
.description('Update a character\'s description or edit their generated image directly via AI prompt')
|
|
402
|
-
.requiredOption('
|
|
403
|
-
.requiredOption('
|
|
404
|
-
.requiredOption('
|
|
405
|
-
.option('
|
|
402
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
403
|
+
.requiredOption('--character <character>', 'Character ID (e.g., drelenavance)')
|
|
404
|
+
.requiredOption('--userprompt <userprompt>', 'User instruction to modify the character')
|
|
405
|
+
.option('--editimage', 'Enable direct Image Editing Mode (true). Default is Regenerate Profile Mode (false)', false)
|
|
406
406
|
.option('--flowkey', 'Enable useFlowKey to sync context via FireBase', true)
|
|
407
407
|
.action(async (options) => {
|
|
408
408
|
try {
|
|
@@ -463,8 +463,8 @@ program
|
|
|
463
463
|
program
|
|
464
464
|
.command('scene-status')
|
|
465
465
|
.description('Get scene status snapshot by chapter (with embedded asset URLs)')
|
|
466
|
-
.requiredOption('
|
|
467
|
-
.requiredOption('
|
|
466
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
467
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
468
468
|
.action(async (options) => {
|
|
469
469
|
try {
|
|
470
470
|
const data = await api.get(`/app/scene-status/${options.project}/${options.chapter}`);
|
|
@@ -530,8 +530,8 @@ program
|
|
|
530
530
|
program
|
|
531
531
|
.command('workflow-status')
|
|
532
532
|
.description('Export workflow snapshot for a project/chapter with embedded asset URLs (agent helper)')
|
|
533
|
-
.requiredOption('
|
|
534
|
-
.requiredOption('
|
|
533
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
534
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
535
535
|
.action(async (options) => {
|
|
536
536
|
try {
|
|
537
537
|
const data = await api.get(`/app/workflow-status/${options.project}/${options.chapter}`);
|
|
@@ -627,8 +627,8 @@ program
|
|
|
627
627
|
program
|
|
628
628
|
.command('scene-materialization-status')
|
|
629
629
|
.description('Check how many scenes have been materialized for a chapter')
|
|
630
|
-
.requiredOption('
|
|
631
|
-
.requiredOption('
|
|
630
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
631
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
632
632
|
.action(async (options) => {
|
|
633
633
|
try {
|
|
634
634
|
// Get chapter data to determine expected scenes
|
|
@@ -662,9 +662,9 @@ program
|
|
|
662
662
|
program
|
|
663
663
|
.command('create-scene')
|
|
664
664
|
.description('Create a new scene from text content')
|
|
665
|
-
.requiredOption('
|
|
666
|
-
.requiredOption('
|
|
667
|
-
.requiredOption('
|
|
665
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
666
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
667
|
+
.requiredOption('--content <content...>', 'Array of scene text scripts')
|
|
668
668
|
.option('--flowkey', 'Enable useFlowKey to sync context via FireBase', false)
|
|
669
669
|
.action(async (options) => {
|
|
670
670
|
try {
|
|
@@ -684,11 +684,11 @@ program
|
|
|
684
684
|
program
|
|
685
685
|
.command('add-scene')
|
|
686
686
|
.description('Add a new scene to a chapter with a user prompt')
|
|
687
|
-
.requiredOption('
|
|
688
|
-
.requiredOption('
|
|
689
|
-
.requiredOption('
|
|
687
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
688
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
689
|
+
.requiredOption('--userprompt <userPrompt>', 'User narrative prompt for the new scene')
|
|
690
690
|
.option('--no-regenerate-image', 'Skip automatic image regeneration after creation')
|
|
691
|
-
.option('
|
|
691
|
+
.option('--after-scene-id <afterSceneId>', 'Scene ID to insert after (appends to end if omitted)')
|
|
692
692
|
.action(async (options) => {
|
|
693
693
|
try {
|
|
694
694
|
const payload = {
|
|
@@ -708,12 +708,12 @@ program
|
|
|
708
708
|
program
|
|
709
709
|
.command('edit-scene')
|
|
710
710
|
.description('Edit an existing scene (image prompt) via AI assistance')
|
|
711
|
-
.requiredOption('
|
|
712
|
-
.requiredOption('
|
|
713
|
-
.requiredOption('
|
|
714
|
-
.requiredOption('
|
|
711
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
712
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
713
|
+
.requiredOption('--scene <scene>', 'Scene ID')
|
|
714
|
+
.requiredOption('--userprompt <userprompt>', 'User narrative prompt to modify the scene')
|
|
715
715
|
.option('--no-regenerate', 'Tells backend to NOT automatically trigger regenerating the image')
|
|
716
|
-
.option('
|
|
716
|
+
.option('--request <request>', 'Target a specific past Request ID to edit its output')
|
|
717
717
|
.action(async (options) => {
|
|
718
718
|
try {
|
|
719
719
|
const payload = {
|
|
@@ -736,15 +736,15 @@ program
|
|
|
736
736
|
program
|
|
737
737
|
.command('request')
|
|
738
738
|
.description('Create a job request (GENERATE_IMAGES, GENERATE_VIDEO)')
|
|
739
|
-
.requiredOption('
|
|
740
|
-
.requiredOption('
|
|
741
|
-
.requiredOption('
|
|
742
|
-
.requiredOption('
|
|
743
|
-
.option('
|
|
744
|
-
.option('
|
|
745
|
-
.option('
|
|
746
|
-
.option('
|
|
747
|
-
.option('
|
|
739
|
+
.requiredOption('--type <type>', 'Request type (e.g., GENERATE_IMAGES, GENERATE_VIDEO)')
|
|
740
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
741
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
742
|
+
.requiredOption('--scene <scene>', 'Scene ID')
|
|
743
|
+
.option('--orientation <orientation>', 'Request orientation (HORIZONTAL, VERTICAL)')
|
|
744
|
+
.option('--imagemodel <imagemodel>', 'Image Model (imagen3.5)', 'imagen3.5')
|
|
745
|
+
.option('--videomodel <videomodel>', 'Video Model (veo_3_1_fast, veo_3_1_fast_r2v). Default: veo_3_1_fast', 'veo_3_1_fast')
|
|
746
|
+
.option('--speed <speed>', 'Video Speed (normal, timelapse, slowmotion)', 'normal')
|
|
747
|
+
.option('--endscene <endscene>', 'End Scene ID for continuous video generation')
|
|
748
748
|
.action(async (options) => {
|
|
749
749
|
try {
|
|
750
750
|
const payload = {
|
|
@@ -808,10 +808,10 @@ program
|
|
|
808
808
|
program
|
|
809
809
|
.command('requests')
|
|
810
810
|
.description('Get generation requests/jobs status for the current user')
|
|
811
|
-
.option('
|
|
812
|
-
.option('
|
|
813
|
-
.option('
|
|
814
|
-
.option('
|
|
811
|
+
.option('--limit <n>', 'Return only the N most recent requests', null)
|
|
812
|
+
.option('--project <projectId>', 'Filter by project ID')
|
|
813
|
+
.option('--chapter <chapterId>', 'Filter by chapter ID')
|
|
814
|
+
.option('--status <status>', 'Filter by status (e.g. COMPLETED, FAILED, PROCESSING)')
|
|
815
815
|
.action(async (options) => {
|
|
816
816
|
try {
|
|
817
817
|
const data = await api.get('/app/requests');
|
|
@@ -864,8 +864,8 @@ program
|
|
|
864
864
|
program
|
|
865
865
|
.command('failed-requests')
|
|
866
866
|
.description('List failed requests for a project/chapter (agent helper)')
|
|
867
|
-
.requiredOption('
|
|
868
|
-
.option('
|
|
867
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
868
|
+
.option('--chapter <chapter>', 'Chapter ID')
|
|
869
869
|
.action(async (options) => {
|
|
870
870
|
try {
|
|
871
871
|
const all = unwrapData(await api.get('/app/requests'));
|
|
@@ -885,10 +885,10 @@ program
|
|
|
885
885
|
program
|
|
886
886
|
.command('wait-images')
|
|
887
887
|
.description('Wait until all image requests in a chapter finish')
|
|
888
|
-
.requiredOption('
|
|
889
|
-
.requiredOption('
|
|
890
|
-
.option('
|
|
891
|
-
.option('
|
|
888
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
889
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
890
|
+
.option('--interval <sec>', 'Polling interval in seconds', '10')
|
|
891
|
+
.option('--timeout <sec>', 'Timeout in seconds', '1800')
|
|
892
892
|
.option('--require-success', 'Exit non-zero if any scene lacks a successful image asset')
|
|
893
893
|
.action(async (options) => {
|
|
894
894
|
const intervalMs = Math.max(1, Number(options.interval || 10)) * 1000;
|
|
@@ -942,10 +942,10 @@ program
|
|
|
942
942
|
program
|
|
943
943
|
.command('wait-videos')
|
|
944
944
|
.description('Wait until all video requests in a chapter finish')
|
|
945
|
-
.requiredOption('
|
|
946
|
-
.requiredOption('
|
|
947
|
-
.option('
|
|
948
|
-
.option('
|
|
945
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
946
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
947
|
+
.option('--interval <sec>', 'Polling interval in seconds', '10')
|
|
948
|
+
.option('--timeout <sec>', 'Timeout in seconds', '3600')
|
|
949
949
|
.option('--require-success', 'Exit non-zero if any scene lacks a successful video asset')
|
|
950
950
|
.action(async (options) => {
|
|
951
951
|
const intervalMs = Math.max(1, Number(options.interval || 10)) * 1000;
|
|
@@ -1023,11 +1023,11 @@ program
|
|
|
1023
1023
|
program
|
|
1024
1024
|
.command('upscale')
|
|
1025
1025
|
.description('Upscale video output for a specific scene (shortcut for scene upscale endpoint)')
|
|
1026
|
-
.requiredOption('
|
|
1027
|
-
.requiredOption('
|
|
1028
|
-
.requiredOption('
|
|
1029
|
-
.option('
|
|
1030
|
-
.option('
|
|
1026
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
1027
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
1028
|
+
.requiredOption('--scene <scene>', 'Scene ID')
|
|
1029
|
+
.option('--orientation <orientation>', 'Orientation (VERTICAL, HORIZONTAL)', 'VERTICAL')
|
|
1030
|
+
.option('--resolution <resolution>', 'Target resolution (e.g., VIDEO_RESOLUTION_4K)', 'VIDEO_RESOLUTION_4K')
|
|
1031
1031
|
.action(async (options) => {
|
|
1032
1032
|
try {
|
|
1033
1033
|
const payload = {
|
|
@@ -1042,13 +1042,49 @@ program
|
|
|
1042
1042
|
}
|
|
1043
1043
|
});
|
|
1044
1044
|
|
|
1045
|
+
program
|
|
1046
|
+
.command('download-upscale')
|
|
1047
|
+
.description('Download upscaled video for a specific scene')
|
|
1048
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
1049
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
1050
|
+
.requiredOption('--scene <scene>', 'Scene ID')
|
|
1051
|
+
.option('--orientation <orientation>', 'Orientation (vertical, horizontal)', 'vertical')
|
|
1052
|
+
.option('--out <out>', 'Output file path (default: upscaled_<sceneId>_<orientation>.mp4)')
|
|
1053
|
+
.action(async (options) => {
|
|
1054
|
+
try {
|
|
1055
|
+
const orientation = String(options.orientation || 'vertical').toLowerCase();
|
|
1056
|
+
const data = await api.get(`/app/scene/flow-media/${options.project}/${options.chapter}/${options.scene}/${orientation}`);
|
|
1057
|
+
const flowMedia = unwrapData(data);
|
|
1058
|
+
|
|
1059
|
+
if (!flowMedia?.video?.encodedVideo) {
|
|
1060
|
+
console.log(JSON.stringify({ status: "error", message: "No upscaled video found. Run 'veogent upscale' first." }));
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const fs = await import('fs');
|
|
1065
|
+
const path = await import('path');
|
|
1066
|
+
const outFile = options.out || `upscaled_${options.scene}_${orientation}.mp4`;
|
|
1067
|
+
const videoBuffer = Buffer.from(flowMedia.video.encodedVideo, 'base64');
|
|
1068
|
+
fs.writeFileSync(path.resolve(outFile), videoBuffer);
|
|
1069
|
+
|
|
1070
|
+
console.log(JSON.stringify({
|
|
1071
|
+
status: "success",
|
|
1072
|
+
file: path.resolve(outFile),
|
|
1073
|
+
size: `${(videoBuffer.length / 1024 / 1024).toFixed(1)} MB`,
|
|
1074
|
+
orientation,
|
|
1075
|
+
}, null, 2));
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1045
1081
|
// --- YouTube Metadata & Thumbnails ---
|
|
1046
1082
|
program
|
|
1047
1083
|
.command('generate-yt-metadata')
|
|
1048
1084
|
.description('Generate YouTube metadata (Title, Description, Tags) for a chapter')
|
|
1049
|
-
.requiredOption('
|
|
1050
|
-
.requiredOption('
|
|
1051
|
-
.requiredOption('
|
|
1085
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
1086
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
1087
|
+
.requiredOption('--lang <lang>', 'Story language')
|
|
1052
1088
|
.action(async (options) => {
|
|
1053
1089
|
try {
|
|
1054
1090
|
const payload = {
|
|
@@ -1066,9 +1102,9 @@ program
|
|
|
1066
1102
|
program
|
|
1067
1103
|
.command('generate-yt-thumbnail')
|
|
1068
1104
|
.description('Triggers a request to generate a YouTube thumbnail for a chapter')
|
|
1069
|
-
.requiredOption('
|
|
1070
|
-
.requiredOption('
|
|
1071
|
-
.requiredOption('
|
|
1105
|
+
.requiredOption('--project <project>', 'Project ID')
|
|
1106
|
+
.requiredOption('--chapter <chapter>', 'Chapter ID')
|
|
1107
|
+
.requiredOption('--lang <lang>', 'Story language')
|
|
1072
1108
|
.action(async (options) => {
|
|
1073
1109
|
try {
|
|
1074
1110
|
const payload = {
|
|
@@ -1099,12 +1135,12 @@ program
|
|
|
1099
1135
|
program
|
|
1100
1136
|
.command('gen-image')
|
|
1101
1137
|
.description('Generate an image from a text prompt (standalone, no project needed). Costs 3 credits. Uses Imagen 3.5.')
|
|
1102
|
-
.requiredOption('
|
|
1103
|
-
.option('
|
|
1104
|
-
.option('
|
|
1105
|
-
.option('
|
|
1106
|
-
.option('
|
|
1107
|
-
.option('
|
|
1138
|
+
.requiredOption('--prompt <prompt>', 'Text prompt to generate image from (10-2000 chars)')
|
|
1139
|
+
.option('--negative <negative>', 'Negative prompt — things to avoid')
|
|
1140
|
+
.option('--orientation <orientation>', 'Orientation: HORIZONTAL or VERTICAL', 'HORIZONTAL')
|
|
1141
|
+
.option('--wait', 'Wait for completion (poll until done)', false)
|
|
1142
|
+
.option('--interval <sec>', 'Polling interval in seconds (with --wait)', '5')
|
|
1143
|
+
.option('--timeout <sec>', 'Timeout in seconds (with --wait)', '300')
|
|
1108
1144
|
.action(async (options) => {
|
|
1109
1145
|
try {
|
|
1110
1146
|
const payload = {
|
|
@@ -1167,13 +1203,13 @@ program
|
|
|
1167
1203
|
program
|
|
1168
1204
|
.command('gen-video')
|
|
1169
1205
|
.description('Generate a video from a text prompt (standalone, no project needed). Costs 5 credits. Uses Veo 3.1 Fast.')
|
|
1170
|
-
.requiredOption('
|
|
1171
|
-
.option('
|
|
1172
|
-
.option('
|
|
1173
|
-
.option('
|
|
1174
|
-
.option('
|
|
1175
|
-
.option('
|
|
1176
|
-
.option('
|
|
1206
|
+
.requiredOption('--prompt <prompt>', 'Text prompt to generate video from (10-2000 chars)')
|
|
1207
|
+
.option('--negative <negative>', 'Negative prompt — things to avoid')
|
|
1208
|
+
.option('--orientation <orientation>', 'Orientation: HORIZONTAL or VERTICAL', 'HORIZONTAL')
|
|
1209
|
+
.option('--reference <imageUri>', 'Reference image URI for image-to-video generation')
|
|
1210
|
+
.option('--wait', 'Wait for completion (poll until done)', false)
|
|
1211
|
+
.option('--interval <sec>', 'Polling interval in seconds (with --wait)', '10')
|
|
1212
|
+
.option('--timeout <sec>', 'Timeout in seconds (with --wait)', '600')
|
|
1177
1213
|
.action(async (options) => {
|
|
1178
1214
|
try {
|
|
1179
1215
|
const payload = {
|
|
@@ -1237,8 +1273,8 @@ program
|
|
|
1237
1273
|
program
|
|
1238
1274
|
.command('standalone-requests')
|
|
1239
1275
|
.description('List your standalone image/video generation requests')
|
|
1240
|
-
.option('
|
|
1241
|
-
.option('
|
|
1276
|
+
.option('--limit <n>', 'Return only the N most recent', null)
|
|
1277
|
+
.option('--status <status>', 'Filter by status (PENDING, PROCESSING, COMPLETED, FAILED)')
|
|
1242
1278
|
.action(async (options) => {
|
|
1243
1279
|
try {
|
|
1244
1280
|
const data = await api.get('/app/standalone/requests');
|
|
@@ -1333,7 +1369,7 @@ program
|
|
|
1333
1369
|
program
|
|
1334
1370
|
.command('flow-credits')
|
|
1335
1371
|
.description('Fetch plan and credit info from Google AI Sandbox using your Flow key (Bearer token)')
|
|
1336
|
-
.option('
|
|
1372
|
+
.option('--flowkey <flowkey>', 'Flow key (ya29. token). If omitted, uses stored flow key from account.')
|
|
1337
1373
|
.action(async (options) => {
|
|
1338
1374
|
try {
|
|
1339
1375
|
// Determine which flow key to use
|