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,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;
|