@involvex/youtube-music-cli 0.0.5 → 0.0.6

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.
@@ -0,0 +1,9 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: [involvex]
4
+ custom:
5
+ [
6
+ 'https://buymeacoffee.com/involvex',
7
+ 'https://paypal.me/involvex',
8
+ 'https://rewards.bing.com/welcome?rh=14525F68&ref=rafsrchae&form=ML2XE3&OCID=ML2XE3&PUBL=RewardsDO&CREA=ML2XE3',
9
+ ]
package/CHANGELOG.md ADDED
@@ -0,0 +1,69 @@
1
+ ## [0.0.6](https://github.com/involvex/youtube-music-cli/compare/v0.0.5...v0.0.6) (2026-02-18)
2
+
3
+ ### Features
4
+
5
+ - **ui:** add playlist creation and artist playback features ([0f50fd2](https://github.com/involvex/youtube-music-cli/commit/0f50fd2eeda0d340aee26b8fa2f7e5f2356d8042))
6
+
7
+ ## [0.0.5](https://github.com/involvex/youtube-music-cli/compare/v0.0.4...v0.0.5) (2026-02-18)
8
+
9
+ ## [0.0.4](https://github.com/involvex/youtube-music-cli/compare/v0.0.3...v0.0.4) (2026-02-18)
10
+
11
+ ### Bug Fixes
12
+
13
+ - resolve three bugs - discord rpc, search history key, resume ([90c5306](https://github.com/involvex/youtube-music-cli/commit/90c530698f08c355e11d31048844b2e1d6a312ef))
14
+ - **youtube:** guard suggestions parsing errors ([aca832e](https://github.com/involvex/youtube-music-cli/commit/aca832e27bb244f8b42b33060f2640f3cc003f7f))
15
+
16
+ ### Features
17
+
18
+ - **assets:** add new icons and images ([a0d6558](https://github.com/involvex/youtube-music-cli/commit/a0d6558ed2fd42b470e7123eaac5ce0c602db051))
19
+
20
+ ## [0.0.3](https://github.com/involvex/youtube-music-cli/compare/v0.0.2...v0.0.3) (2026-02-18)
21
+
22
+ ### Features
23
+
24
+ - add playback speed control, new themes, and notifications ([426360a](https://github.com/involvex/youtube-music-cli/commit/426360adfde0a19d7cf9706371f5bc15a4e7640b))
25
+
26
+ ## [0.0.2](https://github.com/involvex/youtube-music-cli/compare/v0.0.1...v0.0.2) (2026-02-18)
27
+
28
+ ## [0.0.1](https://github.com/involvex/youtube-music-cli/compare/32798e7dd129656b9786fc435466203a0c913705...v0.0.1) (2026-02-18)
29
+
30
+ ### Bug Fixes
31
+
32
+ - **api:** resolve 404 error during search and improve reliability ([c26f80f](https://github.com/involvex/youtube-music-cli/commit/c26f80f3c7f4de075393da7cc378aada8809604a))
33
+ - **api:** resolve search runtime error and integrate real streaming ([3c8066c](https://github.com/involvex/youtube-music-cli/commit/3c8066c37386ed6b2d50249cbeec4b30e012960d))
34
+ - clear queue when playing from search results to match indices ([272690d](https://github.com/involvex/youtube-music-cli/commit/272690d2f1dd0f81fecfdad4e916f4a6f42392a2))
35
+ - **cli:** resolve Ink crash, prevent double instances, and add features ([2dfa274](https://github.com/involvex/youtube-music-cli/commit/2dfa2747a1729037e3f88656579042892aec0b26))
36
+ - **cli:** resolve search input issues, terminal auto-scrolling, and UI duplication ([f3898ad](https://github.com/involvex/youtube-music-cli/commit/f3898adde09d244e6705d970ab2c3af75cd7aec7))
37
+ - **cli:** resolve search trigger and improve UI stability ([ccce4b1](https://github.com/involvex/youtube-music-cli/commit/ccce4b1a354432f04e771a7ddd957bd7ec5b8e6d))
38
+ - **cli:** resolve terminal flooding, fix search selection, and modernize React imports ([1bdfae3](https://github.com/involvex/youtube-music-cli/commit/1bdfae307ee45cafe722590ed64c5c1fd22322ae))
39
+ - **hooks:** resolve memory leak in useKeyboard hook ([94f4d39](https://github.com/involvex/youtube-music-cli/commit/94f4d3974a4ad0a5e609f5cc3003933978fd9008))
40
+ - **lint:** disable no-explicit-any for react-hooks plugin in eslint.config.ts ([7a54c2a](https://github.com/involvex/youtube-music-cli/commit/7a54c2ab04dd6dfbfcc80a3c8ec86fdcb66c05c8))
41
+ - **lint:** resolve react-hooks/exhaustive-deps error and fix hook bugs ([19cf971](https://github.com/involvex/youtube-music-cli/commit/19cf9712b049e494fba1b86e5e08f37c5f24c91e))
42
+ - **security:** implement URL sanitization and resolve linting errors ([adb1d8f](https://github.com/involvex/youtube-music-cli/commit/adb1d8fa02830d470c14912a0c1155441224ec01))
43
+ - simplify VOLUME_UP and VOLUME_DOWN keybindings ([571b136](https://github.com/involvex/youtube-music-cli/commit/571b136bfefefc122e9da82ce4e615d3d576b9e3))
44
+ - **ui:** refine help display, fix help nav, and update key hints ([adb8afb](https://github.com/involvex/youtube-music-cli/commit/adb8afbba95c1495431502d9049e67bac10295a4))
45
+
46
+ ### Features
47
+
48
+ - add .gemini agent config for CLI UI design ([e27269e](https://github.com/involvex/youtube-music-cli/commit/e27269e88bd73a23bf668ef1374f4525d06a69bd))
49
+ - add @distube/ytdl-core dependency for YouTube downloading ([da5a8a7](https://github.com/involvex/youtube-music-cli/commit/da5a8a70cf4d65ff90dff3e7956c5d4e72740b7d))
50
+ - add compile script for building standalone executable ([ce3d731](https://github.com/involvex/youtube-music-cli/commit/ce3d7314e42f852b4c05eb8436463a1572ef6ed0))
51
+ - add config screen with keyboard navigation ([f9566cd](https://github.com/involvex/youtube-music-cli/commit/f9566cdd4f4818b158ecc5c45dae73b2af544f44))
52
+ - add Help component for keyboard shortcuts display ([32798e7](https://github.com/involvex/youtube-music-cli/commit/32798e7dd129656b9786fc435466203a0c913705))
53
+ - add player state persistence and npm publish workflow ([df7e5ce](https://github.com/involvex/youtube-music-cli/commit/df7e5ce10d13406ec0e284ff91ab8d1c38c23084))
54
+ - add plugin system API docs, templates, and context provider ([06392dc](https://github.com/involvex/youtube-music-cli/commit/06392dc95253fe90ddfc77d0bfbb0455b517fff6))
55
+ - add plugin system infrastructure and improve navigation ([626ada6](https://github.com/involvex/youtube-music-cli/commit/626ada679d530b66733ad58a553e72bd7b94755a))
56
+ - add react-devtools-core dependency and bun build script ([44a2a90](https://github.com/involvex/youtube-music-cli/commit/44a2a90a6c738d3bc4637bccf0d8513b2967c363))
57
+ - add ShortcutsBar component and prevent duplicate track playback ([af1fe32](https://github.com/involvex/youtube-music-cli/commit/af1fe32c42a49ba3ac12bb5e081dcfb31b5771c6))
58
+ - **cli:** fix search typing, add headless mode and control commands ([506653d](https://github.com/involvex/youtube-music-cli/commit/506653d5e1e7098a87002f039a7051f7f8e7ce76))
59
+ - **layouts:** optimize components with React.memo and responsive padding ([924991c](https://github.com/involvex/youtube-music-cli/commit/924991cc85ae7c96f38760ecce139e58175af8d1))
60
+ - migrate audio player from play-sound to mpv ([ac1aeb3](https://github.com/involvex/youtube-music-cli/commit/ac1aeb3652ec49a5e74c0519d41055e715eb4499))
61
+ - move PlayerControls to MainLayout for global key bindings ([4190bf0](https://github.com/involvex/youtube-music-cli/commit/4190bf0393a4f1c8602d0603a953b36d4351d89d))
62
+ - **player:** Add IPC-based player event monitoring for mpv ([5a40ab0](https://github.com/involvex/youtube-music-cli/commit/5a40ab06679adf6569914075440dce833f1c1226))
63
+ - **ui:** implement responsive layout, adjustable search limit, and fix search navigation ([78150d6](https://github.com/involvex/youtube-music-cli/commit/78150d675746879cce2d329333009cd8cfe8cc4d))
64
+
65
+ ### Performance Improvements
66
+
67
+ - **cli:** optimize UI rendering and fix search result selection ([17e9f7e](https://github.com/involvex/youtube-music-cli/commit/17e9f7efc07980852fa7b1c45e002b2abd93cfd8))
68
+ - memoize view components and remove redundant useEffect in SearchResults ([9b90902](https://github.com/involvex/youtube-music-cli/commit/9b90902d27416df0ca5bd2854e5afb46ab24b128))
69
+ - throttle progress updates and fix exit handler stale closure ([162b732](https://github.com/involvex/youtube-music-cli/commit/162b73292fb21a3eae2cf998a8a02dbb0adc5446))
@@ -16,7 +16,10 @@ export default function Suggestions() {
16
16
  const [selectedIndex, setSelectedIndex] = useState(0);
17
17
  useEffect(() => {
18
18
  if (playerState.currentTrack?.videoId) {
19
- getSuggestions(playerState.currentTrack.videoId).then(setSuggestions);
19
+ getSuggestions(playerState.currentTrack.videoId).then(tracks => {
20
+ setSuggestions(tracks);
21
+ setSelectedIndex(0);
22
+ });
20
23
  }
21
24
  }, [playerState.currentTrack?.videoId, getSuggestions]);
22
25
  const navigateUp = useCallback(() => {
@@ -2,10 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Playlist list component
3
3
  import { Box, Text } from 'ink';
4
4
  import { useTheme } from "../../hooks/useTheme.js";
5
- import { getConfigService } from "../../services/config/config.service.js";
5
+ import { usePlaylist } from "../../hooks/usePlaylist.js";
6
+ import { useKeyBinding } from "../../hooks/useKeyboard.js";
7
+ import { KEYBINDINGS } from "../../utils/constants.js";
8
+ import { useState, useCallback } from 'react';
6
9
  export default function PlaylistList() {
7
10
  const { theme } = useTheme();
8
- const config = getConfigService();
9
- const playlists = config.get('playlists');
10
- return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Box, { borderStyle: "double", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Playlists" }) }), playlists.length === 0 ? (_jsx(Text, { color: theme.colors.dim, children: "No playlists yet" })) : (playlists.map((playlist, index) => (_jsxs(Box, { paddingX: 1, children: [_jsxs(Text, { color: theme.colors.primary, children: [index + 1, "."] }), _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: playlist.name }), _jsxs(Text, { color: theme.colors.dim, children: [_jsx(Text, { children: " " }), "(", playlist.tracks?.length || 0, " tracks)"] })] }, playlist.playlistId || index)))), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.colors.dim, children: ["Press ", _jsx(Text, { color: theme.colors.text, children: "c" }), " to create playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Esc" }), " to go back"] }) })] }));
11
+ const { playlists, createPlaylist } = usePlaylist();
12
+ const [lastCreated, setLastCreated] = useState(null);
13
+ const handleCreate = useCallback(() => {
14
+ const name = `Playlist ${playlists.length + 1}`;
15
+ createPlaylist(name);
16
+ setLastCreated(name);
17
+ }, [playlists.length, createPlaylist]);
18
+ useKeyBinding(KEYBINDINGS.CREATE_PLAYLIST, handleCreate);
19
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Box, { borderStyle: "double", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Playlists" }) }), playlists.length === 0 ? (_jsx(Text, { color: theme.colors.dim, children: "No playlists yet" })) : (playlists.map((playlist, index) => (_jsxs(Box, { paddingX: 1, children: [_jsxs(Text, { color: theme.colors.primary, children: [index + 1, "."] }), _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: playlist.name }), _jsxs(Text, { color: theme.colors.dim, children: [_jsx(Text, { children: " " }), "(", playlist.tracks?.length || 0, " tracks)"] })] }, playlist.playlistId || index)))), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.dim, children: ["Press ", _jsx(Text, { color: theme.colors.text, children: "c" }), " to create playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Esc" }), " to go back"] }), lastCreated && (_jsxs(Text, { color: theme.colors.accent, children: [" Created ", lastCreated] }))] })] }));
11
20
  }
@@ -11,13 +11,15 @@ import { truncate } from "../../utils/format.js";
11
11
  import { useCallback, useRef, useEffect } from 'react';
12
12
  import { logger } from "../../services/logger/logger.service.js";
13
13
  import { useTerminalSize } from "../../hooks/useTerminalSize.js";
14
+ import { getMusicService } from "../../services/youtube-music/api.js";
14
15
  // Generate unique component instance ID
15
16
  let instanceCounter = 0;
16
17
  function SearchResults({ results, selectedIndex, isActive = true }) {
17
18
  const { theme } = useTheme();
18
19
  const { dispatch } = useNavigation();
19
- const { play } = usePlayer();
20
+ const { play, dispatch: playerDispatch } = usePlayer();
20
21
  const { columns } = useTerminalSize();
22
+ const musicService = getMusicService();
21
23
  // Track component instance and last action time for debouncing
22
24
  const instanceIdRef = useRef(++instanceCounter);
23
25
  const lastSelectTime = useRef(0);
@@ -45,7 +47,7 @@ function SearchResults({ results, selectedIndex, isActive = true }) {
45
47
  }
46
48
  }, [selectedIndex, results.length, dispatch, isActive]);
47
49
  // Play selected result
48
- const playSelected = useCallback(() => {
50
+ const playSelected = useCallback(async () => {
49
51
  logger.debug('SearchResults', 'playSelected called', {
50
52
  isActive,
51
53
  selectedIndex,
@@ -62,12 +64,43 @@ function SearchResults({ results, selectedIndex, isActive = true }) {
62
64
  // Clear queue when playing from search results to ensure indices match
63
65
  play(selected.data, { clearQueue: true });
64
66
  }
67
+ else if (selected && selected.type === 'artist') {
68
+ const artistName = 'name' in selected.data ? selected.data.name : '';
69
+ if (!artistName) {
70
+ logger.warn('SearchResults', 'Artist name missing, cannot search songs');
71
+ return;
72
+ }
73
+ try {
74
+ const response = await musicService.search(artistName, {
75
+ type: 'songs',
76
+ limit: 20,
77
+ });
78
+ const tracks = response.results
79
+ .filter(result => result.type === 'song')
80
+ .map(result => result.data);
81
+ if (tracks.length === 0) {
82
+ logger.warn('SearchResults', 'No songs found for artist', {
83
+ artistName,
84
+ });
85
+ return;
86
+ }
87
+ // Replace queue with artist songs and start playback
88
+ playerDispatch({ category: 'CLEAR_QUEUE' });
89
+ playerDispatch({ category: 'SET_QUEUE', queue: tracks });
90
+ playerDispatch({ category: 'PLAY', track: tracks[0] });
91
+ }
92
+ catch (error) {
93
+ logger.error('SearchResults', 'Failed to play artist songs', {
94
+ error,
95
+ });
96
+ }
97
+ }
65
98
  else {
66
- logger.warn('SearchResults', 'Selected item is not a song', {
99
+ logger.warn('SearchResults', 'Selected item is not playable', {
67
100
  type: selected?.type,
68
101
  });
69
102
  }
70
- }, [selectedIndex, results, play, isActive]);
103
+ }, [selectedIndex, results, play, isActive, musicService, playerDispatch]);
71
104
  // Play selected result handler (memoized to prevent duplicate registrations)
72
105
  const handleSelect = useCallback(() => {
73
106
  const now = Date.now();
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@involvex/youtube-music-cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "- A Commandline music player for youtube-music",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,7 +15,11 @@
15
15
  "ymc": "dist/source/cli.js"
16
16
  },
17
17
  "files": [
18
- "dist"
18
+ "dist",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "LICENSE",
22
+ ".github/FUNDING.yml"
19
23
  ],
20
24
  "scripts": {
21
25
  "prebuild": "bun run format && bun run lint:fix && bun run typecheck",
@@ -29,27 +33,24 @@
29
33
  "lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern dist",
30
34
  "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix --ignore-pattern dist",
31
35
  "start": "bun run dist/source/cli.js",
32
- "test": "prettier --check . && xo && ava",
36
+ "test": "prettier --check . && bun run lint && ava",
33
37
  "typecheck": "tsc --noEmit",
34
38
  "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
35
39
  "clean": "rimraf dist",
36
40
  "release": "powershell -File scripts/release.ps1"
37
41
  },
38
42
  "xo": {
39
- "extends": "xo-react",
43
+ "react": true,
40
44
  "prettier": true,
41
45
  "rules": {
42
46
  "react/prop-types": "off"
43
- }
47
+ },
48
+ "semicolon": true
44
49
  },
45
50
  "prettier": "@vdemedes/prettier-config",
46
51
  "ava": {
47
- "extensions": {
48
- "ts": "module",
49
- "tsx": "module"
50
- },
51
- "nodeArguments": [
52
- "--loader=ts-node/esm"
52
+ "files": [
53
+ "tests/**/*.js"
53
54
  ]
54
55
  },
55
56
  "dependencies": {
package/dist/test.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};
package/dist/test.js DELETED
@@ -1,13 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { render } from 'ink-testing-library';
3
- import App from './source/app.js';
4
- import chalk from 'chalk';
5
- import test from 'ava';
6
- test('greet unknown user', t => {
7
- const { lastFrame } = render(_jsx(App, { flags: { help: true } }));
8
- t.is(lastFrame(), `Hello, ${chalk.green('Stranger')}`);
9
- });
10
- test('greet user with a name', t => {
11
- const { lastFrame } = render(_jsx(App, { flags: { version: true } }));
12
- t.is(lastFrame(), `Hello, ${chalk.green('Jane')}`);
13
- });