bazaar.it 0.1.0 → 0.2.0

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 +489 -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 +529 -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 +186 -0
  42. package/dist/lib/api.js +717 -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 +39 -5
package/dist/repl.js ADDED
@@ -0,0 +1,764 @@
1
+ import * as readline from 'readline';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { loadConfig, saveConfig, hasAuth, getConfigPath } from './lib/config.js';
5
+ import { showBanner } from './lib/banner.js';
6
+ import { CLI_VERSION } from './lib/version.js';
7
+ import { apiRequest, streamGeneration } from './lib/api.js';
8
+ import { success, error, table, formatDuration, formatRelativeTime } from './lib/output.js';
9
+ // Simple status indicator for REPL (avoids ora terminal conflicts)
10
+ function status(text) {
11
+ process.stdout.write(chalk.gray(`${text}...`));
12
+ return {
13
+ done: () => {
14
+ process.stdout.write(chalk.green(' ✓\n'));
15
+ },
16
+ fail: () => {
17
+ process.stdout.write(chalk.red(' ✗\n'));
18
+ },
19
+ };
20
+ }
21
+ // Commands for tab completion
22
+ const COMMANDS = [
23
+ 'project list', 'project create', 'project use', 'project current', 'project delete',
24
+ 'scenes list', 'scenes code', 'scenes delete',
25
+ 'auth login', 'auth logout', 'auth status',
26
+ 'help', 'exit', 'quit',
27
+ '/help', '/status', '/use', '/clear', '/history', '/exit',
28
+ ];
29
+ /**
30
+ * Tab completion function
31
+ */
32
+ function completer(line) {
33
+ const hits = COMMANDS.filter((c) => c.startsWith(line.toLowerCase()));
34
+ return [hits.length ? hits : COMMANDS, line];
35
+ }
36
+ /**
37
+ * Start the interactive REPL
38
+ */
39
+ export async function startRepl() {
40
+ // Load config
41
+ const config = loadConfig();
42
+ // Show banner
43
+ showBanner(CLI_VERSION, {
44
+ hasApiKey: Boolean(config.apiKey),
45
+ apiUrl: config.apiUrl,
46
+ activeProject: config.activeProjectId,
47
+ });
48
+ // Initialize state
49
+ const state = {
50
+ config,
51
+ history: [],
52
+ running: true,
53
+ };
54
+ // Create readline interface with tab completion
55
+ const rl = readline.createInterface({
56
+ input: process.stdin,
57
+ output: process.stdout,
58
+ prompt: chalk.hex('#a855f7')('baz') + chalk.gray('> '),
59
+ historySize: 100,
60
+ completer,
61
+ });
62
+ // Welcome message
63
+ console.log(chalk.gray('Type commands or prompts. Use /help for available commands.'));
64
+ console.log();
65
+ rl.prompt();
66
+ // Track if we're processing a command
67
+ let processing = false;
68
+ // Handle input
69
+ rl.on('line', (line) => {
70
+ const input = line.trim();
71
+ if (!input) {
72
+ rl.prompt();
73
+ return;
74
+ }
75
+ // Add to history
76
+ state.history.push(input);
77
+ // Mark as processing to prevent premature exit
78
+ processing = true;
79
+ // Execute command (async, but we don't await at the event handler level)
80
+ executeInput(input, state)
81
+ .catch((err) => {
82
+ // Catch ALL errors to prevent REPL from crashing
83
+ const message = err instanceof Error ? err.message : String(err);
84
+ error(message);
85
+ if (process.env.DEBUG) {
86
+ console.error(err);
87
+ }
88
+ })
89
+ .finally(() => {
90
+ processing = false;
91
+ // Prompt again if still running
92
+ if (state.running) {
93
+ console.log();
94
+ rl.prompt();
95
+ }
96
+ });
97
+ });
98
+ // Prevent unhandled rejections from crashing
99
+ process.on('unhandledRejection', (reason) => {
100
+ error(`Unexpected error: ${reason}`);
101
+ rl.prompt();
102
+ });
103
+ // Handle close - but don't exit while command is running
104
+ rl.on('close', () => {
105
+ console.log();
106
+ console.log(chalk.gray('Goodbye!'));
107
+ state.running = false;
108
+ // Don't force exit - let the event loop drain naturally
109
+ });
110
+ // Handle errors on readline
111
+ rl.on('error', (err) => {
112
+ console.error(chalk.red('Readline error:'), err.message);
113
+ });
114
+ // Keep the process alive
115
+ process.stdin.resume();
116
+ // Handle SIGINT (Ctrl+C)
117
+ rl.on('SIGINT', () => {
118
+ console.log();
119
+ console.log(chalk.gray('(Use /exit or Ctrl+D to quit)'));
120
+ rl.prompt();
121
+ });
122
+ }
123
+ /**
124
+ * Execute user input (command or prompt)
125
+ */
126
+ async function executeInput(input, state) {
127
+ // Slash commands
128
+ if (input.startsWith('/')) {
129
+ await executeSlashCommand(input, state);
130
+ return;
131
+ }
132
+ // Regular commands (project list, scenes code, etc.)
133
+ const words = input.split(/\s+/);
134
+ const cmd = words[0]?.toLowerCase();
135
+ // Route to handlers
136
+ switch (cmd) {
137
+ case 'project':
138
+ case 'projects':
139
+ await handleProject(words.slice(1), state);
140
+ break;
141
+ case 'scenes':
142
+ case 'scene':
143
+ await handleScenes(words.slice(1), state);
144
+ break;
145
+ case 'auth':
146
+ await handleAuth(words.slice(1), state);
147
+ break;
148
+ case 'help':
149
+ showHelp();
150
+ break;
151
+ case 'status':
152
+ await showProjectStatus(state);
153
+ break;
154
+ case 'exit':
155
+ case 'quit':
156
+ case 'q':
157
+ state.running = false;
158
+ console.log(chalk.gray('Goodbye!'));
159
+ process.exit(0);
160
+ break;
161
+ default:
162
+ // Treat as a prompt to the AI agent
163
+ await handlePrompt(input, state);
164
+ }
165
+ }
166
+ /**
167
+ * Execute slash commands
168
+ */
169
+ async function executeSlashCommand(input, state) {
170
+ const [cmd, ...args] = input.slice(1).split(/\s+/);
171
+ switch (cmd.toLowerCase()) {
172
+ case 'help':
173
+ case 'h':
174
+ showHelp();
175
+ break;
176
+ case 'exit':
177
+ case 'quit':
178
+ case 'q':
179
+ state.running = false;
180
+ console.log(chalk.gray('Goodbye!'));
181
+ process.exit(0);
182
+ break;
183
+ case 'clear':
184
+ case 'cls':
185
+ console.clear();
186
+ showBanner(CLI_VERSION, {
187
+ hasApiKey: Boolean(state.config.apiKey),
188
+ apiUrl: state.config.apiUrl,
189
+ activeProject: state.config.activeProjectId,
190
+ });
191
+ break;
192
+ case 'status':
193
+ showStatus(state);
194
+ break;
195
+ case 'history':
196
+ showHistory(state);
197
+ break;
198
+ case 'use':
199
+ if (args[0]) {
200
+ await setActiveProject(args[0], state);
201
+ }
202
+ else {
203
+ error('Usage: /use <project-id>');
204
+ }
205
+ break;
206
+ default:
207
+ error(`Unknown command: /${cmd}`, 'Type /help for available commands');
208
+ }
209
+ }
210
+ /**
211
+ * Handle project commands
212
+ */
213
+ async function handleProject(args, state) {
214
+ const subcmd = args[0]?.toLowerCase();
215
+ if (!hasAuth(state.config)) {
216
+ error('Not authenticated', 'Run: auth login <api-key>');
217
+ return;
218
+ }
219
+ switch (subcmd) {
220
+ case 'list':
221
+ case 'ls':
222
+ await listProjects(state);
223
+ break;
224
+ case 'create':
225
+ const formatIdx = args.indexOf('--format');
226
+ const formatValue = formatIdx >= 0 ? args[formatIdx + 1] : undefined;
227
+ const nameIdx = args.indexOf('--name');
228
+ const nameEnd = formatIdx > 0 ? formatIdx : undefined;
229
+ const name = nameIdx >= 0 ? args.slice(nameIdx + 1, nameEnd).join(' ') : args.slice(1, nameEnd).join(' ');
230
+ if (!name) {
231
+ error('Usage: project create <name> or project create --name "My Project"');
232
+ return;
233
+ }
234
+ await createProject(name, state, formatValue);
235
+ break;
236
+ case 'use':
237
+ if (args[1]) {
238
+ await setActiveProject(args[1], state);
239
+ }
240
+ else {
241
+ error('Usage: project use <id>');
242
+ }
243
+ break;
244
+ case 'current':
245
+ case 'info':
246
+ await showCurrentProject(state);
247
+ break;
248
+ case 'delete':
249
+ case 'rm':
250
+ if (args[1]) {
251
+ await deleteProject(args[1], state);
252
+ }
253
+ else {
254
+ error('Usage: project delete <id>');
255
+ }
256
+ break;
257
+ default:
258
+ console.log('Project commands:');
259
+ console.log(' project list List all projects');
260
+ console.log(' project create <name> [--format portrait|landscape|square] Create a new project');
261
+ console.log(' project use <id> Set active project');
262
+ console.log(' project current Show active project');
263
+ console.log(' project delete <id> Delete a project');
264
+ }
265
+ }
266
+ /**
267
+ * Handle scenes commands
268
+ */
269
+ async function handleScenes(args, state) {
270
+ const subcmd = args[0]?.toLowerCase();
271
+ if (!hasAuth(state.config)) {
272
+ error('Not authenticated', 'Run: auth login <api-key>');
273
+ return;
274
+ }
275
+ if (!state.config.activeProjectId) {
276
+ error('No active project', 'Run: project use <id>');
277
+ return;
278
+ }
279
+ switch (subcmd) {
280
+ case 'list':
281
+ case 'ls':
282
+ await listScenes(state);
283
+ break;
284
+ case 'code':
285
+ const sceneId = args[1];
286
+ await showSceneCode(sceneId, state);
287
+ break;
288
+ case 'delete':
289
+ case 'rm':
290
+ if (args[1]) {
291
+ await deleteScene(args[1], state);
292
+ }
293
+ else {
294
+ error('Usage: scenes delete <scene-id>');
295
+ }
296
+ break;
297
+ case 'set-code':
298
+ if (args[1]) {
299
+ console.log(chalk.gray('set-code is best used from the CLI directly:'));
300
+ console.log(chalk.cyan(` baz scenes set-code ${args[1]} --file <path>`));
301
+ console.log(chalk.cyan(` baz scenes set-code ${args[1]} --code "<tsx>"`));
302
+ console.log(chalk.gray(' Or pipe: cat scene.tsx | baz scenes set-code ' + args[1]));
303
+ }
304
+ else {
305
+ error('Usage: scenes set-code <scene-id> --file <path>');
306
+ }
307
+ break;
308
+ default:
309
+ console.log('Scene commands:');
310
+ console.log(' scenes list List all scenes');
311
+ console.log(' scenes code <id> Show scene code');
312
+ console.log(' scenes code --all Show all scene code');
313
+ console.log(' scenes set-code <id> Replace scene code (use from CLI)');
314
+ console.log(' scenes delete <id> Delete a scene');
315
+ }
316
+ }
317
+ /**
318
+ * Handle auth commands
319
+ */
320
+ async function handleAuth(args, state) {
321
+ const subcmd = args[0]?.toLowerCase();
322
+ switch (subcmd) {
323
+ case 'login':
324
+ const apiKey = args[1];
325
+ if (!apiKey) {
326
+ error('Usage: auth login <api-key>');
327
+ return;
328
+ }
329
+ if (!apiKey.startsWith('baz_sk_')) {
330
+ error('Invalid API key format', 'API keys should start with "baz_sk_"');
331
+ return;
332
+ }
333
+ saveConfig({ apiKey });
334
+ state.config.apiKey = apiKey;
335
+ success('API key saved');
336
+ break;
337
+ case 'logout':
338
+ saveConfig({ apiKey: undefined });
339
+ state.config.apiKey = undefined;
340
+ success('Logged out');
341
+ break;
342
+ case 'status':
343
+ showStatus(state);
344
+ break;
345
+ default:
346
+ console.log('Auth commands:');
347
+ console.log(' auth login <key> Configure API key');
348
+ console.log(' auth logout Clear credentials');
349
+ console.log(' auth status Show auth status');
350
+ }
351
+ }
352
+ /**
353
+ * Handle prompt (send to AI agent)
354
+ */
355
+ async function handlePrompt(prompt, state) {
356
+ if (!hasAuth(state.config)) {
357
+ error('Not authenticated', 'Run: auth login <api-key>');
358
+ return;
359
+ }
360
+ if (!state.config.activeProjectId) {
361
+ error('No active project', 'Run: project use <id> or project create <name>');
362
+ return;
363
+ }
364
+ console.log(chalk.gray(`Project: ${state.config.activeProjectId.slice(0, 8)}`));
365
+ console.log();
366
+ let currentSpinner = null;
367
+ try {
368
+ const result = await streamGeneration(state.config, {
369
+ projectId: state.config.activeProjectId,
370
+ prompt,
371
+ mode: 'agent',
372
+ onEvent: (event) => {
373
+ switch (event.type) {
374
+ case 'thinking':
375
+ if (currentSpinner)
376
+ currentSpinner.stop();
377
+ currentSpinner = ora(chalk.blue('Thinking...')).start();
378
+ break;
379
+ case 'tool_use':
380
+ if (currentSpinner)
381
+ currentSpinner.succeed();
382
+ currentSpinner = ora(chalk.yellow(`Using: ${event.tool}`)).start();
383
+ break;
384
+ case 'code_gen':
385
+ if (currentSpinner)
386
+ currentSpinner.succeed();
387
+ currentSpinner = ora(chalk.magenta('Generating code...')).start();
388
+ break;
389
+ case 'compile':
390
+ if (currentSpinner)
391
+ currentSpinner.succeed();
392
+ currentSpinner = ora(chalk.cyan('Compiling...')).start();
393
+ break;
394
+ case 'scene_created':
395
+ if (currentSpinner)
396
+ currentSpinner.succeed(chalk.green(`✓ Created: ${event.sceneName || event.sceneId}`));
397
+ currentSpinner = null;
398
+ break;
399
+ case 'scene_updated':
400
+ if (currentSpinner)
401
+ currentSpinner.succeed(chalk.green(`✓ Updated: ${event.sceneName || event.sceneId}`));
402
+ currentSpinner = null;
403
+ break;
404
+ case 'error':
405
+ if (currentSpinner)
406
+ currentSpinner.fail(chalk.red(event.message));
407
+ currentSpinner = null;
408
+ break;
409
+ case 'complete':
410
+ if (currentSpinner)
411
+ currentSpinner.succeed();
412
+ currentSpinner = null;
413
+ break;
414
+ }
415
+ },
416
+ });
417
+ if (currentSpinner)
418
+ currentSpinner.stop();
419
+ if (result.success) {
420
+ console.log();
421
+ success('Done');
422
+ if (result.scenesCreated.length > 0) {
423
+ console.log(` Created: ${result.scenesCreated.length} scene(s)`);
424
+ }
425
+ if (result.scenesUpdated.length > 0) {
426
+ console.log(` Updated: ${result.scenesUpdated.length} scene(s)`);
427
+ }
428
+ if (result.summary) {
429
+ console.log();
430
+ console.log(chalk.gray(result.summary));
431
+ }
432
+ }
433
+ else {
434
+ error('Agent failed');
435
+ if (result.errors?.length > 0) {
436
+ result.errors.forEach((e) => console.log(chalk.red(` - ${e}`)));
437
+ }
438
+ }
439
+ }
440
+ catch (err) {
441
+ if (currentSpinner)
442
+ currentSpinner.fail();
443
+ throw err;
444
+ }
445
+ }
446
+ // ============================================
447
+ // Command implementations
448
+ // ============================================
449
+ async function listProjects(state) {
450
+ const s = status('Loading projects');
451
+ try {
452
+ const result = await apiRequest(state.config, 'project.list', { limit: 20 });
453
+ s.done();
454
+ const projects = Array.isArray(result) ? result : (result.projects || []);
455
+ if (projects.length === 0) {
456
+ console.log(chalk.gray('No projects. Create one with: project create <name>'));
457
+ return;
458
+ }
459
+ const rows = projects.map((p) => {
460
+ const isActive = p.id === state.config.activeProjectId;
461
+ const marker = isActive ? chalk.green('→') : ' ';
462
+ const title = p.title || p.name || 'Untitled';
463
+ const displayName = isActive ? chalk.bold(title) : title;
464
+ const sceneCount = p.props?.scenes?.length || p.sceneCount || 0;
465
+ return [marker, p.id.slice(0, 8), displayName, `${sceneCount}`, formatRelativeTime(p.updatedAt)];
466
+ });
467
+ table(['', 'ID', 'Name', 'Scenes', 'Updated'], rows);
468
+ }
469
+ catch (err) {
470
+ s.fail();
471
+ throw err;
472
+ }
473
+ }
474
+ async function createProject(name, state, format) {
475
+ const s = status('Creating project');
476
+ const normalizedFormat = format?.toLowerCase();
477
+ const validFormat = normalizedFormat && ['landscape', 'portrait', 'square'].includes(normalizedFormat)
478
+ ? normalizedFormat
479
+ : undefined;
480
+ const result = await apiRequest(state.config, 'project.create', {
481
+ title: name,
482
+ initialMessage: name,
483
+ ...(validFormat ? { format: validFormat } : {}),
484
+ });
485
+ s.done();
486
+ const projectId = result.projectId || result.id;
487
+ if (!projectId) {
488
+ error('Failed to create project');
489
+ return;
490
+ }
491
+ const finalTitle = result?.title || name;
492
+ // Auto-activate
493
+ saveConfig({ activeProjectId: projectId });
494
+ state.config.activeProjectId = projectId;
495
+ success(`Created: ${chalk.bold(finalTitle)}`);
496
+ console.log(` ID: ${projectId}`);
497
+ if (validFormat) {
498
+ console.log(` Format: ${validFormat}`);
499
+ }
500
+ if (finalTitle !== name) {
501
+ console.log(chalk.gray(' Title adjusted to keep it unique for your account'));
502
+ }
503
+ console.log(chalk.gray(' Set as active project'));
504
+ }
505
+ async function setActiveProject(id, state) {
506
+ // Expand short IDs
507
+ let projectId = id;
508
+ // If it looks like a short ID, try to find the full one
509
+ if (id.length < 20) {
510
+ const s = status('Finding project');
511
+ try {
512
+ const result = await apiRequest(state.config, 'project.list', { limit: 100 });
513
+ const projects = Array.isArray(result) ? result : (result.projects || []);
514
+ const match = projects.find((p) => p.id.startsWith(id));
515
+ if (match) {
516
+ projectId = match.id;
517
+ }
518
+ s.done();
519
+ }
520
+ catch {
521
+ s.fail();
522
+ }
523
+ }
524
+ // Verify project exists
525
+ const s = status('Verifying');
526
+ try {
527
+ const result = await apiRequest(state.config, 'project.getById', { id: projectId });
528
+ s.done();
529
+ saveConfig({ activeProjectId: projectId });
530
+ state.config.activeProjectId = projectId;
531
+ success(`Active: ${chalk.bold(result.title || result.name || 'Untitled')} (${projectId.slice(0, 8)})`);
532
+ }
533
+ catch (err) {
534
+ s.fail();
535
+ error(`Project not found: ${id}`);
536
+ }
537
+ }
538
+ async function showCurrentProject(state) {
539
+ if (!state.config.activeProjectId) {
540
+ console.log(chalk.gray('No active project.'));
541
+ return;
542
+ }
543
+ const s = status('Fetching');
544
+ const result = await apiRequest(state.config, 'project.getById', { id: state.config.activeProjectId });
545
+ s.done();
546
+ console.log(chalk.cyan('Active Project'));
547
+ console.log(` Name: ${chalk.bold(result.title || result.name || 'Untitled')}`);
548
+ console.log(` ID: ${result.id}`);
549
+ console.log(` Scenes: ${result.props?.scenes?.length || 0}`);
550
+ console.log(` Updated: ${formatRelativeTime(result.updatedAt)}`);
551
+ }
552
+ async function deleteProject(id, state) {
553
+ const s = status('Deleting');
554
+ await apiRequest(state.config, 'project.delete', { id });
555
+ s.done();
556
+ if (state.config.activeProjectId === id) {
557
+ saveConfig({ activeProjectId: undefined });
558
+ state.config.activeProjectId = undefined;
559
+ }
560
+ success(`Deleted: ${id}`);
561
+ }
562
+ async function listScenes(state) {
563
+ const s = status('Fetching scenes');
564
+ const project = await apiRequest(state.config, 'project.getById', { id: state.config.activeProjectId });
565
+ s.done();
566
+ const scenes = project.props?.scenes || [];
567
+ if (scenes.length === 0) {
568
+ console.log(chalk.gray('No scenes. Create one by typing a prompt.'));
569
+ return;
570
+ }
571
+ const fps = 30;
572
+ let totalFrames = 0;
573
+ scenes.forEach((s) => totalFrames += s.durationInFrames || 0);
574
+ console.log(chalk.gray(`${scenes.length} scenes, ${formatDuration(totalFrames / fps)} total`));
575
+ console.log();
576
+ const rows = scenes.map((s, i) => {
577
+ const dur = (s.durationInFrames || 0) / fps;
578
+ const status = s.compilationError ? chalk.red('✗') : chalk.green('✓');
579
+ return [String(i + 1), s.id?.slice(0, 8) || '?', s.name || 'Untitled', formatDuration(dur), status];
580
+ });
581
+ table(['#', 'ID', 'Name', 'Duration', ''], rows);
582
+ }
583
+ async function showSceneCode(sceneId, state) {
584
+ const s = status('Fetching');
585
+ const project = await apiRequest(state.config, 'project.getById', { id: state.config.activeProjectId });
586
+ s.done();
587
+ const scenes = project.props?.scenes || [];
588
+ if (!sceneId || sceneId === '--all') {
589
+ // Show all
590
+ for (const scene of scenes) {
591
+ console.log(chalk.cyan(`// === ${scene.name || 'Untitled'} (${scene.id?.slice(0, 8)}) ===`));
592
+ console.log(scene.tsxCode || '// No code');
593
+ console.log();
594
+ }
595
+ }
596
+ else {
597
+ // Find by ID or index
598
+ let scene = scenes.find((s) => s.id === sceneId || s.id?.startsWith(sceneId));
599
+ if (!scene && !isNaN(parseInt(sceneId))) {
600
+ scene = scenes[parseInt(sceneId) - 1]; // 1-indexed
601
+ }
602
+ if (!scene) {
603
+ error(`Scene not found: ${sceneId}`);
604
+ return;
605
+ }
606
+ console.log(chalk.cyan(`// ${scene.name || 'Untitled'}`));
607
+ console.log(scene.tsxCode || '// No code');
608
+ }
609
+ }
610
+ async function deleteScene(sceneId, state) {
611
+ if (!state.config.activeProjectId) {
612
+ error('No active project', 'Run: project use <id>');
613
+ return;
614
+ }
615
+ const s = status('Deleting');
616
+ await apiRequest(state.config, 'scenes.deleteScene', {
617
+ projectId: state.config.activeProjectId,
618
+ sceneId,
619
+ });
620
+ s.done();
621
+ success(`Deleted scene: ${sceneId}`);
622
+ }
623
+ // ============================================
624
+ // UI helpers
625
+ // ============================================
626
+ function showHelp() {
627
+ console.log(chalk.cyan('Bazaar CLI - Interactive Mode'));
628
+ console.log();
629
+ console.log(chalk.bold('Prompts:'));
630
+ console.log(' Just type to send a prompt to the AI agent');
631
+ console.log(' Example: "Add a title scene with my logo"');
632
+ console.log();
633
+ console.log(chalk.bold('Commands:'));
634
+ console.log(' project list List projects');
635
+ console.log(' project create <name> Create project');
636
+ console.log(' project use <id> Set active project');
637
+ console.log(' project current Show active project');
638
+ console.log();
639
+ console.log(' scenes list List scenes');
640
+ console.log(' scenes code <id> Show scene code');
641
+ console.log(' scenes delete <id> Delete scene');
642
+ console.log();
643
+ console.log(' auth login <key> Set API key');
644
+ console.log(' auth status Show auth status');
645
+ console.log();
646
+ console.log(chalk.bold('Slash Commands:'));
647
+ console.log(' /help Show this help');
648
+ console.log(' /status Show current status');
649
+ console.log(' /use <id> Quick set project');
650
+ console.log(' /clear Clear screen');
651
+ console.log(' /history Show command history');
652
+ console.log(' /exit Exit (or Ctrl+D)');
653
+ }
654
+ function showStatus(state) {
655
+ console.log(chalk.cyan('Status'));
656
+ console.log();
657
+ if (hasAuth(state.config)) {
658
+ console.log(chalk.green('✓'), 'Authenticated');
659
+ console.log(` Key: baz_sk_****${state.config.apiKey?.slice(-4) || ''}`);
660
+ }
661
+ else {
662
+ console.log(chalk.yellow('○'), 'Not authenticated');
663
+ }
664
+ console.log();
665
+ console.log(`API: ${state.config.apiUrl}`);
666
+ console.log(`Project: ${state.config.activeProjectId?.slice(0, 8) || chalk.gray('none')}`);
667
+ console.log(`Config: ${getConfigPath()}`);
668
+ }
669
+ function showHistory(state) {
670
+ if (state.history.length === 0) {
671
+ console.log(chalk.gray('No history yet.'));
672
+ return;
673
+ }
674
+ console.log(chalk.cyan('History'));
675
+ state.history.slice(-20).forEach((cmd, i) => {
676
+ console.log(chalk.gray(`${i + 1}.`), cmd);
677
+ });
678
+ }
679
+ /**
680
+ * Show full project status with timeline
681
+ */
682
+ async function showProjectStatus(state) {
683
+ if (!hasAuth(state.config)) {
684
+ error('Not authenticated', 'Run: auth login <api-key>');
685
+ return;
686
+ }
687
+ if (!state.config.activeProjectId) {
688
+ error('No active project', 'Run: project use <id>');
689
+ return;
690
+ }
691
+ const s = status('Fetching project');
692
+ const project = await apiRequest(state.config, 'project.getById', { id: state.config.activeProjectId });
693
+ s.done();
694
+ const scenes = project.props?.scenes || [];
695
+ const fps = 30;
696
+ // Calculate stats
697
+ let totalFrames = 0;
698
+ let errorCount = 0;
699
+ const trackSet = new Set();
700
+ scenes.forEach((scene) => {
701
+ const end = (scene.startFrame || 0) + (scene.durationInFrames || 0);
702
+ if (end > totalFrames)
703
+ totalFrames = end;
704
+ if (scene.compilationError)
705
+ errorCount++;
706
+ trackSet.add(scene.track ?? 0);
707
+ });
708
+ const totalDuration = totalFrames / fps;
709
+ const trackCount = trackSet.size || 1;
710
+ // Print status
711
+ console.log();
712
+ console.log(chalk.bold('Project Status'));
713
+ console.log(chalk.gray('─'.repeat(50)));
714
+ console.log();
715
+ console.log(` ${chalk.dim('Project:')} ${project.title || 'Untitled'}`);
716
+ console.log(` ${chalk.dim('ID:')} ${state.config.activeProjectId.slice(0, 8)}...`);
717
+ console.log();
718
+ // Stats
719
+ const statsLine = `Scenes: ${chalk.bold(scenes.length)} | Tracks: ${chalk.bold(trackCount)} | Duration: ${chalk.bold(formatDuration(totalDuration))}`;
720
+ const errorPart = errorCount > 0 ? chalk.red(` | Errors: ${errorCount}`) : chalk.green(' | ✓ OK');
721
+ console.log(` ${statsLine}${errorPart}`);
722
+ console.log();
723
+ // Simple ASCII timeline
724
+ if (scenes.length > 0 && totalFrames > 0) {
725
+ const width = 40;
726
+ const sortedTracks = Array.from(trackSet).sort((a, b) => a - b);
727
+ console.log(chalk.dim(' Timeline:'));
728
+ for (const trackNum of sortedTracks) {
729
+ const trackScenes = scenes.filter((s) => (s.track ?? 0) === trackNum);
730
+ let bar = '·'.repeat(width);
731
+ for (const scene of trackScenes) {
732
+ const startFrame = scene.startFrame || 0;
733
+ const duration = scene.durationInFrames || 0;
734
+ const startPos = Math.floor((startFrame / totalFrames) * width);
735
+ const endPos = Math.min(Math.ceil(((startFrame + duration) / totalFrames) * width), width);
736
+ const sceneWidth = Math.max(1, endPos - startPos);
737
+ const char = scene.compilationError ? '!' : '█';
738
+ const block = char.repeat(sceneWidth);
739
+ bar = bar.slice(0, startPos) + block + bar.slice(startPos + sceneWidth);
740
+ }
741
+ const coloredBar = bar.replace(/!/g, chalk.red('!')).replace(/█/g, chalk.green('█'));
742
+ console.log(` T${trackNum} │${coloredBar}│`);
743
+ }
744
+ console.log(` └${'─'.repeat(width)}┘`);
745
+ console.log(` ${chalk.dim('0:00')}${' '.repeat(width - 8)}${chalk.dim(formatDuration(totalDuration))}`);
746
+ console.log();
747
+ }
748
+ // Scene list (abbreviated)
749
+ if (scenes.length > 0) {
750
+ console.log(chalk.dim(' Scenes:'));
751
+ const maxShow = 5;
752
+ const showScenes = scenes.slice(0, maxShow);
753
+ showScenes.forEach((scene, i) => {
754
+ const dur = (scene.durationInFrames || 0) / fps;
755
+ const status = scene.compilationError ? chalk.red('✗') : chalk.green('✓');
756
+ const track = scene.track ?? 0;
757
+ console.log(` ${status} ${(scene.name || 'Untitled').padEnd(20).slice(0, 20)} ${chalk.dim(`${formatDuration(dur)} T${track}`)}`);
758
+ });
759
+ if (scenes.length > maxShow) {
760
+ console.log(chalk.dim(` ... and ${scenes.length - maxShow} more`));
761
+ }
762
+ console.log();
763
+ }
764
+ }