bazaar.it 0.2.0 → 0.2.2
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 +7 -13
- package/dist/commands/auth.js +32 -13
- package/dist/commands/export.js +109 -27
- package/dist/commands/loop.js +86 -20
- package/dist/commands/media.js +23 -5
- package/dist/commands/project.js +11 -11
- package/dist/commands/prompt.js +110 -54
- package/dist/commands/scenes.js +4 -4
- package/dist/commands/status.js +34 -12
- package/dist/lib/api.d.ts +2 -0
- package/dist/lib/api.js +2 -0
- package/dist/lib/config.d.ts +5 -2
- package/dist/lib/config.js +32 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ $ baz
|
|
|
66
66
|
██████╔╝██║ ██║███████╗
|
|
67
67
|
╚═════╝ ╚═╝ ╚═╝╚══════╝
|
|
68
68
|
|
|
69
|
-
AI-powered video generation v0.
|
|
69
|
+
AI-powered video generation v0.2.2
|
|
70
70
|
─────────────────────────────────────
|
|
71
71
|
Type a command or prompt. Use 'help' for commands.
|
|
72
72
|
|
|
@@ -97,11 +97,7 @@ baz auth logout
|
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
**Get an API Key:**
|
|
100
|
-
|
|
101
|
-
2. **For service accounts**: Use the admin script:
|
|
102
|
-
```bash
|
|
103
|
-
npx tsx scripts/create-service-account.ts maya@bazaar.it "Maya Bot" "Maya CLI"
|
|
104
|
-
```
|
|
100
|
+
Sign in at [bazaar.it](https://bazaar.it), click your balance in the top bar, then select **API Keys** to generate one.
|
|
105
101
|
|
|
106
102
|
### Projects
|
|
107
103
|
|
|
@@ -161,14 +157,11 @@ baz prompt "Create something cool" --verbose
|
|
|
161
157
|
# List all scenes in active project
|
|
162
158
|
baz scenes list
|
|
163
159
|
|
|
164
|
-
# Get scene details
|
|
165
|
-
baz scenes list
|
|
166
|
-
|
|
167
160
|
# Get scene TSX code
|
|
168
161
|
baz scenes code <scene-id>
|
|
169
162
|
|
|
170
|
-
#
|
|
171
|
-
baz scenes code <scene-id>
|
|
163
|
+
# Replace scene TSX code from file
|
|
164
|
+
baz scenes set-code <scene-id> --file ./scene.tsx
|
|
172
165
|
```
|
|
173
166
|
|
|
174
167
|
### Media Upload
|
|
@@ -350,6 +343,7 @@ Returns:
|
|
|
350
343
|
|
|
351
344
|
```
|
|
352
345
|
--json Output as JSON (for scripting)
|
|
346
|
+
--compact Compact JSON output (token-efficient for agents)
|
|
353
347
|
--verbose Show detailed output
|
|
354
348
|
--config <path> Custom config file path
|
|
355
349
|
--api-url <url> Override API URL
|
|
@@ -410,7 +404,7 @@ export BAZ_API_KEY="$BAZAAR_API_KEY"
|
|
|
410
404
|
baz prompt "Create demo video: $PR_DESCRIPTION" --project-id "$PROJECT_ID"
|
|
411
405
|
|
|
412
406
|
# Export when ready
|
|
413
|
-
baz export --json > export-result.json
|
|
407
|
+
baz export start --wait --json > export-result.json
|
|
414
408
|
```
|
|
415
409
|
|
|
416
410
|
### Complex Workflow
|
|
@@ -438,7 +432,7 @@ baz project use <project-id>
|
|
|
438
432
|
|
|
439
433
|
### "Insufficient balance"
|
|
440
434
|
|
|
441
|
-
Check balance at https://bazaar.it
|
|
435
|
+
Check your balance in the app at [bazaar.it](https://bazaar.it) — visible in the top bar after signing in.
|
|
442
436
|
|
|
443
437
|
### Connection Issues
|
|
444
438
|
|
package/dist/commands/auth.js
CHANGED
|
@@ -4,6 +4,15 @@ import { loadConfig, saveConfig, hasAuth, getConfigPath } from '../lib/config.js
|
|
|
4
4
|
import { success, error, info, output } from '../lib/output.js';
|
|
5
5
|
export const authCommand = new Command('auth')
|
|
6
6
|
.description('Manage authentication');
|
|
7
|
+
function getGlobalOptsFromActionArgs(actionArgs) {
|
|
8
|
+
for (let i = actionArgs.length - 1; i >= 0; i--) {
|
|
9
|
+
const maybeCmd = actionArgs[i];
|
|
10
|
+
if (maybeCmd && typeof maybeCmd.optsWithGlobals === 'function') {
|
|
11
|
+
return maybeCmd.optsWithGlobals();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
7
16
|
/**
|
|
8
17
|
* baz auth login
|
|
9
18
|
*/
|
|
@@ -11,7 +20,13 @@ authCommand
|
|
|
11
20
|
.command('login')
|
|
12
21
|
.description('Configure API key for authentication')
|
|
13
22
|
.argument('[api-key]', 'API key (or set BAZ_API_KEY env var)')
|
|
14
|
-
.action(async (
|
|
23
|
+
.action(async (...actionArgs) => {
|
|
24
|
+
const apiKey = actionArgs[0];
|
|
25
|
+
const globalOpts = getGlobalOptsFromActionArgs(actionArgs);
|
|
26
|
+
const config = loadConfig({
|
|
27
|
+
configPath: globalOpts.config,
|
|
28
|
+
apiUrl: globalOpts.apiUrl,
|
|
29
|
+
});
|
|
15
30
|
// If no key provided, check environment
|
|
16
31
|
const key = apiKey || process.env.BAZ_API_KEY;
|
|
17
32
|
if (!key) {
|
|
@@ -23,7 +38,7 @@ authCommand
|
|
|
23
38
|
console.log(' 1. Pass directly: baz auth login <your-api-key>');
|
|
24
39
|
console.log(' 2. Environment: export BAZ_API_KEY=<your-api-key>');
|
|
25
40
|
console.log();
|
|
26
|
-
console.log(chalk.gray('Get your API key at https://bazaar.it/api'));
|
|
41
|
+
console.log(chalk.gray('Get your API key at https://bazaar.it/settings/api-keys'));
|
|
27
42
|
process.exit(1);
|
|
28
43
|
}
|
|
29
44
|
// Validate key format
|
|
@@ -32,9 +47,8 @@ authCommand
|
|
|
32
47
|
process.exit(1);
|
|
33
48
|
}
|
|
34
49
|
// Save to config first (needed for the validation request)
|
|
35
|
-
saveConfig({ apiKey: key });
|
|
50
|
+
saveConfig({ apiKey: key, apiUrl: config.apiUrl }, { configPath: globalOpts.config });
|
|
36
51
|
// Validate key against server
|
|
37
|
-
const config = loadConfig();
|
|
38
52
|
try {
|
|
39
53
|
const url = `${config.apiUrl}/api/trpc/apiKey.list`;
|
|
40
54
|
const res = await fetch(url, {
|
|
@@ -43,10 +57,10 @@ authCommand
|
|
|
43
57
|
});
|
|
44
58
|
if (res.ok) {
|
|
45
59
|
success('Authenticated successfully');
|
|
46
|
-
console.log(` Key saved to ${chalk.gray(getConfigPath())}`);
|
|
60
|
+
console.log(` Key saved to ${chalk.gray(getConfigPath(globalOpts.config))}`);
|
|
47
61
|
}
|
|
48
62
|
else if (res.status === 401) {
|
|
49
|
-
saveConfig({ apiKey: undefined });
|
|
63
|
+
saveConfig({ apiKey: undefined, apiUrl: config.apiUrl }, { configPath: globalOpts.config });
|
|
50
64
|
error('Invalid API key', 'The key was not recognized by the server. Check for typos or generate a new key.');
|
|
51
65
|
process.exit(1);
|
|
52
66
|
}
|
|
@@ -66,8 +80,8 @@ authCommand
|
|
|
66
80
|
authCommand
|
|
67
81
|
.command('status')
|
|
68
82
|
.description('Show current authentication status')
|
|
69
|
-
.action(async (
|
|
70
|
-
const globalOpts =
|
|
83
|
+
.action(async (...actionArgs) => {
|
|
84
|
+
const globalOpts = getGlobalOptsFromActionArgs(actionArgs);
|
|
71
85
|
const config = loadConfig({
|
|
72
86
|
configPath: globalOpts.config,
|
|
73
87
|
apiUrl: globalOpts.apiUrl,
|
|
@@ -76,10 +90,10 @@ authCommand
|
|
|
76
90
|
authenticated: hasAuth(config),
|
|
77
91
|
apiUrl: config.apiUrl,
|
|
78
92
|
activeProjectId: config.activeProjectId || null,
|
|
79
|
-
configFile: getConfigPath(),
|
|
93
|
+
configFile: getConfigPath(globalOpts.config),
|
|
80
94
|
};
|
|
81
95
|
if (globalOpts.json) {
|
|
82
|
-
output(data, { json: true });
|
|
96
|
+
output(data, { json: true, compact: globalOpts.compact });
|
|
83
97
|
return;
|
|
84
98
|
}
|
|
85
99
|
console.log(chalk.cyan('Bazaar CLI Status'));
|
|
@@ -95,7 +109,7 @@ authCommand
|
|
|
95
109
|
console.log();
|
|
96
110
|
console.log(`API URL: ${config.apiUrl}`);
|
|
97
111
|
console.log(`Active Project: ${config.activeProjectId || chalk.gray('none')}`);
|
|
98
|
-
console.log(`Config: ${getConfigPath()}`);
|
|
112
|
+
console.log(`Config: ${getConfigPath(globalOpts.config)}`);
|
|
99
113
|
});
|
|
100
114
|
/**
|
|
101
115
|
* baz auth logout
|
|
@@ -103,7 +117,12 @@ authCommand
|
|
|
103
117
|
authCommand
|
|
104
118
|
.command('logout')
|
|
105
119
|
.description('Clear stored credentials')
|
|
106
|
-
.action(async () => {
|
|
107
|
-
|
|
120
|
+
.action(async (...actionArgs) => {
|
|
121
|
+
const globalOpts = getGlobalOptsFromActionArgs(actionArgs);
|
|
122
|
+
const config = loadConfig({
|
|
123
|
+
configPath: globalOpts.config,
|
|
124
|
+
apiUrl: globalOpts.apiUrl,
|
|
125
|
+
});
|
|
126
|
+
saveConfig({ apiKey: undefined, apiUrl: config.apiUrl }, { configPath: globalOpts.config });
|
|
108
127
|
success('Logged out. API key removed from config.');
|
|
109
128
|
});
|
package/dist/commands/export.js
CHANGED
|
@@ -68,7 +68,7 @@ exportCommand
|
|
|
68
68
|
}
|
|
69
69
|
process.exit(64); // Input error exit code
|
|
70
70
|
}
|
|
71
|
-
const spinner = ora('Starting export...').start();
|
|
71
|
+
const spinner = globalOpts.json ? null : ora('Starting export...').start();
|
|
72
72
|
const startTime = Date.now();
|
|
73
73
|
const timeoutMs = parseInt(options.timeout, 10) * 1000;
|
|
74
74
|
try {
|
|
@@ -80,7 +80,8 @@ exportCommand
|
|
|
80
80
|
const renderId = result.renderId || result.id;
|
|
81
81
|
// If --wait flag, block until complete
|
|
82
82
|
if (options.wait) {
|
|
83
|
-
spinner
|
|
83
|
+
if (spinner)
|
|
84
|
+
spinner.text = 'Rendering...';
|
|
84
85
|
const checkStatus = async () => {
|
|
85
86
|
return await apiRequest(config, 'render.getRenderStatus', { renderId });
|
|
86
87
|
};
|
|
@@ -90,7 +91,7 @@ exportCommand
|
|
|
90
91
|
// Check timeout
|
|
91
92
|
const elapsed = Date.now() - startTime;
|
|
92
93
|
if (elapsed > timeoutMs) {
|
|
93
|
-
spinner
|
|
94
|
+
spinner?.fail();
|
|
94
95
|
if (globalOpts.json) {
|
|
95
96
|
console.log(JSON.stringify({
|
|
96
97
|
type: 'error',
|
|
@@ -122,11 +123,12 @@ exportCommand
|
|
|
122
123
|
}));
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
|
-
spinner
|
|
126
|
+
if (spinner)
|
|
127
|
+
spinner.text = `Rendering... ${progress}%`;
|
|
126
128
|
await new Promise(r => setTimeout(r, 3000));
|
|
127
129
|
status = await checkStatus();
|
|
128
130
|
}
|
|
129
|
-
spinner
|
|
131
|
+
spinner?.stop();
|
|
130
132
|
if (status.status === 'completed' || status.status === 'done') {
|
|
131
133
|
const finalResult = {
|
|
132
134
|
type: 'export_completed',
|
|
@@ -137,7 +139,7 @@ exportCommand
|
|
|
137
139
|
continue: false,
|
|
138
140
|
};
|
|
139
141
|
if (globalOpts.json) {
|
|
140
|
-
output(finalResult, { json: true });
|
|
142
|
+
output(finalResult, { json: true, compact: globalOpts.compact });
|
|
141
143
|
}
|
|
142
144
|
else {
|
|
143
145
|
success('Export complete!');
|
|
@@ -168,14 +170,14 @@ exportCommand
|
|
|
168
170
|
}
|
|
169
171
|
else {
|
|
170
172
|
// Non-blocking mode (original behavior)
|
|
171
|
-
spinner
|
|
173
|
+
spinner?.stop();
|
|
172
174
|
if (globalOpts.json) {
|
|
173
175
|
output({
|
|
174
176
|
type: 'export_started',
|
|
175
177
|
exportId: renderId,
|
|
176
178
|
status: 'pending',
|
|
177
179
|
continue: true, // Agent should poll for completion
|
|
178
|
-
}, { json: true });
|
|
180
|
+
}, { json: true, compact: globalOpts.compact });
|
|
179
181
|
return;
|
|
180
182
|
}
|
|
181
183
|
success(`Export started!`);
|
|
@@ -186,7 +188,7 @@ exportCommand
|
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
190
|
catch (err) {
|
|
189
|
-
spinner
|
|
191
|
+
spinner?.stop();
|
|
190
192
|
if (err instanceof ApiError) {
|
|
191
193
|
if (globalOpts.json) {
|
|
192
194
|
output({ ...err.toJSON(), continue: false }, { json: true, compact: globalOpts.compact });
|
|
@@ -237,18 +239,19 @@ exportCommand
|
|
|
237
239
|
return result;
|
|
238
240
|
};
|
|
239
241
|
if (options.wait) {
|
|
240
|
-
const spinner = ora('Waiting for export to complete...').start();
|
|
242
|
+
const spinner = globalOpts.json ? null : ora('Waiting for export to complete...').start();
|
|
241
243
|
try {
|
|
242
244
|
let status = await checkStatus();
|
|
243
245
|
while (status.status === 'pending' || status.status === 'rendering' || status.status === 'in_progress') {
|
|
244
246
|
const progress = status.progress || 0;
|
|
245
|
-
spinner
|
|
247
|
+
if (spinner)
|
|
248
|
+
spinner.text = `Rendering... ${progress}%`;
|
|
246
249
|
await new Promise(r => setTimeout(r, 3000));
|
|
247
250
|
status = await checkStatus();
|
|
248
251
|
}
|
|
249
|
-
spinner
|
|
252
|
+
spinner?.stop();
|
|
250
253
|
if (globalOpts.json) {
|
|
251
|
-
output(status, { json: true });
|
|
254
|
+
output(status, { json: true, compact: globalOpts.compact });
|
|
252
255
|
return;
|
|
253
256
|
}
|
|
254
257
|
if (status.status === 'completed' || status.status === 'done') {
|
|
@@ -263,18 +266,40 @@ exportCommand
|
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
catch (err) {
|
|
266
|
-
spinner
|
|
267
|
-
|
|
269
|
+
spinner?.stop();
|
|
270
|
+
if (err instanceof ApiError) {
|
|
271
|
+
if (globalOpts.json) {
|
|
272
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
error(err.message, err.suggestion);
|
|
276
|
+
}
|
|
277
|
+
process.exit(err.exitCode);
|
|
278
|
+
}
|
|
279
|
+
if (globalOpts.json) {
|
|
280
|
+
output({
|
|
281
|
+
type: 'error',
|
|
282
|
+
code: 'UNKNOWN',
|
|
283
|
+
message: err.message,
|
|
284
|
+
category: 'fatal',
|
|
285
|
+
retryable: false,
|
|
286
|
+
transient: false,
|
|
287
|
+
exitCode: 1,
|
|
288
|
+
}, { json: true, compact: globalOpts.compact });
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
error(err.message);
|
|
292
|
+
}
|
|
268
293
|
process.exit(1);
|
|
269
294
|
}
|
|
270
295
|
}
|
|
271
296
|
else {
|
|
272
|
-
const spinner = ora('Checking status...').start();
|
|
297
|
+
const spinner = globalOpts.json ? null : ora('Checking status...').start();
|
|
273
298
|
try {
|
|
274
299
|
const status = await checkStatus();
|
|
275
|
-
spinner
|
|
300
|
+
spinner?.stop();
|
|
276
301
|
if (globalOpts.json) {
|
|
277
|
-
output(status, { json: true });
|
|
302
|
+
output(status, { json: true, compact: globalOpts.compact });
|
|
278
303
|
return;
|
|
279
304
|
}
|
|
280
305
|
const statusColor = status.status === 'completed' || status.status === 'done' ? chalk.green :
|
|
@@ -292,8 +317,30 @@ exportCommand
|
|
|
292
317
|
}
|
|
293
318
|
}
|
|
294
319
|
catch (err) {
|
|
295
|
-
spinner
|
|
296
|
-
|
|
320
|
+
spinner?.stop();
|
|
321
|
+
if (err instanceof ApiError) {
|
|
322
|
+
if (globalOpts.json) {
|
|
323
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
error(err.message, err.suggestion);
|
|
327
|
+
}
|
|
328
|
+
process.exit(err.exitCode);
|
|
329
|
+
}
|
|
330
|
+
if (globalOpts.json) {
|
|
331
|
+
output({
|
|
332
|
+
type: 'error',
|
|
333
|
+
code: 'UNKNOWN',
|
|
334
|
+
message: err.message,
|
|
335
|
+
category: 'fatal',
|
|
336
|
+
retryable: false,
|
|
337
|
+
transient: false,
|
|
338
|
+
exitCode: 1,
|
|
339
|
+
}, { json: true, compact: globalOpts.compact });
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
error(err.message);
|
|
343
|
+
}
|
|
297
344
|
process.exit(1);
|
|
298
345
|
}
|
|
299
346
|
}
|
|
@@ -320,18 +367,31 @@ exportCommand
|
|
|
320
367
|
projectId = getProjectId(config, globalOpts.projectId);
|
|
321
368
|
}
|
|
322
369
|
catch (err) {
|
|
323
|
-
|
|
324
|
-
|
|
370
|
+
if (globalOpts.json) {
|
|
371
|
+
output({
|
|
372
|
+
type: 'error',
|
|
373
|
+
code: 'VALIDATION',
|
|
374
|
+
message: err.message,
|
|
375
|
+
category: 'validation',
|
|
376
|
+
retryable: false,
|
|
377
|
+
transient: false,
|
|
378
|
+
exitCode: 64,
|
|
379
|
+
}, { json: true, compact: globalOpts.compact });
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
error(err.message);
|
|
383
|
+
}
|
|
384
|
+
process.exit(64);
|
|
325
385
|
}
|
|
326
|
-
const spinner = ora('Fetching exports...').start();
|
|
386
|
+
const spinner = globalOpts.json ? null : ora('Fetching exports...').start();
|
|
327
387
|
try {
|
|
328
388
|
const result = await apiRequest(config, 'render.listRenders', {
|
|
329
389
|
projectId,
|
|
330
390
|
limit: parseInt(options.limit, 10),
|
|
331
391
|
});
|
|
332
|
-
spinner
|
|
392
|
+
spinner?.stop();
|
|
333
393
|
if (globalOpts.json) {
|
|
334
|
-
output(result, { json: true });
|
|
394
|
+
output(result, { json: true, compact: globalOpts.compact });
|
|
335
395
|
return;
|
|
336
396
|
}
|
|
337
397
|
const renders = Array.isArray(result) ? result : result.renders || [];
|
|
@@ -353,8 +413,30 @@ exportCommand
|
|
|
353
413
|
}
|
|
354
414
|
}
|
|
355
415
|
catch (err) {
|
|
356
|
-
spinner
|
|
357
|
-
|
|
416
|
+
spinner?.stop();
|
|
417
|
+
if (err instanceof ApiError) {
|
|
418
|
+
if (globalOpts.json) {
|
|
419
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
error(err.message, err.suggestion);
|
|
423
|
+
}
|
|
424
|
+
process.exit(err.exitCode);
|
|
425
|
+
}
|
|
426
|
+
if (globalOpts.json) {
|
|
427
|
+
output({
|
|
428
|
+
type: 'error',
|
|
429
|
+
code: 'UNKNOWN',
|
|
430
|
+
message: err.message,
|
|
431
|
+
category: 'fatal',
|
|
432
|
+
retryable: false,
|
|
433
|
+
transient: false,
|
|
434
|
+
exitCode: 1,
|
|
435
|
+
}, { json: true, compact: globalOpts.compact });
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
error(err.message);
|
|
439
|
+
}
|
|
358
440
|
process.exit(1);
|
|
359
441
|
}
|
|
360
442
|
});
|
package/dist/commands/loop.js
CHANGED
|
@@ -75,7 +75,7 @@ function parseBudgetMs(input) {
|
|
|
75
75
|
}
|
|
76
76
|
export const loopCommand = new Command('loop')
|
|
77
77
|
.description('Iterate prompt → review in a basic OODA loop')
|
|
78
|
-
.argument('
|
|
78
|
+
.argument('[text]', 'The prompt text (or use --file)')
|
|
79
79
|
.option('--mode <mode>', 'Generation mode: agent (default), agent-max, multi-scene', 'agent')
|
|
80
80
|
.option('--max', 'Shorthand for --mode agent-max')
|
|
81
81
|
.option('--max-iterations <n>', 'Maximum iterations (default: 3)', '3')
|
|
@@ -93,6 +93,15 @@ export const loopCommand = new Command('loop')
|
|
|
93
93
|
const agentModeEnv = process.env.BAZ_AGENT === '1';
|
|
94
94
|
const jsonOutput = globalOpts.json || agentModeEnv;
|
|
95
95
|
const streamJson = options.streamJson === true || agentModeEnv;
|
|
96
|
+
const emitCliError = (payload, fallbackMessage, fallbackSuggestion) => {
|
|
97
|
+
if (streamJson || jsonOutput) {
|
|
98
|
+
console.log(JSON.stringify(payload));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
error(fallbackMessage, fallbackSuggestion);
|
|
102
|
+
}
|
|
103
|
+
process.exit(typeof payload.exitCode === 'number' ? payload.exitCode : 1);
|
|
104
|
+
};
|
|
96
105
|
const config = loadConfig({
|
|
97
106
|
configPath: globalOpts.config,
|
|
98
107
|
apiUrl: globalOpts.apiUrl,
|
|
@@ -141,13 +150,27 @@ export const loopCommand = new Command('loop')
|
|
|
141
150
|
promptText = readFileSync(options.file, 'utf-8').trim();
|
|
142
151
|
}
|
|
143
152
|
catch {
|
|
144
|
-
|
|
145
|
-
|
|
153
|
+
emitCliError({
|
|
154
|
+
type: 'error',
|
|
155
|
+
code: 'VALIDATION',
|
|
156
|
+
message: `Failed to read file: ${options.file}`,
|
|
157
|
+
category: 'validation',
|
|
158
|
+
retryable: false,
|
|
159
|
+
continue: false,
|
|
160
|
+
exitCode: 64,
|
|
161
|
+
}, `Failed to read file: ${options.file}`);
|
|
146
162
|
}
|
|
147
163
|
}
|
|
148
164
|
if (!promptText) {
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
emitCliError({
|
|
166
|
+
type: 'error',
|
|
167
|
+
code: 'VALIDATION',
|
|
168
|
+
message: 'No prompt provided',
|
|
169
|
+
category: 'validation',
|
|
170
|
+
retryable: false,
|
|
171
|
+
continue: false,
|
|
172
|
+
exitCode: 64,
|
|
173
|
+
}, 'No prompt provided');
|
|
151
174
|
}
|
|
152
175
|
const mode = options.max ? 'agent-max' : options.mode;
|
|
153
176
|
const requirements = normalizeRequirements(options.requirements);
|
|
@@ -159,39 +182,60 @@ export const loopCommand = new Command('loop')
|
|
|
159
182
|
if (options.plan)
|
|
160
183
|
maxIterations = 1;
|
|
161
184
|
if (budgetMs !== undefined && budgetMs <= 0) {
|
|
162
|
-
|
|
163
|
-
|
|
185
|
+
emitCliError({
|
|
186
|
+
type: 'error',
|
|
187
|
+
code: 'VALIDATION',
|
|
188
|
+
message: 'Budget must be a positive duration (e.g., 30s, 2m)',
|
|
189
|
+
category: 'validation',
|
|
190
|
+
retryable: false,
|
|
191
|
+
continue: false,
|
|
192
|
+
exitCode: 64,
|
|
193
|
+
}, 'Budget must be a positive duration (e.g., 30s, 2m)');
|
|
164
194
|
}
|
|
165
195
|
// Handle image uploads
|
|
166
196
|
const imageUrls = [];
|
|
167
197
|
if (options.image && options.image.length > 0) {
|
|
168
|
-
const uploadSpinner = ora('Uploading images...').start();
|
|
198
|
+
const uploadSpinner = (streamJson || jsonOutput) ? null : ora('Uploading images...').start();
|
|
169
199
|
try {
|
|
170
200
|
for (const imagePath of options.image) {
|
|
171
201
|
if (!existsSync(imagePath)) {
|
|
172
|
-
uploadSpinner
|
|
173
|
-
|
|
174
|
-
|
|
202
|
+
uploadSpinner?.fail();
|
|
203
|
+
emitCliError({
|
|
204
|
+
type: 'error',
|
|
205
|
+
code: 'NOT_FOUND',
|
|
206
|
+
message: `Image file not found: ${imagePath}`,
|
|
207
|
+
category: 'validation',
|
|
208
|
+
retryable: false,
|
|
209
|
+
continue: false,
|
|
210
|
+
exitCode: 64,
|
|
211
|
+
}, `Image file not found: ${imagePath}`);
|
|
175
212
|
}
|
|
176
213
|
const imageBuffer = readFileSync(imagePath);
|
|
177
214
|
const url = await uploadImage(config, { projectId, buffer: imageBuffer, filename: imagePath });
|
|
178
215
|
imageUrls.push(url);
|
|
179
216
|
}
|
|
180
|
-
uploadSpinner
|
|
217
|
+
uploadSpinner?.succeed(`Uploaded ${imageUrls.length} image${imageUrls.length > 1 ? 's' : ''}`);
|
|
181
218
|
}
|
|
182
219
|
catch (err) {
|
|
183
|
-
uploadSpinner
|
|
220
|
+
uploadSpinner?.fail();
|
|
184
221
|
const errMsg = err instanceof ApiError
|
|
185
222
|
? `${err.message}${err.suggestion ? ` — ${err.suggestion}` : ''}`
|
|
186
223
|
: err.message;
|
|
187
|
-
|
|
188
|
-
|
|
224
|
+
emitCliError({
|
|
225
|
+
type: 'error',
|
|
226
|
+
code: err instanceof ApiError ? err.code : 'UNKNOWN',
|
|
227
|
+
message: `Failed to upload images: ${errMsg}`,
|
|
228
|
+
category: err instanceof ApiError ? err.category : 'fatal',
|
|
229
|
+
retryable: err instanceof ApiError ? err.retryable : false,
|
|
230
|
+
continue: false,
|
|
231
|
+
exitCode: err instanceof ApiError ? err.exitCode : 1,
|
|
232
|
+
}, `Failed to upload images: ${errMsg}`);
|
|
189
233
|
}
|
|
190
234
|
}
|
|
191
235
|
// Handle URL attachments
|
|
192
236
|
const urlContents = [];
|
|
193
237
|
if (options.url && options.url.length > 0) {
|
|
194
|
-
const urlSpinner = ora('Fetching URLs...').start();
|
|
238
|
+
const urlSpinner = (streamJson || jsonOutput) ? null : ora('Fetching URLs...').start();
|
|
195
239
|
try {
|
|
196
240
|
for (const url of options.url) {
|
|
197
241
|
const result = await fetchUrlContent(url);
|
|
@@ -201,15 +245,22 @@ export const loopCommand = new Command('loop')
|
|
|
201
245
|
content: result.content.slice(0, 10000),
|
|
202
246
|
});
|
|
203
247
|
}
|
|
204
|
-
urlSpinner
|
|
248
|
+
urlSpinner?.succeed(`Fetched ${urlContents.length} URL${urlContents.length > 1 ? 's' : ''}`);
|
|
205
249
|
}
|
|
206
250
|
catch (err) {
|
|
207
|
-
urlSpinner
|
|
251
|
+
urlSpinner?.fail();
|
|
208
252
|
const errMsg = err instanceof ApiError
|
|
209
253
|
? `${err.message}${err.details ? ` — ${err.details}` : ''}`
|
|
210
254
|
: err.message;
|
|
211
|
-
|
|
212
|
-
|
|
255
|
+
emitCliError({
|
|
256
|
+
type: 'error',
|
|
257
|
+
code: err instanceof ApiError ? err.code : 'UNKNOWN',
|
|
258
|
+
message: `Failed to fetch URL: ${errMsg}`,
|
|
259
|
+
category: err instanceof ApiError ? err.category : 'fatal',
|
|
260
|
+
retryable: err instanceof ApiError ? err.retryable : false,
|
|
261
|
+
continue: false,
|
|
262
|
+
exitCode: err instanceof ApiError ? err.exitCode : 1,
|
|
263
|
+
}, `Failed to fetch URL: ${errMsg}`);
|
|
213
264
|
}
|
|
214
265
|
if (urlContents.length > 0) {
|
|
215
266
|
const urlContext = urlContents.map(u => `--- Content from ${u.url}${u.title ? ` (${u.title})` : ''} ---\n${u.content}`).join('\n\n');
|
|
@@ -249,6 +300,7 @@ export const loopCommand = new Command('loop')
|
|
|
249
300
|
let currentPrompt = basePrompt;
|
|
250
301
|
const results = [];
|
|
251
302
|
const loopStart = Date.now();
|
|
303
|
+
let fatalErrorMessage = null;
|
|
252
304
|
const emit = (payload) => {
|
|
253
305
|
if (streamJson) {
|
|
254
306
|
console.log(JSON.stringify(payload));
|
|
@@ -376,6 +428,7 @@ export const loopCommand = new Command('loop')
|
|
|
376
428
|
if (spinner)
|
|
377
429
|
spinner.stop();
|
|
378
430
|
const message = err instanceof Error ? err.message : String(err);
|
|
431
|
+
fatalErrorMessage = message;
|
|
379
432
|
if (streamJson || jsonOutput) {
|
|
380
433
|
console.log(JSON.stringify({
|
|
381
434
|
type: 'error',
|
|
@@ -535,4 +588,17 @@ export const loopCommand = new Command('loop')
|
|
|
535
588
|
projectId,
|
|
536
589
|
mode,
|
|
537
590
|
});
|
|
591
|
+
if (fatalErrorMessage) {
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
const lastResult = results.length > 0 ? results[results.length - 1] : null;
|
|
595
|
+
if (lastResult) {
|
|
596
|
+
if (lastResult.success === false) {
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
if (Array.isArray(lastResult.requirements?.missing) &&
|
|
600
|
+
lastResult.requirements.missing.length > 0) {
|
|
601
|
+
process.exit(65);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
538
604
|
});
|
package/dist/commands/media.js
CHANGED
|
@@ -4,7 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import { readFileSync, existsSync, statSync } from 'fs';
|
|
5
5
|
import { basename, extname } from 'path';
|
|
6
6
|
import { loadConfig, hasAuth, getProjectId } from '../lib/config.js';
|
|
7
|
-
import { apiRequest, uploadMedia } from '../lib/api.js';
|
|
7
|
+
import { apiRequest, uploadMedia, ApiError } from '../lib/api.js';
|
|
8
8
|
import { error, output, table } from '../lib/output.js';
|
|
9
9
|
/**
|
|
10
10
|
* Format file size in human-readable format
|
|
@@ -204,7 +204,7 @@ mediaCommand
|
|
|
204
204
|
console.log(chalk.gray(` Project: ${projectId}`));
|
|
205
205
|
console.log();
|
|
206
206
|
}
|
|
207
|
-
const spinner = ora('Uploading...').start();
|
|
207
|
+
const spinner = globalOpts.json ? null : ora('Uploading...').start();
|
|
208
208
|
try {
|
|
209
209
|
const buffer = readFileSync(filePath);
|
|
210
210
|
const result = await uploadMedia(config, {
|
|
@@ -213,9 +213,9 @@ mediaCommand
|
|
|
213
213
|
filename,
|
|
214
214
|
mimeType,
|
|
215
215
|
});
|
|
216
|
-
spinner
|
|
216
|
+
spinner?.succeed('Upload complete');
|
|
217
217
|
if (globalOpts.json) {
|
|
218
|
-
output(result, { json: true });
|
|
218
|
+
output(result, { json: true, compact: globalOpts.compact });
|
|
219
219
|
}
|
|
220
220
|
else {
|
|
221
221
|
console.log();
|
|
@@ -230,7 +230,16 @@ mediaCommand
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
catch (err) {
|
|
233
|
-
spinner
|
|
233
|
+
spinner?.fail('Upload failed');
|
|
234
|
+
if (err instanceof ApiError) {
|
|
235
|
+
if (globalOpts.json) {
|
|
236
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
error(err.message, err.suggestion);
|
|
240
|
+
}
|
|
241
|
+
process.exit(err.exitCode);
|
|
242
|
+
}
|
|
234
243
|
if (globalOpts.json) {
|
|
235
244
|
output({
|
|
236
245
|
type: 'error',
|
|
@@ -343,6 +352,15 @@ mediaCommand
|
|
|
343
352
|
}
|
|
344
353
|
catch (err) {
|
|
345
354
|
spinner?.stop();
|
|
355
|
+
if (err instanceof ApiError) {
|
|
356
|
+
if (globalOpts.json) {
|
|
357
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
error(err.message, err.suggestion);
|
|
361
|
+
}
|
|
362
|
+
process.exit(err.exitCode);
|
|
363
|
+
}
|
|
346
364
|
if (globalOpts.json) {
|
|
347
365
|
output({
|
|
348
366
|
type: 'error',
|
package/dist/commands/project.js
CHANGED
|
@@ -115,7 +115,7 @@ projectCommand
|
|
|
115
115
|
spinner?.stop();
|
|
116
116
|
// Set as active project unless --no-activate
|
|
117
117
|
if (options.activate !== false) {
|
|
118
|
-
saveConfig({ activeProjectId: projectId });
|
|
118
|
+
saveConfig({ activeProjectId: projectId }, { configPath: globalOpts.config });
|
|
119
119
|
}
|
|
120
120
|
if (globalOpts.json) {
|
|
121
121
|
output({
|
|
@@ -178,7 +178,7 @@ projectCommand
|
|
|
178
178
|
updatedAt: p.updatedAt,
|
|
179
179
|
createdAt: p.createdAt,
|
|
180
180
|
}));
|
|
181
|
-
output(lean, { json: true });
|
|
181
|
+
output(lean, { json: true, compact: globalOpts.compact });
|
|
182
182
|
return;
|
|
183
183
|
}
|
|
184
184
|
if (projects.length === 0) {
|
|
@@ -229,9 +229,9 @@ projectCommand
|
|
|
229
229
|
try {
|
|
230
230
|
const result = await apiRequest(config, 'project.getById', { id });
|
|
231
231
|
spinner?.stop();
|
|
232
|
-
saveConfig({ activeProjectId: id });
|
|
232
|
+
saveConfig({ activeProjectId: id }, { configPath: globalOpts.config });
|
|
233
233
|
if (globalOpts.json) {
|
|
234
|
-
output({ activeProjectId: id, project: result }, { json: true });
|
|
234
|
+
output({ activeProjectId: id, project: result }, { json: true, compact: globalOpts.compact });
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
237
237
|
success(`Active project: ${chalk.bold(result.title || result.name || 'Untitled')} (${id})`);
|
|
@@ -256,7 +256,7 @@ projectCommand
|
|
|
256
256
|
});
|
|
257
257
|
if (!config.activeProjectId) {
|
|
258
258
|
if (globalOpts.json) {
|
|
259
|
-
output({ activeProjectId: null }, { json: true });
|
|
259
|
+
output({ activeProjectId: null }, { json: true, compact: globalOpts.compact });
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
262
|
console.log(chalk.gray('No active project set.'));
|
|
@@ -273,7 +273,7 @@ projectCommand
|
|
|
273
273
|
});
|
|
274
274
|
spinner?.stop();
|
|
275
275
|
if (globalOpts.json) {
|
|
276
|
-
output(result, { json: true });
|
|
276
|
+
output(result, { json: true, compact: globalOpts.compact });
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
279
|
console.log(chalk.cyan('Active Project'));
|
|
@@ -361,7 +361,7 @@ projectCommand
|
|
|
361
361
|
const duplicatedProject = await apiRequest(config, 'project.getById', { id: newProjectId }).catch(() => null);
|
|
362
362
|
const activated = options.activate !== false;
|
|
363
363
|
if (activated) {
|
|
364
|
-
saveConfig({ activeProjectId: newProjectId });
|
|
364
|
+
saveConfig({ activeProjectId: newProjectId }, { configPath: globalOpts.config });
|
|
365
365
|
}
|
|
366
366
|
spinner?.stop();
|
|
367
367
|
const payload = {
|
|
@@ -477,7 +477,7 @@ projectCommand
|
|
|
477
477
|
const project = await apiRequest(config, 'project.getById', { id: newProjectId }).catch(() => null);
|
|
478
478
|
const activated = options.activate !== false;
|
|
479
479
|
if (activated) {
|
|
480
|
-
saveConfig({ activeProjectId: newProjectId });
|
|
480
|
+
saveConfig({ activeProjectId: newProjectId }, { configPath: globalOpts.config });
|
|
481
481
|
}
|
|
482
482
|
spinner?.stop();
|
|
483
483
|
const payload = {
|
|
@@ -758,7 +758,7 @@ projectCommand
|
|
|
758
758
|
const confirmed = await confirmAction(`Delete project ${id}? This cannot be undone.`);
|
|
759
759
|
if (!confirmed) {
|
|
760
760
|
if (globalOpts.json) {
|
|
761
|
-
output({ deleted: false, id, reason: 'cancelled' }, { json: true });
|
|
761
|
+
output({ deleted: false, id, reason: 'cancelled' }, { json: true, compact: globalOpts.compact });
|
|
762
762
|
return;
|
|
763
763
|
}
|
|
764
764
|
console.log(chalk.gray('Cancelled. Use --force to skip confirmation.'));
|
|
@@ -771,10 +771,10 @@ projectCommand
|
|
|
771
771
|
spinner?.stop();
|
|
772
772
|
// Clear active project if it was deleted
|
|
773
773
|
if (config.activeProjectId === id) {
|
|
774
|
-
saveConfig({ activeProjectId: undefined });
|
|
774
|
+
saveConfig({ activeProjectId: undefined }, { configPath: globalOpts.config });
|
|
775
775
|
}
|
|
776
776
|
if (globalOpts.json) {
|
|
777
|
-
output({ deleted: true, id }, { json: true });
|
|
777
|
+
output({ deleted: true, id }, { json: true, compact: globalOpts.compact });
|
|
778
778
|
return;
|
|
779
779
|
}
|
|
780
780
|
success(`Deleted project: ${id}`);
|
package/dist/commands/prompt.js
CHANGED
|
@@ -39,57 +39,58 @@ export const promptCommand = new Command('prompt')
|
|
|
39
39
|
.option('--plan', 'Create a recipe/plan without executing (handshake protocol)')
|
|
40
40
|
.option('--requirements <list>', 'Requirements to verify against (comma-separated)')
|
|
41
41
|
.option('--budget <dollars>', 'Max budget per run in USD (default: 5)', parseFloat)
|
|
42
|
+
.option('--model <model>', 'Agent model: fast, default, max')
|
|
42
43
|
.action(async (text, options, cmd) => {
|
|
43
44
|
const globalOpts = cmd.optsWithGlobals();
|
|
44
45
|
const agentModeEnv = process.env.BAZ_AGENT === '1';
|
|
45
46
|
const jsonOutput = globalOpts.json || agentModeEnv;
|
|
47
|
+
const streamJson = options.streamJson === true || agentModeEnv;
|
|
48
|
+
const emitCliError = (payload, fallbackMessage, fallbackSuggestion) => {
|
|
49
|
+
if (streamJson || jsonOutput) {
|
|
50
|
+
console.log(JSON.stringify(payload));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
error(fallbackMessage, fallbackSuggestion);
|
|
54
|
+
}
|
|
55
|
+
process.exit(typeof payload.exitCode === 'number' ? payload.exitCode : 1);
|
|
56
|
+
};
|
|
46
57
|
const config = loadConfig({
|
|
47
58
|
configPath: globalOpts.config,
|
|
48
59
|
apiUrl: globalOpts.apiUrl,
|
|
49
60
|
projectId: globalOpts.projectId,
|
|
50
61
|
});
|
|
51
62
|
if (!hasAuth(config)) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
error('Not authenticated', 'Run: baz auth login <api-key>');
|
|
67
|
-
}
|
|
68
|
-
process.exit(13); // Auth error exit code
|
|
63
|
+
emitCliError({
|
|
64
|
+
type: 'error',
|
|
65
|
+
code: 'AUTH_MISSING',
|
|
66
|
+
errorType: 'AUTH_MISSING',
|
|
67
|
+
message: 'Not authenticated',
|
|
68
|
+
category: 'auth',
|
|
69
|
+
retryable: false,
|
|
70
|
+
transient: false,
|
|
71
|
+
suggestion: 'Run: baz auth login <api-key>',
|
|
72
|
+
continue: false,
|
|
73
|
+
exitCode: 13,
|
|
74
|
+
}, 'Not authenticated', 'Run: baz auth login <api-key>');
|
|
69
75
|
}
|
|
70
76
|
// Get project ID
|
|
71
|
-
let projectId;
|
|
77
|
+
let projectId = '';
|
|
72
78
|
try {
|
|
73
79
|
projectId = getProjectId(config, globalOpts.projectId);
|
|
74
80
|
}
|
|
75
81
|
catch (err) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
error(err.message);
|
|
91
|
-
}
|
|
92
|
-
process.exit(64); // Input error exit code
|
|
82
|
+
emitCliError({
|
|
83
|
+
type: 'error',
|
|
84
|
+
code: 'VALIDATION',
|
|
85
|
+
errorType: 'VALIDATION',
|
|
86
|
+
message: err.message,
|
|
87
|
+
category: 'validation',
|
|
88
|
+
retryable: false,
|
|
89
|
+
transient: false,
|
|
90
|
+
suggestion: 'Set active project with: baz project use <id>',
|
|
91
|
+
continue: false,
|
|
92
|
+
exitCode: 64,
|
|
93
|
+
}, err.message);
|
|
93
94
|
}
|
|
94
95
|
// Get prompt text: --file takes precedence, then positional arg
|
|
95
96
|
let promptText = text || '';
|
|
@@ -98,46 +99,83 @@ export const promptCommand = new Command('prompt')
|
|
|
98
99
|
promptText = readFileSync(options.file, 'utf-8').trim();
|
|
99
100
|
}
|
|
100
101
|
catch (err) {
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
emitCliError({
|
|
103
|
+
type: 'error',
|
|
104
|
+
code: 'VALIDATION',
|
|
105
|
+
errorType: 'VALIDATION',
|
|
106
|
+
message: `Failed to read file: ${options.file}`,
|
|
107
|
+
category: 'validation',
|
|
108
|
+
retryable: false,
|
|
109
|
+
transient: false,
|
|
110
|
+
continue: false,
|
|
111
|
+
exitCode: 64,
|
|
112
|
+
}, `Failed to read file: ${options.file}`);
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
115
|
if (!promptText) {
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
emitCliError({
|
|
117
|
+
type: 'error',
|
|
118
|
+
code: 'VALIDATION',
|
|
119
|
+
errorType: 'VALIDATION',
|
|
120
|
+
message: 'No prompt provided. Pass text as argument or use --file <path>',
|
|
121
|
+
category: 'validation',
|
|
122
|
+
retryable: false,
|
|
123
|
+
transient: false,
|
|
124
|
+
continue: false,
|
|
125
|
+
exitCode: 64,
|
|
126
|
+
}, 'No prompt provided. Pass text as argument or use --file <path>');
|
|
108
127
|
}
|
|
109
128
|
// Determine mode
|
|
110
129
|
const mode = options.max ? 'agent-max' : options.mode;
|
|
111
130
|
// Handle image uploads
|
|
112
131
|
const imageUrls = [];
|
|
113
132
|
if (options.image && options.image.length > 0) {
|
|
114
|
-
const uploadSpinner = ora('Uploading images...').start();
|
|
133
|
+
const uploadSpinner = (streamJson || jsonOutput) ? null : ora('Uploading images...').start();
|
|
115
134
|
try {
|
|
116
135
|
for (const imagePath of options.image) {
|
|
117
136
|
if (!existsSync(imagePath)) {
|
|
118
|
-
uploadSpinner
|
|
119
|
-
|
|
120
|
-
|
|
137
|
+
uploadSpinner?.fail();
|
|
138
|
+
emitCliError({
|
|
139
|
+
type: 'error',
|
|
140
|
+
code: 'NOT_FOUND',
|
|
141
|
+
errorType: 'NOT_FOUND',
|
|
142
|
+
message: `Image file not found: ${imagePath}`,
|
|
143
|
+
category: 'validation',
|
|
144
|
+
retryable: false,
|
|
145
|
+
transient: false,
|
|
146
|
+
continue: false,
|
|
147
|
+
exitCode: 64,
|
|
148
|
+
}, `Image file not found: ${imagePath}`);
|
|
121
149
|
}
|
|
122
150
|
const imageBuffer = readFileSync(imagePath);
|
|
123
151
|
const url = await uploadImage(config, { projectId, buffer: imageBuffer, filename: imagePath });
|
|
124
152
|
imageUrls.push(url);
|
|
125
153
|
}
|
|
126
|
-
uploadSpinner
|
|
154
|
+
uploadSpinner?.succeed(`Uploaded ${imageUrls.length} image${imageUrls.length > 1 ? 's' : ''}`);
|
|
127
155
|
}
|
|
128
156
|
catch (err) {
|
|
129
|
-
uploadSpinner
|
|
157
|
+
uploadSpinner?.fail();
|
|
130
158
|
const errMsg = err instanceof ApiError
|
|
131
159
|
? `${err.message}${err.suggestion ? ` — ${err.suggestion}` : ''}`
|
|
132
160
|
: err.message;
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
emitCliError({
|
|
162
|
+
type: 'error',
|
|
163
|
+
code: err instanceof ApiError ? err.code : 'UNKNOWN',
|
|
164
|
+
errorType: err instanceof ApiError ? err.code : 'UNKNOWN',
|
|
165
|
+
message: `Failed to upload images: ${errMsg}`,
|
|
166
|
+
category: err instanceof ApiError ? err.category : 'fatal',
|
|
167
|
+
retryable: err instanceof ApiError ? err.retryable : false,
|
|
168
|
+
transient: err instanceof ApiError ? err.transient : false,
|
|
169
|
+
suggestion: err instanceof ApiError ? err.suggestion : undefined,
|
|
170
|
+
continue: false,
|
|
171
|
+
exitCode: err instanceof ApiError ? err.exitCode : 1,
|
|
172
|
+
}, `Failed to upload images: ${errMsg}`);
|
|
135
173
|
}
|
|
136
174
|
}
|
|
137
175
|
// Handle URL attachments
|
|
138
176
|
const urlContents = [];
|
|
139
177
|
if (options.url && options.url.length > 0) {
|
|
140
|
-
const urlSpinner = ora('Fetching URLs...').start();
|
|
178
|
+
const urlSpinner = (streamJson || jsonOutput) ? null : ora('Fetching URLs...').start();
|
|
141
179
|
try {
|
|
142
180
|
for (const url of options.url) {
|
|
143
181
|
const result = await fetchUrlContent(url);
|
|
@@ -147,15 +185,25 @@ export const promptCommand = new Command('prompt')
|
|
|
147
185
|
content: result.content.slice(0, 10000), // Limit content length
|
|
148
186
|
});
|
|
149
187
|
}
|
|
150
|
-
urlSpinner
|
|
188
|
+
urlSpinner?.succeed(`Fetched ${urlContents.length} URL${urlContents.length > 1 ? 's' : ''}`);
|
|
151
189
|
}
|
|
152
190
|
catch (err) {
|
|
153
|
-
urlSpinner
|
|
191
|
+
urlSpinner?.fail();
|
|
154
192
|
const errMsg = err instanceof ApiError
|
|
155
193
|
? `${err.message}${err.details ? ` — ${err.details}` : ''}`
|
|
156
194
|
: err.message;
|
|
157
|
-
|
|
158
|
-
|
|
195
|
+
emitCliError({
|
|
196
|
+
type: 'error',
|
|
197
|
+
code: err instanceof ApiError ? err.code : 'UNKNOWN',
|
|
198
|
+
errorType: err instanceof ApiError ? err.code : 'UNKNOWN',
|
|
199
|
+
message: `Failed to fetch URL: ${errMsg}`,
|
|
200
|
+
category: err instanceof ApiError ? err.category : 'fatal',
|
|
201
|
+
retryable: err instanceof ApiError ? err.retryable : false,
|
|
202
|
+
transient: err instanceof ApiError ? err.transient : false,
|
|
203
|
+
suggestion: err instanceof ApiError ? err.suggestion : undefined,
|
|
204
|
+
continue: false,
|
|
205
|
+
exitCode: err instanceof ApiError ? err.exitCode : 1,
|
|
206
|
+
}, `Failed to fetch URL: ${errMsg}`);
|
|
159
207
|
}
|
|
160
208
|
// Prepend URL content to the prompt
|
|
161
209
|
if (urlContents.length > 0) {
|
|
@@ -163,8 +211,6 @@ export const promptCommand = new Command('prompt')
|
|
|
163
211
|
promptText = `${urlContext}\n\n---\n\nUser request: ${promptText}`;
|
|
164
212
|
}
|
|
165
213
|
}
|
|
166
|
-
// For bots: --stream-json outputs NDJSON (newline-delimited JSON) with timestamps
|
|
167
|
-
const streamJson = options.streamJson === true || agentModeEnv;
|
|
168
214
|
// Show mode info (skip for JSON modes)
|
|
169
215
|
if (!jsonOutput && !streamJson && options.stream !== false) {
|
|
170
216
|
console.log(chalk.cyan(`Mode: ${mode}`));
|
|
@@ -231,6 +277,15 @@ export const promptCommand = new Command('prompt')
|
|
|
231
277
|
// Track budget from complete event for summary
|
|
232
278
|
let budgetData;
|
|
233
279
|
try {
|
|
280
|
+
// Resolve model alias to full model ID
|
|
281
|
+
const MODEL_ALIASES = {
|
|
282
|
+
fast: 'claude-haiku-4-5',
|
|
283
|
+
max: 'claude-opus-4-6',
|
|
284
|
+
default: 'claude-sonnet-4-5',
|
|
285
|
+
};
|
|
286
|
+
const resolvedModel = options.model
|
|
287
|
+
? MODEL_ALIASES[options.model] || options.model
|
|
288
|
+
: undefined; // Let api.ts default (Sonnet) handle it
|
|
234
289
|
const result = await streamGeneration(config, {
|
|
235
290
|
projectId,
|
|
236
291
|
prompt: promptText,
|
|
@@ -240,6 +295,7 @@ export const promptCommand = new Command('prompt')
|
|
|
240
295
|
planOnly: options.plan, // Handshake: create plan without executing
|
|
241
296
|
requirements: options.requirements, // Requirements for verification
|
|
242
297
|
budgetUsd: options.budget, // Per-run budget in USD
|
|
298
|
+
modelOverride: resolvedModel,
|
|
243
299
|
onEvent: options.stream !== false ? (event) => {
|
|
244
300
|
// Handle streaming output
|
|
245
301
|
// Stream JSON / JSON mode: output raw event lines, but still track plan/workflow for summaries.
|
package/dist/commands/scenes.js
CHANGED
|
@@ -110,7 +110,7 @@ scenesCommand
|
|
|
110
110
|
hasCode: Boolean(s.tsxCode),
|
|
111
111
|
hasCompilationError: Boolean(s.compilationError),
|
|
112
112
|
}));
|
|
113
|
-
output(lean, { json: true });
|
|
113
|
+
output(lean, { json: true, compact: globalOpts.compact });
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
if (!scenes || scenes.length === 0) {
|
|
@@ -246,7 +246,7 @@ scenesCommand
|
|
|
246
246
|
const code = scene.tsxCode || '// No code available';
|
|
247
247
|
const name = scene.name || 'Untitled';
|
|
248
248
|
if (globalOpts.json) {
|
|
249
|
-
output({ sceneId, name, code }, { json: true });
|
|
249
|
+
output({ sceneId, name, code }, { json: true, compact: globalOpts.compact });
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
252
252
|
if (options.output) {
|
|
@@ -310,7 +310,7 @@ scenesCommand
|
|
|
310
310
|
const confirmed = await confirmAction(`Delete scene ${sceneId}? This cannot be undone.`);
|
|
311
311
|
if (!confirmed) {
|
|
312
312
|
if (globalOpts.json) {
|
|
313
|
-
output({ deleted: false, sceneId, reason: 'cancelled' }, { json: true });
|
|
313
|
+
output({ deleted: false, sceneId, reason: 'cancelled' }, { json: true, compact: globalOpts.compact });
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
316
|
console.log(chalk.gray('Cancelled. Use --force to skip confirmation.'));
|
|
@@ -325,7 +325,7 @@ scenesCommand
|
|
|
325
325
|
});
|
|
326
326
|
spinner?.stop();
|
|
327
327
|
if (globalOpts.json) {
|
|
328
|
-
output({ deleted: true, sceneId, projectId }, { json: true });
|
|
328
|
+
output({ deleted: true, sceneId, projectId }, { json: true, compact: globalOpts.compact });
|
|
329
329
|
return;
|
|
330
330
|
}
|
|
331
331
|
success(`Deleted scene: ${sceneId}`);
|
package/dist/commands/status.js
CHANGED
|
@@ -2,8 +2,8 @@ import { Command } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { loadConfig, hasAuth, getProjectId } from '../lib/config.js';
|
|
5
|
-
import { apiRequest } from '../lib/api.js';
|
|
6
|
-
import { error, formatDuration } from '../lib/output.js';
|
|
5
|
+
import { apiRequest, ApiError } from '../lib/api.js';
|
|
6
|
+
import { error, formatDuration, output } from '../lib/output.js';
|
|
7
7
|
/**
|
|
8
8
|
* Render an ASCII timeline of scenes
|
|
9
9
|
*/
|
|
@@ -79,7 +79,7 @@ export const statusCommand = new Command('status')
|
|
|
79
79
|
});
|
|
80
80
|
if (!hasAuth(config)) {
|
|
81
81
|
if (globalOpts.json) {
|
|
82
|
-
|
|
82
|
+
output({
|
|
83
83
|
type: 'error',
|
|
84
84
|
code: 'AUTH_MISSING',
|
|
85
85
|
message: 'Not authenticated',
|
|
@@ -88,7 +88,7 @@ export const statusCommand = new Command('status')
|
|
|
88
88
|
transient: false,
|
|
89
89
|
exitCode: 13,
|
|
90
90
|
suggestion: 'Run: baz auth login <api-key>',
|
|
91
|
-
})
|
|
91
|
+
}, { json: true, compact: globalOpts.compact });
|
|
92
92
|
}
|
|
93
93
|
else {
|
|
94
94
|
error('Not authenticated', 'Run: baz auth login <api-key>');
|
|
@@ -101,7 +101,7 @@ export const statusCommand = new Command('status')
|
|
|
101
101
|
}
|
|
102
102
|
catch (err) {
|
|
103
103
|
if (globalOpts.json) {
|
|
104
|
-
|
|
104
|
+
output({
|
|
105
105
|
type: 'error',
|
|
106
106
|
code: 'VALIDATION',
|
|
107
107
|
message: err.message,
|
|
@@ -109,14 +109,14 @@ export const statusCommand = new Command('status')
|
|
|
109
109
|
retryable: false,
|
|
110
110
|
transient: false,
|
|
111
111
|
exitCode: 64,
|
|
112
|
-
})
|
|
112
|
+
}, { json: true, compact: globalOpts.compact });
|
|
113
113
|
}
|
|
114
114
|
else {
|
|
115
115
|
error(err.message);
|
|
116
116
|
}
|
|
117
117
|
process.exit(64);
|
|
118
118
|
}
|
|
119
|
-
const spinner = ora('Fetching project status...').start();
|
|
119
|
+
const spinner = globalOpts.json ? null : ora('Fetching project status...').start();
|
|
120
120
|
try {
|
|
121
121
|
// Use full project query as canonical source of scene state.
|
|
122
122
|
const projectResult = await apiRequest(config, 'project.getFullProject', {
|
|
@@ -124,7 +124,7 @@ export const statusCommand = new Command('status')
|
|
|
124
124
|
include: ['scenes'],
|
|
125
125
|
includeSceneCode: false,
|
|
126
126
|
});
|
|
127
|
-
spinner
|
|
127
|
+
spinner?.stop();
|
|
128
128
|
const project = projectResult.project || projectResult;
|
|
129
129
|
const scenes = projectResult.scenes || [];
|
|
130
130
|
const fps = 30;
|
|
@@ -149,7 +149,7 @@ export const statusCommand = new Command('status')
|
|
|
149
149
|
const trackCount = trackSet.size || 1;
|
|
150
150
|
// JSON output
|
|
151
151
|
if (globalOpts.json) {
|
|
152
|
-
|
|
152
|
+
output({
|
|
153
153
|
projectId,
|
|
154
154
|
title: project.title || 'Untitled',
|
|
155
155
|
scenes: scenes.length,
|
|
@@ -164,7 +164,7 @@ export const statusCommand = new Command('status')
|
|
|
164
164
|
durationInFrames: getDurationFrames(s),
|
|
165
165
|
hasError: hasError(s),
|
|
166
166
|
})),
|
|
167
|
-
},
|
|
167
|
+
}, { json: true, compact: globalOpts.compact });
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
// Pretty output
|
|
@@ -212,8 +212,30 @@ export const statusCommand = new Command('status')
|
|
|
212
212
|
console.log();
|
|
213
213
|
}
|
|
214
214
|
catch (err) {
|
|
215
|
-
spinner
|
|
216
|
-
|
|
215
|
+
spinner?.stop();
|
|
216
|
+
if (err instanceof ApiError) {
|
|
217
|
+
if (globalOpts.json) {
|
|
218
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
error(err.message, err.suggestion);
|
|
222
|
+
}
|
|
223
|
+
process.exit(err.exitCode);
|
|
224
|
+
}
|
|
225
|
+
if (globalOpts.json) {
|
|
226
|
+
output({
|
|
227
|
+
type: 'error',
|
|
228
|
+
code: 'UNKNOWN',
|
|
229
|
+
message: err.message,
|
|
230
|
+
category: 'fatal',
|
|
231
|
+
retryable: false,
|
|
232
|
+
transient: false,
|
|
233
|
+
exitCode: 1,
|
|
234
|
+
}, { json: true, compact: globalOpts.compact });
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
error(err.message);
|
|
238
|
+
}
|
|
217
239
|
process.exit(1);
|
|
218
240
|
}
|
|
219
241
|
});
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -138,6 +138,8 @@ export declare function streamGeneration(config: Config, options: {
|
|
|
138
138
|
requirements?: string;
|
|
139
139
|
/** Per-run budget in USD (default: server decides, typically $5) */
|
|
140
140
|
budgetUsd?: number;
|
|
141
|
+
/** Model override (default: claude-sonnet-4-5 for CLI) */
|
|
142
|
+
modelOverride?: string;
|
|
141
143
|
}): Promise<StreamResult>;
|
|
142
144
|
export interface StreamEvent {
|
|
143
145
|
type: 'thinking' | 'tool_use' | 'code_gen' | 'compile' | 'scene_created' | 'scene_updated' | 'error' | 'complete' | 'text' | 'recipe_created' | 'workflow_created' | 'video_plan_created' | 'awaiting_approval' | 'ready' | 'asset_generated' | 'raw';
|
package/dist/lib/api.js
CHANGED
|
@@ -522,6 +522,8 @@ export async function streamGeneration(config, options) {
|
|
|
522
522
|
requirements: options.requirements,
|
|
523
523
|
// Budget control
|
|
524
524
|
...(options.budgetUsd != null && options.budgetUsd > 0 ? { budgetUsd: options.budgetUsd } : {}),
|
|
525
|
+
// Model — CLI defaults to Sonnet to keep costs reasonable
|
|
526
|
+
modelOverride: options.modelOverride || 'claude-sonnet-4-5',
|
|
525
527
|
}),
|
|
526
528
|
});
|
|
527
529
|
if (!response.ok) {
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface Config {
|
|
|
7
7
|
outputFormat?: 'pretty' | 'json';
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
+
export declare const PRODUCTION_API_URL = "https://bazaar.it";
|
|
10
11
|
/**
|
|
11
12
|
* Load config from file, environment, and CLI options
|
|
12
13
|
*/
|
|
@@ -18,7 +19,9 @@ export declare function loadConfig(options?: {
|
|
|
18
19
|
/**
|
|
19
20
|
* Save config to file
|
|
20
21
|
*/
|
|
21
|
-
export declare function saveConfig(updates: Partial<Config
|
|
22
|
+
export declare function saveConfig(updates: Partial<Config>, options?: {
|
|
23
|
+
configPath?: string;
|
|
24
|
+
}): void;
|
|
22
25
|
/**
|
|
23
26
|
* Get active project ID or throw if not set
|
|
24
27
|
*/
|
|
@@ -30,4 +33,4 @@ export declare function hasAuth(config: Config): boolean;
|
|
|
30
33
|
/**
|
|
31
34
|
* Get config file path for display
|
|
32
35
|
*/
|
|
33
|
-
export declare function getConfigPath(): string;
|
|
36
|
+
export declare function getConfigPath(configPath?: string): string;
|
package/dist/lib/config.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
|
-
import { join } from 'path';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
export const PRODUCTION_API_URL = 'https://bazaar.it';
|
|
5
|
+
const KNOWN_STAGING_API_URLS = new Set([
|
|
6
|
+
'https://coco.bazaar.it',
|
|
7
|
+
]);
|
|
4
8
|
const DEFAULT_CONFIG = {
|
|
5
|
-
apiUrl:
|
|
9
|
+
apiUrl: PRODUCTION_API_URL,
|
|
6
10
|
defaults: {
|
|
7
11
|
mode: 'agent',
|
|
8
12
|
outputFormat: 'pretty',
|
|
@@ -10,6 +14,15 @@ const DEFAULT_CONFIG = {
|
|
|
10
14
|
};
|
|
11
15
|
const CONFIG_DIR = join(homedir(), '.bazaar');
|
|
12
16
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
17
|
+
function normalizeApiUrl(apiUrl, options) {
|
|
18
|
+
const normalized = (apiUrl || PRODUCTION_API_URL).trim().replace(/\/+$/, '');
|
|
19
|
+
// Guard against accidentally shipping/sticking users on staging.
|
|
20
|
+
// Staging is still allowed via explicit override: --api-url, BAZ_API_URL, or BAZ_ALLOW_STAGING=1.
|
|
21
|
+
if (!options?.allowKnownStaging && KNOWN_STAGING_API_URLS.has(normalized)) {
|
|
22
|
+
return PRODUCTION_API_URL;
|
|
23
|
+
}
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
13
26
|
/**
|
|
14
27
|
* Load config from file, environment, and CLI options
|
|
15
28
|
*/
|
|
@@ -48,21 +61,27 @@ export function loadConfig(options) {
|
|
|
48
61
|
if (options?.projectId) {
|
|
49
62
|
config.activeProjectId = options.projectId;
|
|
50
63
|
}
|
|
64
|
+
const allowKnownStaging = process.env.BAZ_ALLOW_STAGING === '1' ||
|
|
65
|
+
Boolean(process.env.BAZ_API_URL) ||
|
|
66
|
+
Boolean(options?.apiUrl);
|
|
67
|
+
config.apiUrl = normalizeApiUrl(config.apiUrl, { allowKnownStaging });
|
|
51
68
|
return config;
|
|
52
69
|
}
|
|
53
70
|
/**
|
|
54
71
|
* Save config to file
|
|
55
72
|
*/
|
|
56
|
-
export function saveConfig(updates) {
|
|
73
|
+
export function saveConfig(updates, options) {
|
|
74
|
+
const configPath = options?.configPath || CONFIG_FILE;
|
|
75
|
+
const configDir = dirname(configPath);
|
|
57
76
|
// Ensure directory exists
|
|
58
|
-
if (!existsSync(
|
|
59
|
-
mkdirSync(
|
|
77
|
+
if (!existsSync(configDir)) {
|
|
78
|
+
mkdirSync(configDir, { recursive: true });
|
|
60
79
|
}
|
|
61
80
|
// Load existing config
|
|
62
81
|
let config = { ...DEFAULT_CONFIG };
|
|
63
|
-
if (existsSync(
|
|
82
|
+
if (existsSync(configPath)) {
|
|
64
83
|
try {
|
|
65
|
-
const fileContent = readFileSync(
|
|
84
|
+
const fileContent = readFileSync(configPath, 'utf-8');
|
|
66
85
|
config = { ...config, ...JSON.parse(fileContent) };
|
|
67
86
|
}
|
|
68
87
|
catch {
|
|
@@ -71,8 +90,11 @@ export function saveConfig(updates) {
|
|
|
71
90
|
}
|
|
72
91
|
// Apply updates
|
|
73
92
|
config = { ...config, ...updates };
|
|
93
|
+
const allowKnownStaging = process.env.BAZ_ALLOW_STAGING === '1' ||
|
|
94
|
+
Object.prototype.hasOwnProperty.call(updates, 'apiUrl');
|
|
95
|
+
config.apiUrl = normalizeApiUrl(config.apiUrl, { allowKnownStaging });
|
|
74
96
|
// Write back
|
|
75
|
-
writeFileSync(
|
|
97
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
76
98
|
}
|
|
77
99
|
/**
|
|
78
100
|
* Get active project ID or throw if not set
|
|
@@ -94,6 +116,6 @@ export function hasAuth(config) {
|
|
|
94
116
|
/**
|
|
95
117
|
* Get config file path for display
|
|
96
118
|
*/
|
|
97
|
-
export function getConfigPath() {
|
|
98
|
-
return CONFIG_FILE;
|
|
119
|
+
export function getConfigPath(configPath) {
|
|
120
|
+
return configPath || CONFIG_FILE;
|
|
99
121
|
}
|