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.
Files changed (57) hide show
  1. package/README.md +485 -3
  2. package/bin/baz.js +6 -1
  3. package/dist/commands/auth.d.ts +2 -0
  4. package/dist/commands/auth.js +109 -0
  5. package/dist/commands/capabilities.d.ts +2 -0
  6. package/dist/commands/capabilities.js +44 -0
  7. package/dist/commands/context.d.ts +13 -0
  8. package/dist/commands/context.js +498 -0
  9. package/dist/commands/export.d.ts +2 -0
  10. package/dist/commands/export.js +360 -0
  11. package/dist/commands/logs.d.ts +2 -0
  12. package/dist/commands/logs.js +180 -0
  13. package/dist/commands/loop.d.ts +2 -0
  14. package/dist/commands/loop.js +538 -0
  15. package/dist/commands/mcp.d.ts +2 -0
  16. package/dist/commands/mcp.js +143 -0
  17. package/dist/commands/media.d.ts +2 -0
  18. package/dist/commands/media.js +362 -0
  19. package/dist/commands/project.d.ts +2 -0
  20. package/dist/commands/project.js +786 -0
  21. package/dist/commands/prompt.d.ts +2 -0
  22. package/dist/commands/prompt.js +540 -0
  23. package/dist/commands/recipe.d.ts +15 -0
  24. package/dist/commands/recipe.js +607 -0
  25. package/dist/commands/review.d.ts +17 -0
  26. package/dist/commands/review.js +345 -0
  27. package/dist/commands/scenes.d.ts +2 -0
  28. package/dist/commands/scenes.js +481 -0
  29. package/dist/commands/share.d.ts +2 -0
  30. package/dist/commands/share.js +226 -0
  31. package/dist/commands/state.d.ts +2 -0
  32. package/dist/commands/state.js +171 -0
  33. package/dist/commands/status.d.ts +2 -0
  34. package/dist/commands/status.js +219 -0
  35. package/dist/commands/template.d.ts +2 -0
  36. package/dist/commands/template.js +123 -0
  37. package/dist/commands/verify.d.ts +2 -0
  38. package/dist/commands/verify.js +150 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +124 -0
  41. package/dist/lib/api.d.ts +188 -0
  42. package/dist/lib/api.js +719 -0
  43. package/dist/lib/banner.d.ts +12 -0
  44. package/dist/lib/banner.js +69 -0
  45. package/dist/lib/config.d.ts +33 -0
  46. package/dist/lib/config.js +99 -0
  47. package/dist/lib/output.d.ts +52 -0
  48. package/dist/lib/output.js +162 -0
  49. package/dist/lib/project-state.d.ts +52 -0
  50. package/dist/lib/project-state.js +178 -0
  51. package/dist/lib/sse.d.ts +168 -0
  52. package/dist/lib/sse.js +227 -0
  53. package/dist/lib/version.d.ts +1 -0
  54. package/dist/lib/version.js +3 -0
  55. package/dist/repl.d.ts +4 -0
  56. package/dist/repl.js +764 -0
  57. package/package.json +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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const shareCommand: Command;
@@ -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
+ });