bazaar.it 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +485 -3
- package/bin/baz.js +6 -1
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +109 -0
- package/dist/commands/capabilities.d.ts +2 -0
- package/dist/commands/capabilities.js +44 -0
- package/dist/commands/context.d.ts +13 -0
- package/dist/commands/context.js +498 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +360 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +180 -0
- package/dist/commands/loop.d.ts +2 -0
- package/dist/commands/loop.js +538 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +143 -0
- package/dist/commands/media.d.ts +2 -0
- package/dist/commands/media.js +362 -0
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +786 -0
- package/dist/commands/prompt.d.ts +2 -0
- package/dist/commands/prompt.js +540 -0
- package/dist/commands/recipe.d.ts +15 -0
- package/dist/commands/recipe.js +607 -0
- package/dist/commands/review.d.ts +17 -0
- package/dist/commands/review.js +345 -0
- package/dist/commands/scenes.d.ts +2 -0
- package/dist/commands/scenes.js +481 -0
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +226 -0
- package/dist/commands/state.d.ts +2 -0
- package/dist/commands/state.js +171 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +219 -0
- package/dist/commands/template.d.ts +2 -0
- package/dist/commands/template.js +123 -0
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.js +150 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +124 -0
- package/dist/lib/api.d.ts +188 -0
- package/dist/lib/api.js +719 -0
- package/dist/lib/banner.d.ts +12 -0
- package/dist/lib/banner.js +69 -0
- package/dist/lib/config.d.ts +33 -0
- package/dist/lib/config.js +99 -0
- package/dist/lib/output.d.ts +52 -0
- package/dist/lib/output.js +162 -0
- package/dist/lib/project-state.d.ts +52 -0
- package/dist/lib/project-state.js +178 -0
- package/dist/lib/sse.d.ts +168 -0
- package/dist/lib/sse.js +227 -0
- package/dist/lib/version.d.ts +1 -0
- package/dist/lib/version.js +3 -0
- package/dist/repl.d.ts +4 -0
- package/dist/repl.js +764 -0
- package/package.json +32 -5
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
5
|
+
import { loadConfig, hasAuth, getProjectId } from '../lib/config.js';
|
|
6
|
+
import { apiRequest, ApiError } from '../lib/api.js';
|
|
7
|
+
import { success, error, output, table, formatDuration, confirmAction } from '../lib/output.js';
|
|
8
|
+
function handleScenesError(err, globalOpts) {
|
|
9
|
+
if (err instanceof ApiError) {
|
|
10
|
+
if (globalOpts.json) {
|
|
11
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
error(err.message, err.suggestion);
|
|
15
|
+
}
|
|
16
|
+
process.exit(err.exitCode);
|
|
17
|
+
}
|
|
18
|
+
if (globalOpts.json) {
|
|
19
|
+
output({
|
|
20
|
+
type: 'error',
|
|
21
|
+
code: 'UNKNOWN',
|
|
22
|
+
message: err.message || 'Unknown error',
|
|
23
|
+
category: 'fatal',
|
|
24
|
+
retryable: false,
|
|
25
|
+
transient: false,
|
|
26
|
+
exitCode: 1,
|
|
27
|
+
}, { json: true, compact: globalOpts.compact });
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
error(err.message);
|
|
31
|
+
}
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
function exitScenesAuthError(globalOpts) {
|
|
35
|
+
if (globalOpts.json) {
|
|
36
|
+
output({
|
|
37
|
+
type: 'error',
|
|
38
|
+
code: 'AUTH_MISSING',
|
|
39
|
+
message: 'Not authenticated',
|
|
40
|
+
category: 'auth',
|
|
41
|
+
retryable: false,
|
|
42
|
+
transient: false,
|
|
43
|
+
exitCode: 13,
|
|
44
|
+
suggestion: 'Run: baz auth login <api-key>',
|
|
45
|
+
}, { json: true, compact: globalOpts.compact });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
error('Not authenticated', 'Run: baz auth login <api-key>');
|
|
49
|
+
}
|
|
50
|
+
process.exit(13);
|
|
51
|
+
}
|
|
52
|
+
export const scenesCommand = new Command('scenes')
|
|
53
|
+
.description('Manage scenes in active project');
|
|
54
|
+
/**
|
|
55
|
+
* baz scenes list
|
|
56
|
+
*/
|
|
57
|
+
scenesCommand
|
|
58
|
+
.command('list')
|
|
59
|
+
.description('List all scenes in active project')
|
|
60
|
+
.action(async (options, cmd) => {
|
|
61
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
62
|
+
const config = loadConfig({
|
|
63
|
+
configPath: globalOpts.config,
|
|
64
|
+
apiUrl: globalOpts.apiUrl,
|
|
65
|
+
projectId: globalOpts.projectId,
|
|
66
|
+
});
|
|
67
|
+
if (!hasAuth(config)) {
|
|
68
|
+
exitScenesAuthError(globalOpts);
|
|
69
|
+
}
|
|
70
|
+
let projectId;
|
|
71
|
+
try {
|
|
72
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (globalOpts.json) {
|
|
76
|
+
output({
|
|
77
|
+
type: 'error',
|
|
78
|
+
code: 'VALIDATION',
|
|
79
|
+
message: err.message,
|
|
80
|
+
category: 'validation',
|
|
81
|
+
retryable: false,
|
|
82
|
+
transient: false,
|
|
83
|
+
exitCode: 64,
|
|
84
|
+
}, { json: true, compact: globalOpts.compact });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
error(err.message);
|
|
88
|
+
}
|
|
89
|
+
process.exit(64);
|
|
90
|
+
}
|
|
91
|
+
const spinner = globalOpts.json ? null : ora('Fetching scenes...').start();
|
|
92
|
+
try {
|
|
93
|
+
// Use getFullProject to get actual scene data from database
|
|
94
|
+
const project = await apiRequest(config, 'project.getFullProject', {
|
|
95
|
+
id: projectId,
|
|
96
|
+
include: ['scenes'],
|
|
97
|
+
});
|
|
98
|
+
spinner?.stop();
|
|
99
|
+
const scenes = project.scenes || [];
|
|
100
|
+
if (globalOpts.json) {
|
|
101
|
+
// Lean output: strip TSX code (can be huge), keep only what agents need
|
|
102
|
+
const lean = scenes.map((s) => ({
|
|
103
|
+
id: s.id,
|
|
104
|
+
name: s.name || 'Untitled',
|
|
105
|
+
track: s.track ?? 0,
|
|
106
|
+
order: s.order ?? 0,
|
|
107
|
+
start: s.props?.start ?? 0,
|
|
108
|
+
duration: s.duration ?? 0,
|
|
109
|
+
durationSeconds: (s.duration ?? 0) / 30,
|
|
110
|
+
hasCode: Boolean(s.tsxCode),
|
|
111
|
+
hasCompilationError: Boolean(s.compilationError),
|
|
112
|
+
}));
|
|
113
|
+
output(lean, { json: true });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!scenes || scenes.length === 0) {
|
|
117
|
+
console.log(chalk.gray('No scenes in this project.'));
|
|
118
|
+
console.log(chalk.gray('Create one with: baz prompt "Add a scene..."'));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Calculate total duration
|
|
122
|
+
const fps = 30;
|
|
123
|
+
let totalFrames = 0;
|
|
124
|
+
scenes.forEach((s) => {
|
|
125
|
+
totalFrames += s.duration || 0;
|
|
126
|
+
});
|
|
127
|
+
console.log(`Project: ${projectId.slice(0, 8)}... (${scenes.length} scenes, ${formatDuration(totalFrames / fps)} total)`);
|
|
128
|
+
console.log();
|
|
129
|
+
const rows = scenes.map((s, i) => {
|
|
130
|
+
const durationSec = (s.duration || 0) / fps;
|
|
131
|
+
const timeRange = formatDuration(durationSec);
|
|
132
|
+
// Check if scene has code
|
|
133
|
+
const hasCode = Boolean(s.tsxCode);
|
|
134
|
+
const status = hasCode
|
|
135
|
+
? chalk.green('✓ has code')
|
|
136
|
+
: chalk.yellow('○ no code');
|
|
137
|
+
return [
|
|
138
|
+
String(s.order ?? i + 1),
|
|
139
|
+
s.id?.slice(0, 12) || 'unknown',
|
|
140
|
+
s.name || 'Untitled',
|
|
141
|
+
timeRange,
|
|
142
|
+
status,
|
|
143
|
+
];
|
|
144
|
+
});
|
|
145
|
+
table(['#', 'ID', 'Name', 'Duration', 'Status'], rows);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
spinner?.stop();
|
|
149
|
+
handleScenesError(err, globalOpts);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
/**
|
|
153
|
+
* baz scenes code <scene-id>
|
|
154
|
+
*/
|
|
155
|
+
scenesCommand
|
|
156
|
+
.command('code [scene-id]')
|
|
157
|
+
.description('Show TSX code for a scene')
|
|
158
|
+
.option('--all', 'Show code for all scenes')
|
|
159
|
+
.option('--output <path>', 'Write to file instead of stdout')
|
|
160
|
+
.action(async (sceneId, options, cmd) => {
|
|
161
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
162
|
+
const config = loadConfig({
|
|
163
|
+
configPath: globalOpts.config,
|
|
164
|
+
apiUrl: globalOpts.apiUrl,
|
|
165
|
+
projectId: globalOpts.projectId,
|
|
166
|
+
});
|
|
167
|
+
if (!hasAuth(config)) {
|
|
168
|
+
exitScenesAuthError(globalOpts);
|
|
169
|
+
}
|
|
170
|
+
let projectId;
|
|
171
|
+
try {
|
|
172
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
if (globalOpts.json) {
|
|
176
|
+
output({
|
|
177
|
+
type: 'error',
|
|
178
|
+
code: 'VALIDATION',
|
|
179
|
+
message: err.message,
|
|
180
|
+
category: 'validation',
|
|
181
|
+
retryable: false,
|
|
182
|
+
transient: false,
|
|
183
|
+
exitCode: 64,
|
|
184
|
+
}, { json: true, compact: globalOpts.compact });
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
error(err.message);
|
|
188
|
+
}
|
|
189
|
+
process.exit(64);
|
|
190
|
+
}
|
|
191
|
+
if (!sceneId && !options.all) {
|
|
192
|
+
if (globalOpts.json) {
|
|
193
|
+
output({
|
|
194
|
+
type: 'error',
|
|
195
|
+
code: 'VALIDATION',
|
|
196
|
+
message: 'Provide a scene ID or use --all',
|
|
197
|
+
category: 'validation',
|
|
198
|
+
retryable: false,
|
|
199
|
+
transient: false,
|
|
200
|
+
exitCode: 64,
|
|
201
|
+
}, { json: true, compact: globalOpts.compact });
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
error('Provide a scene ID or use --all');
|
|
205
|
+
}
|
|
206
|
+
process.exit(64);
|
|
207
|
+
}
|
|
208
|
+
const spinner = globalOpts.json ? null : ora('Fetching scene code...').start();
|
|
209
|
+
try {
|
|
210
|
+
// Use getFullProject to get actual scene data with tsxCode from database
|
|
211
|
+
const project = await apiRequest(config, 'project.getFullProject', {
|
|
212
|
+
id: projectId,
|
|
213
|
+
include: ['scenes'],
|
|
214
|
+
});
|
|
215
|
+
spinner?.stop();
|
|
216
|
+
// Scenes come from database with tsxCode field
|
|
217
|
+
const scenes = project.scenes || [];
|
|
218
|
+
if (options.all) {
|
|
219
|
+
let allCode = '';
|
|
220
|
+
for (const scene of scenes) {
|
|
221
|
+
allCode += `// ========================\n`;
|
|
222
|
+
allCode += `// Scene: ${scene.name || 'Untitled'}\n`;
|
|
223
|
+
allCode += `// ID: ${scene.id}\n`;
|
|
224
|
+
allCode += `// ========================\n\n`;
|
|
225
|
+
allCode += scene.tsxCode || '// No code available\n';
|
|
226
|
+
allCode += '\n\n';
|
|
227
|
+
}
|
|
228
|
+
if (options.output) {
|
|
229
|
+
writeFileSync(options.output, allCode);
|
|
230
|
+
success(`Code written to ${options.output}`);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
console.log(allCode);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const scene = scenes.find((s) => s.id === sceneId);
|
|
238
|
+
if (!scene) {
|
|
239
|
+
error(`Scene not found: ${sceneId}`);
|
|
240
|
+
console.log(chalk.gray('\nAvailable scenes:'));
|
|
241
|
+
scenes.forEach((s) => {
|
|
242
|
+
console.log(chalk.gray(` ${s.id.slice(0, 12)} ${s.name || 'Untitled'}`));
|
|
243
|
+
});
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
const code = scene.tsxCode || '// No code available';
|
|
247
|
+
const name = scene.name || 'Untitled';
|
|
248
|
+
if (globalOpts.json) {
|
|
249
|
+
output({ sceneId, name, code }, { json: true });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (options.output) {
|
|
253
|
+
writeFileSync(options.output, code);
|
|
254
|
+
success(`Code written to ${options.output}`);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
console.log(chalk.gray(`// Scene: ${name}`));
|
|
258
|
+
console.log(chalk.gray(`// ID: ${sceneId}`));
|
|
259
|
+
console.log();
|
|
260
|
+
console.log(code);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
spinner?.stop();
|
|
266
|
+
handleScenesError(err, globalOpts);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
// scenes preview: not yet implemented — removed from public surface.
|
|
270
|
+
// When ready, register a 'preview <scene-id>' subcommand here.
|
|
271
|
+
/**
|
|
272
|
+
* baz scenes delete <scene-id>
|
|
273
|
+
*/
|
|
274
|
+
scenesCommand
|
|
275
|
+
.command('delete <scene-id>')
|
|
276
|
+
.description('Delete a scene')
|
|
277
|
+
.option('--force', 'Skip confirmation')
|
|
278
|
+
.action(async (sceneId, options, cmd) => {
|
|
279
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
280
|
+
const config = loadConfig({
|
|
281
|
+
configPath: globalOpts.config,
|
|
282
|
+
apiUrl: globalOpts.apiUrl,
|
|
283
|
+
projectId: globalOpts.projectId,
|
|
284
|
+
});
|
|
285
|
+
if (!hasAuth(config)) {
|
|
286
|
+
exitScenesAuthError(globalOpts);
|
|
287
|
+
}
|
|
288
|
+
let projectId;
|
|
289
|
+
try {
|
|
290
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
if (globalOpts.json) {
|
|
294
|
+
output({
|
|
295
|
+
type: 'error',
|
|
296
|
+
code: 'VALIDATION',
|
|
297
|
+
message: err.message,
|
|
298
|
+
category: 'validation',
|
|
299
|
+
retryable: false,
|
|
300
|
+
transient: false,
|
|
301
|
+
exitCode: 64,
|
|
302
|
+
}, { json: true, compact: globalOpts.compact });
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
error(err.message);
|
|
306
|
+
}
|
|
307
|
+
process.exit(64);
|
|
308
|
+
}
|
|
309
|
+
if (!options.force) {
|
|
310
|
+
const confirmed = await confirmAction(`Delete scene ${sceneId}? This cannot be undone.`);
|
|
311
|
+
if (!confirmed) {
|
|
312
|
+
if (globalOpts.json) {
|
|
313
|
+
output({ deleted: false, sceneId, reason: 'cancelled' }, { json: true });
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
console.log(chalk.gray('Cancelled. Use --force to skip confirmation.'));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const spinner = globalOpts.json ? null : ora('Deleting scene...').start();
|
|
321
|
+
try {
|
|
322
|
+
await apiRequest(config, 'scenes.deleteScene', {
|
|
323
|
+
projectId,
|
|
324
|
+
sceneId,
|
|
325
|
+
});
|
|
326
|
+
spinner?.stop();
|
|
327
|
+
if (globalOpts.json) {
|
|
328
|
+
output({ deleted: true, sceneId, projectId }, { json: true });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
success(`Deleted scene: ${sceneId}`);
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
spinner?.stop();
|
|
335
|
+
handleScenesError(err, globalOpts);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
/**
|
|
339
|
+
* baz scenes set-code <scene-id>
|
|
340
|
+
*
|
|
341
|
+
* Deterministic code replacement for AI agents.
|
|
342
|
+
* Agent reads code with `baz scenes code <id>`, edits locally, writes back here.
|
|
343
|
+
* No LLM in the middle — the caller owns the edit.
|
|
344
|
+
*/
|
|
345
|
+
scenesCommand
|
|
346
|
+
.command('set-code <scene-id>')
|
|
347
|
+
.description('Replace scene TSX code (for agent-driven edits)')
|
|
348
|
+
.option('--file <path>', 'Read new code from file')
|
|
349
|
+
.option('--code <code>', 'Provide new code inline')
|
|
350
|
+
.option('--overwrite-duration', 'Also update scene duration from code')
|
|
351
|
+
.action(async (sceneId, options, cmd) => {
|
|
352
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
353
|
+
const config = loadConfig({
|
|
354
|
+
configPath: globalOpts.config,
|
|
355
|
+
apiUrl: globalOpts.apiUrl,
|
|
356
|
+
projectId: globalOpts.projectId,
|
|
357
|
+
});
|
|
358
|
+
if (!hasAuth(config)) {
|
|
359
|
+
exitScenesAuthError(globalOpts);
|
|
360
|
+
}
|
|
361
|
+
let projectId;
|
|
362
|
+
try {
|
|
363
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
if (globalOpts.json) {
|
|
367
|
+
output({
|
|
368
|
+
type: 'error',
|
|
369
|
+
code: 'VALIDATION',
|
|
370
|
+
message: err.message,
|
|
371
|
+
category: 'validation',
|
|
372
|
+
retryable: false,
|
|
373
|
+
transient: false,
|
|
374
|
+
exitCode: 64,
|
|
375
|
+
}, { json: true, compact: globalOpts.compact });
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
error(err.message);
|
|
379
|
+
}
|
|
380
|
+
process.exit(64);
|
|
381
|
+
}
|
|
382
|
+
// Get code from --file, --code, or stdin
|
|
383
|
+
let code;
|
|
384
|
+
if (options.file) {
|
|
385
|
+
if (!existsSync(options.file)) {
|
|
386
|
+
if (globalOpts.json) {
|
|
387
|
+
output({
|
|
388
|
+
type: 'error',
|
|
389
|
+
code: 'VALIDATION',
|
|
390
|
+
message: `File not found: ${options.file}`,
|
|
391
|
+
category: 'validation',
|
|
392
|
+
retryable: false,
|
|
393
|
+
transient: false,
|
|
394
|
+
exitCode: 64,
|
|
395
|
+
}, { json: true, compact: globalOpts.compact });
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
error(`File not found: ${options.file}`);
|
|
399
|
+
}
|
|
400
|
+
process.exit(64);
|
|
401
|
+
}
|
|
402
|
+
code = readFileSync(options.file, 'utf-8');
|
|
403
|
+
}
|
|
404
|
+
else if (options.code) {
|
|
405
|
+
code = options.code;
|
|
406
|
+
}
|
|
407
|
+
else if (!process.stdin.isTTY) {
|
|
408
|
+
// Read from stdin when piped
|
|
409
|
+
const chunks = [];
|
|
410
|
+
for await (const chunk of process.stdin) {
|
|
411
|
+
chunks.push(chunk);
|
|
412
|
+
}
|
|
413
|
+
code = Buffer.concat(chunks).toString('utf-8');
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
if (globalOpts.json) {
|
|
417
|
+
output({
|
|
418
|
+
type: 'error',
|
|
419
|
+
code: 'VALIDATION',
|
|
420
|
+
message: 'Provide code via --file, --code, or stdin pipe',
|
|
421
|
+
category: 'validation',
|
|
422
|
+
retryable: false,
|
|
423
|
+
transient: false,
|
|
424
|
+
exitCode: 64,
|
|
425
|
+
}, { json: true, compact: globalOpts.compact });
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
error('Provide code via --file, --code, or stdin pipe');
|
|
429
|
+
}
|
|
430
|
+
process.exit(64);
|
|
431
|
+
}
|
|
432
|
+
if (!code.trim()) {
|
|
433
|
+
if (globalOpts.json) {
|
|
434
|
+
output({
|
|
435
|
+
type: 'error',
|
|
436
|
+
code: 'VALIDATION',
|
|
437
|
+
message: 'Code is empty',
|
|
438
|
+
category: 'validation',
|
|
439
|
+
retryable: false,
|
|
440
|
+
transient: false,
|
|
441
|
+
exitCode: 64,
|
|
442
|
+
}, { json: true, compact: globalOpts.compact });
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
error('Code is empty');
|
|
446
|
+
}
|
|
447
|
+
process.exit(64);
|
|
448
|
+
}
|
|
449
|
+
const spinner = globalOpts.json ? null : ora('Updating scene code...').start();
|
|
450
|
+
try {
|
|
451
|
+
const result = await apiRequest(config, 'scenes.updateSceneCode', {
|
|
452
|
+
projectId,
|
|
453
|
+
sceneId,
|
|
454
|
+
code,
|
|
455
|
+
overwriteDuration: options.overwriteDuration || false,
|
|
456
|
+
});
|
|
457
|
+
spinner?.stop();
|
|
458
|
+
if (globalOpts.json) {
|
|
459
|
+
output({
|
|
460
|
+
success: true,
|
|
461
|
+
sceneId,
|
|
462
|
+
projectId,
|
|
463
|
+
revision: result.newRevision,
|
|
464
|
+
compilationError: result.scene?.compilationError || null,
|
|
465
|
+
}, { json: true, compact: globalOpts.compact });
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (result.scene?.compilationError) {
|
|
469
|
+
console.log(chalk.yellow(`\u26a0 Code saved but has compilation error:`));
|
|
470
|
+
console.log(chalk.yellow(` ${result.scene.compilationError}`));
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
success(`Scene code updated: ${sceneId}`);
|
|
474
|
+
}
|
|
475
|
+
console.log(chalk.gray(` Revision: ${result.newRevision}`));
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
spinner?.stop();
|
|
479
|
+
handleScenesError(err, globalOpts);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { loadConfig, hasAuth, getProjectId } from '../lib/config.js';
|
|
5
|
+
import { apiRequest, ApiError } from '../lib/api.js';
|
|
6
|
+
import { success, error, output, table, confirmAction } from '../lib/output.js';
|
|
7
|
+
function handleShareError(err, globalOpts) {
|
|
8
|
+
if (err instanceof ApiError) {
|
|
9
|
+
if (globalOpts.json) {
|
|
10
|
+
output(err.toJSON(), { json: true, compact: globalOpts.compact });
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
error(err.message, err.suggestion);
|
|
14
|
+
}
|
|
15
|
+
process.exit(err.exitCode);
|
|
16
|
+
}
|
|
17
|
+
if (globalOpts.json) {
|
|
18
|
+
output({
|
|
19
|
+
type: 'error',
|
|
20
|
+
code: 'UNKNOWN',
|
|
21
|
+
message: err.message || 'Unknown error',
|
|
22
|
+
category: 'fatal',
|
|
23
|
+
retryable: false,
|
|
24
|
+
transient: false,
|
|
25
|
+
exitCode: 1,
|
|
26
|
+
}, { json: true, compact: globalOpts.compact });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
error(err.message);
|
|
30
|
+
}
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
function exitShareAuthError(globalOpts) {
|
|
34
|
+
if (globalOpts.json) {
|
|
35
|
+
output({
|
|
36
|
+
type: 'error',
|
|
37
|
+
code: 'AUTH_MISSING',
|
|
38
|
+
message: 'Not authenticated',
|
|
39
|
+
category: 'auth',
|
|
40
|
+
retryable: false,
|
|
41
|
+
transient: false,
|
|
42
|
+
exitCode: 13,
|
|
43
|
+
suggestion: 'Run: baz auth login <api-key>',
|
|
44
|
+
}, { json: true, compact: globalOpts.compact });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
error('Not authenticated', 'Run: baz auth login <api-key>');
|
|
48
|
+
}
|
|
49
|
+
process.exit(13);
|
|
50
|
+
}
|
|
51
|
+
export const shareCommand = new Command('share')
|
|
52
|
+
.description('Create and manage share links for projects');
|
|
53
|
+
/**
|
|
54
|
+
* baz share (default — create/get share link for active project)
|
|
55
|
+
*/
|
|
56
|
+
shareCommand
|
|
57
|
+
.command('create', { isDefault: true })
|
|
58
|
+
.description('Create a share link for the active project')
|
|
59
|
+
.option('--title <title>', 'Custom title for the shared page')
|
|
60
|
+
.option('--description <desc>', 'Description for the shared page')
|
|
61
|
+
.action(async (options, cmd) => {
|
|
62
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
63
|
+
const config = loadConfig({
|
|
64
|
+
configPath: globalOpts.config,
|
|
65
|
+
apiUrl: globalOpts.apiUrl,
|
|
66
|
+
projectId: globalOpts.projectId,
|
|
67
|
+
});
|
|
68
|
+
if (!hasAuth(config)) {
|
|
69
|
+
exitShareAuthError(globalOpts);
|
|
70
|
+
}
|
|
71
|
+
let projectId;
|
|
72
|
+
try {
|
|
73
|
+
projectId = getProjectId(config, globalOpts.projectId);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (globalOpts.json) {
|
|
77
|
+
output({
|
|
78
|
+
type: 'error',
|
|
79
|
+
code: 'VALIDATION',
|
|
80
|
+
message: err.message,
|
|
81
|
+
category: 'validation',
|
|
82
|
+
retryable: false,
|
|
83
|
+
transient: false,
|
|
84
|
+
exitCode: 64,
|
|
85
|
+
}, { json: true, compact: globalOpts.compact });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
error(err.message);
|
|
89
|
+
}
|
|
90
|
+
process.exit(64);
|
|
91
|
+
}
|
|
92
|
+
const spinner = globalOpts.json ? null : ora('Creating share link...').start();
|
|
93
|
+
try {
|
|
94
|
+
// Check if share already exists
|
|
95
|
+
const existing = await apiRequest(config, 'share.getProjectShare', {
|
|
96
|
+
projectId,
|
|
97
|
+
});
|
|
98
|
+
if (existing) {
|
|
99
|
+
spinner?.stop();
|
|
100
|
+
if (globalOpts.json) {
|
|
101
|
+
output({
|
|
102
|
+
shareId: existing.id,
|
|
103
|
+
shareUrl: existing.shareUrl,
|
|
104
|
+
created: false,
|
|
105
|
+
}, { json: true, compact: globalOpts.compact });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
success('Share link already exists');
|
|
109
|
+
console.log(chalk.cyan(` ${existing.shareUrl}`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Create new share
|
|
113
|
+
const result = await apiRequest(config, 'share.createShare', {
|
|
114
|
+
projectId,
|
|
115
|
+
title: options.title,
|
|
116
|
+
description: options.description,
|
|
117
|
+
});
|
|
118
|
+
spinner?.stop();
|
|
119
|
+
if (globalOpts.json) {
|
|
120
|
+
output({
|
|
121
|
+
shareId: result.shareId,
|
|
122
|
+
shareUrl: result.shareUrl,
|
|
123
|
+
created: true,
|
|
124
|
+
}, { json: true, compact: globalOpts.compact });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
success('Share link created');
|
|
128
|
+
console.log(chalk.cyan(` ${result.shareUrl}`));
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
spinner?.stop();
|
|
132
|
+
handleShareError(err, globalOpts);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* baz share list
|
|
137
|
+
*/
|
|
138
|
+
shareCommand
|
|
139
|
+
.command('list')
|
|
140
|
+
.description('List all your shared projects')
|
|
141
|
+
.action(async (options, cmd) => {
|
|
142
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
143
|
+
const config = loadConfig({
|
|
144
|
+
configPath: globalOpts.config,
|
|
145
|
+
apiUrl: globalOpts.apiUrl,
|
|
146
|
+
projectId: globalOpts.projectId,
|
|
147
|
+
});
|
|
148
|
+
if (!hasAuth(config)) {
|
|
149
|
+
exitShareAuthError(globalOpts);
|
|
150
|
+
}
|
|
151
|
+
const spinner = globalOpts.json ? null : ora('Fetching shares...').start();
|
|
152
|
+
try {
|
|
153
|
+
const shares = await apiRequest(config, 'share.getMyShares', {});
|
|
154
|
+
spinner?.stop();
|
|
155
|
+
if (globalOpts.json) {
|
|
156
|
+
output(shares.map((s) => ({
|
|
157
|
+
id: s.id,
|
|
158
|
+
title: s.title || s.project?.title || 'Untitled',
|
|
159
|
+
shareUrl: s.shareUrl,
|
|
160
|
+
viewCount: s.viewCount,
|
|
161
|
+
createdAt: s.createdAt,
|
|
162
|
+
})), { json: true, compact: globalOpts.compact });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!shares || shares.length === 0) {
|
|
166
|
+
console.log(chalk.gray('No shared projects.'));
|
|
167
|
+
console.log(chalk.gray('Create one with: baz share'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
console.log(`${shares.length} shared project${shares.length > 1 ? 's' : ''}\n`);
|
|
171
|
+
const rows = shares.map((s) => [
|
|
172
|
+
s.id?.slice(0, 12) || 'unknown',
|
|
173
|
+
s.title || s.project?.title || 'Untitled',
|
|
174
|
+
String(s.viewCount ?? 0),
|
|
175
|
+
s.shareUrl,
|
|
176
|
+
]);
|
|
177
|
+
table(['ID', 'Title', 'Views', 'URL'], rows);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
spinner?.stop();
|
|
181
|
+
handleShareError(err, globalOpts);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
/**
|
|
185
|
+
* baz share delete <share-id>
|
|
186
|
+
*/
|
|
187
|
+
shareCommand
|
|
188
|
+
.command('delete <share-id>')
|
|
189
|
+
.description('Delete a share link')
|
|
190
|
+
.option('--force', 'Skip confirmation')
|
|
191
|
+
.action(async (shareId, options, cmd) => {
|
|
192
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
193
|
+
const config = loadConfig({
|
|
194
|
+
configPath: globalOpts.config,
|
|
195
|
+
apiUrl: globalOpts.apiUrl,
|
|
196
|
+
projectId: globalOpts.projectId,
|
|
197
|
+
});
|
|
198
|
+
if (!hasAuth(config)) {
|
|
199
|
+
exitShareAuthError(globalOpts);
|
|
200
|
+
}
|
|
201
|
+
if (!options.force) {
|
|
202
|
+
const confirmed = await confirmAction(`Delete share link ${shareId}? This cannot be undone.`);
|
|
203
|
+
if (!confirmed) {
|
|
204
|
+
if (globalOpts.json) {
|
|
205
|
+
output({ deleted: false, shareId, reason: 'cancelled' }, { json: true, compact: globalOpts.compact });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
console.log(chalk.gray('Cancelled. Use --force to skip confirmation.'));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const spinner = globalOpts.json ? null : ora('Deleting share...').start();
|
|
213
|
+
try {
|
|
214
|
+
await apiRequest(config, 'share.deleteShare', { shareId });
|
|
215
|
+
spinner?.stop();
|
|
216
|
+
if (globalOpts.json) {
|
|
217
|
+
output({ deleted: true, shareId }, { json: true, compact: globalOpts.compact });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
success(`Deleted share: ${shareId}`);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
spinner?.stop();
|
|
224
|
+
handleShareError(err, globalOpts);
|
|
225
|
+
}
|
|
226
|
+
});
|