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.
- package/README.md +489 -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 +529 -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 +186 -0
- package/dist/lib/api.js +717 -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 +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
|
+
}
|