bazaar.it 0.1.0 → 0.2.1
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/README.md +485 -3
- package/bin/baz.js +6 -1
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +109 -0
- package/dist/commands/capabilities.d.ts +2 -0
- package/dist/commands/capabilities.js +44 -0
- package/dist/commands/context.d.ts +13 -0
- package/dist/commands/context.js +498 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +360 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +180 -0
- package/dist/commands/loop.d.ts +2 -0
- package/dist/commands/loop.js +538 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +143 -0
- package/dist/commands/media.d.ts +2 -0
- package/dist/commands/media.js +362 -0
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +786 -0
- package/dist/commands/prompt.d.ts +2 -0
- package/dist/commands/prompt.js +540 -0
- package/dist/commands/recipe.d.ts +15 -0
- package/dist/commands/recipe.js +607 -0
- package/dist/commands/review.d.ts +17 -0
- package/dist/commands/review.js +345 -0
- package/dist/commands/scenes.d.ts +2 -0
- package/dist/commands/scenes.js +481 -0
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +226 -0
- package/dist/commands/state.d.ts +2 -0
- package/dist/commands/state.js +171 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +219 -0
- package/dist/commands/template.d.ts +2 -0
- package/dist/commands/template.js +123 -0
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.js +150 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +124 -0
- package/dist/lib/api.d.ts +188 -0
- package/dist/lib/api.js +719 -0
- package/dist/lib/banner.d.ts +12 -0
- package/dist/lib/banner.js +69 -0
- package/dist/lib/config.d.ts +33 -0
- package/dist/lib/config.js +99 -0
- package/dist/lib/output.d.ts +52 -0
- package/dist/lib/output.js +162 -0
- package/dist/lib/project-state.d.ts +52 -0
- package/dist/lib/project-state.js +178 -0
- package/dist/lib/sse.d.ts +168 -0
- package/dist/lib/sse.js +227 -0
- package/dist/lib/version.d.ts +1 -0
- package/dist/lib/version.js +3 -0
- package/dist/repl.d.ts +4 -0
- package/dist/repl.js +764 -0
- package/package.json +32 -5
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recipe Commands - Agent Handshake Protocol
|
|
3
|
+
*
|
|
4
|
+
* Implements the REVIEW → APPROVE/EDIT → EXECUTE flow for external agents.
|
|
5
|
+
* See: memory-bank/sprints/sprint501_baz_cli/HANDSHAKE-PROTOCOL.md
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* - baz recipe show → Review the current plan
|
|
9
|
+
* - baz recipe execute → Approve and run
|
|
10
|
+
* - baz recipe edit → Modify before executing
|
|
11
|
+
* - baz recipe clear → Reject/abandon
|
|
12
|
+
* - baz recipe assets → List generated assets
|
|
13
|
+
*/
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import ora from 'ora';
|
|
17
|
+
import { loadConfig, hasAuth, getProjectId } from '../lib/config.js';
|
|
18
|
+
import { apiRequest, ApiError } from '../lib/api.js';
|
|
19
|
+
import { success, error, output, outputNDJSON, table } from '../lib/output.js';
|
|
20
|
+
function exitRecipeAuthError(globalOpts) {
|
|
21
|
+
if (globalOpts.json) {
|
|
22
|
+
output({
|
|
23
|
+
type: 'error',
|
|
24
|
+
code: 'AUTH_MISSING',
|
|
25
|
+
message: 'Not authenticated',
|
|
26
|
+
category: 'auth',
|
|
27
|
+
retryable: false,
|
|
28
|
+
transient: false,
|
|
29
|
+
exitCode: 13,
|
|
30
|
+
suggestion: 'Run: baz auth login <api-key>',
|
|
31
|
+
continue: false,
|
|
32
|
+
}, { json: true, compact: globalOpts.compact });
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
error('Not authenticated', 'Run: baz auth login <api-key>');
|
|
36
|
+
}
|
|
37
|
+
process.exit(13);
|
|
38
|
+
}
|
|
39
|
+
function exitRecipeProjectValidationError(message, globalOpts) {
|
|
40
|
+
if (globalOpts.json) {
|
|
41
|
+
output({
|
|
42
|
+
type: 'error',
|
|
43
|
+
code: 'VALIDATION',
|
|
44
|
+
message,
|
|
45
|
+
category: 'validation',
|
|
46
|
+
retryable: false,
|
|
47
|
+
transient: false,
|
|
48
|
+
exitCode: 64,
|
|
49
|
+
continue: false,
|
|
50
|
+
}, { json: true, compact: globalOpts.compact });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
error(message);
|
|
54
|
+
}
|
|
55
|
+
process.exit(64);
|
|
56
|
+
}
|
|
57
|
+
export const recipeCommand = new Command('recipe')
|
|
58
|
+
.description('Manage video recipes (agent handshake protocol)');
|
|
59
|
+
/**
|
|
60
|
+
* baz recipe show
|
|
61
|
+
* Phase 2: REVIEW - External agent inspects the plan
|
|
62
|
+
*/
|
|
63
|
+
recipeCommand
|
|
64
|
+
.command('show')
|
|
65
|
+
.description('Show current recipe for review')
|
|
66
|
+
.action(async (options, cmd) => {
|
|
67
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
68
|
+
const config = loadConfig({
|
|
69
|
+
configPath: globalOpts.config,
|
|
70
|
+
apiUrl: globalOpts.apiUrl,
|
|
71
|
+
projectId: globalOpts.projectId,
|
|
72
|
+
});
|
|
73
|
+
if (!hasAuth(config)) {
|
|
74
|
+
exitRecipeAuthError(globalOpts);
|
|
75
|
+
}
|
|
76
|
+
let projectId;
|
|
77
|
+
try {
|
|
78
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
exitRecipeProjectValidationError(err.message, globalOpts);
|
|
82
|
+
}
|
|
83
|
+
const spinner = globalOpts.json ? null : ora('Fetching recipe...').start();
|
|
84
|
+
try {
|
|
85
|
+
const result = await apiRequest(config, 'recipe.getByProject', {
|
|
86
|
+
projectId,
|
|
87
|
+
});
|
|
88
|
+
spinner?.stop();
|
|
89
|
+
if (!result || !result.recipe) {
|
|
90
|
+
if (globalOpts.json) {
|
|
91
|
+
output({
|
|
92
|
+
type: 'no_recipe',
|
|
93
|
+
message: 'No recipe found for this project',
|
|
94
|
+
projectId,
|
|
95
|
+
continue: false,
|
|
96
|
+
}, { json: true, compact: globalOpts.compact });
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(chalk.gray('No recipe found for this project.'));
|
|
100
|
+
console.log(chalk.gray('Create one with: baz prompt "..." --plan'));
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (globalOpts.json) {
|
|
105
|
+
output({
|
|
106
|
+
type: 'recipe',
|
|
107
|
+
executionId: result.executionId,
|
|
108
|
+
status: result.status,
|
|
109
|
+
recipe: result.recipe,
|
|
110
|
+
videoPlan: result.videoPlan,
|
|
111
|
+
sourceUrl: result.sourceUrl,
|
|
112
|
+
createdAt: result.createdAt,
|
|
113
|
+
continue: true, // Agent should decide: approve, edit, or reject
|
|
114
|
+
}, { json: true, compact: globalOpts.compact });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Human-readable output
|
|
118
|
+
const recipe = result.recipe;
|
|
119
|
+
console.log(chalk.bold(`\n📋 Recipe: ${recipe.title}`));
|
|
120
|
+
console.log(chalk.gray(`Execution ID: ${result.executionId}`));
|
|
121
|
+
console.log(chalk.gray(`Status: ${result.status}`));
|
|
122
|
+
console.log();
|
|
123
|
+
if (recipe.voiceovers && recipe.voiceovers.length > 0) {
|
|
124
|
+
console.log(chalk.bold('🎙️ Voiceovers:'));
|
|
125
|
+
recipe.voiceovers.forEach((vo, i) => {
|
|
126
|
+
const duration = vo.duration ? ` (${vo.duration.toFixed(1)}s)` : '';
|
|
127
|
+
console.log(` ${i + 1}. [${vo.id.slice(0, 8)}]${duration}`);
|
|
128
|
+
console.log(chalk.gray(` "${vo.text.slice(0, 80)}${vo.text.length > 80 ? '...' : ''}"`));
|
|
129
|
+
});
|
|
130
|
+
console.log();
|
|
131
|
+
}
|
|
132
|
+
if (recipe.scenes && recipe.scenes.length > 0) {
|
|
133
|
+
console.log(chalk.bold('🎬 Scenes:'));
|
|
134
|
+
recipe.scenes.forEach((scene, i) => {
|
|
135
|
+
const trackLabel = `Track ${scene.track ?? 1}`;
|
|
136
|
+
console.log(` ${i + 1}. ${scene.name} (${scene.type}, ${trackLabel})`);
|
|
137
|
+
console.log(chalk.gray(` [${scene.id.slice(0, 8)}] "${scene.prompt.slice(0, 60)}..."`));
|
|
138
|
+
});
|
|
139
|
+
console.log();
|
|
140
|
+
}
|
|
141
|
+
if (recipe.music) {
|
|
142
|
+
console.log(chalk.bold('🎵 Music:'));
|
|
143
|
+
console.log(chalk.gray(` "${recipe.music.prompt}"`));
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
console.log(chalk.cyan('Next steps:'));
|
|
147
|
+
console.log(chalk.gray(' baz recipe execute # Approve and run'));
|
|
148
|
+
console.log(chalk.gray(' baz recipe edit ... # Modify first'));
|
|
149
|
+
console.log(chalk.gray(' baz recipe clear # Reject/abandon'));
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
spinner?.stop();
|
|
153
|
+
if (err instanceof ApiError) {
|
|
154
|
+
if (globalOpts.json) {
|
|
155
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
error(err.message, err.suggestion);
|
|
159
|
+
}
|
|
160
|
+
process.exit(err.exitCode);
|
|
161
|
+
}
|
|
162
|
+
error(err.message);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
/**
|
|
167
|
+
* baz recipe execute
|
|
168
|
+
* Phase 3A: APPROVE - Execute the approved plan
|
|
169
|
+
*/
|
|
170
|
+
recipeCommand
|
|
171
|
+
.command('execute')
|
|
172
|
+
.description('Execute the current recipe (approve and run)')
|
|
173
|
+
.option('--stream-json', 'Stream progress events as NDJSON')
|
|
174
|
+
.action(async (options, cmd) => {
|
|
175
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
176
|
+
const streamJson = options.streamJson || globalOpts.json;
|
|
177
|
+
const config = loadConfig({
|
|
178
|
+
configPath: globalOpts.config,
|
|
179
|
+
apiUrl: globalOpts.apiUrl,
|
|
180
|
+
projectId: globalOpts.projectId,
|
|
181
|
+
});
|
|
182
|
+
if (!hasAuth(config)) {
|
|
183
|
+
if (streamJson) {
|
|
184
|
+
outputNDJSON({
|
|
185
|
+
type: 'error',
|
|
186
|
+
code: 'AUTH_MISSING',
|
|
187
|
+
message: 'Not authenticated',
|
|
188
|
+
category: 'auth',
|
|
189
|
+
retryable: false,
|
|
190
|
+
continue: false,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
error('Not authenticated', 'Run: baz auth login <api-key>');
|
|
195
|
+
}
|
|
196
|
+
if (streamJson) {
|
|
197
|
+
process.exit(13);
|
|
198
|
+
}
|
|
199
|
+
exitRecipeAuthError(globalOpts);
|
|
200
|
+
}
|
|
201
|
+
let projectId;
|
|
202
|
+
try {
|
|
203
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
if (streamJson) {
|
|
207
|
+
outputNDJSON({
|
|
208
|
+
type: 'error',
|
|
209
|
+
code: 'NOT_FOUND',
|
|
210
|
+
message: err.message,
|
|
211
|
+
category: 'validation',
|
|
212
|
+
retryable: false,
|
|
213
|
+
continue: false,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
error(err.message);
|
|
218
|
+
}
|
|
219
|
+
if (streamJson) {
|
|
220
|
+
process.exit(64);
|
|
221
|
+
}
|
|
222
|
+
exitRecipeProjectValidationError(err.message, globalOpts);
|
|
223
|
+
}
|
|
224
|
+
// First, get the current recipe
|
|
225
|
+
if (streamJson) {
|
|
226
|
+
outputNDJSON({
|
|
227
|
+
type: 'execution_started',
|
|
228
|
+
message: 'Fetching recipe for execution...',
|
|
229
|
+
projectId,
|
|
230
|
+
continue: true,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const spinner = streamJson ? null : ora('Fetching recipe...').start();
|
|
234
|
+
try {
|
|
235
|
+
const recipeResult = await apiRequest(config, 'recipe.getByProject', {
|
|
236
|
+
projectId,
|
|
237
|
+
});
|
|
238
|
+
if (!recipeResult || !recipeResult.recipe) {
|
|
239
|
+
spinner?.stop();
|
|
240
|
+
if (streamJson) {
|
|
241
|
+
outputNDJSON({
|
|
242
|
+
type: 'error',
|
|
243
|
+
code: 'NOT_FOUND',
|
|
244
|
+
message: 'No recipe found. Create one first with: baz prompt "..." --plan',
|
|
245
|
+
category: 'validation',
|
|
246
|
+
retryable: false,
|
|
247
|
+
continue: false,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
error('No recipe found', 'Create one first with: baz prompt "..." --plan');
|
|
252
|
+
}
|
|
253
|
+
process.exit(64);
|
|
254
|
+
}
|
|
255
|
+
if (spinner)
|
|
256
|
+
spinner.text = 'Executing recipe...';
|
|
257
|
+
if (streamJson) {
|
|
258
|
+
outputNDJSON({
|
|
259
|
+
type: 'execution_running',
|
|
260
|
+
executionId: recipeResult.executionId,
|
|
261
|
+
recipeTitle: recipeResult.recipe.title,
|
|
262
|
+
scenesCount: recipeResult.recipe.scenes?.length || 0,
|
|
263
|
+
voiceoversCount: recipeResult.recipe.voiceovers?.length || 0,
|
|
264
|
+
continue: true,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
// Execute the recipe
|
|
268
|
+
const executeResult = await apiRequest(config, 'recipe.execute', {
|
|
269
|
+
executionId: recipeResult.executionId,
|
|
270
|
+
recipe: recipeResult.recipe,
|
|
271
|
+
});
|
|
272
|
+
spinner?.stop();
|
|
273
|
+
if (!executeResult.success) {
|
|
274
|
+
if (streamJson) {
|
|
275
|
+
outputNDJSON({
|
|
276
|
+
type: 'error',
|
|
277
|
+
code: 'SERVER',
|
|
278
|
+
message: executeResult.error || 'Recipe execution failed',
|
|
279
|
+
category: 'transient',
|
|
280
|
+
retryable: true,
|
|
281
|
+
continue: false,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
error('Recipe execution failed', executeResult.error);
|
|
286
|
+
}
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
// Success!
|
|
290
|
+
if (streamJson) {
|
|
291
|
+
outputNDJSON({
|
|
292
|
+
type: 'execution_complete',
|
|
293
|
+
success: true,
|
|
294
|
+
executionId: recipeResult.executionId,
|
|
295
|
+
totalDuration: executeResult.totalDuration,
|
|
296
|
+
sceneIds: executeResult.sceneIds,
|
|
297
|
+
scenesCreated: executeResult.sceneIds?.length || 0,
|
|
298
|
+
continue: false, // Agent should verify and potentially iterate
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
success(`Recipe executed successfully!`);
|
|
303
|
+
console.log(chalk.gray(` Duration: ${executeResult.totalDuration?.toFixed(1) || '?'}s`));
|
|
304
|
+
console.log(chalk.gray(` Scenes: ${executeResult.sceneIds?.length || 0} created`));
|
|
305
|
+
console.log();
|
|
306
|
+
console.log(chalk.cyan('Next steps:'));
|
|
307
|
+
console.log(chalk.gray(' baz scenes list # Verify result'));
|
|
308
|
+
console.log(chalk.gray(' baz export start --wait # Export video'));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
spinner?.stop();
|
|
313
|
+
if (err instanceof ApiError) {
|
|
314
|
+
if (streamJson) {
|
|
315
|
+
outputNDJSON({ ...err.toJSON(), continue: false });
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
error(err.message, err.suggestion);
|
|
319
|
+
}
|
|
320
|
+
process.exit(err.exitCode);
|
|
321
|
+
}
|
|
322
|
+
if (streamJson) {
|
|
323
|
+
outputNDJSON({
|
|
324
|
+
type: 'error',
|
|
325
|
+
code: 'UNKNOWN',
|
|
326
|
+
message: err.message,
|
|
327
|
+
category: 'fatal',
|
|
328
|
+
retryable: false,
|
|
329
|
+
continue: false,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
error(err.message);
|
|
334
|
+
}
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
/**
|
|
339
|
+
* baz recipe edit
|
|
340
|
+
* Phase 3B: EDIT - Modify the plan before executing
|
|
341
|
+
*/
|
|
342
|
+
recipeCommand
|
|
343
|
+
.command('edit')
|
|
344
|
+
.description('Edit the current recipe before executing')
|
|
345
|
+
.option('--voiceover <id>', 'Voiceover ID to edit')
|
|
346
|
+
.option('--scene <id>', 'Scene ID to edit')
|
|
347
|
+
.option('--text <text>', 'New voiceover text')
|
|
348
|
+
.option('--prompt <prompt>', 'New scene prompt')
|
|
349
|
+
.option('--name <name>', 'New scene name')
|
|
350
|
+
.option('--add-scene', 'Add a new scene instead of editing')
|
|
351
|
+
.option('--type <type>', 'Scene type for new scene (motion_graphics, image, etc.)')
|
|
352
|
+
.option('--track <n>', 'Track number for new scene', parseInt)
|
|
353
|
+
.option('--remove-voiceover <id>', 'Remove a voiceover by ID')
|
|
354
|
+
.option('--remove-scene <id>', 'Remove a scene by ID')
|
|
355
|
+
.action(async (options, cmd) => {
|
|
356
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
357
|
+
const config = loadConfig({
|
|
358
|
+
configPath: globalOpts.config,
|
|
359
|
+
apiUrl: globalOpts.apiUrl,
|
|
360
|
+
projectId: globalOpts.projectId,
|
|
361
|
+
});
|
|
362
|
+
if (!hasAuth(config)) {
|
|
363
|
+
exitRecipeAuthError(globalOpts);
|
|
364
|
+
}
|
|
365
|
+
let projectId;
|
|
366
|
+
try {
|
|
367
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
exitRecipeProjectValidationError(err.message, globalOpts);
|
|
371
|
+
}
|
|
372
|
+
const spinner = globalOpts.json ? null : ora('Fetching recipe...').start();
|
|
373
|
+
try {
|
|
374
|
+
// Get current recipe
|
|
375
|
+
const recipeResult = await apiRequest(config, 'recipe.getByProject', {
|
|
376
|
+
projectId,
|
|
377
|
+
});
|
|
378
|
+
if (!recipeResult || !recipeResult.recipe) {
|
|
379
|
+
spinner?.stop();
|
|
380
|
+
error('No recipe found', 'Create one first with: baz prompt "..." --plan');
|
|
381
|
+
process.exit(64);
|
|
382
|
+
}
|
|
383
|
+
const recipe = recipeResult.recipe;
|
|
384
|
+
let modified = false;
|
|
385
|
+
// Edit voiceover
|
|
386
|
+
if (options.voiceover && options.text) {
|
|
387
|
+
const vo = recipe.voiceovers.find((v) => v.id === options.voiceover || v.id.startsWith(options.voiceover));
|
|
388
|
+
if (!vo) {
|
|
389
|
+
spinner?.stop();
|
|
390
|
+
error(`Voiceover not found: ${options.voiceover}`);
|
|
391
|
+
process.exit(64);
|
|
392
|
+
}
|
|
393
|
+
vo.text = options.text;
|
|
394
|
+
modified = true;
|
|
395
|
+
}
|
|
396
|
+
// Edit scene
|
|
397
|
+
if (options.scene) {
|
|
398
|
+
const scene = recipe.scenes.find((s) => s.id === options.scene || s.id.startsWith(options.scene));
|
|
399
|
+
if (!scene) {
|
|
400
|
+
spinner?.stop();
|
|
401
|
+
error(`Scene not found: ${options.scene}`);
|
|
402
|
+
process.exit(64);
|
|
403
|
+
}
|
|
404
|
+
if (options.prompt) {
|
|
405
|
+
scene.prompt = options.prompt;
|
|
406
|
+
modified = true;
|
|
407
|
+
}
|
|
408
|
+
if (options.name) {
|
|
409
|
+
scene.name = options.name;
|
|
410
|
+
modified = true;
|
|
411
|
+
}
|
|
412
|
+
if (options.track !== undefined) {
|
|
413
|
+
scene.track = options.track;
|
|
414
|
+
modified = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Add new scene
|
|
418
|
+
if (options.addScene) {
|
|
419
|
+
if (!options.name || !options.prompt) {
|
|
420
|
+
spinner?.stop();
|
|
421
|
+
error('--add-scene requires --name and --prompt');
|
|
422
|
+
process.exit(65);
|
|
423
|
+
}
|
|
424
|
+
const newScene = {
|
|
425
|
+
id: crypto.randomUUID(),
|
|
426
|
+
name: options.name,
|
|
427
|
+
type: options.type || 'motion_graphics',
|
|
428
|
+
prompt: options.prompt,
|
|
429
|
+
track: options.track ?? 1,
|
|
430
|
+
};
|
|
431
|
+
recipe.scenes.push(newScene);
|
|
432
|
+
modified = true;
|
|
433
|
+
}
|
|
434
|
+
// Remove voiceover
|
|
435
|
+
if (options.removeVoiceover) {
|
|
436
|
+
const idx = recipe.voiceovers.findIndex((v) => v.id === options.removeVoiceover || v.id.startsWith(options.removeVoiceover));
|
|
437
|
+
if (idx === -1) {
|
|
438
|
+
spinner?.stop();
|
|
439
|
+
error(`Voiceover not found: ${options.removeVoiceover}`);
|
|
440
|
+
process.exit(64);
|
|
441
|
+
}
|
|
442
|
+
recipe.voiceovers.splice(idx, 1);
|
|
443
|
+
modified = true;
|
|
444
|
+
}
|
|
445
|
+
// Remove scene
|
|
446
|
+
if (options.removeScene) {
|
|
447
|
+
const idx = recipe.scenes.findIndex((s) => s.id === options.removeScene || s.id.startsWith(options.removeScene));
|
|
448
|
+
if (idx === -1) {
|
|
449
|
+
spinner?.stop();
|
|
450
|
+
error(`Scene not found: ${options.removeScene}`);
|
|
451
|
+
process.exit(64);
|
|
452
|
+
}
|
|
453
|
+
recipe.scenes.splice(idx, 1);
|
|
454
|
+
modified = true;
|
|
455
|
+
}
|
|
456
|
+
if (!modified) {
|
|
457
|
+
spinner?.stop();
|
|
458
|
+
error('No modifications specified', 'Use --voiceover, --scene, --add-scene, --remove-voiceover, or --remove-scene');
|
|
459
|
+
process.exit(65);
|
|
460
|
+
}
|
|
461
|
+
if (spinner)
|
|
462
|
+
spinner.text = 'Saving changes...';
|
|
463
|
+
// Update the recipe
|
|
464
|
+
await apiRequest(config, 'recipe.update', {
|
|
465
|
+
executionId: recipeResult.executionId,
|
|
466
|
+
recipe,
|
|
467
|
+
});
|
|
468
|
+
spinner?.stop();
|
|
469
|
+
if (globalOpts.json) {
|
|
470
|
+
output({
|
|
471
|
+
type: 'recipe_updated',
|
|
472
|
+
executionId: recipeResult.executionId,
|
|
473
|
+
recipe,
|
|
474
|
+
continue: true, // Agent can continue editing or execute
|
|
475
|
+
}, { json: true, compact: globalOpts.compact });
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
success('Recipe updated');
|
|
479
|
+
console.log(chalk.cyan('Next steps:'));
|
|
480
|
+
console.log(chalk.gray(' baz recipe show # Review changes'));
|
|
481
|
+
console.log(chalk.gray(' baz recipe execute # Run the recipe'));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
spinner?.stop();
|
|
486
|
+
if (err instanceof ApiError) {
|
|
487
|
+
if (globalOpts.json) {
|
|
488
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
error(err.message, err.suggestion);
|
|
492
|
+
}
|
|
493
|
+
process.exit(err.exitCode);
|
|
494
|
+
}
|
|
495
|
+
error(err.message);
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
/**
|
|
500
|
+
* baz recipe clear
|
|
501
|
+
* Phase 3C: REJECT - Abandon the current plan
|
|
502
|
+
*/
|
|
503
|
+
recipeCommand
|
|
504
|
+
.command('clear')
|
|
505
|
+
.description('Clear/abandon the current recipe')
|
|
506
|
+
.action(async (options, cmd) => {
|
|
507
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
508
|
+
const config = loadConfig({
|
|
509
|
+
configPath: globalOpts.config,
|
|
510
|
+
apiUrl: globalOpts.apiUrl,
|
|
511
|
+
projectId: globalOpts.projectId,
|
|
512
|
+
});
|
|
513
|
+
if (!hasAuth(config)) {
|
|
514
|
+
exitRecipeAuthError(globalOpts);
|
|
515
|
+
}
|
|
516
|
+
let projectId;
|
|
517
|
+
try {
|
|
518
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
519
|
+
}
|
|
520
|
+
catch (err) {
|
|
521
|
+
exitRecipeProjectValidationError(err.message, globalOpts);
|
|
522
|
+
}
|
|
523
|
+
// For now, "clear" just means the agent starts fresh with a new prompt
|
|
524
|
+
// We could add a recipe.delete endpoint, but typically agents just create a new recipe
|
|
525
|
+
if (globalOpts.json) {
|
|
526
|
+
output({
|
|
527
|
+
type: 'recipe_cleared',
|
|
528
|
+
message: 'Recipe abandoned. Start fresh with: baz prompt "..." --plan',
|
|
529
|
+
projectId,
|
|
530
|
+
continue: true, // Agent can start over
|
|
531
|
+
}, { json: true, compact: globalOpts.compact });
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
success('Recipe cleared');
|
|
535
|
+
console.log(chalk.gray('Start fresh with: baz prompt "..." --plan'));
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
/**
|
|
539
|
+
* baz recipe assets
|
|
540
|
+
* List generated assets from recipe execution
|
|
541
|
+
*/
|
|
542
|
+
recipeCommand
|
|
543
|
+
.command('assets')
|
|
544
|
+
.description('List assets generated by recipe execution')
|
|
545
|
+
.action(async (options, cmd) => {
|
|
546
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
547
|
+
const config = loadConfig({
|
|
548
|
+
configPath: globalOpts.config,
|
|
549
|
+
apiUrl: globalOpts.apiUrl,
|
|
550
|
+
projectId: globalOpts.projectId,
|
|
551
|
+
});
|
|
552
|
+
if (!hasAuth(config)) {
|
|
553
|
+
exitRecipeAuthError(globalOpts);
|
|
554
|
+
}
|
|
555
|
+
let projectId;
|
|
556
|
+
try {
|
|
557
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
558
|
+
}
|
|
559
|
+
catch (err) {
|
|
560
|
+
exitRecipeProjectValidationError(err.message, globalOpts);
|
|
561
|
+
}
|
|
562
|
+
const spinner = globalOpts.json ? null : ora('Fetching generated assets...').start();
|
|
563
|
+
try {
|
|
564
|
+
const assets = await apiRequest(config, 'recipe.getGeneratedAssets', {
|
|
565
|
+
projectId,
|
|
566
|
+
});
|
|
567
|
+
spinner?.stop();
|
|
568
|
+
if (globalOpts.json) {
|
|
569
|
+
output({
|
|
570
|
+
type: 'assets',
|
|
571
|
+
projectId,
|
|
572
|
+
assets,
|
|
573
|
+
count: assets.length,
|
|
574
|
+
continue: false,
|
|
575
|
+
}, { json: true, compact: globalOpts.compact });
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (assets.length === 0) {
|
|
579
|
+
console.log(chalk.gray('No generated assets yet.'));
|
|
580
|
+
console.log(chalk.gray('Execute a recipe first: baz recipe execute'));
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
console.log(chalk.bold(`\n🎨 Generated Assets (${assets.length})`));
|
|
584
|
+
console.log();
|
|
585
|
+
const rows = assets.map((asset) => [
|
|
586
|
+
asset.assetId.slice(0, 12),
|
|
587
|
+
asset.type,
|
|
588
|
+
asset.name,
|
|
589
|
+
asset.duration ? `${asset.duration.toFixed(1)}s` : '-',
|
|
590
|
+
]);
|
|
591
|
+
table(['ID', 'Type', 'Name', 'Duration'], rows);
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
spinner?.stop();
|
|
595
|
+
if (err instanceof ApiError) {
|
|
596
|
+
if (globalOpts.json) {
|
|
597
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
error(err.message, err.suggestion);
|
|
601
|
+
}
|
|
602
|
+
process.exit(err.exitCode);
|
|
603
|
+
}
|
|
604
|
+
error(err.message);
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Command - Full State Dump for External Agent Evaluation
|
|
3
|
+
*
|
|
4
|
+
* Returns all the data an external agent needs to evaluate the video:
|
|
5
|
+
* - Original goal and requirements
|
|
6
|
+
* - Each scene with actual TSX code
|
|
7
|
+
* - Voiceovers with text
|
|
8
|
+
* - AI-generated asset prompts (Seedance, etc.)
|
|
9
|
+
* - Timing information
|
|
10
|
+
*
|
|
11
|
+
* The external agent (Maya, Claude, GPT) then makes its own intelligent
|
|
12
|
+
* judgment about whether the video meets the original goal.
|
|
13
|
+
*
|
|
14
|
+
* See: memory-bank/sprints/sprint501_baz_cli/HANDSHAKE-PROTOCOL.md
|
|
15
|
+
*/
|
|
16
|
+
import { Command } from 'commander';
|
|
17
|
+
export declare const reviewCommand: Command;
|