reelsort 0.2.3 → 0.2.4

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/dist/cli.js CHANGED
@@ -1676,6 +1676,35 @@ var findShowFolder = (destRoot, title) => {
1676
1676
  }
1677
1677
  }).find((f) => normalize(f) === target) ?? null;
1678
1678
  };
1679
+ var findShowFolderByContent = (destRoot, title) => {
1680
+ if (!(0, import_fs14.existsSync)(destRoot)) return null;
1681
+ const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1682
+ const target = normalize(title);
1683
+ const matchesTitle = (name) => {
1684
+ if (!isTvEpisodeName(name)) return false;
1685
+ const p = parseDownloadName(name);
1686
+ return !!p && normalize(p.title) === target;
1687
+ };
1688
+ for (const folder of (0, import_fs14.readdirSync)(destRoot)) {
1689
+ try {
1690
+ const folderPath = (0, import_path14.resolve)(destRoot, folder);
1691
+ if (!(0, import_fs14.lstatSync)(folderPath).isDirectory()) continue;
1692
+ const children = (0, import_fs14.readdirSync)(folderPath);
1693
+ if (children.some(matchesTitle)) return folder;
1694
+ for (const child of children) {
1695
+ if (!isSeasonDirName(child)) continue;
1696
+ try {
1697
+ const seasonPath = (0, import_path14.resolve)(folderPath, child);
1698
+ if (!(0, import_fs14.lstatSync)(seasonPath).isDirectory()) continue;
1699
+ if ((0, import_fs14.readdirSync)(seasonPath).some(matchesTitle)) return folder;
1700
+ } catch {
1701
+ }
1702
+ }
1703
+ } catch {
1704
+ }
1705
+ }
1706
+ return null;
1707
+ };
1679
1708
  var findSeasonFolder = (showPath, season) => {
1680
1709
  if (!(0, import_fs14.existsSync)(showPath)) return null;
1681
1710
  const folders = (0, import_fs14.readdirSync)(showPath).filter((f) => {
@@ -1705,6 +1734,7 @@ var typeColor = {
1705
1734
  book: import_termkit14.Color.white.yellow,
1706
1735
  ps3: import_termkit14.Color.white.magenta
1707
1736
  };
1737
+ var typeGlyph = (t) => typeColor[t].encoder("\u25CF");
1708
1738
  var typeTag = (t) => isVerbose() ? import_termkit14.Color.white.faint.encoder(` (${t})`) : "";
1709
1739
  var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
1710
1740
  const config = getConfig();
@@ -1798,7 +1828,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1798
1828
  recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
1799
1829
  }
1800
1830
  }
1801
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
1831
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
1802
1832
  return true;
1803
1833
  };
1804
1834
  spinner_default.start();
@@ -1859,7 +1889,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1859
1889
  moveFolder(entryPath, destPath);
1860
1890
  recordImport(sessionId, entryPath, destPath, "move");
1861
1891
  }
1862
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
1892
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
1863
1893
  imported++;
1864
1894
  continue;
1865
1895
  }
@@ -1884,7 +1914,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1884
1914
  }
1885
1915
  recordImport(sessionId, entryPath, destPath, "move");
1886
1916
  }
1887
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book.encoder(entry)}${typeTag("book")}`);
1917
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book.encoder(entry)}${typeTag("book")}`);
1888
1918
  imported++;
1889
1919
  continue;
1890
1920
  }
@@ -1952,7 +1982,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1952
1982
  showPath = registeredShow.path;
1953
1983
  showFolderName = showPath.split("/").pop() ?? registeredShow.path;
1954
1984
  } else {
1955
- const existingFolder = findShowFolder(destRoot, resolvedTitle);
1985
+ const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
1956
1986
  if (existingFolder) {
1957
1987
  showFolderName = existingFolder;
1958
1988
  showPath = (0, import_path14.resolve)(destRoot, existingFolder);
@@ -2033,7 +2063,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2033
2063
  }
2034
2064
  recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
2035
2065
  }
2036
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
2066
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
2037
2067
  imported++;
2038
2068
  continue;
2039
2069
  }
@@ -2046,7 +2076,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2046
2076
  }
2047
2077
  if (pendingMovies.length > 0) {
2048
2078
  spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
2049
- for (const p of pendingMovies) spinner_default.info(` ${typeColor.movie.encoder("?")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
2079
+ for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
2050
2080
  let toProcess = [];
2051
2081
  if (interactive) {
2052
2082
  spinner_default.stop();
@@ -2075,7 +2105,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2075
2105
  }
2076
2106
  if (pendingTv.length > 0) {
2077
2107
  spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
2078
- for (const p of pendingTv) spinner_default.info(` ${typeColor.tv.encoder("?")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
2108
+ for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
2079
2109
  skipped += pendingTv.length;
2080
2110
  }
2081
2111
  if (ignoreSet.size > 0) {
@@ -2580,7 +2610,7 @@ var watch = async ({ hardlink = false, auto = false }) => {
2580
2610
  var watch_default = watch;
2581
2611
 
2582
2612
  // package.json
2583
- var version = "0.2.3";
2613
+ var version = "0.2.4";
2584
2614
 
2585
2615
  // src/program.ts
2586
2616
  var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -2618,6 +2648,12 @@ var program = import_termkit19.Program.command("reelsort").version(version).desc
2618
2648
  var program_default = program;
2619
2649
 
2620
2650
  // src/cli.ts
2651
+ if (!process.stdout.isTTY) {
2652
+ const { FORCE_COLOR, MSYSTEM, WT_SESSION, TERM } = process.env;
2653
+ if (FORCE_COLOR || MSYSTEM || WT_SESSION || TERM?.startsWith("xterm")) {
2654
+ Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true });
2655
+ }
2656
+ }
2621
2657
  var run = async (args) => {
2622
2658
  try {
2623
2659
  await program_default.parse(args);
package/dist/index.js CHANGED
@@ -1386,6 +1386,35 @@ var findShowFolder = (destRoot, title) => {
1386
1386
  }
1387
1387
  }).find((f) => normalize(f) === target) ?? null;
1388
1388
  };
1389
+ var findShowFolderByContent = (destRoot, title) => {
1390
+ if (!(0, import_fs10.existsSync)(destRoot)) return null;
1391
+ const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1392
+ const target = normalize(title);
1393
+ const matchesTitle = (name) => {
1394
+ if (!isTvEpisodeName(name)) return false;
1395
+ const p = parseDownloadName(name);
1396
+ return !!p && normalize(p.title) === target;
1397
+ };
1398
+ for (const folder of (0, import_fs10.readdirSync)(destRoot)) {
1399
+ try {
1400
+ const folderPath = (0, import_path11.resolve)(destRoot, folder);
1401
+ if (!(0, import_fs10.lstatSync)(folderPath).isDirectory()) continue;
1402
+ const children = (0, import_fs10.readdirSync)(folderPath);
1403
+ if (children.some(matchesTitle)) return folder;
1404
+ for (const child of children) {
1405
+ if (!isSeasonDirName(child)) continue;
1406
+ try {
1407
+ const seasonPath = (0, import_path11.resolve)(folderPath, child);
1408
+ if (!(0, import_fs10.lstatSync)(seasonPath).isDirectory()) continue;
1409
+ if ((0, import_fs10.readdirSync)(seasonPath).some(matchesTitle)) return folder;
1410
+ } catch {
1411
+ }
1412
+ }
1413
+ } catch {
1414
+ }
1415
+ }
1416
+ return null;
1417
+ };
1389
1418
  var findSeasonFolder = (showPath, season) => {
1390
1419
  if (!(0, import_fs10.existsSync)(showPath)) return null;
1391
1420
  const folders = (0, import_fs10.readdirSync)(showPath).filter((f) => {
@@ -1415,6 +1444,7 @@ var typeColor = {
1415
1444
  book: import_termkit10.Color.white.yellow,
1416
1445
  ps3: import_termkit10.Color.white.magenta
1417
1446
  };
1447
+ var typeGlyph = (t) => typeColor[t].encoder("\u25CF");
1418
1448
  var typeTag = (t) => isVerbose() ? import_termkit10.Color.white.faint.encoder(` (${t})`) : "";
1419
1449
  var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
1420
1450
  const config = getConfig();
@@ -1508,7 +1538,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1508
1538
  recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
1509
1539
  }
1510
1540
  }
1511
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
1541
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
1512
1542
  return true;
1513
1543
  };
1514
1544
  spinner_default.start();
@@ -1569,7 +1599,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1569
1599
  moveFolder(entryPath, destPath);
1570
1600
  recordImport(sessionId, entryPath, destPath, "move");
1571
1601
  }
1572
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
1602
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
1573
1603
  imported++;
1574
1604
  continue;
1575
1605
  }
@@ -1594,7 +1624,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1594
1624
  }
1595
1625
  recordImport(sessionId, entryPath, destPath, "move");
1596
1626
  }
1597
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book.encoder(entry)}${typeTag("book")}`);
1627
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book.encoder(entry)}${typeTag("book")}`);
1598
1628
  imported++;
1599
1629
  continue;
1600
1630
  }
@@ -1662,7 +1692,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1662
1692
  showPath = registeredShow.path;
1663
1693
  showFolderName = showPath.split("/").pop() ?? registeredShow.path;
1664
1694
  } else {
1665
- const existingFolder = findShowFolder(destRoot, resolvedTitle);
1695
+ const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
1666
1696
  if (existingFolder) {
1667
1697
  showFolderName = existingFolder;
1668
1698
  showPath = (0, import_path11.resolve)(destRoot, existingFolder);
@@ -1743,7 +1773,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1743
1773
  }
1744
1774
  recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
1745
1775
  }
1746
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
1776
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
1747
1777
  imported++;
1748
1778
  continue;
1749
1779
  }
@@ -1756,7 +1786,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1756
1786
  }
1757
1787
  if (pendingMovies.length > 0) {
1758
1788
  spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
1759
- for (const p of pendingMovies) spinner_default.info(` ${typeColor.movie.encoder("?")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
1789
+ for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
1760
1790
  let toProcess = [];
1761
1791
  if (interactive) {
1762
1792
  spinner_default.stop();
@@ -1785,7 +1815,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1785
1815
  }
1786
1816
  if (pendingTv.length > 0) {
1787
1817
  spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
1788
- for (const p of pendingTv) spinner_default.info(` ${typeColor.tv.encoder("?")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
1818
+ for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
1789
1819
  skipped += pendingTv.length;
1790
1820
  }
1791
1821
  if (ignoreSet.size > 0) {
package/dist/index.mjs CHANGED
@@ -1310,6 +1310,35 @@ var findShowFolder = (destRoot, title) => {
1310
1310
  }
1311
1311
  }).find((f) => normalize(f) === target) ?? null;
1312
1312
  };
1313
+ var findShowFolderByContent = (destRoot, title) => {
1314
+ if (!existsSync9(destRoot)) return null;
1315
+ const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1316
+ const target = normalize(title);
1317
+ const matchesTitle = (name) => {
1318
+ if (!isTvEpisodeName(name)) return false;
1319
+ const p = parseDownloadName(name);
1320
+ return !!p && normalize(p.title) === target;
1321
+ };
1322
+ for (const folder of readdirSync7(destRoot)) {
1323
+ try {
1324
+ const folderPath = resolve8(destRoot, folder);
1325
+ if (!lstatSync4(folderPath).isDirectory()) continue;
1326
+ const children = readdirSync7(folderPath);
1327
+ if (children.some(matchesTitle)) return folder;
1328
+ for (const child of children) {
1329
+ if (!isSeasonDirName(child)) continue;
1330
+ try {
1331
+ const seasonPath = resolve8(folderPath, child);
1332
+ if (!lstatSync4(seasonPath).isDirectory()) continue;
1333
+ if (readdirSync7(seasonPath).some(matchesTitle)) return folder;
1334
+ } catch {
1335
+ }
1336
+ }
1337
+ } catch {
1338
+ }
1339
+ }
1340
+ return null;
1341
+ };
1313
1342
  var findSeasonFolder = (showPath, season) => {
1314
1343
  if (!existsSync9(showPath)) return null;
1315
1344
  const folders = readdirSync7(showPath).filter((f) => {
@@ -1339,6 +1368,7 @@ var typeColor = {
1339
1368
  book: Color9.white.yellow,
1340
1369
  ps3: Color9.white.magenta
1341
1370
  };
1371
+ var typeGlyph = (t) => typeColor[t].encoder("\u25CF");
1342
1372
  var typeTag = (t) => isVerbose() ? Color9.white.faint.encoder(` (${t})`) : "";
1343
1373
  var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
1344
1374
  const config = getConfig();
@@ -1432,7 +1462,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1432
1462
  recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
1433
1463
  }
1434
1464
  }
1435
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
1465
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
1436
1466
  return true;
1437
1467
  };
1438
1468
  spinner_default.start();
@@ -1493,7 +1523,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1493
1523
  moveFolder(entryPath, destPath);
1494
1524
  recordImport(sessionId, entryPath, destPath, "move");
1495
1525
  }
1496
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
1526
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
1497
1527
  imported++;
1498
1528
  continue;
1499
1529
  }
@@ -1518,7 +1548,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1518
1548
  }
1519
1549
  recordImport(sessionId, entryPath, destPath, "move");
1520
1550
  }
1521
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book.encoder(entry)}${typeTag("book")}`);
1551
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book.encoder(entry)}${typeTag("book")}`);
1522
1552
  imported++;
1523
1553
  continue;
1524
1554
  }
@@ -1586,7 +1616,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1586
1616
  showPath = registeredShow.path;
1587
1617
  showFolderName = showPath.split("/").pop() ?? registeredShow.path;
1588
1618
  } else {
1589
- const existingFolder = findShowFolder(destRoot, resolvedTitle);
1619
+ const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
1590
1620
  if (existingFolder) {
1591
1621
  showFolderName = existingFolder;
1592
1622
  showPath = resolve8(destRoot, existingFolder);
@@ -1667,7 +1697,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1667
1697
  }
1668
1698
  recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
1669
1699
  }
1670
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
1700
+ spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
1671
1701
  imported++;
1672
1702
  continue;
1673
1703
  }
@@ -1680,7 +1710,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1680
1710
  }
1681
1711
  if (pendingMovies.length > 0) {
1682
1712
  spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
1683
- for (const p of pendingMovies) spinner_default.info(` ${typeColor.movie.encoder("?")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
1713
+ for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
1684
1714
  let toProcess = [];
1685
1715
  if (interactive) {
1686
1716
  spinner_default.stop();
@@ -1709,7 +1739,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1709
1739
  }
1710
1740
  if (pendingTv.length > 0) {
1711
1741
  spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
1712
- for (const p of pendingTv) spinner_default.info(` ${typeColor.tv.encoder("?")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
1742
+ for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
1713
1743
  skipped += pendingTv.length;
1714
1744
  }
1715
1745
  if (ignoreSet.size > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reelsort",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "CLI to rename, organize, and manage your movie and TV library — Plex-compatible naming, hardlink support, and automated watch mode.",
5
5
  "keywords": [
6
6
  "media",