reelsort 0.2.7 → 0.2.9

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 (4) hide show
  1. package/dist/cli.js +358 -359
  2. package/dist/index.js +282 -290
  3. package/dist/index.mjs +252 -260
  4. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -23,13 +23,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  mod
24
24
  ));
25
25
 
26
- // src/program.ts
26
+ // src/cli.ts
27
27
  var import_termkit19 = require("termkit");
28
28
 
29
+ // src/program.ts
30
+ var import_termkit18 = require("termkit");
31
+
29
32
  // src/actions/add.ts
30
33
  var import_fs3 = require("fs");
31
34
  var import_path3 = require("path");
32
- var import_termkit2 = require("termkit");
35
+ var import_termkit = require("termkit");
33
36
 
34
37
  // src/config.ts
35
38
  var import_fs = require("fs");
@@ -286,60 +289,20 @@ var searchTv = async (title, apiKey) => {
286
289
  }
287
290
  };
288
291
 
289
- // src/refs/spinner.ts
290
- var import_termkit = require("termkit");
291
- var Spinner = class {
292
- spinner;
293
- constructor() {
294
- this.spinner = new import_termkit.Spinner();
295
- }
296
- get text() {
297
- return this.spinner.text;
298
- }
299
- set text(t) {
300
- this.spinner.text = t;
301
- }
302
- start(s) {
303
- if (s) this.spinner.text = s;
304
- this.spinner.start();
305
- return this;
306
- }
307
- info(s) {
308
- this.spinner.info(s);
309
- return this;
310
- }
311
- warn(s) {
312
- this.spinner.warn(s);
313
- return this;
314
- }
315
- fail(s) {
316
- this.spinner.fail(s);
317
- return this;
318
- }
319
- succeed(s) {
320
- this.spinner.succeed(s);
321
- return this;
322
- }
323
- stop() {
324
- this.spinner.stop();
325
- return this;
326
- }
327
- };
328
- var spinner_default = new Spinner();
329
-
330
292
  // src/actions/add.ts
293
+ var spinner = new import_termkit.Spinner();
331
294
  var add = async ({ name }) => {
332
295
  const config = getConfig();
333
296
  if (!config.tmdbApiKey) throw new Error("TMDb API key required \u2014 run: reelsort config set tmdb-key <key>");
334
297
  const destRoot = config.dest.tv;
335
298
  if (!destRoot) throw new Error("no TV destination configured \u2014 run: reelsort config set dest tv <dir>");
336
- spinner_default.start(`searching TMDb for "${name}"`);
299
+ spinner.update(`searching TMDb for "${name}"`).start();
337
300
  const results = await searchTv(name, config.tmdbApiKey);
338
- spinner_default.stop();
301
+ spinner.stop();
339
302
  if (results.length === 0) throw new Error(`no TMDb results for "${name}"`);
340
303
  let picked = results.length === 1 ? results[0] : null;
341
304
  if (!picked) {
342
- const select = new import_termkit2.Select();
305
+ const select = new import_termkit.Select();
343
306
  const items = results.map((r) => ({
344
307
  label: r.year ? `${r.title} (${r.year})` : r.title,
345
308
  description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
@@ -353,15 +316,16 @@ var add = async ({ name }) => {
353
316
  const showPath = (0, import_path3.resolve)(destRoot, folderName);
354
317
  (0, import_fs3.mkdirSync)(showPath, { recursive: true });
355
318
  upsertShow(showPath, picked.id, picked.title);
356
- spinner_default.start();
357
- spinner_default.succeed(`added ${import_termkit2.Color.green.encoder(folderName)}`);
358
- spinner_default.stop();
319
+ spinner.start();
320
+ spinner.succeed(`added ${import_termkit.Color.green.encoder(folderName)}`);
321
+ spinner.stop();
359
322
  };
360
323
  var add_default = add;
361
324
 
362
325
  // src/actions/clean.ts
363
326
  var import_fs4 = require("fs");
364
- var import_termkit3 = require("termkit");
327
+ var import_termkit2 = require("termkit");
328
+ var spinner2 = new import_termkit2.Spinner();
365
329
  var parseOlderThan = (s) => {
366
330
  const match = s.match(/^(\d+)([dhm])$/);
367
331
  if (!match) return null;
@@ -371,11 +335,11 @@ var parseOlderThan = (s) => {
371
335
  return ms;
372
336
  };
373
337
  var clean = async ({ dryRun, olderThan }) => {
374
- spinner_default.start();
338
+ spinner2.start();
375
339
  const imports = getCleanableImports();
376
340
  if (imports.length === 0) {
377
- spinner_default.info("nothing to clean");
378
- spinner_default.stop();
341
+ spinner2.info("nothing to clean");
342
+ spinner2.stop();
379
343
  return;
380
344
  }
381
345
  const cutoffMs = olderThan ? parseOlderThan(olderThan) : null;
@@ -394,30 +358,30 @@ var clean = async ({ dryRun, olderThan }) => {
394
358
  continue;
395
359
  }
396
360
  if (dryRun) {
397
- spinner_default.succeed(`[dry] would remove ${import_termkit3.Color.white.encoder(imp.sourcePath)}`);
361
+ spinner2.succeed(`[dry] would remove ${import_termkit2.Color.white.encoder(imp.sourcePath)}`);
398
362
  cleaned++;
399
363
  continue;
400
364
  }
401
365
  try {
402
366
  (0, import_fs4.rmSync)(imp.sourcePath, { recursive: true, force: true });
403
367
  deleteImport(imp.id);
404
- spinner_default.succeed(`removed ${import_termkit3.Color.white.encoder(imp.sourcePath)}`);
368
+ spinner2.succeed(`removed ${import_termkit2.Color.white.encoder(imp.sourcePath)}`);
405
369
  cleaned++;
406
370
  } catch {
407
- spinner_default.warn(`locked or inaccessible, skipped: ${import_termkit3.Color.white.encoder(imp.sourcePath)}`);
371
+ spinner2.warn(`locked or inaccessible, skipped: ${import_termkit2.Color.white.encoder(imp.sourcePath)}`);
408
372
  skipped++;
409
373
  }
410
374
  }
411
- spinner_default.succeed(`cleaned ${cleaned} items`);
412
- if (skipped) spinner_default.info(`skipped ${skipped} items`);
413
- spinner_default.stop();
375
+ spinner2.succeed(`cleaned ${cleaned} items`);
376
+ if (skipped) spinner2.info(`skipped ${skipped} items`);
377
+ spinner2.stop();
414
378
  };
415
379
  var clean_default = clean;
416
380
 
417
381
  // src/actions/config.ts
418
382
  var import_fs5 = require("fs");
419
383
  var import_path4 = require("path");
420
- var import_termkit4 = require("termkit");
384
+ var import_termkit3 = require("termkit");
421
385
 
422
386
  // src/helpers/formatEpisode.ts
423
387
  var DEFAULT_EPISODE_FORMAT = "{s}x{ee} - {title}";
@@ -436,6 +400,7 @@ var formatEpisode = (season, episode, format = DEFAULT_EPISODE_FORMAT, double =
436
400
  };
437
401
 
438
402
  // src/actions/config.ts
403
+ var spinner3 = new import_termkit3.Spinner();
439
404
  var DEST_TYPES = ["movie", "tv", "ps3", "book"];
440
405
  var getSourceEntries = (sources) => {
441
406
  const entries = [];
@@ -459,27 +424,27 @@ var sourceAdd = async ({ dir }) => {
459
424
  const resolved = (0, import_path4.resolve)(dir);
460
425
  const config = getConfig();
461
426
  if (config.sources.includes(resolved)) {
462
- spinner_default.start();
463
- spinner_default.info(`source already configured: ${import_termkit4.Color.white.encoder(resolved)}`);
464
- spinner_default.stop();
427
+ spinner3.start();
428
+ spinner3.info(`source already configured: ${import_termkit3.Color.white.encoder(resolved)}`);
429
+ spinner3.stop();
465
430
  return;
466
431
  }
467
432
  config.sources.push(resolved);
468
433
  saveConfig(config);
469
- spinner_default.start();
470
- spinner_default.succeed(`added source: ${import_termkit4.Color.white.encoder(resolved)}`);
471
- spinner_default.stop();
434
+ spinner3.start();
435
+ spinner3.succeed(`added source: ${import_termkit3.Color.white.encoder(resolved)}`);
436
+ spinner3.stop();
472
437
  };
473
438
  var sourceRemove = async ({ dir }) => {
474
439
  const config = getConfig();
475
440
  if (!dir) {
476
441
  if (config.sources.length === 0) {
477
- spinner_default.start();
478
- spinner_default.warn("no sources configured");
479
- spinner_default.stop();
442
+ spinner3.start();
443
+ spinner3.warn("no sources configured");
444
+ spinner3.stop();
480
445
  return;
481
446
  }
482
- const select = new import_termkit4.Select();
447
+ const select = new import_termkit3.Select();
483
448
  const picked = await select.ask("Which source do you want to remove?", config.sources.map((s) => ({ label: s, value: s })));
484
449
  if (!picked) return;
485
450
  dir = picked.value;
@@ -487,16 +452,16 @@ var sourceRemove = async ({ dir }) => {
487
452
  const resolved = (0, import_path4.resolve)(dir);
488
453
  const index = config.sources.indexOf(resolved);
489
454
  if (index === -1) {
490
- spinner_default.start();
491
- spinner_default.warn(`source not found: ${import_termkit4.Color.white.encoder(resolved)}`);
492
- spinner_default.stop();
455
+ spinner3.start();
456
+ spinner3.warn(`source not found: ${import_termkit3.Color.white.encoder(resolved)}`);
457
+ spinner3.stop();
493
458
  return;
494
459
  }
495
460
  config.sources.splice(index, 1);
496
461
  saveConfig(config);
497
- spinner_default.start();
498
- spinner_default.succeed(`removed source: ${import_termkit4.Color.white.encoder(resolved)}`);
499
- spinner_default.stop();
462
+ spinner3.start();
463
+ spinner3.succeed(`removed source: ${import_termkit3.Color.white.encoder(resolved)}`);
464
+ spinner3.stop();
500
465
  };
501
466
  var destAdd = async ({ type, dir }) => {
502
467
  if (!DEST_TYPES.includes(type)) {
@@ -506,21 +471,21 @@ var destAdd = async ({ type, dir }) => {
506
471
  const config = getConfig();
507
472
  config.dest[type] = resolved;
508
473
  saveConfig(config);
509
- spinner_default.start();
510
- spinner_default.succeed(`set ${type} destination: ${import_termkit4.Color.green.encoder(resolved)}`);
511
- spinner_default.stop();
474
+ spinner3.start();
475
+ spinner3.succeed(`set ${type} destination: ${import_termkit3.Color.green.encoder(resolved)}`);
476
+ spinner3.stop();
512
477
  };
513
478
  var destRemove = async ({ type }) => {
514
479
  const config = getConfig();
515
480
  if (!type) {
516
481
  const configured = DEST_TYPES.filter((t) => config.dest[t]);
517
482
  if (configured.length === 0) {
518
- spinner_default.start();
519
- spinner_default.warn("no destinations configured");
520
- spinner_default.stop();
483
+ spinner3.start();
484
+ spinner3.warn("no destinations configured");
485
+ spinner3.stop();
521
486
  return;
522
487
  }
523
- const select = new import_termkit4.Select();
488
+ const select = new import_termkit3.Select();
524
489
  const picked = await select.ask("Which destination do you want to remove?", configured.map((t) => ({ label: `${t.padEnd(6)} ${config.dest[t]}`, value: t })));
525
490
  if (!picked) return;
526
491
  type = picked.value;
@@ -529,16 +494,16 @@ var destRemove = async ({ type }) => {
529
494
  throw new Error(`unknown type '${type}', expected: ${DEST_TYPES.join(", ")}`);
530
495
  }
531
496
  if (!config.dest[type]) {
532
- spinner_default.start();
533
- spinner_default.warn(`no ${type} destination configured`);
534
- spinner_default.stop();
497
+ spinner3.start();
498
+ spinner3.warn(`no ${type} destination configured`);
499
+ spinner3.stop();
535
500
  return;
536
501
  }
537
502
  delete config.dest[type];
538
503
  saveConfig(config);
539
- spinner_default.start();
540
- spinner_default.succeed(`removed ${type} destination`);
541
- spinner_default.stop();
504
+ spinner3.start();
505
+ spinner3.succeed(`removed ${type} destination`);
506
+ spinner3.stop();
542
507
  };
543
508
  var ignore = async ({ name }) => {
544
509
  const config = getConfig();
@@ -547,12 +512,12 @@ var ignore = async ({ name }) => {
547
512
  if (names.length === 0) {
548
513
  const entries = getSourceEntries(config.sources);
549
514
  if (entries.length === 0) {
550
- spinner_default.start();
551
- spinner_default.warn("no files found in configured sources");
552
- spinner_default.stop();
515
+ spinner3.start();
516
+ spinner3.warn("no files found in configured sources");
517
+ spinner3.stop();
553
518
  return;
554
519
  }
555
- const ms = new import_termkit4.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
520
+ const ms = new import_termkit3.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
556
521
  const items = entries.map((e) => ({ label: e }));
557
522
  const picked = await ms.ask("Select files to ignore during scan:", items);
558
523
  if (!picked || picked.length === 0) return;
@@ -566,67 +531,67 @@ var ignore = async ({ name }) => {
566
531
  }
567
532
  }
568
533
  if (added.length === 0) {
569
- spinner_default.start();
570
- spinner_default.info("all selected files are already ignored");
571
- spinner_default.stop();
534
+ spinner3.start();
535
+ spinner3.info("all selected files are already ignored");
536
+ spinner3.stop();
572
537
  return;
573
538
  }
574
539
  saveConfig(config);
575
- spinner_default.start();
576
- for (const n of added) spinner_default.succeed(`ignoring: ${import_termkit4.Color.white.encoder(n)}`);
577
- spinner_default.stop();
540
+ spinner3.start();
541
+ for (const n of added) spinner3.succeed(`ignoring: ${import_termkit3.Color.white.encoder(n)}`);
542
+ spinner3.stop();
578
543
  };
579
544
  var ignoreRemove = async ({ name }) => {
580
545
  const config = getConfig();
581
546
  const list2 = config.ignore ?? [];
582
547
  if (!name) {
583
548
  if (list2.length === 0) {
584
- spinner_default.start();
585
- spinner_default.warn("ignore list is empty");
586
- spinner_default.stop();
549
+ spinner3.start();
550
+ spinner3.warn("ignore list is empty");
551
+ spinner3.stop();
587
552
  return;
588
553
  }
589
- const ms = new import_termkit4.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
554
+ const ms = new import_termkit3.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
590
555
  const items = list2.map((n) => ({ label: n }));
591
556
  const picked = await ms.ask("Select files to remove from ignore list:", items);
592
557
  if (!picked || picked.length === 0) return;
593
558
  const toRemove = new Set(picked.map((p) => p.label));
594
559
  config.ignore = list2.filter((n) => !toRemove.has(n));
595
560
  saveConfig(config);
596
- spinner_default.start();
597
- for (const n of [...toRemove]) spinner_default.succeed(`removed from ignore list: ${import_termkit4.Color.white.encoder(n)}`);
598
- spinner_default.stop();
561
+ spinner3.start();
562
+ for (const n of [...toRemove]) spinner3.succeed(`removed from ignore list: ${import_termkit3.Color.white.encoder(n)}`);
563
+ spinner3.stop();
599
564
  return;
600
565
  }
601
566
  const index = list2.indexOf(name);
602
567
  if (index === -1) {
603
- spinner_default.start();
604
- spinner_default.warn(`not in ignore list: ${import_termkit4.Color.white.encoder(name)}`);
605
- spinner_default.stop();
568
+ spinner3.start();
569
+ spinner3.warn(`not in ignore list: ${import_termkit3.Color.white.encoder(name)}`);
570
+ spinner3.stop();
606
571
  return;
607
572
  }
608
573
  config.ignore.splice(index, 1);
609
574
  saveConfig(config);
610
- spinner_default.start();
611
- spinner_default.succeed(`removed from ignore list: ${import_termkit4.Color.white.encoder(name)}`);
612
- spinner_default.stop();
575
+ spinner3.start();
576
+ spinner3.succeed(`removed from ignore list: ${import_termkit3.Color.white.encoder(name)}`);
577
+ spinner3.stop();
613
578
  };
614
579
  var configSet = async ({ key, subkey, value }) => {
615
580
  const config = getConfig();
616
581
  if (key === "language") {
617
582
  config.language = subkey;
618
583
  saveConfig(config);
619
- spinner_default.start();
620
- spinner_default.succeed(`set subtitle language: ${import_termkit4.Color.green.encoder(subkey)}`);
621
- spinner_default.stop();
584
+ spinner3.start();
585
+ spinner3.succeed(`set subtitle language: ${import_termkit3.Color.green.encoder(subkey)}`);
586
+ spinner3.stop();
622
587
  return;
623
588
  }
624
589
  if (key === "tmdb-key") {
625
590
  config.tmdbApiKey = subkey;
626
591
  saveConfig(config);
627
- spinner_default.start();
628
- spinner_default.succeed(`set TMDb API key`);
629
- spinner_default.stop();
592
+ spinner3.start();
593
+ spinner3.succeed(`set TMDb API key`);
594
+ spinner3.stop();
630
595
  return;
631
596
  }
632
597
  if (key === "format") {
@@ -646,9 +611,9 @@ var configSet = async ({ key, subkey, value }) => {
646
611
  throw new Error(`unknown format key '${subkey}', expected: movie, episode, season`);
647
612
  }
648
613
  saveConfig(config);
649
- spinner_default.start();
650
- spinner_default.succeed(`set ${subkey} format: ${import_termkit4.Color.green.encoder(value ?? subkey)}`);
651
- spinner_default.stop();
614
+ spinner3.start();
615
+ spinner3.succeed(`set ${subkey} format: ${import_termkit3.Color.green.encoder(value ?? subkey)}`);
616
+ spinner3.stop();
652
617
  return;
653
618
  }
654
619
  throw new Error(`unknown key '${key}', expected: language, tmdb-key, format`);
@@ -659,7 +624,7 @@ var configShow = async () => {
659
624
  if (config.sources.length === 0) {
660
625
  console.log(" (none)");
661
626
  } else {
662
- for (const s of config.sources) console.log(` ${import_termkit4.Color.white.encoder(s)}`);
627
+ for (const s of config.sources) console.log(` ${import_termkit3.Color.white.encoder(s)}`);
663
628
  }
664
629
  console.log("\nDestinations:");
665
630
  const entries = DEST_TYPES.map((t) => ({ type: t, path: config.dest[t] })).filter((e) => e.path);
@@ -667,20 +632,20 @@ var configShow = async () => {
667
632
  console.log(" (none)");
668
633
  } else {
669
634
  for (const { type, path } of entries) {
670
- console.log(` ${type.padEnd(6)} ${import_termkit4.Color.green.encoder(path)}`);
635
+ console.log(` ${type.padEnd(6)} ${import_termkit3.Color.green.encoder(path)}`);
671
636
  }
672
637
  }
673
638
  console.log(`
674
- Subtitle language: ${import_termkit4.Color.green.encoder(config.language ?? "eng (default)")}`);
675
- console.log(`TMDb API key: ${config.tmdbApiKey ? import_termkit4.Color.green.encoder("configured") : import_termkit4.Color.red.encoder("not set")}`);
676
- console.log(`Movie format: ${import_termkit4.Color.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
677
- console.log(`Episode format: ${import_termkit4.Color.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
678
- console.log(`Season folder: ${import_termkit4.Color.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
639
+ Subtitle language: ${import_termkit3.Color.green.encoder(config.language ?? "eng (default)")}`);
640
+ console.log(`TMDb API key: ${config.tmdbApiKey ? import_termkit3.Color.green.encoder("configured") : import_termkit3.Color.red.encoder("not set")}`);
641
+ console.log(`Movie format: ${import_termkit3.Color.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
642
+ console.log(`Episode format: ${import_termkit3.Color.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
643
+ console.log(`Season folder: ${import_termkit3.Color.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
679
644
  console.log("\nIgnored files:");
680
645
  if (!config.ignore || config.ignore.length === 0) {
681
646
  console.log(" (none)");
682
647
  } else {
683
- for (const name of config.ignore) console.log(` ${import_termkit4.Color.white.encoder(name)}`);
648
+ for (const name of config.ignore) console.log(` ${import_termkit3.Color.white.encoder(name)}`);
684
649
  }
685
650
  console.log();
686
651
  };
@@ -688,12 +653,13 @@ Subtitle language: ${import_termkit4.Color.green.encoder(config.language ?? "eng
688
653
  // src/actions/differences.ts
689
654
  var import_fs6 = require("fs");
690
655
  var import_path5 = require("path");
691
- var import_termkit5 = require("termkit");
656
+ var import_termkit4 = require("termkit");
657
+ var spinner4 = new import_termkit4.Spinner();
692
658
  var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore: ignore2 }) => {
693
659
  let dir1 = rawDir1;
694
660
  let dir2 = rawDir2;
695
- spinner_default.text = `checking differences between ${import_termkit5.Color.white.encoder(dir1)} and ${import_termkit5.Color.white.encoder(dir2)}`;
696
- spinner_default.start();
661
+ spinner4.update(`checking differences between ${import_termkit4.Color.white.encoder(dir1)} and ${import_termkit4.Color.white.encoder(dir2)}`);
662
+ spinner4.start();
697
663
  dir1 = (0, import_path5.resolve)(dir1);
698
664
  dir2 = (0, import_path5.resolve)(dir2);
699
665
  if (!(0, import_fs6.existsSync)(dir1)) throw new Error(`dir1 ${dir1} does not exist`);
@@ -728,27 +694,28 @@ var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore: ignore2 }
728
694
  removed.push(l);
729
695
  }
730
696
  }
731
- spinner_default.succeed(`checked differences between ${import_termkit5.Color.white.encoder(dir1)} and ${import_termkit5.Color.white.encoder(dir2)}`);
732
- spinner_default.succeed(`found ${added.length} added files`);
733
- spinner_default.succeed(`found ${removed.length} removed files`);
734
- spinner_default.stop();
735
- for (const i of added) console.log(`${import_termkit5.Color.green.encoder("added")} ${i}`);
736
- for (const i of removed) console.log(`${import_termkit5.Color.red.encoder("removed")} ${i}`);
697
+ spinner4.succeed(`checked differences between ${import_termkit4.Color.white.encoder(dir1)} and ${import_termkit4.Color.white.encoder(dir2)}`);
698
+ spinner4.succeed(`found ${added.length} added files`);
699
+ spinner4.succeed(`found ${removed.length} removed files`);
700
+ spinner4.stop();
701
+ for (const i of added) console.log(`${import_termkit4.Color.green.encoder("added")} ${i}`);
702
+ for (const i of removed) console.log(`${import_termkit4.Color.red.encoder("removed")} ${i}`);
737
703
  };
738
704
  var differences_default = differences;
739
705
 
740
706
  // src/actions/ended.ts
741
- var import_termkit6 = require("termkit");
707
+ var import_termkit5 = require("termkit");
708
+ var spinner5 = new import_termkit5.Spinner();
742
709
  var ended = async ({ remove }) => {
743
710
  const shows2 = getShows();
744
711
  const candidates = shows2.filter((s) => remove ? s.ended : !s.ended);
745
712
  if (candidates.length === 0) {
746
- spinner_default.start();
747
- spinner_default.info(remove ? "no ended shows to restore" : "no active shows to mark as ended");
748
- spinner_default.stop();
713
+ spinner5.start();
714
+ spinner5.info(remove ? "no ended shows to restore" : "no active shows to mark as ended");
715
+ spinner5.stop();
749
716
  return;
750
717
  }
751
- const select = new import_termkit6.Select();
718
+ const select = new import_termkit5.Select();
752
719
  const items = candidates.map((s) => ({
753
720
  label: s.path.split("/").pop() ?? s.path,
754
721
  path: s.path
@@ -757,19 +724,19 @@ var ended = async ({ remove }) => {
757
724
  const picked = await select.ask(prompt, items);
758
725
  if (!picked) return;
759
726
  setShowEnded(picked.path, !remove);
760
- spinner_default.start();
727
+ spinner5.start();
761
728
  if (remove) {
762
- spinner_default.succeed(`marked as active: ${import_termkit6.Color.green.encoder(picked.label)}`);
729
+ spinner5.succeed(`marked as active: ${import_termkit5.Color.green.encoder(picked.label)}`);
763
730
  } else {
764
- spinner_default.succeed(`marked as ended: ${import_termkit6.Color.green.encoder(picked.label)}`);
731
+ spinner5.succeed(`marked as ended: ${import_termkit5.Color.green.encoder(picked.label)}`);
765
732
  }
766
- spinner_default.stop();
733
+ spinner5.stop();
767
734
  };
768
735
  var ended_default = ended;
769
736
 
770
737
  // src/actions/history.ts
771
738
  var import_path6 = require("path");
772
- var import_termkit7 = require("termkit");
739
+ var import_termkit6 = require("termkit");
773
740
  var history = async ({ limit, imports }) => {
774
741
  if (imports) {
775
742
  const sessions = getImportHistory(limit ?? 10);
@@ -781,11 +748,11 @@ var history = async ({ limit, imports }) => {
781
748
  const date = new Date(session.sessionId);
782
749
  const label = isNaN(date.getTime()) ? session.sessionId : date.toLocaleString();
783
750
  console.log(`
784
- ${import_termkit7.Color.yellow.encoder(label)} (${session.records.length} item${session.records.length !== 1 ? "s" : ""})`);
751
+ ${import_termkit6.Color.yellow.encoder(label)} (${session.records.length} item${session.records.length !== 1 ? "s" : ""})`);
785
752
  for (const r of session.records) {
786
753
  const src = (0, import_path6.basename)(r.sourcePath);
787
- const dest = import_termkit7.Color.green.encoder(r.destinationPath);
788
- const mode = r.mode !== "move" ? ` ${import_termkit7.Color.white.encoder(`[${r.mode}]`)}` : "";
754
+ const dest = import_termkit6.Color.green.encoder(r.destinationPath);
755
+ const mode = r.mode !== "move" ? ` ${import_termkit6.Color.white.encoder(`[${r.mode}]`)}` : "";
789
756
  console.log(` ${src} \u2192 ${dest}${mode}`);
790
757
  }
791
758
  }
@@ -800,11 +767,11 @@ ${import_termkit7.Color.yellow.encoder(label)} (${session.records.length} item$
800
767
  const label = isNaN(date.getTime()) ? session.sessionId : date.toLocaleString();
801
768
  const folders = session.records.filter((r) => (0, import_path6.extname)(r.newPath) === "");
802
769
  console.log(`
803
- ${import_termkit7.Color.yellow.encoder(label)} (${folders.length} item${folders.length !== 1 ? "s" : ""})`);
770
+ ${import_termkit6.Color.yellow.encoder(label)} (${folders.length} item${folders.length !== 1 ? "s" : ""})`);
804
771
  for (const r of folders) {
805
772
  const oldName = (0, import_path6.basename)(r.oldPath);
806
773
  const newName = (0, import_path6.basename)(r.newPath);
807
- console.log(` ${import_termkit7.Color.white.encoder(oldName)} \u2192 ${import_termkit7.Color.green.encoder(newName)}`);
774
+ console.log(` ${import_termkit6.Color.white.encoder(oldName)} \u2192 ${import_termkit6.Color.green.encoder(newName)}`);
808
775
  }
809
776
  }
810
777
  }
@@ -815,7 +782,8 @@ var history_default = history;
815
782
  // src/actions/link.ts
816
783
  var import_fs7 = require("fs");
817
784
  var import_path7 = require("path");
818
- var import_termkit8 = require("termkit");
785
+ var import_termkit7 = require("termkit");
786
+ var spinner6 = new import_termkit7.Spinner();
819
787
  var parseShowTitle = (folderName) => {
820
788
  const withoutYear = folderName.replace(/\s*\(\d{4}\)\s*$/, "").trim();
821
789
  return withoutYear || folderName;
@@ -842,49 +810,49 @@ var link = async ({ force }) => {
842
810
  skipped++;
843
811
  continue;
844
812
  }
845
- spinner_default.start(`linking ${import_termkit8.Color.white.encoder(show)}`);
813
+ spinner6.update(`linking ${import_termkit7.Color.white.encoder(show)}`).start();
846
814
  const title = parseShowTitle(show);
847
815
  const results = await searchTv(title, config.tmdbApiKey);
848
816
  if (results.length === 0) {
849
- spinner_default.warn(`not found in TMDb: ${show}`);
817
+ spinner6.warn(`not found in TMDb: ${show}`);
850
818
  notFound++;
851
819
  continue;
852
820
  }
853
821
  if (results.length === 1) {
854
822
  upsertShow(showPath, results[0].id, results[0].title);
855
- spinner_default.succeed(`${show} \u2192 ${import_termkit8.Color.green.encoder(results[0].title)} (${results[0].year})`);
823
+ spinner6.succeed(`${show} \u2192 ${import_termkit7.Color.green.encoder(results[0].title)} (${results[0].year})`);
856
824
  linked++;
857
825
  continue;
858
826
  }
859
- spinner_default.stop();
860
- const select = new import_termkit8.Select();
827
+ spinner6.stop();
828
+ const select = new import_termkit7.Select();
861
829
  const items = results.map((r) => ({
862
830
  label: r.year ? `${r.title} (${r.year})` : r.title,
863
831
  description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
864
832
  ...r
865
833
  }));
866
834
  const picked = await select.ask(`Multiple shows found for "${show}":`, items);
867
- spinner_default.start();
835
+ spinner6.start();
868
836
  if (picked) {
869
837
  upsertShow(showPath, picked.id, picked.title);
870
- spinner_default.succeed(`${show} \u2192 ${import_termkit8.Color.green.encoder(picked.title)} (${picked.year})`);
838
+ spinner6.succeed(`${show} \u2192 ${import_termkit7.Color.green.encoder(picked.title)} (${picked.year})`);
871
839
  linked++;
872
840
  } else {
873
- spinner_default.info(`skipped: ${show}`);
841
+ spinner6.info(`skipped: ${show}`);
874
842
  skipped++;
875
843
  }
876
844
  }
877
- spinner_default.succeed(`linked ${linked} show${linked !== 1 ? "s" : ""}`);
878
- if (notFound) spinner_default.warn(`not found in TMDb: ${notFound}`);
879
- if (skipped) spinner_default.info(`skipped ${skipped} (already linked \u2014 use --force to re-link)`);
880
- spinner_default.stop();
845
+ spinner6.succeed(`linked ${linked} show${linked !== 1 ? "s" : ""}`);
846
+ if (notFound) spinner6.warn(`not found in TMDb: ${notFound}`);
847
+ if (skipped) spinner6.info(`skipped ${skipped} (already linked \u2014 use --force to re-link)`);
848
+ spinner6.stop();
881
849
  };
882
850
  var link_default = link;
883
851
 
884
852
  // src/actions/list.ts
885
853
  var import_fs9 = require("fs");
886
854
  var import_path9 = require("path");
887
- var import_termkit9 = require("termkit");
855
+ var import_termkit8 = require("termkit");
888
856
 
889
857
  // src/helpers/dirSize.ts
890
858
  var import_fs8 = require("fs");
@@ -1024,7 +992,7 @@ var list = async ({ type, missingSubs, codec: codecFilter, resolution: resFilter
1024
992
  const destRoot = config.dest[t];
1025
993
  if (!(0, import_fs9.existsSync)(destRoot)) {
1026
994
  console.log(`
1027
- ${t.toUpperCase()} ${import_termkit9.Color.white.encoder(destRoot)} (not found)`);
995
+ ${t.toUpperCase()} ${import_termkit8.Color.white.encoder(destRoot)} (not found)`);
1028
996
  continue;
1029
997
  }
1030
998
  const folders = (0, import_fs9.readdirSync)(destRoot).filter((f) => {
@@ -1055,8 +1023,8 @@ ${t.toUpperCase()} ${import_termkit9.Color.white.encoder(destRoot)} (not found
1055
1023
  return yearDiff !== 0 ? yearDiff : a.title.localeCompare(b.title);
1056
1024
  });
1057
1025
  console.log(`
1058
- ${import_termkit9.Color.yellow.encoder(t.toUpperCase())} ${import_termkit9.Color.white.encoder(destRoot)}`);
1059
- new import_termkit9.Table(
1026
+ ${import_termkit8.Color.yellow.encoder(t.toUpperCase())} ${import_termkit8.Color.white.encoder(destRoot)}`);
1027
+ new import_termkit8.Table(
1060
1028
  filtered.map((e) => ({
1061
1029
  title: e.title,
1062
1030
  year: e.year,
@@ -1073,7 +1041,7 @@ ${import_termkit9.Color.yellow.encoder(t.toUpperCase())} ${import_termkit9.Colo
1073
1041
  { key: "resolution", title: "Res", value: (v) => v ?? "\u2014" },
1074
1042
  { key: "codec", title: "Codec", value: (v) => v ?? "\u2014" },
1075
1043
  { key: "size", title: "Size" },
1076
- { key: "sub", title: "Sub", value: (v) => v ? import_termkit9.Color.green.encoder("\u2713") : import_termkit9.Color.red.encoder("\u2717") }
1044
+ { key: "sub", title: "Sub", value: (v) => v ? import_termkit8.Color.green.encoder("\u2713") : import_termkit8.Color.red.encoder("\u2717") }
1077
1045
  ]
1078
1046
  }
1079
1047
  ).print();
@@ -1086,7 +1054,7 @@ var list_default = list;
1086
1054
  // src/actions/missing.ts
1087
1055
  var import_fs10 = require("fs");
1088
1056
  var import_path10 = require("path");
1089
- var import_termkit10 = require("termkit");
1057
+ var import_termkit9 = require("termkit");
1090
1058
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1091
1059
  var parseSeasonNumber = (folderName) => {
1092
1060
  const match = folderName.match(/(?:season|s)\s*0*(\d+)/i);
@@ -1148,7 +1116,7 @@ var missing = async ({ show: showFilter }) => {
1148
1116
  if (showMissing.length > 0) {
1149
1117
  totalMissing += showMissing.length;
1150
1118
  console.log(`
1151
- ${import_termkit10.Color.yellow.encoder(show)}`);
1119
+ ${import_termkit9.Color.yellow.encoder(show)}`);
1152
1120
  for (const line of showMissing) console.log(line);
1153
1121
  }
1154
1122
  }
@@ -1169,7 +1137,7 @@ var missing_default = missing;
1169
1137
  var import_child_process = require("child_process");
1170
1138
  var import_fs11 = require("fs");
1171
1139
  var import_path11 = require("path");
1172
- var import_termkit11 = require("termkit");
1140
+ var import_termkit10 = require("termkit");
1173
1141
 
1174
1142
  // src/refs/verbose.ts
1175
1143
  var _verbose = false;
@@ -1179,6 +1147,7 @@ var setVerbose = (v) => {
1179
1147
  var isVerbose = () => _verbose;
1180
1148
 
1181
1149
  // src/actions/probe.ts
1150
+ var spinner7 = new import_termkit10.Spinner();
1182
1151
  var DEST_TYPES3 = ["movie", "tv", "ps3"];
1183
1152
  var CODEC_MAP2 = {
1184
1153
  hevc: "x265",
@@ -1241,10 +1210,10 @@ var walkVideoFiles = (dir, depth = 0, maxDepth = 3) => {
1241
1210
  return results;
1242
1211
  };
1243
1212
  var probe = async ({ type, force }) => {
1244
- spinner_default.start();
1213
+ spinner7.start();
1245
1214
  if (!isFfprobeAvailable()) {
1246
- spinner_default.fail("ffprobe not found \u2014 install ffmpeg to use this command");
1247
- spinner_default.stop();
1215
+ spinner7.fail("ffprobe not found \u2014 install ffmpeg to use this command");
1216
+ spinner7.stop();
1248
1217
  return;
1249
1218
  }
1250
1219
  const config = getConfig();
@@ -1254,30 +1223,30 @@ var probe = async ({ type, force }) => {
1254
1223
  for (const t of types) {
1255
1224
  const destRoot = config.dest[t];
1256
1225
  if (!(0, import_fs11.existsSync)(destRoot)) continue;
1257
- spinner_default.text = `scanning ${import_termkit11.Color.white.encoder(destRoot)}`;
1226
+ spinner7.update(`scanning ${import_termkit10.Color.white.encoder(destRoot)}`);
1258
1227
  const files = walkVideoFiles(destRoot);
1259
1228
  for (const filePath of files) {
1260
1229
  if (!force && getMediaInfo(filePath)) {
1261
- if (isVerbose()) spinner_default.info(`already probed: ${filePath}`);
1230
+ if (isVerbose()) spinner7.info(`already probed: ${filePath}`);
1262
1231
  skipped++;
1263
1232
  continue;
1264
1233
  }
1265
- spinner_default.text = `probing ${import_termkit11.Color.white.encoder(filePath)}`;
1234
+ spinner7.update(`probing ${import_termkit10.Color.white.encoder(filePath)}`);
1266
1235
  const result = runFfprobe(filePath);
1267
1236
  if (!result) {
1268
- if (isVerbose()) spinner_default.warn(`ffprobe failed: ${filePath}`);
1237
+ if (isVerbose()) spinner7.warn(`ffprobe failed: ${filePath}`);
1269
1238
  failed++;
1270
1239
  continue;
1271
1240
  }
1272
1241
  upsertMediaInfo(filePath, result.codec, result.resolution, result.width, result.height, result.duration);
1273
- if (isVerbose()) spinner_default.succeed(`${result.resolution ?? "?"} ${result.codec ?? "?"} ${filePath}`);
1242
+ if (isVerbose()) spinner7.succeed(`${result.resolution ?? "?"} ${result.codec ?? "?"} ${filePath}`);
1274
1243
  probed++;
1275
1244
  }
1276
1245
  }
1277
- spinner_default.succeed(`probed ${probed} files`);
1278
- if (skipped) spinner_default.info(`skipped ${skipped} already indexed`);
1279
- if (failed) spinner_default.warn(`failed ${failed} files`);
1280
- spinner_default.stop();
1246
+ spinner7.succeed(`probed ${probed} files`);
1247
+ if (skipped) spinner7.info(`skipped ${skipped} already indexed`);
1248
+ if (failed) spinner7.warn(`failed ${failed} files`);
1249
+ spinner7.stop();
1281
1250
  };
1282
1251
  var probe_default = probe;
1283
1252
 
@@ -1285,7 +1254,7 @@ var probe_default = probe;
1285
1254
  var import_fs12 = require("fs");
1286
1255
  var import_path12 = require("path");
1287
1256
  var import_rimraf = require("rimraf");
1288
- var import_termkit12 = require("termkit");
1257
+ var import_termkit11 = require("termkit");
1289
1258
 
1290
1259
  // src/helpers/findSubtitle.ts
1291
1260
  var LANGUAGE_ALIASES = {
@@ -1340,21 +1309,22 @@ var titleCase_default = (s) => {
1340
1309
  };
1341
1310
 
1342
1311
  // src/actions/rename.ts
1312
+ var spinner8 = new import_termkit11.Spinner();
1343
1313
  var rename = async ({ dir: inputDir, type }) => {
1344
1314
  const dir = (0, import_path12.resolve)(inputDir);
1345
1315
  const sessionId = (/* @__PURE__ */ new Date()).toISOString();
1346
1316
  const config = getConfig();
1347
1317
  const language = config.language ?? "eng";
1348
1318
  const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
1349
- spinner_default.text = `renaming in ${import_termkit12.Color.white.encoder(dir)}`;
1350
- spinner_default.start();
1319
+ spinner8.update(`renaming in ${import_termkit11.Color.white.encoder(dir)}`);
1320
+ spinner8.start();
1351
1321
  if (!(0, import_fs12.existsSync)(dir)) throw new Error(`dir ${dir} does not exist`);
1352
1322
  const list2 = (0, import_fs12.readdirSync)(dir);
1353
1323
  let renamed = 0, removed = 0, skipped = 0;
1354
1324
  for (const [index, entry] of list2.entries()) {
1355
- spinner_default.text = `renaming in ${import_termkit12.Color.white.encoder(dir)} ${index + 1}/${list2.length}`;
1325
+ spinner8.update(`renaming in ${import_termkit11.Color.white.encoder(dir)} ${index + 1}/${list2.length}`);
1356
1326
  if (!(0, import_fs12.lstatSync)((0, import_path12.resolve)(dir, entry)).isDirectory()) {
1357
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1327
+ if (isVerbose()) spinner8.info(`skipped ${entry}`);
1358
1328
  skipped++;
1359
1329
  continue;
1360
1330
  }
@@ -1364,7 +1334,7 @@ var rename = async ({ dir: inputDir, type }) => {
1364
1334
  const nameMatch = entry.match(/(?<=\[).+?(?=\])/);
1365
1335
  const id = entry.split("-")[0];
1366
1336
  if (!nameMatch || !id) {
1367
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1337
+ if (isVerbose()) spinner8.info(`skipped ${entry}`);
1368
1338
  skipped++;
1369
1339
  continue;
1370
1340
  }
@@ -1372,19 +1342,19 @@ var rename = async ({ dir: inputDir, type }) => {
1372
1342
  const ps3New = (0, import_path12.resolve)(dir, `${nameMatch[0]} [${id}]`);
1373
1343
  (0, import_fs12.renameSync)(ps3Old, ps3New);
1374
1344
  recordRename(sessionId, ps3Old, ps3New);
1375
- spinner_default.succeed(`${nameMatch[0]} [${id}]`);
1345
+ spinner8.succeed(`${nameMatch[0]} [${id}]`);
1376
1346
  renamed++;
1377
1347
  continue;
1378
1348
  }
1379
1349
  const yearMatch = entry.match(/\([^\d]*(\d+)[^\d]*\)/);
1380
1350
  if (!yearMatch) {
1381
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1351
+ if (isVerbose()) spinner8.info(`skipped ${entry}`);
1382
1352
  skipped++;
1383
1353
  continue;
1384
1354
  }
1385
1355
  const year = yearMatch[0];
1386
1356
  if (year.length !== 6) {
1387
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1357
+ if (isVerbose()) spinner8.info(`skipped ${entry}`);
1388
1358
  skipped++;
1389
1359
  continue;
1390
1360
  }
@@ -1395,20 +1365,20 @@ var rename = async ({ dir: inputDir, type }) => {
1395
1365
  return videoExtensions_default.includes(ext2) && title.split(" ").reduce((a, w) => f.toLowerCase().includes(w.toLowerCase()) ? a : false, true);
1396
1366
  });
1397
1367
  if (!video) {
1398
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1368
+ if (isVerbose()) spinner8.info(`skipped ${entry}`);
1399
1369
  skipped++;
1400
1370
  continue;
1401
1371
  }
1402
1372
  const ext = video.match(/([^.]+$)/)?.[0];
1403
1373
  if (!ext) {
1404
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1374
+ if (isVerbose()) spinner8.info(`skipped ${entry}`);
1405
1375
  skipped++;
1406
1376
  continue;
1407
1377
  }
1408
1378
  const yearNum = parseInt(year.replace(/\D/g, ""));
1409
1379
  const formatted = formatMovieName(movieFormat, title, yearNum);
1410
1380
  if (entry === formatted && video === `${formatted}.${ext}`) {
1411
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1381
+ if (isVerbose()) spinner8.info(`skipped ${entry}`);
1412
1382
  skipped++;
1413
1383
  continue;
1414
1384
  }
@@ -1431,25 +1401,26 @@ var rename = async ({ dir: inputDir, type }) => {
1431
1401
  (0, import_fs12.renameSync)(folderOld, folderNew);
1432
1402
  recordRename(sessionId, fileOld, fileNew);
1433
1403
  recordRename(sessionId, folderOld, folderNew);
1434
- spinner_default.succeed(formatted);
1404
+ spinner8.succeed(formatted);
1435
1405
  renamed++;
1436
1406
  }
1437
- spinner_default.succeed(`renamed ${renamed} files`);
1438
- if (removed) spinner_default.info(`removed ${removed} files`);
1439
- spinner_default.info(`skipped ${skipped} files`);
1440
- spinner_default.succeed(`done in ${import_termkit12.Color.green.encoder(dir)}`);
1441
- spinner_default.stop();
1407
+ spinner8.succeed(`renamed ${renamed} files`);
1408
+ if (removed) spinner8.info(`removed ${removed} files`);
1409
+ spinner8.info(`skipped ${skipped} files`);
1410
+ spinner8.succeed(`done in ${import_termkit11.Color.green.encoder(dir)}`);
1411
+ spinner8.stop();
1442
1412
  };
1443
1413
  var rename_default = rename;
1444
1414
 
1445
1415
  // src/actions/reset.ts
1446
1416
  var import_fs13 = require("fs");
1447
1417
  var import_path13 = require("path");
1448
- var import_termkit13 = require("termkit");
1418
+ var import_termkit12 = require("termkit");
1419
+ var spinner9 = new import_termkit12.Spinner();
1449
1420
  var reset = async ({ dir: inputDir, double }) => {
1450
1421
  let dir = inputDir;
1451
- spinner_default.text = `resetting episodes in ${import_termkit13.Color.white.encoder(dir)}`;
1452
- spinner_default.start();
1422
+ spinner9.update(`resetting episodes in ${import_termkit12.Color.white.encoder(dir)}`);
1423
+ spinner9.start();
1453
1424
  dir = (0, import_path13.resolve)(dir);
1454
1425
  if (!(0, import_fs13.existsSync)(dir)) throw new Error(`dir ${dir} does not exist`);
1455
1426
  const list2 = (0, import_fs13.readdirSync)(dir).sort();
@@ -1465,7 +1436,7 @@ var reset = async ({ dir: inputDir, double }) => {
1465
1436
  if (!seasonNum) throw new Error(`unable to identify season number`);
1466
1437
  const parentFolder = (0, import_path13.basename)((0, import_path13.dirname)(dir));
1467
1438
  const showTitle = parentFolder.match(/^(.+?)\s*(?:\(\d{4}\))?$/)?.[1]?.trim() || void 0;
1468
- spinner_default.info(`identified as season ${seasonNum}${showTitle ? ` of ${showTitle}` : ""}`);
1439
+ spinner9.info(`identified as season ${seasonNum}${showTitle ? ` of ${showTitle}` : ""}`);
1469
1440
  const sublist = list2.filter((f) => {
1470
1441
  const ext = f.match(/([^.]+$)/)?.[0];
1471
1442
  return videoExtensions_default.includes(ext) && f.split(" ").reduce((a, w) => f.toLowerCase().includes(w.toLowerCase()) ? a : false, true);
@@ -1477,7 +1448,7 @@ var reset = async ({ dir: inputDir, double }) => {
1477
1448
  const episodeFormat = getConfig().format?.episode;
1478
1449
  let renamed = 0, skipped = other.length;
1479
1450
  for (const [index, i] of sublist.entries()) {
1480
- spinner_default.text = `resetting episodes in ${import_termkit13.Color.white.encoder(dir)} ${index}/${list2.length}`;
1451
+ spinner9.update(`resetting episodes in ${import_termkit12.Color.white.encoder(dir)} ${index}/${list2.length}`);
1481
1452
  const ext = i.match(/([^.]+$)/)?.[0];
1482
1453
  const episode = double ? index * 2 + 1 : index + 1;
1483
1454
  const name = `${formatEpisode(seasonNum, episode, episodeFormat, double, showTitle)}.${ext}`;
@@ -1488,17 +1459,17 @@ var reset = async ({ dir: inputDir, double }) => {
1488
1459
  (0, import_fs13.renameSync)((0, import_path13.resolve)(dir, i), (0, import_path13.resolve)(dir, name));
1489
1460
  renamed++;
1490
1461
  }
1491
- spinner_default.succeed(`renamed ${renamed} files`);
1492
- spinner_default.info(`skipped ${skipped} files`);
1493
- spinner_default.succeed(`done in ${import_termkit13.Color.green.encoder(dir)}`);
1494
- spinner_default.stop();
1462
+ spinner9.succeed(`renamed ${renamed} files`);
1463
+ spinner9.info(`skipped ${skipped} files`);
1464
+ spinner9.succeed(`done in ${import_termkit12.Color.green.encoder(dir)}`);
1465
+ spinner9.stop();
1495
1466
  };
1496
1467
  var reset_default = reset;
1497
1468
 
1498
1469
  // src/actions/scan.ts
1499
1470
  var import_fs15 = require("fs");
1500
1471
  var import_path15 = require("path");
1501
- var import_termkit14 = require("termkit");
1472
+ var import_termkit13 = require("termkit");
1502
1473
 
1503
1474
  // src/helpers/detectEdition.ts
1504
1475
  var EDITIONS = [
@@ -1601,6 +1572,7 @@ var parseDownloadName = (rawName) => {
1601
1572
  var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
1602
1573
 
1603
1574
  // src/actions/scan.ts
1575
+ var spinner10 = new import_termkit13.Spinner();
1604
1576
  var sameDev = (a, b) => {
1605
1577
  try {
1606
1578
  let bExisting = b;
@@ -1733,46 +1705,49 @@ var gatherEntries = (source) => {
1733
1705
  }
1734
1706
  return result;
1735
1707
  };
1736
- var findShowFolder = (destRoot, title) => {
1737
- if (!(0, import_fs15.existsSync)(destRoot)) return null;
1738
- const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1739
- const target = normalize(title);
1740
- return (0, import_fs15.readdirSync)(destRoot).filter((f) => {
1708
+ var nTitle = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1709
+ var showDirIndexCache = /* @__PURE__ */ new Map();
1710
+ var getShowDirIndex = (destRoot) => {
1711
+ if (showDirIndexCache.has(destRoot)) return showDirIndexCache.get(destRoot);
1712
+ const idx = /* @__PURE__ */ new Map();
1713
+ showDirIndexCache.set(destRoot, idx);
1714
+ if (!(0, import_fs15.existsSync)(destRoot)) return idx;
1715
+ let dirs;
1716
+ try {
1717
+ dirs = (0, import_fs15.readdirSync)(destRoot);
1718
+ } catch {
1719
+ return idx;
1720
+ }
1721
+ for (const dir of dirs) {
1741
1722
  try {
1742
- return (0, import_fs15.lstatSync)((0, import_path15.resolve)(destRoot, f)).isDirectory();
1723
+ if (!(0, import_fs15.lstatSync)((0, import_path15.resolve)(destRoot, dir)).isDirectory()) continue;
1743
1724
  } catch {
1744
- return false;
1725
+ continue;
1745
1726
  }
1746
- }).find((f) => normalize(f) === target) ?? null;
1747
- };
1748
- var findShowFolderByContent = (destRoot, title) => {
1749
- if (!(0, import_fs15.existsSync)(destRoot)) return null;
1750
- const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1751
- const target = normalize(title);
1752
- const matchesTitle = (name) => {
1753
- if (!isTvEpisodeName(name)) return false;
1754
- const p = parseDownloadName(name);
1755
- return !!p && normalize(p.title) === target;
1756
- };
1757
- for (const folder of (0, import_fs15.readdirSync)(destRoot)) {
1727
+ idx.set(nTitle(dir), dir);
1758
1728
  try {
1759
- const folderPath = (0, import_path15.resolve)(destRoot, folder);
1760
- if (!(0, import_fs15.lstatSync)(folderPath).isDirectory()) continue;
1761
- const children = (0, import_fs15.readdirSync)(folderPath);
1762
- if (children.some(matchesTitle)) return folder;
1763
- for (const child of children) {
1764
- if (!isSeasonDirName(child)) continue;
1729
+ const children = (0, import_fs15.readdirSync)((0, import_path15.resolve)(destRoot, dir));
1730
+ const tryFile = (name) => {
1731
+ if (!isTvEpisodeName(name)) return;
1732
+ const p = parseDownloadName(name);
1733
+ if (p) {
1734
+ const k = nTitle(p.title);
1735
+ if (!idx.has(k)) idx.set(k, dir);
1736
+ }
1737
+ };
1738
+ children.forEach(tryFile);
1739
+ children.forEach((child) => {
1740
+ if (!isSeasonDirName(child)) return;
1765
1741
  try {
1766
- const seasonPath = (0, import_path15.resolve)(folderPath, child);
1767
- if (!(0, import_fs15.lstatSync)(seasonPath).isDirectory()) continue;
1768
- if ((0, import_fs15.readdirSync)(seasonPath).some(matchesTitle)) return folder;
1742
+ const sp = (0, import_path15.resolve)(destRoot, dir, child);
1743
+ if ((0, import_fs15.lstatSync)(sp).isDirectory()) (0, import_fs15.readdirSync)(sp).forEach(tryFile);
1769
1744
  } catch {
1770
1745
  }
1771
- }
1746
+ });
1772
1747
  } catch {
1773
1748
  }
1774
1749
  }
1775
- return null;
1750
+ return idx;
1776
1751
  };
1777
1752
  var findSeasonFolder = (showPath, season, specialsFolder) => {
1778
1753
  if (!(0, import_fs15.existsSync)(showPath)) return null;
@@ -1802,13 +1777,17 @@ var classifyMovieConfidence = (entry) => {
1802
1777
  return "ambiguous";
1803
1778
  };
1804
1779
  var typeColor = {
1805
- movie: (s) => import_termkit14.Color.cyan.encoder(s),
1806
- tv: (s) => import_termkit14.Color.green.encoder(s),
1807
- book: (s) => import_termkit14.Color.yellow.encoder(s),
1808
- ps3: (s) => import_termkit14.Color.magenta.encoder(s)
1809
- };
1810
- var typeGlyph = (t) => typeColor[t]("\u25CF");
1811
- var typeTag = (t) => isVerbose() ? import_termkit14.Color.white.faint.encoder(` (${t})`) : "";
1780
+ movie: (s) => import_termkit13.Color.yellow.encoder(s),
1781
+ tv: (s) => import_termkit13.Color.blue.encoder(s),
1782
+ book: (s) => import_termkit13.Color.white.encoder(s),
1783
+ ps3: (s) => import_termkit13.Color.magenta.encoder(s)
1784
+ };
1785
+ var typeGlyph = (t) => typeColor[t]("?");
1786
+ var checkGlyph = (t) => typeColor[t]("\u2714");
1787
+ var greyGlyph = import_termkit13.Color.white.faint.encoder("\u25CF");
1788
+ var warnGlyph = import_termkit13.Color.yellow.encoder("\u26A0");
1789
+ var typeTag = (t) => isVerbose() ? import_termkit13.Color.white.faint.encoder(` (${t})`) : "";
1790
+ var sortByEntry = (arr) => arr.sort((a, b) => a.entry.localeCompare(b.entry, void 0, { sensitivity: "base" }));
1812
1791
  var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
1813
1792
  const config = getConfig();
1814
1793
  const sessionId = (/* @__PURE__ */ new Date()).toISOString();
@@ -1816,27 +1795,28 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1816
1795
  const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
1817
1796
  const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
1818
1797
  const specialsFolder = config.specialsFolder ?? "Specials";
1798
+ const dryTag = dryRun ? import_termkit13.Color.white.faint.encoder(" [dry]") : "";
1819
1799
  const lookupMovie = async (parsed) => {
1820
1800
  let tmdbId;
1821
1801
  let resolvedTitle = parsed.title;
1822
1802
  let resolvedYear = parsed.year;
1823
1803
  if (config.tmdbApiKey) {
1824
- spinner_default.text = `TMDb: ${parsed.title}`;
1804
+ spinner10.update(`TMDb: ${parsed.title}`);
1825
1805
  const results = await searchMovie(parsed.title, parsed.year, config.tmdbApiKey);
1826
1806
  if (results.length === 1) {
1827
1807
  tmdbId = results[0].id;
1828
1808
  resolvedTitle = results[0].title;
1829
1809
  resolvedYear = results[0].year ?? parsed.year;
1830
1810
  } else if (results.length > 1) {
1831
- spinner_default.stop();
1832
- const select = new import_termkit14.Select();
1811
+ spinner10.stop();
1812
+ const select = new import_termkit13.Select();
1833
1813
  const items = results.map((r) => ({
1834
1814
  label: r.year ? `${r.title} (${r.year})` : r.title,
1835
1815
  description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
1836
1816
  ...r
1837
1817
  }));
1838
1818
  const picked = await select.ask(`Multiple movies found for "${parsed.title}":`, items);
1839
- spinner_default.start();
1819
+ spinner10.start();
1840
1820
  if (picked) {
1841
1821
  tmdbId = picked.id;
1842
1822
  resolvedTitle = picked.title;
@@ -1851,12 +1831,12 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1851
1831
  const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
1852
1832
  const destFolder = (0, import_path15.resolve)(destRoot, folderName);
1853
1833
  if ((0, import_fs15.existsSync)(destFolder)) {
1854
- spinner_default.warn(`already exists: ${folderName}`);
1834
+ spinner10.log(`${typeColor.movie(folderName)}${typeTag("movie")}`, greyGlyph);
1855
1835
  return false;
1856
1836
  }
1857
1837
  const videoFile = isDir ? findVideo(entryPath) : entry;
1858
1838
  if (!videoFile) {
1859
- if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
1839
+ spinner10.log(`${entry}${typeTag("movie")}`, warnGlyph);
1860
1840
  return false;
1861
1841
  }
1862
1842
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
@@ -1877,7 +1857,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1877
1857
  (0, import_fs15.linkSync)(videoSourcePath, destVideoPath);
1878
1858
  mode = "hardlink";
1879
1859
  } catch {
1880
- spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
1860
+ spinner10.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
1881
1861
  (0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
1882
1862
  mode = "copy";
1883
1863
  }
@@ -1903,10 +1883,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1903
1883
  recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
1904
1884
  }
1905
1885
  }
1906
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie(folderName)}${typeTag("movie")}`);
1886
+ spinner10.log(`${typeColor.movie(folderName)}${typeTag("movie")}${dryTag}`, checkGlyph("movie"));
1907
1887
  return true;
1908
1888
  };
1909
- spinner_default.start();
1889
+ spinner10.start();
1910
1890
  if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
1911
1891
  let imported = 0, skipped = 0;
1912
1892
  const pendingMovies = [];
@@ -1917,15 +1897,19 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1917
1897
  const seenIgnored = /* @__PURE__ */ new Set();
1918
1898
  for (const source of config.sources) {
1919
1899
  if (!(0, import_fs15.existsSync)(source)) {
1920
- spinner_default.warn(`source not found: ${import_termkit14.Color.white.encoder(source)}`);
1900
+ spinner10.warn(`source not found: ${import_termkit13.Color.white.encoder(source)}`);
1921
1901
  continue;
1922
1902
  }
1923
- spinner_default.text = `scanning ${import_termkit14.Color.white.encoder(source)}`;
1924
- for (const { entry, entryPath, isDir } of gatherEntries(source)) {
1925
- spinner_default.text = `scanning: ${entry}`;
1903
+ spinner10.update(`scanning ${import_termkit13.Color.white.encoder(source)}`);
1904
+ const entries = gatherEntries(source).sort(
1905
+ (a, b) => a.entry.localeCompare(b.entry, void 0, { sensitivity: "base" })
1906
+ );
1907
+ for (const { entry, entryPath, isDir } of entries) {
1908
+ spinner10.update(`scanning: ${entry}`);
1926
1909
  if (ignoreSet.has(entry)) {
1927
1910
  seenIgnored.add(entry);
1928
- if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
1911
+ if (isVerbose()) spinner10.log(entry, greyGlyph);
1912
+ skipped++;
1929
1913
  continue;
1930
1914
  }
1931
1915
  const ext = entry.match(/([^.]+$)/)?.[0];
@@ -1945,7 +1929,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1945
1929
  }
1946
1930
  const destRoot = config.dest[detectedType];
1947
1931
  if (!destRoot) {
1948
- if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
1932
+ if (isVerbose()) spinner10.log(`${entry}${typeTag(detectedType)}`, greyGlyph);
1949
1933
  skipped++;
1950
1934
  continue;
1951
1935
  }
@@ -1959,7 +1943,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1959
1943
  const destName = `${nameMatch[0]} [${id}]`;
1960
1944
  const destPath = (0, import_path15.resolve)(destRoot, destName);
1961
1945
  if ((0, import_fs15.existsSync)(destPath)) {
1962
- spinner_default.warn(`already exists: ${destName}`);
1946
+ spinner10.log(`${typeColor.ps3(destName)}${typeTag("ps3")}`, greyGlyph);
1963
1947
  skipped++;
1964
1948
  continue;
1965
1949
  }
@@ -1967,14 +1951,14 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1967
1951
  moveFolder(entryPath, destPath);
1968
1952
  recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
1969
1953
  }
1970
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3(destName)}${typeTag("ps3")}`);
1954
+ spinner10.log(`${typeColor.ps3(destName)}${typeTag("ps3")}${dryTag}`, checkGlyph("ps3"));
1971
1955
  imported++;
1972
1956
  continue;
1973
1957
  }
1974
1958
  if (detectedType === "book") {
1975
1959
  const destPath = (0, import_path15.resolve)(destRoot, entry);
1976
1960
  if ((0, import_fs15.existsSync)(destPath)) {
1977
- spinner_default.warn(`already exists: ${entry}`);
1961
+ spinner10.log(`${typeColor.book(entry)}${typeTag("book")}`, greyGlyph);
1978
1962
  skipped++;
1979
1963
  continue;
1980
1964
  }
@@ -1992,7 +1976,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1992
1976
  }
1993
1977
  recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
1994
1978
  }
1995
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book(entry)}${typeTag("book")}`);
1979
+ spinner10.log(`${typeColor.book(entry)}${typeTag("book")}${dryTag}`, checkGlyph("book"));
1996
1980
  imported++;
1997
1981
  continue;
1998
1982
  }
@@ -2001,10 +1985,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2001
1985
  if (videoCount === 0) {
2002
1986
  if (containsPdf(entryPath)) {
2003
1987
  pendingBooks.push({ entry, entryPath });
2004
- } else if (isVerbose()) {
2005
- spinner_default.info(`no media found, skipped: ${entry}`);
1988
+ } else {
1989
+ if (isVerbose()) spinner10.log(entry, greyGlyph);
1990
+ skipped++;
2006
1991
  }
2007
- skipped++;
2008
1992
  continue;
2009
1993
  }
2010
1994
  if (videoCount >= 2) {
@@ -2014,14 +1998,14 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2014
1998
  }
2015
1999
  const parsed = parseDownloadName(entry);
2016
2000
  if (!parsed) {
2017
- if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
2001
+ if (isVerbose()) spinner10.log(entry, greyGlyph);
2018
2002
  skipped++;
2019
2003
  continue;
2020
2004
  }
2021
2005
  if (detectedType === "movie") {
2022
2006
  const confidence = classifyMovieConfidence(entry);
2023
2007
  if (confidence === "skip") {
2024
- if (isVerbose()) spinner_default.info(`skipped (uncertain): ${entry}`);
2008
+ if (isVerbose()) spinner10.log(entry, greyGlyph);
2025
2009
  skipped++;
2026
2010
  continue;
2027
2011
  }
@@ -2035,22 +2019,22 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2035
2019
  let resolvedYear = parsed.year;
2036
2020
  if (config.tmdbApiKey) {
2037
2021
  if (detectedType === "tv") {
2038
- spinner_default.text = `TMDb: ${parsed.title}`;
2022
+ spinner10.update(`TMDb: ${parsed.title}`);
2039
2023
  const results = await searchTv(parsed.title, config.tmdbApiKey);
2040
2024
  if (results.length === 1) {
2041
2025
  tmdbId = results[0].id;
2042
2026
  resolvedTitle = results[0].title;
2043
2027
  resolvedYear = results[0].year ?? parsed.year;
2044
2028
  } else if (results.length > 1) {
2045
- spinner_default.stop();
2046
- const select = new import_termkit14.Select();
2029
+ spinner10.stop();
2030
+ const select = new import_termkit13.Select();
2047
2031
  const items = results.map((r) => ({
2048
2032
  label: r.year ? `${r.title} (${r.year})` : r.title,
2049
2033
  description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
2050
2034
  ...r
2051
2035
  }));
2052
2036
  const picked = await select.ask(`Multiple shows found for "${parsed.title}":`, items);
2053
- spinner_default.start();
2037
+ spinner10.start();
2054
2038
  if (picked) {
2055
2039
  tmdbId = picked.id;
2056
2040
  resolvedTitle = picked.title;
@@ -2066,7 +2050,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2066
2050
  }
2067
2051
  if (detectedType === "tv") {
2068
2052
  if (parsed.season === void 0) {
2069
- if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
2053
+ if (isVerbose()) spinner10.log(entry, greyGlyph);
2070
2054
  skipped++;
2071
2055
  continue;
2072
2056
  }
@@ -2077,7 +2061,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2077
2061
  showPath = registeredShow.path;
2078
2062
  showFolderName = showPath.split("/").pop() ?? registeredShow.path;
2079
2063
  } else {
2080
- const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
2064
+ const existingFolder = getShowDirIndex(destRoot).get(nTitle(resolvedTitle)) ?? null;
2081
2065
  if (existingFolder) {
2082
2066
  showFolderName = existingFolder;
2083
2067
  showPath = (0, import_path15.resolve)(destRoot, existingFolder);
@@ -2085,6 +2069,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2085
2069
  showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
2086
2070
  showPath = (0, import_path15.resolve)(destRoot, showFolderName);
2087
2071
  if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
2072
+ getShowDirIndex(destRoot).set(nTitle(resolvedTitle), showFolderName);
2088
2073
  } else {
2089
2074
  pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
2090
2075
  continue;
@@ -2094,12 +2079,12 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2094
2079
  const seasonPath = (0, import_path15.resolve)(showPath, seasonFolderName);
2095
2080
  const videoFile = isDir ? findVideo(entryPath) : entry;
2096
2081
  if (!videoFile) {
2097
- if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
2082
+ spinner10.log(`${entry}${typeTag("tv")}`, warnGlyph);
2098
2083
  skipped++;
2099
2084
  continue;
2100
2085
  }
2101
2086
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
2102
- if (tmdbId && config.tmdbApiKey) spinner_default.text = `TMDb: episode name for ${resolvedTitle}`;
2087
+ if (tmdbId && config.tmdbApiKey) spinner10.update(`TMDb: episode name for ${resolvedTitle}`);
2103
2088
  const tmdbEpisodeName = tmdbId && config.tmdbApiKey ? await getEpisodeName(tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
2104
2089
  const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, resolvedTitle, tmdbEpisodeName ?? void 0);
2105
2090
  const destVideoName = `${episodeName}.${videoExt}`;
@@ -2109,17 +2094,17 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2109
2094
  const isRepack = /\brepack\d*\b|\bproper\b/i.test(entry);
2110
2095
  let shouldReplace = force || isRepack;
2111
2096
  if (!shouldReplace && interactive) {
2112
- spinner_default.stop();
2113
- const select = new import_termkit14.Select();
2097
+ spinner10.stop();
2098
+ const select = new import_termkit13.Select();
2114
2099
  const picked = await select.ask(`Already exists \u2014 replace?`, [
2115
2100
  { label: `${showFolderName} / ${seasonFolderName} / ${episodeName}`, value: "replace" },
2116
2101
  { label: "Skip", value: "skip" }
2117
2102
  ]);
2118
- spinner_default.start();
2103
+ spinner10.start();
2119
2104
  shouldReplace = picked?.value === "replace";
2120
2105
  }
2121
2106
  if (!shouldReplace) {
2122
- spinner_default.warn(`already exists: ${episodeName}`);
2107
+ spinner10.log(`${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`, greyGlyph);
2123
2108
  skipped++;
2124
2109
  continue;
2125
2110
  }
@@ -2143,7 +2128,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2143
2128
  (0, import_fs15.linkSync)(videoSourcePath, destVideoPath);
2144
2129
  mode = "hardlink";
2145
2130
  } catch {
2146
- spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2131
+ spinner10.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2147
2132
  (0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
2148
2133
  mode = "copy";
2149
2134
  }
@@ -2160,7 +2145,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2160
2145
  }
2161
2146
  recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
2162
2147
  }
2163
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
2148
+ spinner10.log(`${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}${dryTag}`, checkGlyph("tv"));
2164
2149
  imported++;
2165
2150
  continue;
2166
2151
  }
@@ -2171,25 +2156,27 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2171
2156
  }
2172
2157
  }
2173
2158
  }
2159
+ let uncertainMovies = 0;
2174
2160
  if (pendingMovies.length > 0) {
2175
- spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
2176
- for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
2177
2161
  let toProcess = [];
2178
2162
  if (interactive) {
2179
- spinner_default.stop();
2180
- const ms = new import_termkit14.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
2163
+ spinner10.stop();
2164
+ const ms = new import_termkit13.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
2181
2165
  const items = pendingMovies.map((p) => ({
2182
2166
  label: p.entry.replace(/\/$/, ""),
2183
2167
  description: p.parsed.year ? `parsed: ${p.parsed.title} \xB7 ${p.parsed.year}` : `parsed: ${p.parsed.title}`,
2184
2168
  ...p
2185
2169
  }));
2186
2170
  toProcess = await ms.ask("Select entries to import as movies:", items) ?? [];
2187
- spinner_default.start();
2188
- skipped += pendingMovies.length - toProcess.length;
2171
+ spinner10.start();
2189
2172
  } else if (force) {
2190
2173
  toProcess = pendingMovies;
2191
- } else {
2192
- skipped += pendingMovies.length;
2174
+ }
2175
+ const toSkip = pendingMovies.filter((p) => !toProcess.includes(p));
2176
+ uncertainMovies = toSkip.length;
2177
+ sortByEntry(toSkip);
2178
+ for (const p of toSkip) {
2179
+ spinner10.log(`${typeColor.movie(p.entry.replace(/\/$/, ""))}${typeTag("movie")}`, typeGlyph("movie"));
2193
2180
  }
2194
2181
  for (const p of toProcess) {
2195
2182
  const { tmdbId, resolvedTitle, resolvedYear } = await lookupMovie(p.parsed);
@@ -2201,18 +2188,22 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2201
2188
  }
2202
2189
  }
2203
2190
  if (pendingTv.length > 0) {
2204
- spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
2205
- for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
2206
- skipped += pendingTv.length;
2191
+ pendingTv.sort((a, b) => a.resolvedTitle.localeCompare(b.resolvedTitle, void 0, { sensitivity: "base" }));
2192
+ for (const p of pendingTv) {
2193
+ spinner10.log(`${typeColor.tv(p.resolvedTitle)} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`, typeGlyph("tv"));
2194
+ }
2207
2195
  }
2208
2196
  if (pendingBooks.length > 0) {
2209
- spinner_default.warn(`${pendingBooks.length} uncertain book${pendingBooks.length > 1 ? "s" : ""} skipped \u2014 contains PDFs, review manually`);
2210
- for (const p of pendingBooks) spinner_default.info(` ${typeGlyph("book")} ${p.entry}${typeTag("book")}`);
2197
+ sortByEntry(pendingBooks);
2198
+ for (const p of pendingBooks) {
2199
+ spinner10.log(`${typeColor.book(p.entry)}${typeTag("book")}`, typeGlyph("book"));
2200
+ }
2211
2201
  }
2212
2202
  if (pendingAnime.length > 0) {
2213
- spinner_default.warn(`${pendingAnime.length} uncertain anime/TV director${pendingAnime.length > 1 ? "ies" : "y"} skipped \u2014 multiple videos with no episode naming`);
2214
- for (const p of pendingAnime) spinner_default.info(` ${typeGlyph("tv")} ${p.entry} (${p.videoCount} video${p.videoCount > 1 ? "s" : ""})${typeTag("tv")}`);
2215
- skipped += pendingAnime.length;
2203
+ sortByEntry(pendingAnime);
2204
+ for (const p of pendingAnime) {
2205
+ spinner10.log(`${typeColor.tv(p.entry)} (${p.videoCount} video${p.videoCount > 1 ? "s" : ""})${typeTag("tv")}`, typeGlyph("tv"));
2206
+ }
2216
2207
  }
2217
2208
  if (ignoreSet.size > 0) {
2218
2209
  const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
@@ -2220,18 +2211,23 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2220
2211
  const updated = config.ignore.filter((name) => !stale.includes(name));
2221
2212
  config.ignore = updated;
2222
2213
  saveConfig(config);
2223
- for (const name of stale) spinner_default.info(`removed from ignore list (not found): ${import_termkit14.Color.white.encoder(name)}`);
2214
+ for (const name of stale) spinner10.info(`removed from ignore list (not found): ${import_termkit13.Color.white.encoder(name)}`);
2224
2215
  }
2225
2216
  }
2226
- spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
2227
- if (skipped) spinner_default.info(`skipped ${skipped} items`);
2228
- spinner_default.stop();
2217
+ const summaryParts = [`${dryRun ? "would import" : "imported"} ${imported}`];
2218
+ if (skipped) summaryParts.push(`skipped ${skipped}`);
2219
+ if (uncertainMovies) summaryParts.push(`uncertain movie ${uncertainMovies}`);
2220
+ if (pendingTv.length) summaryParts.push(`uncertain tv ${pendingTv.length}`);
2221
+ if (pendingBooks.length) summaryParts.push(`uncertain book ${pendingBooks.length}`);
2222
+ if (pendingAnime.length) summaryParts.push(`uncertain anime ${pendingAnime.length}`);
2223
+ spinner10.succeed(summaryParts.join(", "));
2224
+ spinner10.stop();
2229
2225
  };
2230
2226
  var scan_default = scan;
2231
2227
 
2232
2228
  // src/actions/shows.ts
2233
2229
  var import_fs16 = require("fs");
2234
- var import_termkit15 = require("termkit");
2230
+ var import_termkit14 = require("termkit");
2235
2231
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
2236
2232
  var shows = async () => {
2237
2233
  const config = getConfig();
@@ -2243,8 +2239,8 @@ var shows = async () => {
2243
2239
  return;
2244
2240
  }
2245
2241
  console.log(`
2246
- ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termkit15.Color.white.encoder(destRoot)}` : ""} (${allShows.length} registered)`);
2247
- new import_termkit15.Table(
2242
+ ${import_termkit14.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termkit14.Color.white.encoder(destRoot)}` : ""} (${allShows.length} registered)`);
2243
+ new import_termkit14.Table(
2248
2244
  allShows.map((show) => ({
2249
2245
  name: show.path.split("/").pop() ?? show.path,
2250
2246
  size: (0, import_fs16.existsSync)(show.path) ? formatSize(dirSize(show.path)) : "\u2014",
@@ -2263,9 +2259,9 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
2263
2259
  if (v) {
2264
2260
  const url = `https://www.themoviedb.org/tv/${v}`;
2265
2261
  const text = process.stdout.isTTY ? hyperlink(url, "\u2713 tmdb") : "\u2713 tmdb";
2266
- return import_termkit15.Color.green.encoder(text);
2262
+ return import_termkit14.Color.green.encoder(text);
2267
2263
  }
2268
- return import_termkit15.Color.red.encoder("\u2717");
2264
+ return import_termkit14.Color.red.encoder("\u2717");
2269
2265
  }
2270
2266
  },
2271
2267
  { key: "ended", title: "Status", value: (v) => v ? dim("ended") : "active" }
@@ -2282,7 +2278,7 @@ var shows_default = shows;
2282
2278
  // src/actions/stats.ts
2283
2279
  var import_fs17 = require("fs");
2284
2280
  var import_path16 = require("path");
2285
- var import_termkit16 = require("termkit");
2281
+ var import_termkit15 = require("termkit");
2286
2282
  var countVideos2 = (dir) => {
2287
2283
  let count = 0;
2288
2284
  try {
@@ -2330,7 +2326,7 @@ var stats = async () => {
2330
2326
  }
2331
2327
  if (rows.length === 0) return;
2332
2328
  console.log();
2333
- new import_termkit16.Table(rows, {
2329
+ new import_termkit15.Table(rows, {
2334
2330
  title: "LIBRARY STATISTICS",
2335
2331
  separator: " ",
2336
2332
  columns: [
@@ -2345,14 +2341,15 @@ var stats_default = stats;
2345
2341
 
2346
2342
  // src/actions/undo.ts
2347
2343
  var import_fs18 = require("fs");
2348
- var import_termkit17 = require("termkit");
2344
+ var import_termkit16 = require("termkit");
2345
+ var spinner11 = new import_termkit16.Spinner();
2349
2346
  var undo = async () => {
2350
- spinner_default.start();
2347
+ spinner11.start();
2351
2348
  const renameRecords = getLastSession();
2352
2349
  const importRecords = getLastImportSession();
2353
2350
  if (renameRecords.length === 0 && importRecords.length === 0) {
2354
- spinner_default.info("nothing to undo");
2355
- spinner_default.stop();
2351
+ spinner11.info("nothing to undo");
2352
+ spinner11.stop();
2356
2353
  return;
2357
2354
  }
2358
2355
  const useImports = importRecords.length > 0 && (renameRecords.length === 0 || importRecords[0].sessionId > renameRecords[0].sessionId);
@@ -2360,40 +2357,40 @@ var undo = async () => {
2360
2357
  let undone2 = 0;
2361
2358
  for (const record of renameRecords) {
2362
2359
  (0, import_fs18.renameSync)(record.newPath, record.oldPath);
2363
- spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.newPath)} \u2192 ${import_termkit17.Color.white.encoder(record.oldPath)}`);
2360
+ spinner11.succeed(`${import_termkit16.Color.green.encoder(record.newPath)} \u2192 ${import_termkit16.Color.white.encoder(record.oldPath)}`);
2364
2361
  undone2++;
2365
2362
  }
2366
2363
  deleteSession(renameRecords[0].sessionId);
2367
- spinner_default.succeed(`undid ${undone2} renames`);
2368
- spinner_default.stop();
2364
+ spinner11.succeed(`undid ${undone2} renames`);
2365
+ spinner11.stop();
2369
2366
  return;
2370
2367
  }
2371
2368
  let undone = 0;
2372
2369
  let skipped = 0;
2373
2370
  for (const record of importRecords) {
2374
2371
  if (record.mode !== "move") {
2375
- spinner_default.info(`skipped ${record.destinationPath} (${record.mode} \u2014 source file unchanged)`);
2372
+ spinner11.info(`skipped ${record.destinationPath} (${record.mode} \u2014 source file unchanged)`);
2376
2373
  skipped++;
2377
2374
  continue;
2378
2375
  }
2379
2376
  if (record.type === "tv") {
2380
- spinner_default.info(`skipped TV import \u2014 season folder cannot be cleanly reversed: ${record.destinationPath}`);
2377
+ spinner11.info(`skipped TV import \u2014 season folder cannot be cleanly reversed: ${record.destinationPath}`);
2381
2378
  skipped++;
2382
2379
  continue;
2383
2380
  }
2384
2381
  if (!(0, import_fs18.existsSync)(record.destinationPath)) {
2385
- spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
2382
+ spinner11.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
2386
2383
  skipped++;
2387
2384
  continue;
2388
2385
  }
2389
2386
  (0, import_fs18.renameSync)(record.destinationPath, record.sourcePath);
2390
- spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit17.Color.white.encoder(record.sourcePath)}`);
2387
+ spinner11.succeed(`${import_termkit16.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit16.Color.white.encoder(record.sourcePath)}`);
2391
2388
  undone++;
2392
2389
  }
2393
2390
  deleteImportSession(importRecords[0].sessionId);
2394
- if (undone > 0) spinner_default.succeed(`undid ${undone} import${undone !== 1 ? "s" : ""}`);
2395
- if (skipped > 0) spinner_default.info(`skipped ${skipped} item${skipped !== 1 ? "s" : ""} (TV or non-move mode)`);
2396
- spinner_default.stop();
2391
+ if (undone > 0) spinner11.succeed(`undid ${undone} import${undone !== 1 ? "s" : ""}`);
2392
+ if (skipped > 0) spinner11.info(`skipped ${skipped} item${skipped !== 1 ? "s" : ""} (TV or non-move mode)`);
2393
+ spinner11.stop();
2397
2394
  };
2398
2395
  var undo_default = undo;
2399
2396
 
@@ -2401,7 +2398,8 @@ var undo_default = undo;
2401
2398
  var import_chokidar = __toESM(require("chokidar"));
2402
2399
  var import_fs19 = require("fs");
2403
2400
  var import_path17 = require("path");
2404
- var import_termkit18 = require("termkit");
2401
+ var import_termkit17 = require("termkit");
2402
+ var spinner12 = new import_termkit17.Spinner();
2405
2403
  var sameDev2 = (a, b) => {
2406
2404
  try {
2407
2405
  let bExisting = b;
@@ -2541,7 +2539,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2541
2539
  }
2542
2540
  const destRoot = config.dest[detectedType];
2543
2541
  if (!destRoot) {
2544
- if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
2542
+ if (isVerbose()) spinner12.info(`no ${detectedType} destination configured, skipped: ${entry}`);
2545
2543
  return;
2546
2544
  }
2547
2545
  if (detectedType === "ps3") {
@@ -2551,18 +2549,18 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2551
2549
  const destName = `${nameMatch[0]} [${id}]`;
2552
2550
  const destPath = (0, import_path17.resolve)(destRoot, destName);
2553
2551
  if ((0, import_fs19.existsSync)(destPath)) {
2554
- spinner_default.warn(`already exists: ${destName}`);
2552
+ spinner12.warn(`already exists: ${destName}`);
2555
2553
  return;
2556
2554
  }
2557
2555
  moveItem(entryPath, destPath);
2558
2556
  recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
2559
- spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(destName)}`);
2557
+ spinner12.succeed(`imported ${import_termkit17.Color.green.encoder(destName)}`);
2560
2558
  return;
2561
2559
  }
2562
2560
  if (detectedType === "book") {
2563
2561
  const destPath = (0, import_path17.resolve)(destRoot, entry);
2564
2562
  if ((0, import_fs19.existsSync)(destPath)) {
2565
- spinner_default.warn(`already exists: ${entry}`);
2563
+ spinner12.warn(`already exists: ${entry}`);
2566
2564
  return;
2567
2565
  }
2568
2566
  if (isDir || isBookDir) {
@@ -2577,17 +2575,17 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2577
2575
  }
2578
2576
  }
2579
2577
  recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
2580
- spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(entry)}`);
2578
+ spinner12.succeed(`imported ${import_termkit17.Color.green.encoder(entry)}`);
2581
2579
  return;
2582
2580
  }
2583
2581
  const parsed = parseDownloadName(entry);
2584
2582
  if (!parsed) {
2585
- if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
2583
+ if (isVerbose()) spinner12.info(`could not parse: ${entry}`);
2586
2584
  return;
2587
2585
  }
2588
2586
  if (detectedType === "tv") {
2589
2587
  if (parsed.season === void 0) {
2590
- if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
2588
+ if (isVerbose()) spinner12.info(`could not detect season from: ${entry}`);
2591
2589
  return;
2592
2590
  }
2593
2591
  const registeredShow = getShowByTitle(parsed.title);
@@ -2601,14 +2599,14 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2601
2599
  showPath = (0, import_path17.resolve)(destRoot, showFolderName);
2602
2600
  upsertShow(showPath, null, parsed.title);
2603
2601
  } else {
2604
- if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
2602
+ if (isVerbose()) spinner12.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
2605
2603
  return;
2606
2604
  }
2607
2605
  const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
2608
2606
  const seasonPath = (0, import_path17.resolve)(showPath, seasonFolderName);
2609
2607
  const videoFile2 = isDir ? findVideo2(entryPath) : entry;
2610
2608
  if (!videoFile2) {
2611
- if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
2609
+ if (isVerbose()) spinner12.info(`no video found in: ${entry}`);
2612
2610
  return;
2613
2611
  }
2614
2612
  const videoExt2 = videoFile2.match(/([^.]+$)/)?.[0];
@@ -2618,7 +2616,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2618
2616
  const destVideoPath = (0, import_path17.resolve)(seasonPath, destVideoName2);
2619
2617
  const videoSourcePath2 = isDir ? (0, import_path17.resolve)(entryPath, videoFile2) : entryPath;
2620
2618
  if ((0, import_fs19.existsSync)(destVideoPath)) {
2621
- spinner_default.warn(`already exists: ${episodeName}`);
2619
+ spinner12.warn(`already exists: ${episodeName}`);
2622
2620
  return;
2623
2621
  }
2624
2622
  const dirFiles2 = isDir ? (0, import_fs19.readdirSync)(entryPath) : [];
@@ -2634,7 +2632,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2634
2632
  (0, import_fs19.linkSync)(videoSourcePath2, destVideoPath);
2635
2633
  mode = "hardlink";
2636
2634
  } catch {
2637
- spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2635
+ spinner12.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2638
2636
  (0, import_fs19.cpSync)(videoSourcePath2, destVideoPath);
2639
2637
  mode = "copy";
2640
2638
  }
@@ -2650,19 +2648,19 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2650
2648
  if (isDir) (0, import_fs19.rmSync)(entryPath, { recursive: true, force: true });
2651
2649
  }
2652
2650
  recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
2653
- spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
2651
+ spinner12.succeed(`imported ${import_termkit17.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
2654
2652
  return;
2655
2653
  }
2656
2654
  const edition = detectEdition(entry);
2657
2655
  const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
2658
2656
  const destFolder = (0, import_path17.resolve)(destRoot, folderName);
2659
2657
  if ((0, import_fs19.existsSync)(destFolder)) {
2660
- spinner_default.warn(`already exists: ${folderName}`);
2658
+ spinner12.warn(`already exists: ${folderName}`);
2661
2659
  return;
2662
2660
  }
2663
2661
  const videoFile = isDir ? findVideo2(entryPath) : entry;
2664
2662
  if (!videoFile) {
2665
- if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
2663
+ if (isVerbose()) spinner12.info(`no video found in: ${entry}`);
2666
2664
  return;
2667
2665
  }
2668
2666
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
@@ -2682,7 +2680,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2682
2680
  (0, import_fs19.linkSync)(videoSourcePath, destVideoPath);
2683
2681
  mode = "hardlink";
2684
2682
  } catch {
2685
- spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
2683
+ spinner12.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
2686
2684
  (0, import_fs19.cpSync)(videoSourcePath, destVideoPath);
2687
2685
  mode = "copy";
2688
2686
  }
@@ -2707,7 +2705,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2707
2705
  }
2708
2706
  recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");
2709
2707
  }
2710
- spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(folderName)}`);
2708
+ spinner12.succeed(`imported ${import_termkit17.Color.green.encoder(folderName)}`);
2711
2709
  };
2712
2710
  var watch = async ({ hardlink = false, auto = false }) => {
2713
2711
  const config = getConfig();
@@ -2726,7 +2724,7 @@ var watch = async ({ hardlink = false, auto = false }) => {
2726
2724
  await processItem(entry, hardlink, language, auto);
2727
2725
  }
2728
2726
  } catch (err) {
2729
- spinner_default.fail(`error processing ${path}: ${err.message}`);
2727
+ spinner12.fail(`error processing ${path}: ${err.message}`);
2730
2728
  }
2731
2729
  }, 5e3)
2732
2730
  );
@@ -2738,16 +2736,16 @@ var watch = async ({ hardlink = false, auto = false }) => {
2738
2736
  });
2739
2737
  watcher.on("addDir", handle);
2740
2738
  watcher.on("add", handle);
2741
- spinner_default.start();
2742
- spinner_default.succeed(`watching ${config.sources.length} source${config.sources.length !== 1 ? "s" : ""}`);
2743
- for (const s of config.sources) spinner_default.info(` ${import_termkit18.Color.white.encoder(s)}`);
2744
- spinner_default.stop();
2739
+ spinner12.start();
2740
+ spinner12.succeed(`watching ${config.sources.length} source${config.sources.length !== 1 ? "s" : ""}`);
2741
+ for (const s of config.sources) spinner12.info(` ${import_termkit17.Color.white.encoder(s)}`);
2742
+ spinner12.stop();
2745
2743
  process.stdin.resume();
2746
2744
  };
2747
2745
  var watch_default = watch;
2748
2746
 
2749
2747
  // package.json
2750
- var version = "0.2.7";
2748
+ var version = "0.2.9";
2751
2749
 
2752
2750
  // src/program.ts
2753
2751
  var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -2756,8 +2754,8 @@ var adapt = (fn) => (options) => {
2756
2754
  setVerbose(!!camel.verbose);
2757
2755
  return fn(camel);
2758
2756
  };
2759
- var { command, option } = import_termkit19.Program;
2760
- var program = import_termkit19.Program.command("reelsort").version(version).description("a cli to manage media").commands([
2757
+ var { command, option } = import_termkit18.Program;
2758
+ var program = import_termkit18.Program.command("reelsort").version(version).description("a cli to manage media").commands([
2761
2759
  command("config").description("manage configuration").commands([
2762
2760
  command("source").description("manage source directories").commands([command("add", "<dir>").description("add a source directory").action(adapt(sourceAdd)), command("remove", "[dir]").description("remove a source directory").action(adapt(sourceRemove))]),
2763
2761
  command("dest").description("manage destinations").commands([command("add", "<type> <dir>").description("set a destination (movie, tv, ps3, book)").action(adapt(destAdd)), command("remove", "[type]").description("remove a destination (movie, tv, ps3, book)").action(adapt(destRemove))]),
@@ -2786,17 +2784,18 @@ var program_default = program;
2786
2784
 
2787
2785
  // src/cli.ts
2788
2786
  if (!process.stdout.isTTY) {
2789
- const { FORCE_COLOR, MSYSTEM, WT_SESSION, TERM } = process.env;
2790
- if (FORCE_COLOR || MSYSTEM || WT_SESSION || TERM?.startsWith("xterm")) {
2787
+ const { FORCE_COLOR, MSYSTEM, WT_SESSION, TERM, PSModulePath } = process.env;
2788
+ if (FORCE_COLOR || MSYSTEM || WT_SESSION || TERM?.startsWith("xterm") || PSModulePath) {
2791
2789
  Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true });
2790
+ Object.defineProperty(process.stdin, "isTTY", { value: true, configurable: true });
2792
2791
  }
2793
2792
  }
2794
2793
  var run = async (args) => {
2795
2794
  try {
2796
2795
  await program_default.parse(args);
2797
2796
  } catch (err) {
2798
- spinner_default.fail(err.message);
2799
- spinner_default.stop();
2797
+ import_termkit19.Spinner.current?.fail(err.message);
2798
+ import_termkit19.Spinner.current?.stop();
2800
2799
  }
2801
2800
  process.exit();
2802
2801
  };