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.
- package/api.js +17 -11
- package/device-auth.js +18 -5
- package/index.js +363 -75
- 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
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
95
|
+
humanError('❌ Failed to retrieve access token from VEOGENT Backend.');
|
|
49
96
|
}
|
|
50
97
|
} catch (error) {
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
|
147
|
+
console.log(JSON.stringify({ status: "success", info: unwrapData(data) }, null, 2));
|
|
96
148
|
} catch (error) {
|
|
97
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
174
|
+
console.log(JSON.stringify(unwrapData(data), null, 2));
|
|
109
175
|
} catch (error) {
|
|
110
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
186
|
+
console.log(JSON.stringify(unwrapData(data), null, 2));
|
|
121
187
|
} catch (error) {
|
|
122
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
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
|
-
.
|
|
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
|
|
230
|
+
console.log(JSON.stringify({ status: "success", descriptionData: unwrapData(data) }, null, 2));
|
|
161
231
|
} catch (error) {
|
|
162
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
-
.
|
|
243
|
+
.option('-s, --sound <sound>', 'Sound effects (true/false)', 'true')
|
|
174
244
|
.requiredOption('-m, --material <material>', 'Image material')
|
|
175
|
-
.
|
|
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
|
|
261
|
+
console.log(JSON.stringify({ status: "success", project: unwrapData(data) }, null, 2));
|
|
192
262
|
} catch (error) {
|
|
193
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
287
|
+
console.log(JSON.stringify(unwrapData(data), null, 2));
|
|
216
288
|
} catch (error) {
|
|
217
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
-
.
|
|
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
|
|
307
|
+
console.log(JSON.stringify({ status: "success", chapterContent: unwrapData(data) }, null, 2));
|
|
236
308
|
} catch (error) {
|
|
237
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
321
|
+
console.log(JSON.stringify(unwrapData(data), null, 2));
|
|
250
322
|
} catch (error) {
|
|
251
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
343
|
+
console.log(JSON.stringify({ status: "success", character: unwrapData(data) }, null, 2));
|
|
272
344
|
} catch (error) {
|
|
273
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
282
|
-
|
|
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
|
-
|
|
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
|
|
453
|
+
console.log(JSON.stringify({ status: "success", scene: unwrapData(data) }, null, 2));
|
|
305
454
|
} catch (error) {
|
|
306
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
480
|
+
console.log(JSON.stringify({ status: "success", scene: unwrapData(data) }, null, 2));
|
|
332
481
|
} catch (error) {
|
|
333
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
543
|
+
console.log(JSON.stringify({ status: "success", request: unwrapData(data) }, null, 2));
|
|
381
544
|
} catch (error) {
|
|
382
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
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:
|
|
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
|
|
676
|
+
console.log(JSON.stringify(unwrapData(data), null, 2));
|
|
405
677
|
} catch (error) {
|
|
406
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
698
|
+
console.log(JSON.stringify({ status: "success", request: unwrapData(data) }, null, 2));
|
|
427
699
|
} catch (error) {
|
|
428
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
719
|
+
console.log(JSON.stringify({ status: "success", metadata: unwrapData(data) }, null, 2));
|
|
448
720
|
} catch (error) {
|
|
449
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
739
|
+
console.log(JSON.stringify({ status: "success", request: unwrapData(data) }, null, 2));
|
|
468
740
|
} catch (error) {
|
|
469
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
|
751
|
+
console.log(JSON.stringify(unwrapData(data), null, 2));
|
|
480
752
|
} catch (error) {
|
|
481
|
-
console.log(JSON.stringify({ status: "error",
|
|
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
|
-
|
|
770
|
+
if (globalOpts().json) {
|
|
771
|
+
emitJson({ status: 'success', data: content });
|
|
772
|
+
} else {
|
|
773
|
+
console.log(content);
|
|
774
|
+
}
|
|
499
775
|
} catch (err) {
|
|
500
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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
|
-
|
|
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
|
|