veogent 1.0.18 → 1.0.20

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/api.js +17 -11
  2. package/device-auth.js +18 -5
  3. package/index.js +363 -75
  4. package/package.json +1 -1
package/api.js CHANGED
@@ -20,18 +20,24 @@ api.interceptors.request.use((config) => {
20
20
  return Promise.reject(error);
21
21
  });
22
22
 
23
- // Interceptor to handle specific errors generically
23
+ // Interceptor to normalize successful payloads and throw structured errors
24
24
  api.interceptors.response.use(
25
- (response) => {
26
- // Return only the response data payload
27
- return response.data;
28
- },
25
+ (response) => response.data,
29
26
  (error) => {
30
- if (error.response && error.response.status === 401) {
31
- console.error('\n❌ Unauthorized! Your token might be expired. Please run `veogent login` again.');
32
- } else {
33
- console.error(`\n❌ Error: ${error.response?.data?.message || error.message}`);
34
- }
35
- process.exit(1);
27
+ const status = error.response?.status || null;
28
+ const payload = error.response?.data || null;
29
+ const message = payload?.error?.message?.en
30
+ || payload?.error?.message
31
+ || payload?.message
32
+ || error.message
33
+ || 'Unknown API error';
34
+
35
+ const wrapped = new Error(message);
36
+ wrapped.name = 'VeogentApiError';
37
+ wrapped.status = status;
38
+ wrapped.payload = payload;
39
+ wrapped.code = payload?.errorCode || payload?.error?.code || null;
40
+ wrapped.retryable = status >= 500;
41
+ throw wrapped;
36
42
  }
37
43
  );
package/device-auth.js CHANGED
@@ -3,7 +3,8 @@ import axios from 'axios';
3
3
  const API_URL = process.env.VGEN_API_URL || 'https://api.veogent.com';
4
4
 
5
5
  export class DeviceAuthFlow {
6
- constructor() {
6
+ constructor(options = {}) {
7
+ this.options = options;
7
8
  this.http = axios.create({
8
9
  baseURL: API_URL,
9
10
  timeout: 15000,
@@ -28,10 +29,22 @@ export class DeviceAuthFlow {
28
29
  interval,
29
30
  } = payload;
30
31
 
31
- console.log('\n--- 🔐 VEOGENT CLI Device Login ---');
32
- console.log(`1) Open this URL on any device/browser:\n ${verificationUriComplete || verificationUri}`);
33
- console.log(`2) Confirm the code: ${userCode}`);
34
- console.log('3) Approve access, then return to this terminal.\n');
32
+ const deviceInfo = {
33
+ userCode,
34
+ verificationUri,
35
+ verificationUriComplete: verificationUriComplete || verificationUri,
36
+ expiresIn: Number(expiresIn) || 600,
37
+ interval: Number(interval) || 5,
38
+ };
39
+
40
+ if (typeof this.options.onDeviceCode === 'function') {
41
+ this.options.onDeviceCode(deviceInfo);
42
+ } else {
43
+ console.log('\n--- 🔐 VEOGENT CLI Device Login ---');
44
+ console.log(`1) Open this URL on any device/browser:\n ${verificationUriComplete || verificationUri}`);
45
+ console.log(`2) Confirm the code: ${userCode}`);
46
+ console.log('3) Approve access, then return to this terminal.\n');
47
+ }
35
48
 
36
49
  return await this.pollForToken({
37
50
  deviceCode,
package/index.js CHANGED
@@ -1,14 +1,53 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import pkg from './package.json' with { type: 'json' };
3
4
  import { api } from './api.js';
4
5
  import { setConfig, clearConfig, getToken } from './config.js';
5
6
 
6
7
  const program = new Command();
7
8
 
9
+ const IMAGE_MODELS = ['imagen3.5', 'imagen4'];
10
+ const VIDEO_MODELS = ['veo_3_1_fast', 'veo_3_1_fast_r2v'];
11
+
12
+ function globalOpts() {
13
+ return program.opts();
14
+ }
15
+
16
+ function emitJson(payload) {
17
+ console.log(JSON.stringify(payload, null, 2));
18
+ }
19
+
20
+ function humanLog(message) {
21
+ if (!globalOpts().json && !globalOpts().agentSafe) console.log(message);
22
+ }
23
+
24
+ function humanError(message) {
25
+ if (!globalOpts().json) console.error(message);
26
+ }
27
+
28
+ function unwrapData(payload) {
29
+ if (payload?.data?.data !== undefined) return payload.data.data;
30
+ if (payload?.data !== undefined) return payload.data;
31
+ return payload;
32
+ }
33
+
34
+ function formatCliError(error) {
35
+ const payload = error?.payload || error?.response?.data || null;
36
+ return {
37
+ code: error?.code || payload?.errorCode || payload?.error?.code || 'CLI_ERROR',
38
+ message: error?.message || payload?.message || 'Unknown error',
39
+ status: error?.status || error?.response?.status || null,
40
+ retryable: typeof error?.retryable === 'boolean' ? error.retryable : false,
41
+ details: payload,
42
+ };
43
+ }
44
+
8
45
  program
9
46
  .name('veogent')
10
47
  .description('CLI to interact with the VEOGENT API')
11
- .version('1.0.0');
48
+ .option('--json', 'Output machine-readable JSON only')
49
+ .option('--agent-safe', 'Agent-safe mode (no emoji/browser surprises, stable output)')
50
+ .version(pkg.version);
12
51
 
13
52
  import { WebAuthFlow } from './auth.js';
14
53
  import { DeviceAuthFlow } from './device-auth.js';
@@ -23,32 +62,44 @@ program
23
62
  try {
24
63
  let response;
25
64
 
26
- if (options.device) {
27
- const deviceAuthFlow = new DeviceAuthFlow();
65
+ const shouldUseDevice = options.device || globalOpts().agentSafe;
66
+
67
+ if (shouldUseDevice) {
68
+ const deviceAuthFlow = new DeviceAuthFlow(globalOpts().json ? {
69
+ onDeviceCode: (info) => emitJson({ status: 'pending_authorization', data: info }),
70
+ } : undefined);
28
71
  const tokenPayload = await deviceAuthFlow.start();
29
72
  response = { data: { access_token: tokenPayload.access_token, email: 'Device Auth User', name: 'CLI User' } };
30
73
  } else {
31
- console.log('\n--- 🌐 VEOGENT CLI Web Authentication ---');
32
- console.log('We will now open your default web browser to authorize VEOGENT CLI.\n');
74
+ humanLog('\n--- 🌐 VEOGENT CLI Web Authentication ---');
75
+ humanLog('We will now open your default web browser to authorize VEOGENT CLI.\n');
33
76
 
34
77
  // Start local web server to catch the callback from the Next.js app
35
78
  const authFlow = new WebAuthFlow(7890); // default port
36
79
  const authResult = await authFlow.start();
37
80
 
38
81
  // Perform backend sign-in using the payload received from the browser
39
- console.log('\n🔄 Credentials received! Saving API Access Token...');
82
+ humanLog('\n🔄 Credentials received! Saving API Access Token...');
40
83
  response = { data: { access_token: authResult.accessToken, email: authResult.uid, name: 'CLI User' } }; // JWT mapped
41
84
  }
42
85
 
43
86
  if (response?.data?.access_token) {
44
87
  setConfig({ token: response.data.access_token, user: response.data });
45
- console.log(`✅ Successfully logged in as: ${response.data.email || response.data.name}`);
46
- console.log(`You can now use all VEOGENT CLI commands!`);
88
+ if (globalOpts().json) {
89
+ emitJson({ status: 'success', user: response.data.email || response.data.name });
90
+ } else {
91
+ humanLog(`✅ Successfully logged in as: ${response.data.email || response.data.name}`);
92
+ humanLog('You can now use all VEOGENT CLI commands!');
93
+ }
47
94
  } else {
48
- console.error('❌ Failed to retrieve access token from VEOGENT Backend.');
95
+ humanError('❌ Failed to retrieve access token from VEOGENT Backend.');
49
96
  }
50
97
  } catch (error) {
51
- console.error('\n❌ Login process failed or was canceled.', error.message);
98
+ if (globalOpts().json) {
99
+ emitJson({ status: 'error', ...formatCliError(error) });
100
+ } else {
101
+ humanError(`\n❌ Login process failed or was canceled. ${error.message}`);
102
+ }
52
103
  }
53
104
  });
54
105
 
@@ -68,15 +119,16 @@ program
68
119
  .action(async () => {
69
120
  const token = getToken();
70
121
  if (!token) {
71
- console.log(JSON.stringify({ error: 'Not logged in' }));
122
+ emitJson({ status: 'error', code: 'NOT_LOGGED_IN', message: 'Not logged in' });
72
123
  return;
73
124
  }
74
125
 
75
126
  try {
76
127
  const response = await api.get('/app/flow-key');
77
- console.log(JSON.stringify({ authenticated: true, flowKey: response.data?.flowKey || null, paymentTier: response.data?.userPaymentTier || null }, null, 2));
128
+ const data = unwrapData(response);
129
+ console.log(JSON.stringify({ authenticated: true, flowKey: data?.flowKey || null, paymentTier: data?.userPaymentTier || null }, null, 2));
78
130
  } catch (error) {
79
- console.log(JSON.stringify({ error: 'Error verifying token' }));
131
+ console.log(JSON.stringify({ status: 'error', ...formatCliError(error) }));
80
132
  }
81
133
  });
82
134
 
@@ -92,22 +144,36 @@ program
92
144
  userPaymentTier: options.tier
93
145
  };
94
146
  const data = await api.patch('/app/flow-key', payload);
95
- console.log(JSON.stringify({ status: "success", info: data.data || data }, null, 2));
147
+ console.log(JSON.stringify({ status: "success", info: unwrapData(data) }, null, 2));
96
148
  } catch (error) {
97
- console.log(JSON.stringify({ status: "error", message: error.response?.data?.message || error.message }));
149
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
98
150
  }
99
151
  });
100
152
 
101
153
  // --- Prompts & Materials ---
154
+ program
155
+ .command('image-models')
156
+ .description('List supported image models')
157
+ .action(() => {
158
+ console.log(JSON.stringify({ data: IMAGE_MODELS }, null, 2));
159
+ });
160
+
161
+ program
162
+ .command('video-models')
163
+ .description('List supported video models')
164
+ .action(() => {
165
+ console.log(JSON.stringify({ data: VIDEO_MODELS }, null, 2));
166
+ });
167
+
102
168
  program
103
169
  .command('image-materials')
104
170
  .description('Get all available Image Material styles')
105
171
  .action(async () => {
106
172
  try {
107
173
  const data = await api.get('/app/project/image-materials');
108
- console.log(JSON.stringify(data.data || data, null, 2));
174
+ console.log(JSON.stringify(unwrapData(data), null, 2));
109
175
  } catch (error) {
110
- console.log(JSON.stringify({ status: "error", message: error.message }));
176
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
111
177
  }
112
178
  });
113
179
 
@@ -117,9 +183,9 @@ program
117
183
  .action(async () => {
118
184
  try {
119
185
  const data = await api.get('/app/custom-prompts');
120
- console.log(JSON.stringify(data.data || data, null, 2));
186
+ console.log(JSON.stringify(unwrapData(data), null, 2));
121
187
  } catch (error) {
122
- console.log(JSON.stringify({ status: "error", message: error.message }));
188
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
123
189
  }
124
190
  });
125
191
  program
@@ -128,8 +194,10 @@ program
128
194
  .action(async () => {
129
195
  try {
130
196
  const data = await api.get('/app/projects');
131
- console.log(JSON.stringify(data, null, 2));
132
- } catch (error) {}
197
+ console.log(JSON.stringify(unwrapData(data), null, 2));
198
+ } catch (error) {
199
+ console.log(JSON.stringify({ status: 'error', ...formatCliError(error) }));
200
+ }
133
201
  });
134
202
 
135
203
  program
@@ -138,8 +206,10 @@ program
138
206
  .action(async (id) => {
139
207
  try {
140
208
  const data = await api.get(`/app/project/${id}`);
141
- console.log(JSON.stringify(data.data, null, 2));
142
- } catch (error) {}
209
+ console.log(JSON.stringify(unwrapData(data), null, 2));
210
+ } catch (error) {
211
+ console.log(JSON.stringify({ status: 'error', ...formatCliError(error) }));
212
+ }
143
213
  });
144
214
 
145
215
  program
@@ -147,19 +217,19 @@ program
147
217
  .description('Generate AI description for a new project based on keywords')
148
218
  .requiredOption('-k, --keyword <keyword>', 'Keywords for the project')
149
219
  .requiredOption('-l, --lang <lang>', 'Story language')
150
- .requiredOption('-p, --promptId <promptId>', 'Custom Prompt ID from custom-prompts')
220
+ .option('-p, --promptId <promptId>', 'Custom Prompt ID from custom-prompts (optional)')
151
221
  .action(async (options) => {
152
222
  try {
153
223
  const payload = {
154
224
  keywords: options.keyword,
155
225
  language: options.lang,
156
- customPromptId: options.promptId,
157
226
  objects: []
158
227
  };
228
+ if (options.promptId) payload.customPromptId = options.promptId;
159
229
  const data = await api.post('/app/description', payload);
160
- console.log(JSON.stringify({ status: "success", descriptionData: data.data || data }, null, 2));
230
+ console.log(JSON.stringify({ status: "success", descriptionData: unwrapData(data) }, null, 2));
161
231
  } catch (error) {
162
- console.log(JSON.stringify({ status: "error", message: error.response?.data?.message || error.message }));
232
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
163
233
  }
164
234
  });
165
235
 
@@ -170,9 +240,9 @@ program
170
240
  .requiredOption('-k, --keyword <keyword>', 'Keyword')
171
241
  .requiredOption('-d, --desc <desc>', 'Description')
172
242
  .requiredOption('-l, --lang <lang>', 'Story language')
173
- .requiredOption('-s, --sound', 'Sound effects (true/false)', true)
243
+ .option('-s, --sound <sound>', 'Sound effects (true/false)', 'true')
174
244
  .requiredOption('-m, --material <material>', 'Image material')
175
- .requiredOption('-c, --chapters <count>', 'Number of chapters', 1)
245
+ .option('-c, --chapters <count>', 'Number of chapters', '1')
176
246
  .option('-C, --customPromptId <customPromptId>', 'Custom Prompt ID')
177
247
  .action(async (options) => {
178
248
  try {
@@ -188,9 +258,9 @@ program
188
258
  if (options.customPromptId) payload.customPromptId = options.customPromptId;
189
259
 
190
260
  const data = await api.post('/app/project', payload);
191
- console.log(JSON.stringify({ status: "success", project: data.data || data }, null, 2));
261
+ console.log(JSON.stringify({ status: "success", project: unwrapData(data) }, null, 2));
192
262
  } catch (error) {
193
- console.log(JSON.stringify({ status: "error", message: error.message }));
263
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
194
264
  }
195
265
  });
196
266
 
@@ -201,8 +271,10 @@ program
201
271
  .action(async (projectId) => {
202
272
  try {
203
273
  const data = await api.get(`/app/chapters/${projectId}`);
204
- console.log(JSON.stringify(data, null, 2));
205
- } catch (error) {}
274
+ console.log(JSON.stringify(unwrapData(data), null, 2));
275
+ } catch (error) {
276
+ console.log(JSON.stringify({ status: 'error', ...formatCliError(error) }));
277
+ }
206
278
  });
207
279
 
208
280
  program
@@ -212,9 +284,9 @@ program
212
284
  .action(async (options) => {
213
285
  try {
214
286
  const data = await api.get(`/app/chapter/recent-chapters?limit=${options.limit}`);
215
- console.log(JSON.stringify(data.data || data, null, 2));
287
+ console.log(JSON.stringify(unwrapData(data), null, 2));
216
288
  } catch (error) {
217
- console.log(JSON.stringify({ status: "error", message: error.message }));
289
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
218
290
  }
219
291
  });
220
292
 
@@ -223,7 +295,7 @@ program
223
295
  .description('Generate content for a specific chapter')
224
296
  .requiredOption('-p, --project <project>', 'Project ID')
225
297
  .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
226
- .requiredOption('-s, --scenes <count>', 'Number of scenes', 1)
298
+ .option('-s, --scenes <count>', 'Number of scenes', '1')
227
299
  .action(async (options) => {
228
300
  try {
229
301
  const payload = {
@@ -232,9 +304,9 @@ program
232
304
  numberScene: parseInt(options.scenes),
233
305
  };
234
306
  const data = await api.post('/app/chapter/content', payload);
235
- console.log(JSON.stringify({ status: "success", chapterContent: data.data || data }, null, 2));
307
+ console.log(JSON.stringify({ status: "success", chapterContent: unwrapData(data) }, null, 2));
236
308
  } catch (error) {
237
- console.log(JSON.stringify({ status: "error", message: error.message }));
309
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
238
310
  }
239
311
  });
240
312
 
@@ -246,9 +318,9 @@ program
246
318
  .action(async (projectId) => {
247
319
  try {
248
320
  const data = await api.get(`/app/characters/${projectId}`);
249
- console.log(JSON.stringify(data.data || data, null, 2));
321
+ console.log(JSON.stringify(unwrapData(data), null, 2));
250
322
  } catch (error) {
251
- console.log(JSON.stringify({ status: "error", message: error.message }));
323
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
252
324
  }
253
325
  });
254
326
 
@@ -268,20 +340,97 @@ program
268
340
  editImageMode: options.editimage === true,
269
341
  };
270
342
  const data = await api.post(`/app/character/${options.project}/${options.character}/update-by-prompt`, payload);
271
- console.log(JSON.stringify({ status: "success", character: data.data || data }, null, 2));
343
+ console.log(JSON.stringify({ status: "success", character: unwrapData(data) }, null, 2));
272
344
  } catch (error) {
273
- console.log(JSON.stringify({ status: "error", message: error.response?.data?.message || error.message }));
345
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
274
346
  }
275
347
  });
276
348
  program
277
349
  .command('scenes <projectId> <chapterId>')
278
- .description('Get all scenes for a specific chapter')
350
+ .description('Get all scenes for a specific chapter (stable/agent-friendly shape)')
279
351
  .action(async (projectId, chapterId) => {
280
352
  try {
281
- const data = await api.get(`/app/scenes/${projectId}/${chapterId}`);
282
- console.log(JSON.stringify(data.data || data, null, 2));
353
+ const raw = await api.get(`/app/scenes/${projectId}/${chapterId}`);
354
+ const unwrapped = unwrapData(raw);
355
+ const scenes = Array.isArray(unwrapped)
356
+ ? unwrapped
357
+ : Array.isArray(unwrapped?.scenes)
358
+ ? unwrapped.scenes
359
+ : Array.isArray(unwrapped?.items)
360
+ ? unwrapped.items
361
+ : [];
362
+
363
+ const normalizedScenes = scenes.map((scene, idx) => ({
364
+ id: scene?.id || scene?.sceneId || null,
365
+ displayOrder: scene?.displayOrder ?? idx,
366
+ scriptSegment: scene?.scriptSegment || scene?.content || null,
367
+ image: {
368
+ status: scene?.imageStatus || scene?.image?.status || null,
369
+ url: scene?.imageUrl || scene?.image?.url || null,
370
+ },
371
+ video: {
372
+ status: scene?.videoStatus || scene?.video?.status || null,
373
+ url: scene?.videoUrl || scene?.video?.url || null,
374
+ },
375
+ raw: scene,
376
+ }));
377
+
378
+ console.log(JSON.stringify({
379
+ data: normalizedScenes,
380
+ meta: {
381
+ total: normalizedScenes.length,
382
+ projectId,
383
+ chapterId,
384
+ },
385
+ }, null, 2));
386
+ } catch (error) {
387
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
388
+ }
389
+ });
390
+
391
+ program
392
+ .command('scene-status')
393
+ .description('Get scene status snapshot by chapter')
394
+ .requiredOption('-p, --project <project>', 'Project ID')
395
+ .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
396
+ .action(async (options) => {
397
+ try {
398
+ const data = await api.get(`/app/scene-status/${options.project}/${options.chapter}`);
399
+ emitJson({ status: 'success', data: unwrapData(data) });
283
400
  } catch (error) {
284
- console.log(JSON.stringify({ status: "error", message: error.message }));
401
+ emitJson({ status: 'error', ...formatCliError(error) });
402
+ }
403
+ });
404
+
405
+ program
406
+ .command('workflow-status')
407
+ .description('Export workflow snapshot for a project/chapter (agent helper)')
408
+ .requiredOption('-p, --project <project>', 'Project ID')
409
+ .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
410
+ .action(async (options) => {
411
+ try {
412
+ const data = await api.get(`/app/workflow-status/${options.project}/${options.chapter}`);
413
+ emitJson({ status: 'success', data: unwrapData(data) });
414
+ } catch (error) {
415
+ // Backward-compatible fallback for old backend
416
+ try {
417
+ const [scenesRaw, requestsRaw] = await Promise.all([
418
+ api.get(`/app/scenes/${options.project}/${options.chapter}`),
419
+ api.get('/app/requests'),
420
+ ]);
421
+
422
+ const scenes = Array.isArray(unwrapData(scenesRaw)) ? unwrapData(scenesRaw) : [];
423
+ const requestsData = unwrapData(requestsRaw);
424
+ const requests = Array.isArray(requestsData) ? requestsData : (requestsData?.items || []);
425
+ const chapterRequests = requests.filter((r) =>
426
+ (r?.project_id === options.project || r?.projectId === options.project) &&
427
+ (r?.chapter_id === options.chapter || r?.chapterId === options.chapter)
428
+ );
429
+
430
+ emitJson({ status: 'success', data: { projectId: options.project, chapterId: options.chapter, scenes, requests: chapterRequests } });
431
+ } catch (fallbackError) {
432
+ emitJson({ status: 'error', ...formatCliError(fallbackError) });
433
+ }
285
434
  }
286
435
  });
287
436
 
@@ -301,9 +450,9 @@ program
301
450
  useFlowKey: options.flowkey === true,
302
451
  };
303
452
  const data = await api.post('/app/scene', payload);
304
- console.log(JSON.stringify({ status: "success", scene: data.data || data }, null, 2));
453
+ console.log(JSON.stringify({ status: "success", scene: unwrapData(data) }, null, 2));
305
454
  } catch (error) {
306
- console.log(JSON.stringify({ status: "error", message: error.message }));
455
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
307
456
  }
308
457
  });
309
458
 
@@ -328,9 +477,9 @@ program
328
477
  }
329
478
 
330
479
  const data = await api.patch(`/app/scene/script-segment/${options.project}/${options.chapter}/${options.scene}`, payload);
331
- console.log(JSON.stringify({ status: "success", scene: data.data || data }, null, 2));
480
+ console.log(JSON.stringify({ status: "success", scene: unwrapData(data) }, null, 2));
332
481
  } catch (error) {
333
- console.log(JSON.stringify({ status: "error", message: error.response?.data?.message || error.message }));
482
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
334
483
  }
335
484
  });
336
485
 
@@ -358,10 +507,24 @@ program
358
507
 
359
508
  // Conditionally add fields based on strict DTO validators
360
509
  if (options.type === 'GENERATE_IMAGES') {
510
+ if (!IMAGE_MODELS.includes(options.imagemodel)) {
511
+ throw Object.assign(new Error('Model is not supported'), {
512
+ code: 'E_IMAGE_MODEL_UNSUPPORTED',
513
+ retryable: false,
514
+ payload: { allowed: IMAGE_MODELS, field: 'imageModel' },
515
+ });
516
+ }
361
517
  payload.imageModel = options.imagemodel;
362
- payload.orientation = options.orientation || 'HORIZONTAL';
518
+ payload.orientation = options.orientation || 'HORIZONTAL';
363
519
  }
364
520
  if (options.type === 'GENERATE_VIDEO') {
521
+ if (!VIDEO_MODELS.includes(options.videomodel)) {
522
+ throw Object.assign(new Error('Model is not supported'), {
523
+ code: 'E_VIDEO_MODEL_UNSUPPORTED',
524
+ retryable: false,
525
+ payload: { allowed: VIDEO_MODELS, field: 'model' },
526
+ });
527
+ }
365
528
  payload.model = options.videomodel;
366
529
  payload.videoSpeedMode = options.speed;
367
530
  if (options.endscene) {
@@ -377,9 +540,9 @@ program
377
540
  }
378
541
 
379
542
  const data = await api.post('/app/request', payload);
380
- console.log(JSON.stringify({ status: "success", request: data.data || data }, null, 2));
543
+ console.log(JSON.stringify({ status: "success", request: unwrapData(data) }, null, 2));
381
544
  } catch (error) {
382
- console.log(JSON.stringify({ status: "error", message: error.response?.data?.message || error.message }));
545
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
383
546
  }
384
547
  });
385
548
 
@@ -389,9 +552,118 @@ program
389
552
  .action(async () => {
390
553
  try {
391
554
  const data = await api.get('/app/requests');
392
- console.log(JSON.stringify(data.data || data, null, 2));
555
+ console.log(JSON.stringify(unwrapData(data), null, 2));
556
+ } catch (error) {
557
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
558
+ }
559
+ });
560
+
561
+ program
562
+ .command('queue-status')
563
+ .description('Get current queue/concurrency status for authenticated user')
564
+ .action(async () => {
565
+ try {
566
+ const data = await api.get('/app/queue-status');
567
+ emitJson({ status: 'success', data: unwrapData(data) });
568
+ } catch (error) {
569
+ emitJson({ status: 'error', ...formatCliError(error) });
570
+ }
571
+ });
572
+
573
+ program
574
+ .command('failed-requests')
575
+ .description('List failed requests for a project/chapter (agent helper)')
576
+ .requiredOption('-p, --project <project>', 'Project ID')
577
+ .option('-c, --chapter <chapter>', 'Chapter ID')
578
+ .action(async (options) => {
579
+ try {
580
+ const all = unwrapData(await api.get('/app/requests'));
581
+ const items = Array.isArray(all) ? all : (all?.items || []);
582
+ const failed = items.filter((r) => {
583
+ const status = String(r?.status || '').toUpperCase();
584
+ const projectOk = (r?.project === options.project) || (r?.projectId === options.project);
585
+ const chapterOk = !options.chapter || (r?.chapter === options.chapter) || (r?.chapterId === options.chapter);
586
+ return projectOk && chapterOk && ['FAILED', 'ERROR', 'REJECTED'].includes(status);
587
+ });
588
+ console.log(JSON.stringify({ data: failed, total: failed.length }, null, 2));
589
+ } catch (error) {
590
+ console.log(JSON.stringify({ status: 'error', ...formatCliError(error) }));
591
+ }
592
+ });
593
+
594
+ program
595
+ .command('wait-images')
596
+ .description('Wait until all image requests in a chapter finish')
597
+ .requiredOption('-p, --project <project>', 'Project ID')
598
+ .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
599
+ .option('-i, --interval <sec>', 'Polling interval in seconds', '10')
600
+ .option('-t, --timeout <sec>', 'Timeout in seconds', '1800')
601
+ .action(async (options) => {
602
+ const intervalMs = Math.max(1, Number(options.interval || 10)) * 1000;
603
+ const timeoutMs = Math.max(30, Number(options.timeout || 1800)) * 1000;
604
+ const startedAt = Date.now();
605
+
606
+ try {
607
+ while (Date.now() - startedAt < timeoutMs) {
608
+ const all = unwrapData(await api.get('/app/requests'));
609
+ const items = Array.isArray(all) ? all : (all?.items || []);
610
+ const filtered = items.filter((r) => {
611
+ const type = String(r?.type || '').toUpperCase();
612
+ const projectOk = (r?.project === options.project) || (r?.projectId === options.project);
613
+ const chapterOk = (r?.chapter === options.chapter) || (r?.chapterId === options.chapter);
614
+ return projectOk && chapterOk && type.includes('IMAGE');
615
+ });
616
+
617
+ const pending = filtered.filter((r) => ['PENDING', 'PROCESSING', 'RUNNING'].includes(String(r?.status || '').toUpperCase()));
618
+ if (pending.length === 0) {
619
+ console.log(JSON.stringify({ status: 'success', data: filtered, message: 'All image requests finished' }, null, 2));
620
+ return;
621
+ }
622
+
623
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
624
+ }
625
+
626
+ console.log(JSON.stringify({ status: 'error', code: 'WAIT_TIMEOUT', message: 'Timeout waiting image requests' }, null, 2));
393
627
  } catch (error) {
394
- console.log(JSON.stringify({ status: "error", message: error.message }));
628
+ console.log(JSON.stringify({ status: 'error', ...formatCliError(error) }));
629
+ }
630
+ });
631
+
632
+ program
633
+ .command('wait-videos')
634
+ .description('Wait until all video requests in a chapter finish')
635
+ .requiredOption('-p, --project <project>', 'Project ID')
636
+ .requiredOption('-c, --chapter <chapter>', 'Chapter ID')
637
+ .option('-i, --interval <sec>', 'Polling interval in seconds', '10')
638
+ .option('-t, --timeout <sec>', 'Timeout in seconds', '3600')
639
+ .action(async (options) => {
640
+ const intervalMs = Math.max(1, Number(options.interval || 10)) * 1000;
641
+ const timeoutMs = Math.max(30, Number(options.timeout || 3600)) * 1000;
642
+ const startedAt = Date.now();
643
+
644
+ try {
645
+ while (Date.now() - startedAt < timeoutMs) {
646
+ const all = unwrapData(await api.get('/app/requests'));
647
+ const items = Array.isArray(all) ? all : (all?.items || []);
648
+ const filtered = items.filter((r) => {
649
+ const type = String(r?.type || '').toUpperCase();
650
+ const projectOk = (r?.project === options.project) || (r?.projectId === options.project);
651
+ const chapterOk = (r?.chapter === options.chapter) || (r?.chapterId === options.chapter);
652
+ return projectOk && chapterOk && type.includes('VIDEO');
653
+ });
654
+
655
+ const pending = filtered.filter((r) => ['PENDING', 'PROCESSING', 'RUNNING'].includes(String(r?.status || '').toUpperCase()));
656
+ if (pending.length === 0) {
657
+ console.log(JSON.stringify({ status: 'success', data: filtered, message: 'All video requests finished' }, null, 2));
658
+ return;
659
+ }
660
+
661
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
662
+ }
663
+
664
+ console.log(JSON.stringify({ status: 'error', code: 'WAIT_TIMEOUT', message: 'Timeout waiting video requests' }, null, 2));
665
+ } catch (error) {
666
+ console.log(JSON.stringify({ status: 'error', ...formatCliError(error) }));
395
667
  }
396
668
  });
397
669
 
@@ -401,9 +673,9 @@ program
401
673
  .action(async (projectId, chapterId, sceneId, type) => {
402
674
  try {
403
675
  const data = await api.get(`/app/request/assets/${projectId}/${chapterId}/${sceneId}?type=${type}`);
404
- console.log(JSON.stringify(data.data || data, null, 2));
676
+ console.log(JSON.stringify(unwrapData(data), null, 2));
405
677
  } catch (error) {
406
- console.log(JSON.stringify({ status: "error", message: error.message }));
678
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
407
679
  }
408
680
  });
409
681
 
@@ -423,9 +695,9 @@ program
423
695
  };
424
696
 
425
697
  const data = await api.post(`/app/scene/upscale/${options.project}/${options.chapter}/${options.scene}`, payload);
426
- console.log(JSON.stringify({ status: "success", request: data.data || data }, null, 2));
698
+ console.log(JSON.stringify({ status: "success", request: unwrapData(data) }, null, 2));
427
699
  } catch (error) {
428
- console.log(JSON.stringify({ status: "error", message: error.response?.data?.message || error.message }));
700
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
429
701
  }
430
702
  });
431
703
 
@@ -444,9 +716,9 @@ program
444
716
  storyLanguage: options.lang
445
717
  };
446
718
  const data = await api.post('/app/youtube/metadata/generate', payload);
447
- console.log(JSON.stringify({ status: "success", metadata: data.data || data }, null, 2));
719
+ console.log(JSON.stringify({ status: "success", metadata: unwrapData(data) }, null, 2));
448
720
  } catch (error) {
449
- console.log(JSON.stringify({ status: "error", message: error.response?.data?.message || error.message }));
721
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
450
722
  }
451
723
  });
452
724
 
@@ -464,9 +736,9 @@ program
464
736
  storyLanguage: options.lang
465
737
  };
466
738
  const data = await api.post('/app/youtube/thumbnails/generate', payload);
467
- console.log(JSON.stringify({ status: "success", request: data.data || data }, null, 2));
739
+ console.log(JSON.stringify({ status: "success", request: unwrapData(data) }, null, 2));
468
740
  } catch (error) {
469
- console.log(JSON.stringify({ status: "error", message: error.response?.data?.message || error.message }));
741
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
470
742
  }
471
743
  });
472
744
 
@@ -476,9 +748,9 @@ program
476
748
  .action(async (projectId, chapterId) => {
477
749
  try {
478
750
  const data = await api.get(`/app/youtube/projects/${projectId}/chapters/${chapterId}/thumbnails`);
479
- console.log(JSON.stringify(data.data || data, null, 2));
751
+ console.log(JSON.stringify(unwrapData(data), null, 2));
480
752
  } catch (error) {
481
- console.log(JSON.stringify({ status: "error", message: error.message }));
753
+ console.log(JSON.stringify({ status: "error", ...formatCliError(error) }));
482
754
  }
483
755
  });
484
756
 
@@ -495,9 +767,17 @@ program
495
767
  const skillPath = path.join(__dirname, 'skills', 'SKILL.md');
496
768
  try {
497
769
  const content = fs.readFileSync(skillPath, 'utf-8');
498
- console.log(content);
770
+ if (globalOpts().json) {
771
+ emitJson({ status: 'success', data: content });
772
+ } else {
773
+ console.log(content);
774
+ }
499
775
  } catch (err) {
500
- console.error('❌ Could not read SKILL.md:', err.message);
776
+ if (globalOpts().json) {
777
+ emitJson({ status: 'error', code: 'SKILL_READ_FAILED', message: err.message });
778
+ } else {
779
+ humanError(`❌ Could not read SKILL.md: ${err.message}`);
780
+ }
501
781
  }
502
782
  });
503
783
 
@@ -506,19 +786,27 @@ program
506
786
  .description('Auto-extract your labs.google Flow Token (ya29.) using a secure browser overlay')
507
787
  .action(async () => {
508
788
  try {
509
- console.log("\n--- 🗝️ VEOGENT Flow Token Extractor ---");
510
- console.log("This will open a chromium window to https://labs.google/fx/tools/flow.");
511
- console.log("Please click 'Create with Flow' and login if requested.");
512
- console.log("We will automatically capture the underlying Bearer Token from Network traffic!\n");
789
+ humanLog("\n--- 🗝️ VEOGENT Flow Token Extractor ---");
790
+ humanLog("This will open a chromium window to https://labs.google/fx/tools/flow.");
791
+ humanLog("Please click 'Create with Flow' and login if requested.");
792
+ humanLog("We will automatically capture the underlying Bearer Token from Network traffic!\n");
513
793
 
514
794
  const token = await obtainFlowToken();
515
-
795
+
516
796
  if (token) {
517
- console.log(`\n🎉 Extracted Flow Token:\n\n${token}\n`);
518
- console.log(`To apply this automatically to your account, run:\nveogent setup-flow -f "${token}"`);
797
+ if (globalOpts().json) {
798
+ emitJson({ status: 'success', flowToken: token });
799
+ } else {
800
+ humanLog(`\n🎉 Extracted Flow Token:\n\n${token}\n`);
801
+ humanLog(`To apply this automatically to your account, run:\nveogent setup-flow -f "${token}"`);
802
+ }
519
803
  }
520
804
  } catch (error) {
521
- console.error("Failed to extract:", error.message);
805
+ if (globalOpts().json) {
806
+ emitJson({ status: 'error', ...formatCliError(error) });
807
+ } else {
808
+ humanError(`Failed to extract: ${error.message}`);
809
+ }
522
810
  }
523
811
  });
524
812
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veogent",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
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": {