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.
Files changed (57) hide show
  1. package/README.md +485 -3
  2. package/bin/baz.js +6 -1
  3. package/dist/commands/auth.d.ts +2 -0
  4. package/dist/commands/auth.js +109 -0
  5. package/dist/commands/capabilities.d.ts +2 -0
  6. package/dist/commands/capabilities.js +44 -0
  7. package/dist/commands/context.d.ts +13 -0
  8. package/dist/commands/context.js +498 -0
  9. package/dist/commands/export.d.ts +2 -0
  10. package/dist/commands/export.js +360 -0
  11. package/dist/commands/logs.d.ts +2 -0
  12. package/dist/commands/logs.js +180 -0
  13. package/dist/commands/loop.d.ts +2 -0
  14. package/dist/commands/loop.js +538 -0
  15. package/dist/commands/mcp.d.ts +2 -0
  16. package/dist/commands/mcp.js +143 -0
  17. package/dist/commands/media.d.ts +2 -0
  18. package/dist/commands/media.js +362 -0
  19. package/dist/commands/project.d.ts +2 -0
  20. package/dist/commands/project.js +786 -0
  21. package/dist/commands/prompt.d.ts +2 -0
  22. package/dist/commands/prompt.js +540 -0
  23. package/dist/commands/recipe.d.ts +15 -0
  24. package/dist/commands/recipe.js +607 -0
  25. package/dist/commands/review.d.ts +17 -0
  26. package/dist/commands/review.js +345 -0
  27. package/dist/commands/scenes.d.ts +2 -0
  28. package/dist/commands/scenes.js +481 -0
  29. package/dist/commands/share.d.ts +2 -0
  30. package/dist/commands/share.js +226 -0
  31. package/dist/commands/state.d.ts +2 -0
  32. package/dist/commands/state.js +171 -0
  33. package/dist/commands/status.d.ts +2 -0
  34. package/dist/commands/status.js +219 -0
  35. package/dist/commands/template.d.ts +2 -0
  36. package/dist/commands/template.js +123 -0
  37. package/dist/commands/verify.d.ts +2 -0
  38. package/dist/commands/verify.js +150 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +124 -0
  41. package/dist/lib/api.d.ts +188 -0
  42. package/dist/lib/api.js +719 -0
  43. package/dist/lib/banner.d.ts +12 -0
  44. package/dist/lib/banner.js +69 -0
  45. package/dist/lib/config.d.ts +33 -0
  46. package/dist/lib/config.js +99 -0
  47. package/dist/lib/output.d.ts +52 -0
  48. package/dist/lib/output.js +162 -0
  49. package/dist/lib/project-state.d.ts +52 -0
  50. package/dist/lib/project-state.js +178 -0
  51. package/dist/lib/sse.d.ts +168 -0
  52. package/dist/lib/sse.js +227 -0
  53. package/dist/lib/version.d.ts +1 -0
  54. package/dist/lib/version.js +3 -0
  55. package/dist/repl.d.ts +4 -0
  56. package/dist/repl.js +764 -0
  57. package/package.json +32 -5
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const promptCommand: Command;
@@ -0,0 +1,540 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { loadConfig, hasAuth, getProjectId } from '../lib/config.js';
6
+ import { streamGeneration, uploadImage, fetchUrlContent, ApiError } from '../lib/api.js';
7
+ import { error, output } from '../lib/output.js';
8
+ /**
9
+ * Collector function for repeatable --image option
10
+ */
11
+ function collect(value, previous) {
12
+ return previous.concat([value]);
13
+ }
14
+ /**
15
+ * Format elapsed time in human-readable format
16
+ */
17
+ function formatElapsed(ms) {
18
+ if (ms < 1000)
19
+ return `${ms}ms`;
20
+ const seconds = Math.floor(ms / 1000);
21
+ if (seconds < 60)
22
+ return `${seconds}s`;
23
+ const minutes = Math.floor(seconds / 60);
24
+ const remainingSecs = seconds % 60;
25
+ return `${minutes}m ${remainingSecs}s`;
26
+ }
27
+ export const promptCommand = new Command('prompt')
28
+ .description('Send a prompt to the autonomous agent')
29
+ .argument('[text]', 'The prompt text (or use --file)')
30
+ .option('--mode <mode>', 'Generation mode: agent (default), agent-max, multi-scene', 'agent')
31
+ .option('--max', 'Shorthand for --mode agent-max')
32
+ .option('--file <path>', 'Read prompt from file')
33
+ .option('--image <path>', 'Attach an image file (can be used multiple times)', collect, [])
34
+ .option('--url <url>', 'Attach a URL for the agent to analyze (can be used multiple times)', collect, [])
35
+ .option('--no-stream', 'Disable streaming output')
36
+ .option('--verbose', 'Show agent\'s full response text')
37
+ .option('--stream-json', 'Output events as newline-delimited JSON (for bots like Maya)')
38
+ .option('--events-only', 'With --stream-json, only emit structured events (skip text/thinking tokens)')
39
+ .option('--plan', 'Create a recipe/plan without executing (handshake protocol)')
40
+ .option('--requirements <list>', 'Requirements to verify against (comma-separated)')
41
+ .option('--budget <dollars>', 'Max budget per run in USD (default: 5)', parseFloat)
42
+ .option('--model <model>', 'Agent model: fast, default, max')
43
+ .action(async (text, options, cmd) => {
44
+ const globalOpts = cmd.optsWithGlobals();
45
+ const agentModeEnv = process.env.BAZ_AGENT === '1';
46
+ const jsonOutput = globalOpts.json || agentModeEnv;
47
+ const config = loadConfig({
48
+ configPath: globalOpts.config,
49
+ apiUrl: globalOpts.apiUrl,
50
+ projectId: globalOpts.projectId,
51
+ });
52
+ if (!hasAuth(config)) {
53
+ if (options.streamJson || jsonOutput) {
54
+ console.log(JSON.stringify({
55
+ type: 'error',
56
+ code: 'AUTH_MISSING',
57
+ errorType: 'AUTH_MISSING',
58
+ message: 'Not authenticated',
59
+ category: 'auth',
60
+ retryable: false,
61
+ transient: false,
62
+ suggestion: 'Run: baz auth login <api-key>',
63
+ continue: false,
64
+ }));
65
+ }
66
+ else {
67
+ error('Not authenticated', 'Run: baz auth login <api-key>');
68
+ }
69
+ process.exit(13); // Auth error exit code
70
+ }
71
+ // Get project ID
72
+ let projectId;
73
+ try {
74
+ projectId = getProjectId(config, globalOpts.projectId);
75
+ }
76
+ catch (err) {
77
+ if (options.streamJson || jsonOutput) {
78
+ console.log(JSON.stringify({
79
+ type: 'error',
80
+ code: 'VALIDATION',
81
+ errorType: 'VALIDATION',
82
+ message: err.message,
83
+ category: 'validation',
84
+ retryable: false,
85
+ transient: false,
86
+ suggestion: 'Set active project with: baz project use <id>',
87
+ continue: false,
88
+ }));
89
+ }
90
+ else {
91
+ error(err.message);
92
+ }
93
+ process.exit(64); // Input error exit code
94
+ }
95
+ // Get prompt text: --file takes precedence, then positional arg
96
+ let promptText = text || '';
97
+ if (options.file) {
98
+ try {
99
+ promptText = readFileSync(options.file, 'utf-8').trim();
100
+ }
101
+ catch (err) {
102
+ error(`Failed to read file: ${options.file}`);
103
+ process.exit(1);
104
+ }
105
+ }
106
+ if (!promptText) {
107
+ error('No prompt provided. Pass text as argument or use --file <path>');
108
+ process.exit(1);
109
+ }
110
+ // Determine mode
111
+ const mode = options.max ? 'agent-max' : options.mode;
112
+ // Handle image uploads
113
+ const imageUrls = [];
114
+ if (options.image && options.image.length > 0) {
115
+ const uploadSpinner = ora('Uploading images...').start();
116
+ try {
117
+ for (const imagePath of options.image) {
118
+ if (!existsSync(imagePath)) {
119
+ uploadSpinner.fail();
120
+ error(`Image file not found: ${imagePath}`);
121
+ process.exit(1);
122
+ }
123
+ const imageBuffer = readFileSync(imagePath);
124
+ const url = await uploadImage(config, { projectId, buffer: imageBuffer, filename: imagePath });
125
+ imageUrls.push(url);
126
+ }
127
+ uploadSpinner.succeed(`Uploaded ${imageUrls.length} image${imageUrls.length > 1 ? 's' : ''}`);
128
+ }
129
+ catch (err) {
130
+ uploadSpinner.fail();
131
+ const errMsg = err instanceof ApiError
132
+ ? `${err.message}${err.suggestion ? ` — ${err.suggestion}` : ''}`
133
+ : err.message;
134
+ error(`Failed to upload images: ${errMsg}`);
135
+ process.exit(1);
136
+ }
137
+ }
138
+ // Handle URL attachments
139
+ const urlContents = [];
140
+ if (options.url && options.url.length > 0) {
141
+ const urlSpinner = ora('Fetching URLs...').start();
142
+ try {
143
+ for (const url of options.url) {
144
+ const result = await fetchUrlContent(url);
145
+ urlContents.push({
146
+ url,
147
+ title: result.title,
148
+ content: result.content.slice(0, 10000), // Limit content length
149
+ });
150
+ }
151
+ urlSpinner.succeed(`Fetched ${urlContents.length} URL${urlContents.length > 1 ? 's' : ''}`);
152
+ }
153
+ catch (err) {
154
+ urlSpinner.fail();
155
+ const errMsg = err instanceof ApiError
156
+ ? `${err.message}${err.details ? ` — ${err.details}` : ''}`
157
+ : err.message;
158
+ error(`Failed to fetch URL: ${errMsg}`);
159
+ process.exit(1);
160
+ }
161
+ // Prepend URL content to the prompt
162
+ if (urlContents.length > 0) {
163
+ const urlContext = urlContents.map(u => `--- Content from ${u.url}${u.title ? ` (${u.title})` : ''} ---\n${u.content}`).join('\n\n');
164
+ promptText = `${urlContext}\n\n---\n\nUser request: ${promptText}`;
165
+ }
166
+ }
167
+ // For bots: --stream-json outputs NDJSON (newline-delimited JSON) with timestamps
168
+ const streamJson = options.streamJson === true || agentModeEnv;
169
+ // Show mode info (skip for JSON modes)
170
+ if (!jsonOutput && !streamJson && options.stream !== false) {
171
+ console.log(chalk.cyan(`Mode: ${mode}`));
172
+ console.log(chalk.gray(`Project: ${projectId}`));
173
+ if (imageUrls.length > 0) {
174
+ console.log(chalk.gray(`Images: ${imageUrls.length} attached`));
175
+ }
176
+ if (urlContents.length > 0) {
177
+ console.log(chalk.gray(`URLs: ${urlContents.length} attached`));
178
+ }
179
+ console.log();
180
+ }
181
+ // Track timing and progress
182
+ const startTime = Date.now();
183
+ let stepCount = 0;
184
+ let lastPhase = '';
185
+ // Stream the generation
186
+ const spinner = options.stream !== false ? null : ora('Processing...').start();
187
+ let currentSpinner = null;
188
+ let textBuffer = '';
189
+ let isFirstText = true;
190
+ let scenesCreatedNames = [];
191
+ let scenesUpdatedNames = [];
192
+ // Tool display names for better UX
193
+ const toolNames = {
194
+ // Scene tools
195
+ add_scene: '🎬 Creating scene',
196
+ edit_scene: '✏️ Editing scene',
197
+ delete_scene: '🗑️ Deleting scene',
198
+ addScene: '🎬 Creating scene',
199
+ editScene: '✏️ Editing scene',
200
+ deleteScene: '🗑️ Deleting scene',
201
+ trimScene: '⏱️ Adjusting timing',
202
+ // Media generation
203
+ generateImage: '🖼️ Generating image',
204
+ generateVideo: '🎥 Generating video',
205
+ generateVoiceover: '🎙️ Generating voiceover',
206
+ generateMusic: '🎵 Generating music',
207
+ // Gathering tools
208
+ fetch_url: '🌐 Fetching URL',
209
+ take_screenshot: '📸 Taking screenshot',
210
+ removeBackground: '✂️ Removing background',
211
+ summarize: '📝 Summarizing content',
212
+ // Code tools
213
+ str_replace_based_edit_tool: '✏️ Editing code',
214
+ browse_composition: '👀 Analyzing composition',
215
+ // Planning & orchestration
216
+ create_video_plan: '📋 Planning video',
217
+ create_recipe: '🧪 Creating recipe',
218
+ workflow: '🔄 Running workflow',
219
+ execute_workflow: '⚡ Executing workflow',
220
+ // Audio tools
221
+ addAudio: '🔊 Adding audio',
222
+ embedAudio: '🎧 Embedding audio',
223
+ // Advanced video (Nano Banana, Veo, etc.)
224
+ generateVeo3: '🎬 Generating Veo video',
225
+ nanoBanana: '🍌 Running Nano Banana',
226
+ NanoBananaService: '🍌 Nano Banana generation',
227
+ seedance: '💃 Generating Seedance video',
228
+ };
229
+ // Track recipes and workflows created
230
+ let recipesCreated = [];
231
+ let workflowsCreated = [];
232
+ // Track budget from complete event for summary
233
+ let budgetData;
234
+ try {
235
+ // Resolve model alias to full model ID
236
+ const MODEL_ALIASES = {
237
+ fast: 'claude-haiku-4-5',
238
+ max: 'claude-opus-4-6',
239
+ default: 'claude-sonnet-4-5',
240
+ };
241
+ const resolvedModel = options.model
242
+ ? MODEL_ALIASES[options.model] || options.model
243
+ : undefined; // Let api.ts default (Sonnet) handle it
244
+ const result = await streamGeneration(config, {
245
+ projectId,
246
+ prompt: promptText,
247
+ mode,
248
+ imageUrls: imageUrls.length > 0 ? imageUrls : undefined,
249
+ includeRaw: streamJson, // Include raw data and timestamps for bot mode
250
+ planOnly: options.plan, // Handshake: create plan without executing
251
+ requirements: options.requirements, // Requirements for verification
252
+ budgetUsd: options.budget, // Per-run budget in USD
253
+ modelOverride: resolvedModel,
254
+ onEvent: options.stream !== false ? (event) => {
255
+ // Handle streaming output
256
+ // Stream JSON / JSON mode: output raw event lines, but still track plan/workflow for summaries.
257
+ if (streamJson || jsonOutput) {
258
+ if (event.type === 'recipe_created') {
259
+ const recipeName = event.recipeName || 'Untitled Recipe';
260
+ recipesCreated.push(recipeName);
261
+ }
262
+ if (event.type === 'workflow_created') {
263
+ const wfName = event.workflowName || 'Untitled Workflow';
264
+ workflowsCreated.push(wfName);
265
+ }
266
+ if (event.type === 'complete' && event.budget) {
267
+ budgetData = event.budget;
268
+ }
269
+ // --events-only (or BAZ_AGENT=1): skip noisy text/thinking tokens and tool_use starts
270
+ const effectiveEventsOnly = options.eventsOnly || agentModeEnv;
271
+ if (effectiveEventsOnly) {
272
+ if (event.type === 'text' || event.type === 'thinking')
273
+ return;
274
+ if (event.type === 'tool_use' && event.action === 'start')
275
+ return;
276
+ }
277
+ console.log(JSON.stringify(event));
278
+ return;
279
+ }
280
+ // Track phase changes for step counting
281
+ const currentPhase = event.type;
282
+ if (currentPhase !== lastPhase && currentPhase !== 'text') {
283
+ if (['tool_use', 'code_gen', 'compile'].includes(currentPhase)) {
284
+ stepCount++;
285
+ }
286
+ lastPhase = currentPhase;
287
+ }
288
+ // Format step prefix with elapsed time
289
+ const elapsed = formatElapsed(Date.now() - startTime);
290
+ const stepPrefix = chalk.dim(`[${elapsed}]`);
291
+ switch (event.type) {
292
+ case 'thinking':
293
+ if (currentSpinner)
294
+ currentSpinner.stop();
295
+ // Show a truncated version of what the agent is thinking
296
+ const thinkingPreview = event.message
297
+ ? event.message.slice(0, 55).replace(/\n/g, ' ') + (event.message.length > 55 ? '...' : '')
298
+ : 'analyzing...';
299
+ currentSpinner = ora(`${stepPrefix} ${chalk.blue(`🧠 ${thinkingPreview}`)}`).start();
300
+ break;
301
+ case 'tool_use':
302
+ if (currentSpinner)
303
+ currentSpinner.succeed();
304
+ const toolDisplay = toolNames[event.tool || ''] || `🔧 ${event.tool}`;
305
+ currentSpinner = ora(`${stepPrefix} ${chalk.yellow(toolDisplay)}`).start();
306
+ break;
307
+ case 'code_gen':
308
+ if (currentSpinner)
309
+ currentSpinner.succeed();
310
+ currentSpinner = ora(`${stepPrefix} ${chalk.magenta('💻 Generating code...')}`).start();
311
+ break;
312
+ case 'compile':
313
+ if (currentSpinner)
314
+ currentSpinner.succeed();
315
+ currentSpinner = ora(`${stepPrefix} ${chalk.cyan('⚙️ Compiling scene...')}`).start();
316
+ break;
317
+ case 'ready':
318
+ console.log(`${stepPrefix} ${chalk.blue(` 🚀 Agent ready`)}${event.budgetUsd ? chalk.gray(` (budget: $${event.budgetUsd.toFixed(2)})`) : ''}${event.model ? chalk.gray(` [${event.model}]`) : ''}`);
319
+ break;
320
+ case 'asset_generated':
321
+ if (currentSpinner)
322
+ currentSpinner.succeed();
323
+ console.log(`${stepPrefix} ${chalk.green(` 🎨 ${event.assetType || 'Asset'}: ${event.message || 'generated'}`)}${event.costUsd ? chalk.gray(` ($${event.costUsd.toFixed(2)})`) : ''}`);
324
+ currentSpinner = null;
325
+ break;
326
+ case 'scene_created':
327
+ if (currentSpinner)
328
+ currentSpinner.succeed();
329
+ const createdName = event.sceneName || event.sceneId?.slice(0, 8) || 'scene';
330
+ scenesCreatedNames.push(createdName);
331
+ console.log(`${stepPrefix} ${chalk.green(` ✅ Created: "${createdName}"`)}`);
332
+ currentSpinner = null;
333
+ break;
334
+ case 'scene_updated':
335
+ if (currentSpinner)
336
+ currentSpinner.succeed();
337
+ const updatedName = event.sceneName || event.sceneId?.slice(0, 8) || 'scene';
338
+ scenesUpdatedNames.push(updatedName);
339
+ console.log(`${stepPrefix} ${chalk.green(` ✅ Updated: "${updatedName}"`)}`);
340
+ currentSpinner = null;
341
+ break;
342
+ case 'error':
343
+ if (currentSpinner)
344
+ currentSpinner.fail();
345
+ console.log(`${stepPrefix} ${chalk.red(` ❌ ${event.message}`)}`);
346
+ currentSpinner = null;
347
+ break;
348
+ case 'complete':
349
+ if (currentSpinner)
350
+ currentSpinner.succeed();
351
+ currentSpinner = null;
352
+ break;
353
+ case 'recipe_created':
354
+ if (currentSpinner)
355
+ currentSpinner.succeed();
356
+ const recipeName = event.recipeName || 'Untitled Recipe';
357
+ recipesCreated.push(recipeName);
358
+ console.log(`${stepPrefix} ${chalk.magenta(` 🧪 Recipe: "${recipeName}"`)}`);
359
+ if (event.recipeDescription) {
360
+ console.log(chalk.dim(` ${event.recipeDescription.slice(0, 60)}...`));
361
+ }
362
+ // In plan mode, signal awaiting approval
363
+ if (options.plan) {
364
+ console.log(`${stepPrefix} ${chalk.cyan(` ⏳ Awaiting approval`)}`);
365
+ console.log(chalk.gray(` Review: baz recipe show --json`));
366
+ console.log(chalk.gray(` Execute: baz recipe execute --stream-json`));
367
+ }
368
+ currentSpinner = null;
369
+ break;
370
+ case 'awaiting_approval':
371
+ if (currentSpinner)
372
+ currentSpinner.succeed();
373
+ console.log(`${stepPrefix} ${chalk.cyan(` ⏳ Plan ready for review`)}`);
374
+ if (event.executionId) {
375
+ console.log(chalk.gray(` Execution ID: ${event.executionId}`));
376
+ }
377
+ console.log(chalk.gray(` Review: baz recipe show --json`));
378
+ console.log(chalk.gray(` Execute: baz recipe execute --stream-json`));
379
+ currentSpinner = null;
380
+ break;
381
+ case 'workflow_created':
382
+ if (currentSpinner)
383
+ currentSpinner.succeed();
384
+ const wfName = event.workflowName || 'Untitled Workflow';
385
+ workflowsCreated.push(wfName);
386
+ console.log(`${stepPrefix} ${chalk.cyan(` 🔄 Workflow: "${wfName}"`)}`);
387
+ if (event.workflowDescription) {
388
+ console.log(chalk.dim(` ${event.workflowDescription.slice(0, 60)}...`));
389
+ }
390
+ currentSpinner = null;
391
+ break;
392
+ case 'video_plan_created':
393
+ if (currentSpinner)
394
+ currentSpinner.succeed();
395
+ console.log(`${stepPrefix} ${chalk.blue(` 📋 Video plan created`)}`);
396
+ if (event.videoPlanSummary) {
397
+ console.log(chalk.dim(` ${event.videoPlanSummary.slice(0, 60)}...`));
398
+ }
399
+ currentSpinner = null;
400
+ break;
401
+ case 'text':
402
+ // Stream text to console (agent's response)
403
+ const showText = options.verbose || globalOpts.verbose;
404
+ if (event.message) {
405
+ textBuffer += event.message;
406
+ if (showText) {
407
+ if (isFirstText) {
408
+ console.log(); // Add newline before text
409
+ console.log(chalk.gray('─'.repeat(50)));
410
+ console.log(chalk.dim('Agent response:'));
411
+ isFirstText = false;
412
+ }
413
+ process.stdout.write(chalk.gray(event.message));
414
+ }
415
+ }
416
+ break;
417
+ }
418
+ } : undefined,
419
+ });
420
+ // Clean up spinner (cast needed due to closure type narrowing)
421
+ if (currentSpinner) {
422
+ currentSpinner.stop();
423
+ }
424
+ if (spinner) {
425
+ spinner.stop();
426
+ }
427
+ // Calculate final elapsed time
428
+ const totalElapsed = formatElapsed(Date.now() - startTime);
429
+ const elapsedMs = Date.now() - startTime;
430
+ // Output final result
431
+ if (streamJson) {
432
+ // In plan mode, emit awaiting_approval instead of summary
433
+ if (options.plan && recipesCreated.length > 0) {
434
+ console.log(JSON.stringify({
435
+ type: 'awaiting_approval',
436
+ timestamp: new Date().toISOString(),
437
+ success: true,
438
+ recipeName: recipesCreated[0],
439
+ recipesCreated,
440
+ requirements: options.requirements?.split(',').map((r) => r.trim()),
441
+ elapsed: totalElapsed,
442
+ elapsedMs,
443
+ message: 'Plan ready for review. Use baz recipe show --json to inspect, baz recipe execute to run.',
444
+ continue: true, // Critical: signals agent should review and decide
445
+ }));
446
+ return;
447
+ }
448
+ // Final summary event for bot consumption - OODA loop signal
449
+ console.log(JSON.stringify({
450
+ type: 'summary',
451
+ timestamp: new Date().toISOString(),
452
+ success: result.success,
453
+ scenesCreated: result.scenesCreated,
454
+ scenesUpdated: result.scenesUpdated,
455
+ errors: result.errors,
456
+ summary: result.summary,
457
+ elapsed: totalElapsed,
458
+ elapsedMs,
459
+ steps: stepCount,
460
+ ...(budgetData && { budget: budgetData }),
461
+ continue: false, // Critical: signals end of generation loop
462
+ }));
463
+ return;
464
+ }
465
+ if (jsonOutput) {
466
+ output({
467
+ ...result,
468
+ elapsed: totalElapsed,
469
+ steps: stepCount,
470
+ continue: false,
471
+ }, { json: true, compact: globalOpts.compact || agentModeEnv });
472
+ return;
473
+ }
474
+ // Add separator if we streamed text
475
+ if (textBuffer && !isFirstText) {
476
+ console.log();
477
+ console.log(chalk.gray('─'.repeat(50)));
478
+ }
479
+ console.log();
480
+ if (result.success) {
481
+ const totalScenes = result.scenesCreated.length + result.scenesUpdated.length;
482
+ const hasRecipes = recipesCreated.length > 0;
483
+ const hasWorkflows = workflowsCreated.length > 0;
484
+ // Build summary line
485
+ const summaryParts = [];
486
+ if (totalScenes > 0)
487
+ summaryParts.push(`${totalScenes} scene${totalScenes > 1 ? 's' : ''}`);
488
+ if (hasRecipes)
489
+ summaryParts.push(`${recipesCreated.length} recipe${recipesCreated.length > 1 ? 's' : ''}`);
490
+ if (hasWorkflows)
491
+ summaryParts.push(`${workflowsCreated.length} workflow${workflowsCreated.length > 1 ? 's' : ''}`);
492
+ // Show completion box
493
+ console.log(chalk.green('┌' + '─'.repeat(48) + '┐'));
494
+ if (summaryParts.length > 0) {
495
+ const summaryText = summaryParts.join(', ');
496
+ console.log(chalk.green('│') + chalk.green.bold(` ✓ Done!`) + chalk.gray(` (${summaryText} in ${totalElapsed})`.padEnd(39).slice(0, 39)) + chalk.green('│'));
497
+ }
498
+ else {
499
+ console.log(chalk.green('│') + chalk.green.bold(` ✓ Agent completed`) + chalk.gray(` in ${totalElapsed}`.padEnd(29)) + chalk.green('│'));
500
+ }
501
+ console.log(chalk.green('└' + '─'.repeat(48) + '┘'));
502
+ // Show details
503
+ if (scenesCreatedNames.length > 0) {
504
+ console.log(chalk.gray(` Scenes created: ${scenesCreatedNames.join(', ')}`));
505
+ }
506
+ if (scenesUpdatedNames.length > 0) {
507
+ console.log(chalk.gray(` Scenes updated: ${scenesUpdatedNames.join(', ')}`));
508
+ }
509
+ if (recipesCreated.length > 0) {
510
+ console.log(chalk.magenta(` Recipes: ${recipesCreated.join(', ')}`));
511
+ }
512
+ if (workflowsCreated.length > 0) {
513
+ console.log(chalk.cyan(` Workflows: ${workflowsCreated.join(', ')}`));
514
+ }
515
+ if (result.summary) {
516
+ console.log();
517
+ console.log(chalk.dim(result.summary));
518
+ }
519
+ }
520
+ else {
521
+ // Show error box
522
+ console.log(chalk.red('┌' + '─'.repeat(48) + '┐'));
523
+ console.log(chalk.red('│') + chalk.red.bold(` ✗ Agent failed`) + chalk.gray(` after ${totalElapsed}`.padEnd(32)) + chalk.red('│'));
524
+ console.log(chalk.red('└' + '─'.repeat(48) + '┘'));
525
+ if (result.errors.length > 0) {
526
+ console.log(chalk.dim('Errors:'));
527
+ result.errors.forEach(e => console.log(chalk.red(` • ${e}`)));
528
+ }
529
+ process.exit(1); // Fatal error - generation failed
530
+ }
531
+ }
532
+ catch (err) {
533
+ if (currentSpinner)
534
+ currentSpinner.fail();
535
+ if (spinner)
536
+ spinner.stop();
537
+ error(err.message);
538
+ process.exit(1);
539
+ }
540
+ });
@@ -0,0 +1,15 @@
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
+ export declare const recipeCommand: Command;