@coreviz/cli 1.0.8 → 1.0.10

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 CHANGED
@@ -52,7 +52,7 @@ npx @coreviz/cli describe path/to/image.jpg
52
52
 
53
53
 
54
54
 
55
- Edit an image with a text prompt:
55
+ Edit an image with a text prompt (🍌 Nano Banan + Flux Kontext in the CLI!):
56
56
 
57
57
  ```bash
58
58
  npx @coreviz/cli edit path/to/image.jpg --prompt "make it cyberpunk style"
package/bin/cli.js CHANGED
@@ -195,25 +195,38 @@ program.command('whoami')
195
195
  }
196
196
  });
197
197
 
198
- program.command('edit <image-path>')
198
+ program.command('edit <image-path> <prompt>')
199
199
  .description('Edit an image using AI')
200
- .option('-p, --prompt <prompt>', 'Text description of the desired edit')
201
- .action(async (imagePath, options) => {
202
- intro(chalk.bgHex('#663399').white('CoreViz'));
200
+ .option('--quiet', 'Suppress UI output (for scripting)')
201
+ .action(async (imagePath, prompt, options) => {
202
+ if (!options.quiet) {
203
+ intro(chalk.bgHex('#663399').white('CoreViz'));
204
+ }
203
205
 
204
206
  const session = config.get('session');
205
207
  if (!session || !session.access_token) {
208
+ if (options.quiet) {
209
+ console.error('Not logged in.');
210
+ process.exit(1);
211
+ }
206
212
  cancel('You are not logged in. Please run `coreviz login` first.');
207
213
  process.exit(1);
208
214
  }
209
215
 
210
216
  if (!fs.existsSync(imagePath)) {
217
+ if (options.quiet) {
218
+ console.error(`File not found: ${imagePath}`);
219
+ process.exit(1);
220
+ }
211
221
  cancel(`File not found: ${imagePath}`);
212
222
  process.exit(1);
213
223
  }
214
224
 
215
- let prompt = options.prompt;
216
225
  if (!prompt) {
226
+ if (options.quiet) {
227
+ console.error('Prompt is required in quiet mode.');
228
+ process.exit(1);
229
+ }
217
230
  prompt = await text({
218
231
  message: 'What would you like to change in the image?',
219
232
  placeholder: 'e.g., "Make it look like a painting" or "Add a red hat"',
@@ -228,8 +241,11 @@ program.command('edit <image-path>')
228
241
  }
229
242
  }
230
243
 
231
- const spinner = yoctoSpinner({ text: "Processing image..." });
232
- spinner.start();
244
+ let spinner;
245
+ if (!options.quiet) {
246
+ spinner = yoctoSpinner({ text: "Processing image..." });
247
+ spinner.start();
248
+ }
233
249
 
234
250
  try {
235
251
  const base64Image = readImageAsBase64(imagePath);
@@ -239,57 +255,199 @@ program.command('edit <image-path>')
239
255
  prompt
240
256
  });
241
257
 
242
- spinner.stop();
258
+ if (spinner) spinner.stop();
243
259
 
244
260
  // Save result
245
261
  const outputFilename = `edited-${Date.now()}-${path.basename(imagePath)}`;
246
262
  const outputBuffer = Buffer.from(resultBase64.replace(/^data:image\/\w+;base64,/, ""), 'base64');
247
263
  fs.writeFileSync(outputFilename, outputBuffer);
248
264
 
249
- outro(chalk.green(`✅ Image edited successfully! Saved as ${outputFilename}`));
265
+ if (options.quiet) {
266
+ console.log(outputFilename);
267
+ } else {
268
+ outro(chalk.green(`✅ Image edited successfully! Saved as ${outputFilename}`));
269
+ }
250
270
 
251
271
  } catch (error) {
252
- spinner.stop();
253
- cancel(`Failed to edit image: ${error.message}`);
272
+ if (spinner) spinner.stop();
273
+ const msg = error.message.includes('credits')
274
+ ? 'Insufficient credits. Please add credits to your account on https://lab.coreviz.io.'
275
+ : `Failed to edit image: ${error.message}`;
276
+ if (options.quiet) {
277
+ console.error(msg);
278
+ } else {
279
+ cancel(msg);
280
+ }
254
281
  process.exit(1);
255
282
  }
256
283
  });
257
284
 
258
285
  program.command('describe <image-path>')
259
286
  .description('Describe an image using AI')
260
- .action(async (imagePath) => {
261
- intro(chalk.bgHex('#663399').white('CoreViz'));
287
+ .option('--quiet', 'Suppress UI output (for scripting)')
288
+ .action(async (imagePath, options) => {
289
+ if (!options.quiet) {
290
+ intro(chalk.bgHex('#663399').white('CoreViz'));
291
+ }
262
292
 
263
293
  const session = config.get('session');
264
294
  if (!session || !session.access_token) {
295
+ if (options.quiet) {
296
+ console.error('Not logged in.');
297
+ process.exit(1);
298
+ }
265
299
  cancel('You are not logged in. Please run `coreviz login` first.');
266
300
  process.exit(1);
267
301
  }
268
302
 
269
303
  if (!fs.existsSync(imagePath)) {
304
+ if (options.quiet) {
305
+ console.error(`File not found: ${imagePath}`);
306
+ process.exit(1);
307
+ }
270
308
  cancel(`File not found: ${imagePath}`);
271
309
  process.exit(1);
272
310
  }
273
311
 
274
- const spinner = yoctoSpinner({ text: "Analyzing image..." });
275
- spinner.start();
312
+ let spinner;
313
+ if (!options.quiet) {
314
+ spinner = yoctoSpinner({ text: "Analyzing image..." });
315
+ spinner.start();
316
+ }
276
317
 
277
318
  try {
278
319
  const base64Image = readImageAsBase64(imagePath);
279
320
  const coreviz = new CoreViz({ token: session.access_token });
280
321
  const description = await coreviz.describe(base64Image);
281
322
 
282
- spinner.stop();
323
+ if (spinner) spinner.stop();
283
324
 
284
- outro(chalk.green('✅ Image description:'));
285
- console.log(description);
325
+ if (options.quiet) {
326
+ console.log(description);
327
+ } else {
328
+ outro(chalk.green('✅ Image description:'));
329
+ console.log(description);
330
+ }
286
331
  } catch (error) {
287
- spinner.stop();
288
- if (error.message === 'Insufficient credits') {
289
- cancel('Insufficient credits. Please add credits to your account.');
332
+ if (spinner) spinner.stop();
333
+ const msg = error.message.includes('credits')
334
+ ? 'Insufficient credits. Please add credits to your account on https://lab.coreviz.io.'
335
+ : `Failed to describe image: ${error.message}`;
336
+
337
+ if (options.quiet) {
338
+ console.error(msg);
339
+ } else {
340
+ cancel(msg);
341
+ }
342
+ process.exit(1);
343
+ }
344
+ });
345
+
346
+ program.command('tag <image-path> <prompt>')
347
+ .description('Generate tags for an image using AI')
348
+ .option('--choices <items>', 'Comma-separated list of possible tags to choose from (optional)', '')
349
+ .option('--single', 'Return only one tag', false)
350
+ .option('-m, --mode <mode>', 'The mode to use for tagging. Defaults to "api".', 'api')
351
+ .option('--quiet', 'Output raw text for scripting (suppresses UI)')
352
+ .action(async (imagePath, prompt, options) => {
353
+ if (!options.quiet) {
354
+ intro(chalk.bgHex('#663399').white('CoreViz'));
355
+ }
356
+
357
+ const session = config.get('session');
358
+ if (!session || !session.access_token) {
359
+ if (options.quiet) {
360
+ console.error('Not logged in.');
361
+ process.exit(1);
362
+ }
363
+ cancel('You are not logged in. Please run `coreviz login` first.');
364
+ process.exit(1);
365
+ }
366
+
367
+ if (!fs.existsSync(imagePath)) {
368
+ if (options.quiet) {
369
+ console.error(`File not found: ${imagePath}`);
290
370
  process.exit(1);
291
371
  }
292
- cancel(`Failed to describe image: ${error.message}`);
372
+ cancel(`File not found: ${imagePath}`);
373
+ process.exit(1);
374
+ }
375
+
376
+ let tagList = options.choices ? options.choices.split(',').map(s => s.trim()) : undefined;
377
+
378
+ if (!prompt) {
379
+ if (tagList && tagList.length > 0) {
380
+ prompt = "Select the best matching tags";
381
+ } else {
382
+ if (options.quiet) {
383
+ console.error('Prompt is required in quiet mode.');
384
+ process.exit(1);
385
+ }
386
+ prompt = await text({
387
+ message: 'What kind of tags do you want to generate?',
388
+ placeholder: 'e.g., "jersey number of the player", "color of the car", etc.',
389
+ validate(value) {
390
+ if (value.length === 0) return `Value is required!`;
391
+ },
392
+ });
393
+
394
+ if (isCancel(prompt)) {
395
+ cancel('Operation cancelled.');
396
+ process.exit(0);
397
+ }
398
+ }
399
+ }
400
+
401
+ let spinner;
402
+ if (!options.quiet) {
403
+ setTimeout(() => {
404
+ if (spinner.isSpinning && options.mode === 'local') {
405
+ spinner.text = "On the first run, it might take a few minutes to load the local model, please wait...";
406
+ } else if (spinner.isSpinning && options.mode === 'api') {
407
+ spinner.text = "This might take a few seconds...";
408
+ }
409
+ }, 8000);
410
+ spinner = yoctoSpinner({ text: "Generating tags..." });
411
+ spinner.start();
412
+ }
413
+
414
+ try {
415
+ const base64Image = readImageAsBase64(imagePath);
416
+ const coreviz = new CoreViz({ token: session.access_token });
417
+
418
+ const response = await coreviz.tag(base64Image, {
419
+ mode: options.mode,
420
+ prompt,
421
+ options: tagList,
422
+ multiple: !options.single
423
+ });
424
+
425
+ if (spinner) spinner.stop();
426
+
427
+ if (options.quiet) {
428
+ if (response.tags && response.tags.length > 0) {
429
+ console.log(response.tags.join('\n'));
430
+ }
431
+ } else {
432
+ if (response.tags && response.tags.length > 0) {
433
+ outro(chalk.green('✅ Tags generated:'));
434
+ response.tags.forEach(tag => console.log(chalk.blue(`• ${tag}`)));
435
+ } else {
436
+ outro(chalk.yellow('No tags generated.'));
437
+ }
438
+ }
439
+
440
+ } catch (error) {
441
+ if (spinner) spinner.stop();
442
+ const msg = error.message.includes('credits')
443
+ ? 'Insufficient credits. Please add credits to your account on https://lab.coreviz.io.'
444
+ : `Failed to generate tags: ${error.message}`;
445
+
446
+ if (options.quiet) {
447
+ console.error(msg);
448
+ } else {
449
+ cancel(msg);
450
+ }
293
451
  process.exit(1);
294
452
  }
295
453
  });
@@ -297,19 +455,29 @@ program.command('describe <image-path>')
297
455
  program.command('search <query>')
298
456
  .description('Search for images in the current directory using AI')
299
457
  .option('-m, --mode <mode>', 'The mode to use for embedding. Defaults to "local".', 'local')
458
+ .option('--quiet', 'Suppress UI output (for scripting)')
300
459
  .action(async (query, options) => {
301
- intro(chalk.bgHex('#663399').white('CoreViz'));
460
+ if (!options.quiet) {
461
+ intro(chalk.bgHex('#663399').white('CoreViz'));
462
+ }
302
463
 
303
464
  const mode = options.mode || 'local';
304
465
 
305
466
  const session = config.get('session');
306
467
  if (!session || !session.access_token) {
468
+ if (options.quiet) {
469
+ console.error('Not logged in.');
470
+ process.exit(1);
471
+ }
307
472
  cancel('You are not logged in. Please run `coreviz login` first.');
308
473
  process.exit(1);
309
474
  }
310
475
 
311
- const spinner = yoctoSpinner({ text: "Indexing directory..." });
312
- spinner.start();
476
+ let spinner;
477
+ if (!options.quiet) {
478
+ spinner = yoctoSpinner({ text: "Indexing directory..." });
479
+ spinner.start();
480
+ }
313
481
 
314
482
  const dbPath = path.join(process.cwd(), '.index.db');
315
483
  const db = new Database(dbPath);
@@ -328,7 +496,12 @@ program.command('search <query>')
328
496
  .filter(file => imageExtensions.includes(path.extname(file).toLowerCase()));
329
497
 
330
498
  if (files.length === 0) {
331
- spinner.stop();
499
+ if (spinner) spinner.stop();
500
+ if (options.quiet) {
501
+ // No images found, just exit with 0 (empty result) or 1?
502
+ // Usually empty search is exit 0 with empty stdout.
503
+ process.exit(0);
504
+ }
332
505
  cancel('No images found in the current directory.');
333
506
  process.exit(0);
334
507
  }
@@ -350,10 +523,13 @@ program.command('search <query>')
350
523
 
351
524
  if (mode === 'local') {
352
525
  // You're using the local model, it might take a few minutes for the model to load on the first run.
353
- spinner.text = "Loading local model, this might take a few minutes to load the first time...";
354
- spinner.start();
355
- await coreviz.embedLocal('text', { type: 'text', mode: mode });
356
- spinner.stop();
526
+ setTimeout(() => {
527
+ if (spinner.isSpinning && mode === 'local') {
528
+ spinner.text = "On the first run, it might take a few minutes to load the local model, please wait...";
529
+ }
530
+ }, 8000);
531
+ await coreviz.embed('text', { type: 'text', mode: mode });
532
+ if (spinner) spinner.stop();
357
533
  }
358
534
 
359
535
  for (const file of files) {
@@ -368,7 +544,7 @@ program.command('search <query>')
368
544
  continue;
369
545
  }
370
546
 
371
- spinner.text = `Indexing ${file}...`;
547
+ if (spinner) spinner.text = `Indexing ${file}...`;
372
548
 
373
549
  try {
374
550
  const base64Image = readImageAsBase64(filePath);
@@ -377,11 +553,17 @@ program.command('search <query>')
377
553
  upsertFile.run(file, mtime, JSON.stringify(embedding));
378
554
  } catch (error) {
379
555
  // Log error but continue
380
- console.error(`Failed to index ${file}: ${error.message}`);
556
+ if (!options.quiet) {
557
+ if (error.message.includes('credits')) {
558
+ cancel('Insufficient credits. Please add credits to your account on https://lab.coreviz.io.');
559
+ process.exit(1);
560
+ }
561
+ console.error(`Failed to index ${file}: ${error.message}`);
562
+ }
381
563
  }
382
564
  }
383
565
 
384
- spinner.text = "Processing search query...";
566
+ if (spinner) spinner.text = "Processing search query...";
385
567
 
386
568
  try {
387
569
  const { embedding: queryEmbedding } = await coreviz.embed(query, { type: 'text', mode: mode });
@@ -395,50 +577,44 @@ program.command('search <query>')
395
577
  const fileEmbedding = JSON.parse(row.embedding);
396
578
 
397
579
  // Calculate cosine similarity
398
- const similarity = cosineSimilarity(queryEmbedding, fileEmbedding);
580
+ const similarity = coreviz.similarity(queryEmbedding, fileEmbedding);
399
581
  results.push({ file: row.path, similarity });
400
582
  }
401
583
 
402
584
  // Sort by similarity descending
403
585
  results.sort((a, b) => b.similarity - a.similarity);
404
586
 
405
- spinner.stop();
587
+ if (spinner) spinner.stop();
406
588
 
407
- outro(chalk.green(`✅ Search results for "${query}"`));
589
+ if (options.quiet) {
590
+ // Output raw file paths (top 5)
591
+ results.slice(0, 5).forEach(result => {
592
+ console.log(result.file);
593
+ });
594
+ } else {
595
+ outro(chalk.green(`✅ Search results for "${query}"`));
408
596
 
409
- // Show top 5 results
410
- results.slice(0, 5).forEach((result, i) => {
411
- const score = (result.similarity * 100).toFixed(1);
412
- console.log(`${i + 1}. ${chalk.bold(result.file)} ${chalk.gray(`(${score}%)`)}`);
413
- });
597
+ // Show top 5 results
598
+ results.slice(0, 5).forEach((result, i) => {
599
+ const score = (result.similarity * 100).toFixed(1);
600
+ console.log(`${i + 1}. ${chalk.bold(result.file)} ${chalk.gray(`(${score}%)`)}`);
601
+ });
602
+ }
414
603
 
415
604
  } catch (error) {
416
- spinner.stop();
417
- cancel(`Search failed: ${error.message}`);
605
+ if (spinner) spinner.stop();
606
+ const msg = `Search failed: ${error.message}`;
607
+ if (options.quiet) {
608
+ console.error(msg);
609
+ } else {
610
+ cancel(msg);
611
+ }
418
612
  process.exit(1);
419
613
  } finally {
420
614
  db.close();
421
615
  }
422
616
  });
423
617
 
424
- function cosineSimilarity(vecA, vecB) {
425
- if (vecA.length !== vecB.length) return 0;
426
-
427
- let dotProduct = 0;
428
- let normA = 0;
429
- let normB = 0;
430
-
431
- for (let i = 0; i < vecA.length; i++) {
432
- dotProduct += vecA[i] * vecB[i];
433
- normA += vecA[i] * vecA[i];
434
- normB += vecB[i] * vecB[i];
435
- }
436
-
437
- if (normA === 0 || normB === 0) return 0;
438
-
439
- return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
440
- }
441
-
442
618
  function readImageAsBase64(imagePath) {
443
619
  const imageBuffer = fs.readFileSync(imagePath);
444
620
  return `data:image/${path.extname(imagePath).slice(1) || 'jpeg'};base64,${imageBuffer.toString('base64')}`;
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ # Loop through all image files
4
+ for file in *.jpg *.png *.jpeg *.webp; do
5
+ [ -e "$file" ] || continue
6
+
7
+ echo "Processing $file..."
8
+
9
+ # Extract jersey number using coreviz tag with --quiet
10
+ jersey_number=$(npx @coreviz/cli edit "$file" --prompt "make it cyberpunk style")
11
+
12
+ echo " Edited image: $edited_image"
13
+ done
File without changes
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+
3
+ # Loop through all image files
4
+ for file in *.jpg *.png *.jpeg *.webp; do
5
+ [ -e "$file" ] || continue
6
+
7
+ echo "Processing $file..."
8
+
9
+ # Extract jersey number using coreviz tag with --quiet
10
+ jersey_number=$(npx @coreviz/cli tag "$file" "What is the player's jersey number? Return only the number." \
11
+ --single \
12
+ --mode local \
13
+ --quiet)
14
+
15
+ if [ -n "$jersey_number" ]; then
16
+ echo " Found jersey number: $jersey_number"
17
+ mkdir -p "player_$jersey_number"
18
+ mv "$file" "player_$jersey_number/"
19
+ else
20
+ echo " Could not detect jersey number for $file"
21
+ fi
22
+ done
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreviz/cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "type": "module",
5
5
  "description": "CoreViz CLI tool",
6
6
  "main": "index.js",
@@ -27,7 +27,7 @@
27
27
  "homepage": "https://github.com/CoreViz/cli#readme",
28
28
  "dependencies": {
29
29
  "@clack/prompts": "^0.11.0",
30
- "@coreviz/sdk": "^1.0.9",
30
+ "@coreviz/sdk": "^1.0.11",
31
31
  "better-auth": "^1.4.2",
32
32
  "better-sqlite3": "^12.4.6",
33
33
  "chalk": "^5.6.2",