@involvex/youtube-music-cli 0.0.29 → 0.0.30

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/CHANGELOG.md CHANGED
@@ -1,3 +1,5 @@
1
+ ## [0.0.30](https://github.com/involvex/youtube-music-cli/compare/v0.0.29...v0.0.30) (2026-02-22)
2
+
1
3
  ## [0.0.29](https://github.com/involvex/youtube-music-cli/compare/v0.0.28...v0.0.29) (2026-02-22)
2
4
 
3
5
  ### Bug Fixes
@@ -1,10 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  // Shortcuts bar component
3
3
  import { Box, Text } from 'ink';
4
4
  import { usePlayer } from "../../hooks/usePlayer.js";
5
5
  import { useTheme } from "../../hooks/useTheme.js";
6
6
  import { useKeyBinding } from "../../hooks/useKeyboard.js";
7
7
  import { KEYBINDINGS } from "../../utils/constants.js";
8
+ import { ICONS } from "../../utils/icons.js";
8
9
  export default function ShortcutsBar() {
9
10
  const { theme } = useTheme();
10
11
  const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, volumeFineUp, volumeFineDown, toggleShuffle, toggleRepeat, } = usePlayer();
@@ -27,7 +28,7 @@ export default function ShortcutsBar() {
27
28
  useKeyBinding(KEYBINDINGS.SHUFFLE, toggleShuffle);
28
29
  useKeyBinding(KEYBINDINGS.REPEAT, toggleRepeat);
29
30
  // Note: SETTINGS keybinding handled by MainLayout to avoid double-dispatch
30
- return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: [_jsx(Text, { color: theme.colors.text, children: "\u23EF [Space]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u23EE [B/\u2190]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u23ED [N/\u2192]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\uD83D\uDD00 [Shift+S]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\uD83D\uDD01 [R]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\uD83D\uDCDA [Shift+P]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u2B07 [Shift+D]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\uD83D\uDD0E [/]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u2753 [?]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\uD83D\uDEF0 [Shift+Q]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\uD83D\uDD0C [Shift+R]" }), " |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u23FB [Q]" })] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: playerState.shuffle ? theme.colors.primary : theme.colors.dim, children: "\uD83D\uDD00" }), ' ', _jsx(Text, { color: playerState.repeat === 'off'
31
+ return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: [ICONS.PLAY_PAUSE_ON, "/", ICONS.PAUSE, " [Space]"] }), ' ', "| ", _jsxs(Text, { color: theme.colors.text, children: [ICONS.PREV, " [B/\u2190]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.NEXT, " [N/\u2192]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.SHUFFLE, " [Shift+S]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.REPEAT_ALL, " [R]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.PLAYLIST, " [Shift+P]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.DOWNLOAD, " [Shift+D]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.SEARCH, " [/]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.HELP, " [?]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.BG_PLAY, " [Shift+Q]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.RESUME, " [Shift+R]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.QUIT, " [Q]"] })] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: playerState.shuffle ? theme.colors.primary : theme.colors.dim, children: ICONS.SHUFFLE }), ' ', _jsx(Text, { color: playerState.repeat === 'off'
31
32
  ? theme.colors.dim
32
- : theme.colors.secondary, children: playerState.repeat === 'one' ? '🔂' : '🔄' }), ' ', _jsx(Text, { color: theme.colors.dim, children: "\uD83D\uDD0A [=/-]" }), ' ', _jsxs(Text, { color: theme.colors.primary, children: [playerState.volume, "%"] })] })] }));
33
+ : theme.colors.secondary, children: playerState.repeat === 'one' ? ICONS.REPEAT_ONE : ICONS.REPEAT_ALL }), ' ', _jsxs(Text, { color: theme.colors.dim, children: [ICONS.VOLUME, " [=/-]"] }), ' ', _jsxs(Text, { color: theme.colors.primary, children: [playerState.volume, "%"] })] })] }));
33
34
  }
@@ -4,6 +4,7 @@ import { Box, Text } from 'ink';
4
4
  import { usePlayer } from "../../hooks/usePlayer.js";
5
5
  import { useTheme } from "../../hooks/useTheme.js";
6
6
  import { formatTime } from "../../utils/format.js";
7
+ import { ICONS } from "../../utils/icons.js";
7
8
  export default function MiniPlayerLayout() {
8
9
  const { theme } = useTheme();
9
10
  const { state } = usePlayer();
@@ -12,7 +13,7 @@ export default function MiniPlayerLayout() {
12
13
  const title = track?.title ?? 'No track playing';
13
14
  const progress = formatTime(state.progress);
14
15
  const duration = formatTime(state.duration);
15
- const playIcon = state.isPlaying ? '▶' : '⏸';
16
+ const playIcon = state.isPlaying ? ICONS.PLAY : ICONS.PAUSE;
16
17
  const vol = `${state.volume}%`;
17
18
  const speed = (state.speed ?? 1.0) !== 1.0 ? ` ${(state.speed ?? 1.0).toFixed(2)}x` : '';
18
19
  return (_jsxs(Box, { flexDirection: "row", paddingX: 1, gap: 1, children: [_jsx(Text, { color: state.isPlaying ? theme.colors.success : theme.colors.dim, children: playIcon }), _jsx(Text, { bold: true, color: theme.colors.primary, children: title }), _jsx(Text, { color: theme.colors.dim, children: "\u2014" }), _jsx(Text, { color: theme.colors.secondary, children: artist }), _jsx(Text, { color: theme.colors.dim, children: "|" }), _jsxs(Text, { color: theme.colors.text, children: [progress, "/", duration] }), _jsx(Text, { color: theme.colors.dim, children: "|" }), _jsxs(Text, { color: theme.colors.text, children: ["vol:", vol] }), speed && _jsx(Text, { color: theme.colors.accent, children: speed }), state.isLoading && _jsx(Text, { color: theme.colors.accent, children: "Loading..." })] }));
@@ -11,6 +11,7 @@ import { useKeyBinding } from "../../hooks/useKeyboard.js";
11
11
  import { KEYBINDINGS, VIEW } from "../../utils/constants.js";
12
12
  import { Box, Text } from 'ink';
13
13
  import { usePlayer } from "../../hooks/usePlayer.js";
14
+ import { ICONS } from "../../utils/icons.js";
14
15
  function SearchLayout() {
15
16
  const { theme } = useTheme();
16
17
  const { state: navState, dispatch } = useNavigation();
@@ -118,7 +119,7 @@ function SearchLayout() {
118
119
  lastAutoSearchedQueryRef.current = null;
119
120
  };
120
121
  }, [dispatch]);
121
- return (_jsxs(Box, { flexDirection: "column", children: [playerState.currentTrack && (_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.dim, children: playerState.isPlaying ? '▶ ' : '⏸ ' }), _jsx(Text, { color: theme.colors.primary, bold: true, children: playerState.currentTrack.title }), playerState.currentTrack.artists &&
122
+ return (_jsxs(Box, { flexDirection: "column", children: [playerState.currentTrack && (_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.dim, children: playerState.isPlaying ? `${ICONS.PLAY} ` : `${ICONS.PAUSE} ` }), _jsx(Text, { color: theme.colors.primary, bold: true, children: playerState.currentTrack.title }), playerState.currentTrack.artists &&
122
123
  playerState.currentTrack.artists.length > 0 && (_jsxs(Text, { color: theme.colors.secondary, children: [' • ', playerState.currentTrack.artists.map(a => a.name).join(', ')] }))] })), _jsxs(Text, { color: theme.colors.dim, children: ["Limit: ", navState.searchLimit, " (Use [ or ] to adjust)"] }), _jsx(SearchBar, { isActive: isTyping && !isSearching, onInput: input => {
123
124
  void performSearch(input);
124
125
  } }), (isLoading || isSearching) && (_jsx(Text, { color: theme.colors.accent, children: "Searching..." })), error && _jsx(Text, { color: theme.colors.error, children: error }), !isLoading && navState.hasSearched && (_jsx(SearchResults, { results: results, selectedIndex: navState.selectedResult, isActive: !isTyping, onMixCreated: handleMixCreated, onDownloadStatus: handleDownloadStatus })), !isLoading && navState.hasSearched && results.length === 0 && !error && (_jsx(Text, { color: theme.colors.dim, children: "No results found" })), actionMessage && (_jsx(Text, { color: theme.colors.accent, children: actionMessage })), _jsx(Text, { color: theme.colors.dim, children: isTyping
@@ -7,6 +7,7 @@ import { formatTime } from "../../utils/format.js";
7
7
  import { useTerminalSize } from "../../hooks/useTerminalSize.js";
8
8
  import { getSleepTimerService } from "../../services/sleep-timer/sleep-timer.service.js";
9
9
  import { useState, useEffect } from 'react';
10
+ import { ICONS } from "../../utils/icons.js";
10
11
  export default function NowPlaying() {
11
12
  const { theme } = useTheme();
12
13
  const { state: playerState } = usePlayer();
@@ -40,5 +41,5 @@ export default function NowPlaying() {
40
41
  const percentage = duration > 0 ? Math.min(100, Math.floor((progress / duration) * 100)) : 0;
41
42
  const barWidth = Math.max(10, columns - 8);
42
43
  const filledWidth = duration > 0 ? Math.floor((progress / duration) * barWidth) : 0;
43
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.colors.primary, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: track.title }), _jsx(Text, { color: theme.colors.dim, children: " \u2022 " }), _jsx(Text, { color: theme.colors.secondary, children: artists })] }), track.album && _jsx(Text, { color: theme.colors.dim, children: track.album.name }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.primary, children: '█'.repeat(Math.min(filledWidth, barWidth)) }), _jsx(Text, { color: theme.colors.dim, children: '░'.repeat(Math.max(0, barWidth - filledWidth)) })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.text, children: formatTime(progress) }), _jsxs(Text, { color: theme.colors.dim, children: [" / ", formatTime(duration), " "] }), _jsxs(Text, { color: theme.colors.dim, children: ["[", percentage, "%]"] }), playerState.isLoading && (_jsx(Text, { color: theme.colors.accent, children: " Loading..." })), !playerState.isPlaying && progress > 0 && (_jsx(Text, { color: theme.colors.dim, children: " \u23F8" })), playerState.shuffle && _jsx(Text, { color: theme.colors.primary, children: " \uD83D\uDD00" }), sleepRemaining !== null && (_jsxs(Text, { color: theme.colors.warning, children: [' ', "\u23FE ", formatTime(sleepRemaining)] }))] }), playerState.error && (_jsx(Text, { color: theme.colors.error, children: playerState.error }))] }));
44
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.colors.primary, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: track.title }), _jsx(Text, { color: theme.colors.dim, children: " \u2022 " }), _jsx(Text, { color: theme.colors.secondary, children: artists })] }), track.album && _jsx(Text, { color: theme.colors.dim, children: track.album.name }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.primary, children: '█'.repeat(Math.min(filledWidth, barWidth)) }), _jsx(Text, { color: theme.colors.dim, children: '░'.repeat(Math.max(0, barWidth - filledWidth)) })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.text, children: formatTime(progress) }), _jsxs(Text, { color: theme.colors.dim, children: [" / ", formatTime(duration), " "] }), _jsxs(Text, { color: theme.colors.dim, children: ["[", percentage, "%]"] }), playerState.isLoading && (_jsx(Text, { color: theme.colors.accent, children: " Loading..." })), !playerState.isPlaying && progress > 0 && (_jsxs(Text, { color: theme.colors.dim, children: [" ", ICONS.PAUSE] })), playerState.shuffle && (_jsxs(Text, { color: theme.colors.primary, children: [" ", ICONS.SHUFFLE] })), sleepRemaining !== null && (_jsxs(Text, { color: theme.colors.warning, children: [' ', "\u23FE ", formatTime(sleepRemaining)] }))] }), playerState.error && (_jsx(Text, { color: theme.colors.error, children: playerState.error }))] }));
44
45
  }
@@ -7,6 +7,7 @@ import { useTheme } from "../../hooks/useTheme.js";
7
7
  import { Box, Text } from 'ink';
8
8
  import { useEffect } from 'react';
9
9
  import { logger } from "../../services/logger/logger.service.js";
10
+ import { ICONS } from "../../utils/icons.js";
10
11
  let mountCount = 0;
11
12
  export default function PlayerControls() {
12
13
  const instanceId = ++mountCount;
@@ -40,5 +41,5 @@ export default function PlayerControls() {
40
41
  useKeyBinding(KEYBINDINGS.SPEED_UP, speedUp);
41
42
  useKeyBinding(KEYBINDINGS.SPEED_DOWN, speedDown);
42
43
  useKeyBinding(KEYBINDINGS.SHUFFLE, toggleShuffle);
43
- return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 2, borderStyle: "classic", borderColor: theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "\u2190 / b" }), "] Prev"] }), _jsx(Text, { color: theme.colors.primary, children: playerState.isPlaying ? (_jsxs(Text, { children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Space" }), "] Pause"] })) : (_jsxs(Text, { children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Space" }), "] Play"] })) }), _jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "\u2192 / n" }), "] Next"] }), _jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "+/-" }), "] Vol: ", playerState.volume, "%"] }), _jsxs(Text, { color: playerState.shuffle ? theme.colors.primary : theme.colors.dim, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Shift+S" }), "]", ' ', playerState.shuffle ? '🔀 ON' : '🔀 OFF'] }), (playerState.speed ?? 1.0) !== 1.0 && (_jsxs(Text, { color: theme.colors.accent, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "<>" }), "]", ' ', (playerState.speed ?? 1.0).toFixed(2), "x"] }))] }));
44
+ return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 2, borderStyle: "classic", borderColor: theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "\u2190 / b" }), "] Prev"] }), _jsx(Text, { color: theme.colors.primary, children: playerState.isPlaying ? (_jsxs(Text, { children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Space" }), "] Pause"] })) : (_jsxs(Text, { children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Space" }), "] Play"] })) }), _jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "\u2192 / n" }), "] Next"] }), _jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "+/-" }), "] Vol: ", playerState.volume, "%"] }), _jsxs(Text, { color: playerState.shuffle ? theme.colors.primary : theme.colors.dim, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Shift+S" }), "]", ' ', playerState.shuffle ? `${ICONS.SHUFFLE} ON` : `${ICONS.SHUFFLE} OFF`] }), (playerState.speed ?? 1.0) !== 1.0 && (_jsxs(Text, { color: theme.colors.accent, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "<>" }), "]", ' ', (playerState.speed ?? 1.0).toFixed(2), "x"] }))] }));
44
45
  }
@@ -0,0 +1,19 @@
1
+ export declare const ICONS: {
2
+ readonly PLAY: "▶";
3
+ readonly PAUSE: "‖";
4
+ readonly PLAY_PAUSE_ON: "▶";
5
+ readonly PLAY_PAUSE_OFF: "‖";
6
+ readonly NEXT: "▶|";
7
+ readonly PREV: "|◀";
8
+ readonly SHUFFLE: "⇄";
9
+ readonly REPEAT_ALL: "↻";
10
+ readonly REPEAT_ONE: "↺";
11
+ readonly PLAYLIST: "☰";
12
+ readonly SEARCH: "/";
13
+ readonly HELP: "?";
14
+ readonly DOWNLOAD: "↓";
15
+ readonly QUIT: "×";
16
+ readonly RESUME: "⟳";
17
+ readonly BG_PLAY: "○";
18
+ readonly VOLUME: "♪";
19
+ };
@@ -0,0 +1,26 @@
1
+ // Universal icon constants using widely-supported Unicode BMP characters.
2
+ // No emoji, no Nerd Font codepoints — renders correctly on any terminal font.
3
+ export const ICONS = {
4
+ // Playback controls
5
+ PLAY: '▶', // U+25B6
6
+ PAUSE: '‖', // U+2016
7
+ PLAY_PAUSE_ON: '▶', // when playing
8
+ PLAY_PAUSE_OFF: '‖', // when paused
9
+ NEXT: '▶|', // next track
10
+ PREV: '|◀', // previous track
11
+ // Playback modes
12
+ SHUFFLE: '⇄', // U+21C4
13
+ REPEAT_ALL: '↻', // U+21BB
14
+ REPEAT_ONE: '↺', // U+21BA
15
+ // Navigation / views
16
+ PLAYLIST: '☰', // U+2630
17
+ SEARCH: '/', // ASCII
18
+ HELP: '?', // ASCII
19
+ // Actions
20
+ DOWNLOAD: '↓', // U+2193
21
+ QUIT: '×', // U+00D7
22
+ RESUME: '⟳', // U+27F3
23
+ BG_PLAY: '○', // U+25CB
24
+ // Status
25
+ VOLUME: '♪', // U+266A
26
+ };
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@involvex/youtube-music-cli",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "description": "- A Commandline music player for youtube-music",
5
5
  "repository": {
6
6
  "type": "git",