@involvex/youtube-music-cli 0.0.5 → 0.0.8
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/.github/FUNDING.yml +9 -0
- package/CHANGELOG.md +86 -0
- package/dist/source/components/common/Help.js +1 -1
- package/dist/source/components/common/ShortcutsBar.js +1 -1
- package/dist/source/components/layouts/MainLayout.js +4 -0
- package/dist/source/components/player/Suggestions.js +4 -1
- package/dist/source/components/playlist/PlaylistList.js +75 -4
- package/dist/source/components/search/SearchBar.js +2 -0
- package/dist/source/components/search/SearchResults.js +37 -4
- package/dist/source/hooks/useKeyboard.js +9 -0
- package/dist/source/hooks/useKeyboardBlocker.d.ts +13 -0
- package/dist/source/hooks/useKeyboardBlocker.js +45 -0
- package/dist/source/hooks/usePlaylist.d.ts +1 -0
- package/dist/source/hooks/usePlaylist.js +8 -0
- package/dist/source/main.js +2 -1
- package/dist/source/utils/constants.d.ts +2 -1
- package/dist/source/utils/constants.js +2 -1
- package/dist/youtube-music-cli.exe +0 -0
- package/package.json +13 -11
- package/dist/test.d.ts +0 -1
- package/dist/test.js +0 -13
|
@@ -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,86 @@
|
|
|
1
|
+
## [0.0.8](https://github.com/involvex/youtube-music-cli/compare/v0.0.7...v0.0.8) (2026-02-18)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **ui:** add keyboard blocker to search bar ([ee533fe](https://github.com/involvex/youtube-music-cli/commit/ee533fe716a203d432170e36149389f5bd48d687))
|
|
6
|
+
|
|
7
|
+
## [0.0.7](https://github.com/involvex/youtube-music-cli/compare/v0.0.6...v0.0.7) (2026-02-18)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **ui:** add plugins and enhance playlist management ([83a043c](https://github.com/involvex/youtube-music-cli/commit/83a043c0956da7023ea198468db8bbc2ce3acb0d))
|
|
12
|
+
|
|
13
|
+
### BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
- **ui:** The keybinding for playlists has changed from 'p' to
|
|
16
|
+
'shift+p' to accommodate the new plugins feature.
|
|
17
|
+
|
|
18
|
+
## [0.0.6](https://github.com/involvex/youtube-music-cli/compare/v0.0.5...v0.0.6) (2026-02-18)
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- **ui:** add playlist creation and artist playback features ([0f50fd2](https://github.com/involvex/youtube-music-cli/commit/0f50fd2eeda0d340aee26b8fa2f7e5f2356d8042))
|
|
23
|
+
|
|
24
|
+
## [0.0.5](https://github.com/involvex/youtube-music-cli/compare/v0.0.4...v0.0.5) (2026-02-18)
|
|
25
|
+
|
|
26
|
+
## [0.0.4](https://github.com/involvex/youtube-music-cli/compare/v0.0.3...v0.0.4) (2026-02-18)
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
- resolve three bugs - discord rpc, search history key, resume ([90c5306](https://github.com/involvex/youtube-music-cli/commit/90c530698f08c355e11d31048844b2e1d6a312ef))
|
|
31
|
+
- **youtube:** guard suggestions parsing errors ([aca832e](https://github.com/involvex/youtube-music-cli/commit/aca832e27bb244f8b42b33060f2640f3cc003f7f))
|
|
32
|
+
|
|
33
|
+
### Features
|
|
34
|
+
|
|
35
|
+
- **assets:** add new icons and images ([a0d6558](https://github.com/involvex/youtube-music-cli/commit/a0d6558ed2fd42b470e7123eaac5ce0c602db051))
|
|
36
|
+
|
|
37
|
+
## [0.0.3](https://github.com/involvex/youtube-music-cli/compare/v0.0.2...v0.0.3) (2026-02-18)
|
|
38
|
+
|
|
39
|
+
### Features
|
|
40
|
+
|
|
41
|
+
- add playback speed control, new themes, and notifications ([426360a](https://github.com/involvex/youtube-music-cli/commit/426360adfde0a19d7cf9706371f5bc15a4e7640b))
|
|
42
|
+
|
|
43
|
+
## [0.0.2](https://github.com/involvex/youtube-music-cli/compare/v0.0.1...v0.0.2) (2026-02-18)
|
|
44
|
+
|
|
45
|
+
## [0.0.1](https://github.com/involvex/youtube-music-cli/compare/32798e7dd129656b9786fc435466203a0c913705...v0.0.1) (2026-02-18)
|
|
46
|
+
|
|
47
|
+
### Bug Fixes
|
|
48
|
+
|
|
49
|
+
- **api:** resolve 404 error during search and improve reliability ([c26f80f](https://github.com/involvex/youtube-music-cli/commit/c26f80f3c7f4de075393da7cc378aada8809604a))
|
|
50
|
+
- **api:** resolve search runtime error and integrate real streaming ([3c8066c](https://github.com/involvex/youtube-music-cli/commit/3c8066c37386ed6b2d50249cbeec4b30e012960d))
|
|
51
|
+
- clear queue when playing from search results to match indices ([272690d](https://github.com/involvex/youtube-music-cli/commit/272690d2f1dd0f81fecfdad4e916f4a6f42392a2))
|
|
52
|
+
- **cli:** resolve Ink crash, prevent double instances, and add features ([2dfa274](https://github.com/involvex/youtube-music-cli/commit/2dfa2747a1729037e3f88656579042892aec0b26))
|
|
53
|
+
- **cli:** resolve search input issues, terminal auto-scrolling, and UI duplication ([f3898ad](https://github.com/involvex/youtube-music-cli/commit/f3898adde09d244e6705d970ab2c3af75cd7aec7))
|
|
54
|
+
- **cli:** resolve search trigger and improve UI stability ([ccce4b1](https://github.com/involvex/youtube-music-cli/commit/ccce4b1a354432f04e771a7ddd957bd7ec5b8e6d))
|
|
55
|
+
- **cli:** resolve terminal flooding, fix search selection, and modernize React imports ([1bdfae3](https://github.com/involvex/youtube-music-cli/commit/1bdfae307ee45cafe722590ed64c5c1fd22322ae))
|
|
56
|
+
- **hooks:** resolve memory leak in useKeyboard hook ([94f4d39](https://github.com/involvex/youtube-music-cli/commit/94f4d3974a4ad0a5e609f5cc3003933978fd9008))
|
|
57
|
+
- **lint:** disable no-explicit-any for react-hooks plugin in eslint.config.ts ([7a54c2a](https://github.com/involvex/youtube-music-cli/commit/7a54c2ab04dd6dfbfcc80a3c8ec86fdcb66c05c8))
|
|
58
|
+
- **lint:** resolve react-hooks/exhaustive-deps error and fix hook bugs ([19cf971](https://github.com/involvex/youtube-music-cli/commit/19cf9712b049e494fba1b86e5e08f37c5f24c91e))
|
|
59
|
+
- **security:** implement URL sanitization and resolve linting errors ([adb1d8f](https://github.com/involvex/youtube-music-cli/commit/adb1d8fa02830d470c14912a0c1155441224ec01))
|
|
60
|
+
- simplify VOLUME_UP and VOLUME_DOWN keybindings ([571b136](https://github.com/involvex/youtube-music-cli/commit/571b136bfefefc122e9da82ce4e615d3d576b9e3))
|
|
61
|
+
- **ui:** refine help display, fix help nav, and update key hints ([adb8afb](https://github.com/involvex/youtube-music-cli/commit/adb8afbba95c1495431502d9049e67bac10295a4))
|
|
62
|
+
|
|
63
|
+
### Features
|
|
64
|
+
|
|
65
|
+
- add .gemini agent config for CLI UI design ([e27269e](https://github.com/involvex/youtube-music-cli/commit/e27269e88bd73a23bf668ef1374f4525d06a69bd))
|
|
66
|
+
- add @distube/ytdl-core dependency for YouTube downloading ([da5a8a7](https://github.com/involvex/youtube-music-cli/commit/da5a8a70cf4d65ff90dff3e7956c5d4e72740b7d))
|
|
67
|
+
- add compile script for building standalone executable ([ce3d731](https://github.com/involvex/youtube-music-cli/commit/ce3d7314e42f852b4c05eb8436463a1572ef6ed0))
|
|
68
|
+
- add config screen with keyboard navigation ([f9566cd](https://github.com/involvex/youtube-music-cli/commit/f9566cdd4f4818b158ecc5c45dae73b2af544f44))
|
|
69
|
+
- add Help component for keyboard shortcuts display ([32798e7](https://github.com/involvex/youtube-music-cli/commit/32798e7dd129656b9786fc435466203a0c913705))
|
|
70
|
+
- add player state persistence and npm publish workflow ([df7e5ce](https://github.com/involvex/youtube-music-cli/commit/df7e5ce10d13406ec0e284ff91ab8d1c38c23084))
|
|
71
|
+
- add plugin system API docs, templates, and context provider ([06392dc](https://github.com/involvex/youtube-music-cli/commit/06392dc95253fe90ddfc77d0bfbb0455b517fff6))
|
|
72
|
+
- add plugin system infrastructure and improve navigation ([626ada6](https://github.com/involvex/youtube-music-cli/commit/626ada679d530b66733ad58a553e72bd7b94755a))
|
|
73
|
+
- add react-devtools-core dependency and bun build script ([44a2a90](https://github.com/involvex/youtube-music-cli/commit/44a2a90a6c738d3bc4637bccf0d8513b2967c363))
|
|
74
|
+
- add ShortcutsBar component and prevent duplicate track playback ([af1fe32](https://github.com/involvex/youtube-music-cli/commit/af1fe32c42a49ba3ac12bb5e081dcfb31b5771c6))
|
|
75
|
+
- **cli:** fix search typing, add headless mode and control commands ([506653d](https://github.com/involvex/youtube-music-cli/commit/506653d5e1e7098a87002f039a7051f7f8e7ce76))
|
|
76
|
+
- **layouts:** optimize components with React.memo and responsive padding ([924991c](https://github.com/involvex/youtube-music-cli/commit/924991cc85ae7c96f38760ecce139e58175af8d1))
|
|
77
|
+
- migrate audio player from play-sound to mpv ([ac1aeb3](https://github.com/involvex/youtube-music-cli/commit/ac1aeb3652ec49a5e74c0519d41055e715eb4499))
|
|
78
|
+
- move PlayerControls to MainLayout for global key bindings ([4190bf0](https://github.com/involvex/youtube-music-cli/commit/4190bf0393a4f1c8602d0603a953b36d4351d89d))
|
|
79
|
+
- **player:** Add IPC-based player event monitoring for mpv ([5a40ab0](https://github.com/involvex/youtube-music-cli/commit/5a40ab06679adf6569914075440dce833f1c1226))
|
|
80
|
+
- **ui:** implement responsive layout, adjustable search limit, and fix search navigation ([78150d6](https://github.com/involvex/youtube-music-cli/commit/78150d675746879cce2d329333009cd8cfe8cc4d))
|
|
81
|
+
|
|
82
|
+
### Performance Improvements
|
|
83
|
+
|
|
84
|
+
- **cli:** optimize UI rendering and fix search result selection ([17e9f7e](https://github.com/involvex/youtube-music-cli/commit/17e9f7efc07980852fa7b1c45e002b2abd93cfd8))
|
|
85
|
+
- memoize view components and remove redundant useEffect in SearchResults ([9b90902](https://github.com/involvex/youtube-music-cli/commit/9b90902d27416df0ca5bd2854e5afb46ab24b128))
|
|
86
|
+
- throttle progress updates and fix exit handler stale closure ([162b732](https://github.com/involvex/youtube-music-cli/commit/162b73292fb21a3eae2cf998a8a02dbb0adc5446))
|
|
@@ -6,5 +6,5 @@ import { useNavigation } from "../../hooks/useNavigation.js";
|
|
|
6
6
|
export default function Help() {
|
|
7
7
|
const { theme } = useTheme();
|
|
8
8
|
const { dispatch: _dispatch } = useNavigation();
|
|
9
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, padding: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Keyboard Shortcuts" }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Global" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "q / Esc" }), " - Quit", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "?" }), " - Help", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "/" }), " - Search", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, padding: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Keyboard Shortcuts" }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Global" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "q / Esc" }), " - Quit", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "?" }), " - Help", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "/" }), " - Search", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Shift+P" }), " - Playlists", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "p" }), " - Plugins", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "g" }), " - Suggestions", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "," }), " - Settings"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Player" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Space" }), " - Play/Pause", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "n" }), " - Next", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "b" }), " - Previous", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "=" }), " - Volume Up", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "-" }), " - Volume Down", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "s" }), " - Toggle Shuffle", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "r" }), " - Toggle Repeat"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Navigation" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Up" }), " /", _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: "k" }), " - Move Up", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Down" }), " /", _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: "j" }), " - Move Down", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Enter" }), " - Select", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Esc" }), " - Go Back"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Search" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Tab" }), " - Switch Search Type", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Esc" }), " - Clear Search", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "[ / ]" }), " - Results Limit"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Playlist" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "a" }), " - Add to Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "d" }), " - Remove from Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "c" }), " - Create Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "D" }), " - Delete Playlist"] }) }), _jsxs(Text, { color: theme.colors.dim, children: ["Press ", _jsx(Text, { color: theme.colors.text, children: "Esc" }), " or", ' ', _jsx(Text, { color: theme.colors.text, children: "?" }), " to close"] })] })] }));
|
|
10
10
|
}
|
|
@@ -31,5 +31,5 @@ export default function ShortcutsBar() {
|
|
|
31
31
|
useKeyBinding(KEYBINDINGS.VOLUME_FINE_UP, volumeFineUp);
|
|
32
32
|
useKeyBinding(KEYBINDINGS.VOLUME_FINE_DOWN, volumeFineDown);
|
|
33
33
|
useKeyBinding(KEYBINDINGS.SETTINGS, goConfig);
|
|
34
|
-
return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: ["Shortcuts: ", _jsx(Text, { color: theme.colors.text, children: "Space" }), " Play/Pause |", ' ', _jsx(Text, { color: theme.colors.text, children: "
|
|
34
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: ["Shortcuts: ", _jsx(Text, { color: theme.colors.text, children: "Space" }), " Play/Pause |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u2192" }), " Next |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u2190" }), " Previous |", ' ', _jsx(Text, { color: theme.colors.text, children: "Shift+P" }), " Playlists |", ' ', _jsx(Text, { color: theme.colors.text, children: "p" }), " Plugins |", ' ', _jsx(Text, { color: theme.colors.text, children: "/" }), " Search |", ' ', _jsx(Text, { color: theme.colors.text, children: "," }), " Settings |", ' ', _jsx(Text, { color: theme.colors.text, children: "?" }), " Help |", ' ', _jsx(Text, { color: theme.colors.text, children: "q" }), " Quit"] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.dim, children: "[=/" }), "-", _jsx(Text, { color: theme.colors.dim, children: "]" }), " Vol:", ' ', _jsxs(Text, { color: theme.colors.primary, children: [playerState.volume, "%"] })] })] }));
|
|
35
35
|
}
|
|
@@ -39,6 +39,9 @@ function MainLayout() {
|
|
|
39
39
|
const goToSuggestions = useCallback(() => {
|
|
40
40
|
dispatch({ category: 'NAVIGATE', view: VIEW.SUGGESTIONS });
|
|
41
41
|
}, [dispatch]);
|
|
42
|
+
const goToPlugins = useCallback(() => {
|
|
43
|
+
dispatch({ category: 'NAVIGATE', view: VIEW.PLUGINS });
|
|
44
|
+
}, [dispatch]);
|
|
42
45
|
const goToSettings = useCallback(() => {
|
|
43
46
|
dispatch({ category: 'NAVIGATE', view: VIEW.SETTINGS });
|
|
44
47
|
}, [dispatch]);
|
|
@@ -69,6 +72,7 @@ function MainLayout() {
|
|
|
69
72
|
useKeyBinding(KEYBINDINGS.QUIT, handleQuit);
|
|
70
73
|
useKeyBinding(KEYBINDINGS.SEARCH, goToSearch);
|
|
71
74
|
useKeyBinding(KEYBINDINGS.PLAYLISTS, goToPlaylists);
|
|
75
|
+
useKeyBinding(KEYBINDINGS.PLUGINS, goToPlugins);
|
|
72
76
|
useKeyBinding(KEYBINDINGS.SUGGESTIONS, goToSuggestions);
|
|
73
77
|
useKeyBinding(KEYBINDINGS.SETTINGS, goToSettings);
|
|
74
78
|
useKeyBinding(KEYBINDINGS.HELP, goToHelp);
|
|
@@ -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(
|
|
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(() => {
|
|
@@ -1,11 +1,82 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Playlist list component
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
import { useCallback, useState } from 'react';
|
|
6
|
+
import { useNavigation } from "../../hooks/useNavigation.js";
|
|
7
|
+
import { useKeyBinding } from "../../hooks/useKeyboard.js";
|
|
8
|
+
import { usePlayer } from "../../hooks/usePlayer.js";
|
|
9
|
+
import { usePlaylist } from "../../hooks/usePlaylist.js";
|
|
4
10
|
import { useTheme } from "../../hooks/useTheme.js";
|
|
5
|
-
import {
|
|
11
|
+
import { useKeyboardBlocker } from "../../hooks/useKeyboardBlocker.js";
|
|
12
|
+
import { KEYBINDINGS } from "../../utils/constants.js";
|
|
6
13
|
export default function PlaylistList() {
|
|
7
14
|
const { theme } = useTheme();
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
15
|
+
const { play, setQueue } = usePlayer();
|
|
16
|
+
const { dispatch } = useNavigation();
|
|
17
|
+
const { playlists, createPlaylist, renamePlaylist } = usePlaylist();
|
|
18
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
19
|
+
const [lastCreated, setLastCreated] = useState(null);
|
|
20
|
+
const [renamingPlaylistId, setRenamingPlaylistId] = useState(null);
|
|
21
|
+
const [renameValue, setRenameValue] = useState('');
|
|
22
|
+
useKeyboardBlocker(renamingPlaylistId !== null);
|
|
23
|
+
const handleCreate = useCallback(() => {
|
|
24
|
+
const name = `Playlist ${playlists.length + 1}`;
|
|
25
|
+
createPlaylist(name);
|
|
26
|
+
setLastCreated(name);
|
|
27
|
+
setSelectedIndex(playlists.length);
|
|
28
|
+
}, [createPlaylist, playlists.length]);
|
|
29
|
+
const navigateUp = useCallback(() => {
|
|
30
|
+
setSelectedIndex(prev => Math.max(0, prev - 1));
|
|
31
|
+
}, []);
|
|
32
|
+
const navigateDown = useCallback(() => {
|
|
33
|
+
setSelectedIndex(prev => Math.min(playlists.length === 0 ? 0 : playlists.length - 1, prev + 1));
|
|
34
|
+
}, [playlists.length]);
|
|
35
|
+
const startPlaylist = useCallback(() => {
|
|
36
|
+
if (renamingPlaylistId)
|
|
37
|
+
return;
|
|
38
|
+
const playlist = playlists[selectedIndex];
|
|
39
|
+
if (!playlist || playlist.tracks.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
setQueue([...playlist.tracks]);
|
|
42
|
+
const firstTrack = playlist.tracks[0];
|
|
43
|
+
if (!firstTrack)
|
|
44
|
+
return;
|
|
45
|
+
play(firstTrack);
|
|
46
|
+
}, [play, playlists, selectedIndex, renamingPlaylistId, setQueue]);
|
|
47
|
+
const handleRename = useCallback(() => {
|
|
48
|
+
const playlist = playlists[selectedIndex];
|
|
49
|
+
if (!playlist)
|
|
50
|
+
return;
|
|
51
|
+
setRenamingPlaylistId(playlist.playlistId);
|
|
52
|
+
setRenameValue(playlist.name);
|
|
53
|
+
}, [playlists, selectedIndex]);
|
|
54
|
+
const handleRenameSubmit = useCallback((value) => {
|
|
55
|
+
if (!renamingPlaylistId)
|
|
56
|
+
return;
|
|
57
|
+
const trimmedValue = value.trim() || `Playlist ${selectedIndex + 1}`;
|
|
58
|
+
renamePlaylist(renamingPlaylistId, trimmedValue);
|
|
59
|
+
setRenamingPlaylistId(null);
|
|
60
|
+
setRenameValue('');
|
|
61
|
+
}, [renamePlaylist, renamingPlaylistId, selectedIndex]);
|
|
62
|
+
const handleBack = useCallback(() => {
|
|
63
|
+
if (renamingPlaylistId) {
|
|
64
|
+
setRenamingPlaylistId(null);
|
|
65
|
+
setRenameValue('');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
dispatch({ category: 'GO_BACK' });
|
|
69
|
+
}, [dispatch, renamingPlaylistId]);
|
|
70
|
+
useKeyBinding(KEYBINDINGS.UP, navigateUp);
|
|
71
|
+
useKeyBinding(KEYBINDINGS.DOWN, navigateDown);
|
|
72
|
+
useKeyBinding(KEYBINDINGS.SELECT, startPlaylist);
|
|
73
|
+
useKeyBinding(['r'], handleRename);
|
|
74
|
+
useKeyBinding(KEYBINDINGS.CREATE_PLAYLIST, handleCreate);
|
|
75
|
+
useKeyBinding(KEYBINDINGS.BACK, handleBack);
|
|
76
|
+
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) => {
|
|
77
|
+
const isSelected = index === selectedIndex;
|
|
78
|
+
const isRenaming = renamingPlaylistId === playlist.playlistId && isSelected;
|
|
79
|
+
const rowBackground = isSelected ? theme.colors.secondary : undefined;
|
|
80
|
+
return (_jsxs(Box, { paddingX: 1, backgroundColor: rowBackground, children: [_jsxs(Text, { color: isSelected ? theme.colors.background : theme.colors.primary, bold: isSelected, children: [index + 1, "."] }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", children: [isRenaming ? (_jsx(TextInput, { value: renameValue, onChange: setRenameValue, onSubmit: handleRenameSubmit, placeholder: "Playlist name", focus: true })) : (_jsx(Text, { color: isSelected ? theme.colors.background : theme.colors.text, bold: isSelected, children: playlist.name })), _jsx(Text, { color: theme.colors.dim, children: ` (${playlist.tracks.length} tracks)` })] })] }, playlist.playlistId));
|
|
81
|
+
})), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.dim, children: [_jsx(Text, { color: theme.colors.text, children: "Enter" }), " to play playlist |", ' ', _jsx(Text, { color: theme.colors.text, children: "r" }), " to rename |", ' ', _jsx(Text, { color: theme.colors.text, children: "c" }), " to create |", ' ', _jsx(Text, { color: theme.colors.text, children: "Esc" }), " to go back"] }), lastCreated && (_jsxs(Text, { color: theme.colors.accent, children: [" Created ", lastCreated] }))] })] }));
|
|
11
82
|
}
|
|
@@ -6,6 +6,7 @@ import React from 'react';
|
|
|
6
6
|
import { SEARCH_TYPE } from "../../utils/constants.js";
|
|
7
7
|
import { useTheme } from "../../hooks/useTheme.js";
|
|
8
8
|
import { useKeyBinding } from "../../hooks/useKeyboard.js";
|
|
9
|
+
import { useKeyboardBlocker } from "../../hooks/useKeyboardBlocker.js";
|
|
9
10
|
import { Box, Text } from 'ink';
|
|
10
11
|
import TextInput from 'ink-text-input';
|
|
11
12
|
import { getConfigService } from "../../services/config/config.service.js";
|
|
@@ -46,6 +47,7 @@ function SearchBar({ onInput, isActive = true }) {
|
|
|
46
47
|
}, [isActive, onInput]);
|
|
47
48
|
useKeyBinding(['tab'], cycleType);
|
|
48
49
|
useKeyBinding(['escape'], clearSearch);
|
|
50
|
+
useKeyboardBlocker(isActive);
|
|
49
51
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.secondary, padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.colors.dim, children: "Type: " }), searchTypes.map((type, index) => (_jsxs(Text, { color: navState.searchType === type
|
|
50
52
|
? theme.colors.primary
|
|
51
53
|
: theme.colors.dim, bold: navState.searchType === type, children: [type, index < searchTypes.length - 1 && ' '] }, type))), _jsx(Text, { color: theme.colors.dim, children: " (Tab to switch)" })] }), isActive && (_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.primary, children: "Search: " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: "Type to search...", focus: isActive })] })), !isActive && (_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.primary, children: "Search: " }), _jsx(Text, { color: theme.colors.dim, children: input || 'Type to search...' })] })), _jsx(Text, { color: theme.colors.dim, children: "Type to search, Enter to search, Tab to change type, Esc to clear" })] }));
|
|
@@ -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
|
|
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();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useCallback, useEffect } from 'react';
|
|
3
3
|
import { useInput } from 'ink';
|
|
4
4
|
import { logger } from "../services/logger/logger.service.js";
|
|
5
|
+
import { useKeyboardBlockContext } from "./useKeyboardBlocker.js";
|
|
5
6
|
// Global registry for key handlers
|
|
6
7
|
const registry = new Set();
|
|
7
8
|
/**
|
|
@@ -23,7 +24,13 @@ export function useKeyBinding(keys, handler) {
|
|
|
23
24
|
* This should be rendered once at the root of the app.
|
|
24
25
|
*/
|
|
25
26
|
export function KeyboardManager() {
|
|
27
|
+
const { blockCount } = useKeyboardBlockContext();
|
|
26
28
|
useInput((input, key) => {
|
|
29
|
+
if (blockCount > 0) {
|
|
30
|
+
// When keyboard input is blocked (e.g., within a focused text input),
|
|
31
|
+
// we deliberately skip executing global shortcuts.
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
27
34
|
// Debug logging for key presses (helps diagnose binding issues)
|
|
28
35
|
if (input || key.ctrl || key.meta || key.shift) {
|
|
29
36
|
logger.debug('KeyboardManager', 'Key pressed', {
|
|
@@ -74,6 +81,8 @@ export function KeyboardManager() {
|
|
|
74
81
|
return false;
|
|
75
82
|
if (hasShift && !key.shift)
|
|
76
83
|
return false;
|
|
84
|
+
if (!hasShift && key.shift)
|
|
85
|
+
return false;
|
|
77
86
|
// Check the actual key
|
|
78
87
|
if (mainKey === 'up' && key.upArrow)
|
|
79
88
|
return true;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
type KeyboardBlockContextValue = {
|
|
3
|
+
blockCount: number;
|
|
4
|
+
increment: () => void;
|
|
5
|
+
decrement: () => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function KeyboardBlockProvider({ children }: {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function useKeyboardBlockContext(): KeyboardBlockContextValue;
|
|
11
|
+
export declare function useKeyboardBlocker(shouldBlock: boolean): void;
|
|
12
|
+
export declare function useIsKeyboardBlocked(): boolean;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
const KeyboardBlockContext = createContext(null);
|
|
4
|
+
export function KeyboardBlockProvider({ children }) {
|
|
5
|
+
const [blockCount, setBlockCount] = useState(0);
|
|
6
|
+
const increment = useCallback(() => {
|
|
7
|
+
setBlockCount(prev => prev + 1);
|
|
8
|
+
}, []);
|
|
9
|
+
const decrement = useCallback(() => {
|
|
10
|
+
setBlockCount(prev => Math.max(0, prev - 1));
|
|
11
|
+
}, []);
|
|
12
|
+
const value = useMemo(() => ({ blockCount, increment, decrement }), [blockCount, increment, decrement]);
|
|
13
|
+
return (_jsx(KeyboardBlockContext.Provider, { value: value, children: children }));
|
|
14
|
+
}
|
|
15
|
+
export function useKeyboardBlockContext() {
|
|
16
|
+
const context = useContext(KeyboardBlockContext);
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error('useKeyboardBlockContext must be used within KeyboardBlockProvider');
|
|
19
|
+
}
|
|
20
|
+
return context;
|
|
21
|
+
}
|
|
22
|
+
export function useKeyboardBlocker(shouldBlock) {
|
|
23
|
+
const { increment, decrement } = useKeyboardBlockContext();
|
|
24
|
+
const blockedRef = useRef(false);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (shouldBlock && !blockedRef.current) {
|
|
27
|
+
increment();
|
|
28
|
+
blockedRef.current = true;
|
|
29
|
+
}
|
|
30
|
+
else if (!shouldBlock && blockedRef.current) {
|
|
31
|
+
decrement();
|
|
32
|
+
blockedRef.current = false;
|
|
33
|
+
}
|
|
34
|
+
return () => {
|
|
35
|
+
if (blockedRef.current) {
|
|
36
|
+
decrement();
|
|
37
|
+
blockedRef.current = false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}, [shouldBlock, increment, decrement]);
|
|
41
|
+
}
|
|
42
|
+
export function useIsKeyboardBlocked() {
|
|
43
|
+
const { blockCount } = useKeyboardBlockContext();
|
|
44
|
+
return blockCount > 0;
|
|
45
|
+
}
|
|
@@ -4,6 +4,7 @@ export declare function usePlaylist(): {
|
|
|
4
4
|
playlists: Playlist[];
|
|
5
5
|
createPlaylist: (name: string) => void;
|
|
6
6
|
deletePlaylist: (playlistId: string) => void;
|
|
7
|
+
renamePlaylist: (playlistId: string, newName: string) => void;
|
|
7
8
|
addTrackToPlaylist: (playlistId: string, track: Track, force?: boolean) => AddTrackResult;
|
|
8
9
|
removeTrackFromPlaylist: (playlistId: string, trackIndex: number) => void;
|
|
9
10
|
};
|
|
@@ -37,6 +37,13 @@ export function usePlaylist() {
|
|
|
37
37
|
configService.set('playlists', updatedPlaylists);
|
|
38
38
|
return 'added';
|
|
39
39
|
}, [playlists, configService]);
|
|
40
|
+
const renamePlaylist = useCallback((playlistId, newName) => {
|
|
41
|
+
const updatedPlaylists = playlists.map(playlist => playlist.playlistId === playlistId
|
|
42
|
+
? { ...playlist, name: newName }
|
|
43
|
+
: playlist);
|
|
44
|
+
setPlaylists(updatedPlaylists);
|
|
45
|
+
configService.set('playlists', updatedPlaylists);
|
|
46
|
+
}, [playlists, configService]);
|
|
40
47
|
const removeTrackFromPlaylist = useCallback((playlistId, trackIndex) => {
|
|
41
48
|
const playlistIndex = playlists.findIndex(p => p.playlistId === playlistId);
|
|
42
49
|
if (playlistIndex === -1)
|
|
@@ -50,6 +57,7 @@ export function usePlaylist() {
|
|
|
50
57
|
playlists,
|
|
51
58
|
createPlaylist,
|
|
52
59
|
deletePlaylist,
|
|
60
|
+
renamePlaylist,
|
|
53
61
|
addTrackToPlaylist,
|
|
54
62
|
removeTrackFromPlaylist,
|
|
55
63
|
};
|
package/dist/source/main.js
CHANGED
|
@@ -7,6 +7,7 @@ import { ThemeProvider } from "./contexts/theme.context.js";
|
|
|
7
7
|
import { PlayerProvider } from "./stores/player.store.js";
|
|
8
8
|
import { ErrorBoundary } from "./components/common/ErrorBoundary.js";
|
|
9
9
|
import { KeyboardManager } from "./hooks/useKeyboard.js";
|
|
10
|
+
import { KeyboardBlockProvider } from "./hooks/useKeyboardBlocker.js";
|
|
10
11
|
import { Box, Text } from 'ink';
|
|
11
12
|
import { useEffect } from 'react';
|
|
12
13
|
import { useNavigation } from "./hooks/useNavigation.js";
|
|
@@ -65,5 +66,5 @@ function HeadlessLayout({ flags }) {
|
|
|
65
66
|
return (_jsx(Box, { padding: 1, children: _jsx(Text, { color: "green", children: "Headless mode active." }) }));
|
|
66
67
|
}
|
|
67
68
|
export default function Main({ flags }) {
|
|
68
|
-
return (_jsx(ErrorBoundary, { children: _jsx(ThemeProvider, { children: _jsx(PlayerProvider, { children: _jsx(NavigationProvider, { children: _jsx(PluginsProvider, { children: _jsxs(Box, { flexDirection: "column", children: [_jsx(KeyboardManager, {}), flags?.headless ? (_jsx(HeadlessLayout, { flags: flags })) : (_jsxs(_Fragment, { children: [_jsx(Initializer, { flags: flags }), _jsx(MainLayout, {})] }))] }) }) }) }) }) }));
|
|
69
|
+
return (_jsx(ErrorBoundary, { children: _jsx(ThemeProvider, { children: _jsx(PlayerProvider, { children: _jsx(NavigationProvider, { children: _jsx(PluginsProvider, { children: _jsx(KeyboardBlockProvider, { children: _jsxs(Box, { flexDirection: "column", children: [_jsx(KeyboardManager, {}), flags?.headless ? (_jsx(HeadlessLayout, { flags: flags })) : (_jsxs(_Fragment, { children: [_jsx(Initializer, { flags: flags }), _jsx(MainLayout, {})] }))] }) }) }) }) }) }) }));
|
|
69
70
|
}
|
|
@@ -30,9 +30,10 @@ export declare const KEYBINDINGS: {
|
|
|
30
30
|
readonly QUIT: readonly ["q", "escape"];
|
|
31
31
|
readonly HELP: readonly ["?"];
|
|
32
32
|
readonly SEARCH: readonly ["/"];
|
|
33
|
-
readonly PLAYLISTS: readonly ["p"];
|
|
33
|
+
readonly PLAYLISTS: readonly ["shift+p"];
|
|
34
34
|
readonly SUGGESTIONS: readonly ["g"];
|
|
35
35
|
readonly SETTINGS: readonly [","];
|
|
36
|
+
readonly PLUGINS: readonly ["p"];
|
|
36
37
|
readonly PLAY_PAUSE: readonly [" "];
|
|
37
38
|
readonly NEXT: readonly ["n", "right"];
|
|
38
39
|
readonly PREVIOUS: readonly ["b", "left"];
|
|
@@ -38,9 +38,10 @@ export const KEYBINDINGS = {
|
|
|
38
38
|
QUIT: ['q', 'escape'],
|
|
39
39
|
HELP: ['?'],
|
|
40
40
|
SEARCH: ['/'],
|
|
41
|
-
PLAYLISTS: ['p'],
|
|
41
|
+
PLAYLISTS: ['shift+p'],
|
|
42
42
|
SUGGESTIONS: ['g'],
|
|
43
43
|
SETTINGS: [','],
|
|
44
|
+
PLUGINS: ['p'],
|
|
44
45
|
// Player
|
|
45
46
|
PLAY_PAUSE: [' '],
|
|
46
47
|
NEXT: ['n', 'right'],
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@involvex/youtube-music-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
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 . &&
|
|
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
|
-
"
|
|
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
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"tsx": "module"
|
|
50
|
-
},
|
|
51
|
-
"nodeArguments": [
|
|
52
|
-
"--loader=ts-node/esm"
|
|
52
|
+
"files": [
|
|
53
|
+
"tests/**/*.js"
|
|
53
54
|
]
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"jiti": "^2.6.1",
|
|
62
63
|
"meow": "^14.0.0",
|
|
63
64
|
"node-notifier": "^10.0.1",
|
|
65
|
+
"discord-rpc": "^4.0.1",
|
|
64
66
|
"node-youtube-music": "^0.10.3",
|
|
65
67
|
"play-sound": "^1.1.6",
|
|
66
68
|
"react": "^19.2.4",
|
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
|
-
});
|