@involvex/youtube-music-cli 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +352 -0
  2. package/dist/eslint.config.d.ts +2 -0
  3. package/dist/eslint.config.js +55 -0
  4. package/dist/source/app.d.ts +4 -0
  5. package/dist/source/app.js +17 -0
  6. package/dist/source/cli.d.ts +2 -0
  7. package/dist/source/cli.js +241 -0
  8. package/dist/source/components/common/ErrorBoundary.d.ts +15 -0
  9. package/dist/source/components/common/ErrorBoundary.js +22 -0
  10. package/dist/source/components/common/Help.d.ts +1 -0
  11. package/dist/source/components/common/Help.js +10 -0
  12. package/dist/source/components/common/ShortcutsBar.d.ts +1 -0
  13. package/dist/source/components/common/ShortcutsBar.js +33 -0
  14. package/dist/source/components/config/ConfigLayout.d.ts +1 -0
  15. package/dist/source/components/config/ConfigLayout.js +84 -0
  16. package/dist/source/components/layouts/MainLayout.d.ts +4 -0
  17. package/dist/source/components/layouts/MainLayout.js +83 -0
  18. package/dist/source/components/layouts/PlayerLayout.d.ts +1 -0
  19. package/dist/source/components/layouts/PlayerLayout.js +10 -0
  20. package/dist/source/components/layouts/PluginsLayout.d.ts +1 -0
  21. package/dist/source/components/layouts/PluginsLayout.js +77 -0
  22. package/dist/source/components/layouts/SearchLayout.d.ts +4 -0
  23. package/dist/source/components/layouts/SearchLayout.js +81 -0
  24. package/dist/source/components/player/NowPlaying.d.ts +1 -0
  25. package/dist/source/components/player/NowPlaying.js +21 -0
  26. package/dist/source/components/player/PlayerControls.d.ts +1 -0
  27. package/dist/source/components/player/PlayerControls.js +41 -0
  28. package/dist/source/components/player/ProgressBar.d.ts +1 -0
  29. package/dist/source/components/player/ProgressBar.js +18 -0
  30. package/dist/source/components/player/QueueList.d.ts +4 -0
  31. package/dist/source/components/player/QueueList.js +30 -0
  32. package/dist/source/components/player/Suggestions.d.ts +1 -0
  33. package/dist/source/components/player/Suggestions.js +47 -0
  34. package/dist/source/components/playlist/PlaylistList.d.ts +1 -0
  35. package/dist/source/components/playlist/PlaylistList.js +11 -0
  36. package/dist/source/components/plugins/PluginInstallDialog.d.ts +5 -0
  37. package/dist/source/components/plugins/PluginInstallDialog.js +41 -0
  38. package/dist/source/components/plugins/PluginsAvailable.d.ts +5 -0
  39. package/dist/source/components/plugins/PluginsAvailable.js +55 -0
  40. package/dist/source/components/plugins/PluginsList.d.ts +8 -0
  41. package/dist/source/components/plugins/PluginsList.js +18 -0
  42. package/dist/source/components/search/SearchBar.d.ts +8 -0
  43. package/dist/source/components/search/SearchBar.js +50 -0
  44. package/dist/source/components/search/SearchResults.d.ts +10 -0
  45. package/dist/source/components/search/SearchResults.js +111 -0
  46. package/dist/source/components/settings/Settings.d.ts +1 -0
  47. package/dist/source/components/settings/Settings.js +42 -0
  48. package/dist/source/components/theme/ThemeSwitcher.d.ts +1 -0
  49. package/dist/source/components/theme/ThemeSwitcher.js +11 -0
  50. package/dist/source/config/themes.config.d.ts +3 -0
  51. package/dist/source/config/themes.config.js +63 -0
  52. package/dist/source/contexts/theme.context.d.ts +13 -0
  53. package/dist/source/contexts/theme.context.js +29 -0
  54. package/dist/source/hooks/useKeyboard.d.ts +10 -0
  55. package/dist/source/hooks/useKeyboard.js +104 -0
  56. package/dist/source/hooks/useNavigation.d.ts +1 -0
  57. package/dist/source/hooks/useNavigation.js +5 -0
  58. package/dist/source/hooks/usePlayer.d.ts +23 -0
  59. package/dist/source/hooks/usePlayer.js +35 -0
  60. package/dist/source/hooks/usePlaylist.d.ts +8 -0
  61. package/dist/source/hooks/usePlaylist.js +50 -0
  62. package/dist/source/hooks/useSearch.d.ts +8 -0
  63. package/dist/source/hooks/useSearch.js +76 -0
  64. package/dist/source/hooks/useTerminalSize.d.ts +4 -0
  65. package/dist/source/hooks/useTerminalSize.js +24 -0
  66. package/dist/source/hooks/useTheme.d.ts +6 -0
  67. package/dist/source/hooks/useTheme.js +5 -0
  68. package/dist/source/hooks/useYouTubeMusic.d.ts +11 -0
  69. package/dist/source/hooks/useYouTubeMusic.js +112 -0
  70. package/dist/source/main.d.ts +4 -0
  71. package/dist/source/main.js +69 -0
  72. package/dist/source/services/config/config.service.d.ts +26 -0
  73. package/dist/source/services/config/config.service.js +125 -0
  74. package/dist/source/services/logger/logger.service.d.ts +10 -0
  75. package/dist/source/services/logger/logger.service.js +52 -0
  76. package/dist/source/services/player/player.service.d.ts +58 -0
  77. package/dist/source/services/player/player.service.js +349 -0
  78. package/dist/source/services/player-state/player-state.service.d.ts +24 -0
  79. package/dist/source/services/player-state/player-state.service.js +122 -0
  80. package/dist/source/services/plugin/plugin-audio-api.d.ts +17 -0
  81. package/dist/source/services/plugin/plugin-audio-api.js +36 -0
  82. package/dist/source/services/plugin/plugin-context.d.ts +5 -0
  83. package/dist/source/services/plugin/plugin-context.js +256 -0
  84. package/dist/source/services/plugin/plugin-hooks.service.d.ts +62 -0
  85. package/dist/source/services/plugin/plugin-hooks.service.js +135 -0
  86. package/dist/source/services/plugin/plugin-installer.service.d.ts +27 -0
  87. package/dist/source/services/plugin/plugin-installer.service.js +247 -0
  88. package/dist/source/services/plugin/plugin-loader.service.d.ts +33 -0
  89. package/dist/source/services/plugin/plugin-loader.service.js +161 -0
  90. package/dist/source/services/plugin/plugin-permissions.service.d.ts +72 -0
  91. package/dist/source/services/plugin/plugin-permissions.service.js +194 -0
  92. package/dist/source/services/plugin/plugin-registry.service.d.ts +76 -0
  93. package/dist/source/services/plugin/plugin-registry.service.js +215 -0
  94. package/dist/source/services/plugin/plugin-ui-api.d.ts +25 -0
  95. package/dist/source/services/plugin/plugin-ui-api.js +46 -0
  96. package/dist/source/services/plugin/plugin-updater.service.d.ts +23 -0
  97. package/dist/source/services/plugin/plugin-updater.service.js +206 -0
  98. package/dist/source/services/youtube-music/api.d.ts +13 -0
  99. package/dist/source/services/youtube-music/api.js +371 -0
  100. package/dist/source/services/youtube-music/search.service.d.ts +11 -0
  101. package/dist/source/services/youtube-music/search.service.js +38 -0
  102. package/dist/source/stores/navigation.store.d.ts +10 -0
  103. package/dist/source/stores/navigation.store.js +67 -0
  104. package/dist/source/stores/player.store.d.ts +28 -0
  105. package/dist/source/stores/player.store.js +458 -0
  106. package/dist/source/stores/plugins.store.d.ts +46 -0
  107. package/dist/source/stores/plugins.store.js +177 -0
  108. package/dist/source/types/actions.d.ts +119 -0
  109. package/dist/source/types/actions.js +1 -0
  110. package/dist/source/types/cli.types.d.ts +14 -0
  111. package/dist/source/types/cli.types.js +1 -0
  112. package/dist/source/types/config.types.d.ts +19 -0
  113. package/dist/source/types/config.types.js +1 -0
  114. package/dist/source/types/keyboard.types.d.ts +5 -0
  115. package/dist/source/types/keyboard.types.js +1 -0
  116. package/dist/source/types/navigation.types.d.ts +14 -0
  117. package/dist/source/types/navigation.types.js +1 -0
  118. package/dist/source/types/player.types.d.ts +16 -0
  119. package/dist/source/types/player.types.js +1 -0
  120. package/dist/source/types/playlist.types.d.ts +12 -0
  121. package/dist/source/types/playlist.types.js +1 -0
  122. package/dist/source/types/plugin.types.d.ts +239 -0
  123. package/dist/source/types/plugin.types.js +1 -0
  124. package/dist/source/types/theme.types.d.ts +18 -0
  125. package/dist/source/types/theme.types.js +1 -0
  126. package/dist/source/types/youtube-music.types.d.ts +35 -0
  127. package/dist/source/types/youtube-music.types.js +1 -0
  128. package/dist/source/types/youtubei.types.d.ts +60 -0
  129. package/dist/source/types/youtubei.types.js +3 -0
  130. package/dist/source/utils/constants.d.ts +65 -0
  131. package/dist/source/utils/constants.js +82 -0
  132. package/dist/source/utils/format.d.ts +3 -0
  133. package/dist/source/utils/format.js +24 -0
  134. package/dist/test.d.ts +1 -0
  135. package/dist/test.js +13 -0
  136. package/package.json +100 -0
@@ -0,0 +1,104 @@
1
+ // Keyboard input handling hook
2
+ import { useCallback, useEffect } from 'react';
3
+ import { useInput } from 'ink';
4
+ import { logger } from "../services/logger/logger.service.js";
5
+ // Global registry for key handlers
6
+ const registry = new Set();
7
+ /**
8
+ * Hook to bind keyboard shortcuts.
9
+ * This uses a centralized manager to avoid multiple useInput calls and memory leaks.
10
+ */
11
+ export function useKeyBinding(keys, handler) {
12
+ const memoizedHandler = useCallback(handler, [handler]);
13
+ useEffect(() => {
14
+ const entry = { keys, handler: memoizedHandler };
15
+ registry.add(entry);
16
+ return () => {
17
+ registry.delete(entry);
18
+ };
19
+ }, [keys, memoizedHandler]);
20
+ }
21
+ /**
22
+ * Global Keyboard Manager Component
23
+ * This should be rendered once at the root of the app.
24
+ */
25
+ export function KeyboardManager() {
26
+ useInput((input, key) => {
27
+ // Debug logging for key presses (helps diagnose binding issues)
28
+ if (input || key.ctrl || key.meta || key.shift) {
29
+ logger.debug('KeyboardManager', 'Key pressed', {
30
+ input,
31
+ ctrl: key.ctrl,
32
+ meta: key.meta,
33
+ shift: key.shift,
34
+ upArrow: key.upArrow,
35
+ downArrow: key.downArrow,
36
+ leftArrow: key.leftArrow,
37
+ rightArrow: key.rightArrow,
38
+ });
39
+ }
40
+ // Global quit handling
41
+ if (key.ctrl && input === 'c') {
42
+ // Exit cleanly without clearing screen (let Ink handle cleanup)
43
+ process.exit(0);
44
+ }
45
+ // Note: Ctrl+L refresh removed to fix scroll-to-top issue
46
+ // Direct ANSI escapes bypass Ink's rendering and cause scrolling problems
47
+ // Dispatch to all registered handlers
48
+ for (const entry of registry) {
49
+ const { keys, handler } = entry;
50
+ for (const binding of keys) {
51
+ const lowerBinding = binding.toLowerCase();
52
+ // Handle special keys
53
+ const isMatch = (lowerBinding === 'escape' && key.escape) ||
54
+ ((lowerBinding === 'return' || lowerBinding === 'enter') &&
55
+ key.return) ||
56
+ (lowerBinding === 'backspace' && key.backspace) ||
57
+ (lowerBinding === 'tab' && key.tab) ||
58
+ (lowerBinding === 'up' && key.upArrow) ||
59
+ (lowerBinding === 'down' && key.downArrow) ||
60
+ (lowerBinding === 'left' && key.leftArrow) ||
61
+ (lowerBinding === 'right' && key.rightArrow) ||
62
+ (lowerBinding === 'pageup' && key.pageUp) ||
63
+ (lowerBinding === 'pagedown' && key.pageDown) ||
64
+ // Handle combinations
65
+ (() => {
66
+ const parts = lowerBinding.split('+');
67
+ const hasCtrl = parts.includes('ctrl');
68
+ const hasMeta = parts.includes('meta') || parts.includes('alt');
69
+ const hasShift = parts.includes('shift');
70
+ const mainKey = parts[parts.length - 1];
71
+ if (hasCtrl && !key.ctrl)
72
+ return false;
73
+ if (hasMeta && !key.meta)
74
+ return false;
75
+ if (hasShift && !key.shift)
76
+ return false;
77
+ // Check the actual key
78
+ if (mainKey === 'up' && key.upArrow)
79
+ return true;
80
+ if (mainKey === 'down' && key.downArrow)
81
+ return true;
82
+ if (mainKey === 'left' && key.leftArrow)
83
+ return true;
84
+ if (mainKey === 'right' && key.rightArrow)
85
+ return true;
86
+ // Handle '=' and '+' specially (+ is shift+=)
87
+ if (mainKey === '=' && input === '=')
88
+ return true;
89
+ if (mainKey === '+' && input === '+')
90
+ return true;
91
+ if (mainKey === '+' && key.shift && input === '=')
92
+ return true; // shift+= produces '+'
93
+ return input.toLowerCase() === mainKey && !key.ctrl && !key.meta;
94
+ })();
95
+ if (isMatch) {
96
+ handler();
97
+ // We don't break here because multiple handlers might want to react
98
+ // but usually only one does.
99
+ }
100
+ }
101
+ }
102
+ });
103
+ return null;
104
+ }
@@ -0,0 +1 @@
1
+ export declare function useNavigation(): import("../stores/navigation.store.tsx").NavigationContextValue;
@@ -0,0 +1,5 @@
1
+ // Navigation hook
2
+ import { useNavigation as useNavigationStore } from "../stores/navigation.store.js";
3
+ export function useNavigation() {
4
+ return useNavigationStore();
5
+ }
@@ -0,0 +1,23 @@
1
+ import type { Track } from '../types/youtube-music.types.ts';
2
+ export declare function usePlayer(): {
3
+ state: import("../types/player.types.ts").PlayerState;
4
+ dispatch: (action: import("../types/player.types.ts").PlayerAction) => void;
5
+ play: (track: Track, options?: {
6
+ clearQueue?: boolean;
7
+ }) => void;
8
+ pause: () => void;
9
+ resume: () => void;
10
+ next: () => void;
11
+ previous: () => void;
12
+ seek: (position: number) => void;
13
+ setVolume: (volume: number) => void;
14
+ volumeUp: () => void;
15
+ volumeDown: () => void;
16
+ toggleShuffle: () => void;
17
+ toggleRepeat: () => void;
18
+ setQueue: (queue: Track[]) => void;
19
+ addToQueue: (track: Track) => void;
20
+ removeFromQueue: (index: number) => void;
21
+ clearQueue: () => void;
22
+ setQueuePosition: (position: number) => void;
23
+ };
@@ -0,0 +1,35 @@
1
+ // Player hook - audio playback orchestration
2
+ import { useCallback } from 'react';
3
+ import { usePlayer as usePlayerStore } from "../stores/player.store.js";
4
+ import { getConfigService } from "../services/config/config.service.js";
5
+ export function usePlayer() {
6
+ const { state, dispatch, ...playerStore } = usePlayerStore();
7
+ const play = useCallback((track, options) => {
8
+ // Clear queue if requested (e.g., playing from search results)
9
+ if (options?.clearQueue) {
10
+ dispatch({ category: 'CLEAR_QUEUE' });
11
+ }
12
+ // Add to queue if not already there
13
+ const isInQueue = state.queue.some(t => t.videoId === track.videoId);
14
+ if (!isInQueue) {
15
+ dispatch({ category: 'ADD_TO_QUEUE', track });
16
+ }
17
+ // Find position and play
18
+ const position = state.queue.findIndex(t => t.videoId === track.videoId);
19
+ if (position >= 0) {
20
+ dispatch({ category: 'SET_QUEUE_POSITION', position });
21
+ }
22
+ else {
23
+ dispatch({ category: 'PLAY', track });
24
+ }
25
+ // Add to history
26
+ const config = getConfigService();
27
+ config.addToHistory(track.videoId);
28
+ }, [state.queue, dispatch]);
29
+ return {
30
+ ...playerStore,
31
+ state,
32
+ dispatch,
33
+ play,
34
+ };
35
+ }
@@ -0,0 +1,8 @@
1
+ import type { Playlist, Track } from '../types/youtube-music.types.ts';
2
+ export declare function usePlaylist(): {
3
+ playlists: Playlist[];
4
+ createPlaylist: (name: string) => void;
5
+ deletePlaylist: (playlistId: string) => void;
6
+ addTrackToPlaylist: (playlistId: string, track: Track) => void;
7
+ removeTrackFromPlaylist: (playlistId: string, trackIndex: number) => void;
8
+ };
@@ -0,0 +1,50 @@
1
+ // Playlist management hook
2
+ import { getConfigService } from "../services/config/config.service.js";
3
+ import { useState, useCallback, useEffect } from 'react';
4
+ export function usePlaylist() {
5
+ const [playlists, setPlaylists] = useState([]);
6
+ const configService = getConfigService();
7
+ useEffect(() => {
8
+ setPlaylists(configService.get('playlists'));
9
+ }, []);
10
+ const createPlaylist = useCallback((name) => {
11
+ const newPlaylist = {
12
+ playlistId: Date.now().toString(),
13
+ name,
14
+ tracks: [],
15
+ };
16
+ const updatedPlaylists = [...playlists, newPlaylist];
17
+ setPlaylists(updatedPlaylists);
18
+ configService.set('playlists', updatedPlaylists);
19
+ }, [playlists, configService]);
20
+ const deletePlaylist = useCallback((playlistId) => {
21
+ const updatedPlaylists = playlists.filter(p => p.playlistId !== playlistId);
22
+ setPlaylists(updatedPlaylists);
23
+ configService.set('playlists', updatedPlaylists);
24
+ }, [playlists, configService]);
25
+ const addTrackToPlaylist = useCallback((playlistId, track) => {
26
+ const playlistIndex = playlists.findIndex(p => p.playlistId === playlistId);
27
+ if (playlistIndex === -1)
28
+ return;
29
+ const updatedPlaylists = [...playlists];
30
+ updatedPlaylists[playlistIndex].tracks.push(track);
31
+ setPlaylists(updatedPlaylists);
32
+ configService.set('playlists', updatedPlaylists);
33
+ }, [playlists, configService]);
34
+ const removeTrackFromPlaylist = useCallback((playlistId, trackIndex) => {
35
+ const playlistIndex = playlists.findIndex(p => p.playlistId === playlistId);
36
+ if (playlistIndex === -1)
37
+ return;
38
+ const updatedPlaylists = [...playlists];
39
+ updatedPlaylists[playlistIndex].tracks.splice(trackIndex, 1);
40
+ setPlaylists(updatedPlaylists);
41
+ configService.set('playlists', updatedPlaylists);
42
+ }, [playlists, configService]);
43
+ return {
44
+ playlists,
45
+ createPlaylist,
46
+ deletePlaylist,
47
+ addTrackToPlaylist,
48
+ removeTrackFromPlaylist,
49
+ };
50
+ }
@@ -0,0 +1,8 @@
1
+ export declare function useSearch(): {
2
+ isLoading: boolean;
3
+ error: string | null;
4
+ searchSongs: (query: string) => Promise<import("../types/youtube-music.types.ts").Track[]>;
5
+ searchAlbums: (query: string) => Promise<import("../types/youtube-music.types.ts").Album[]>;
6
+ searchArtists: (query: string) => Promise<import("../types/youtube-music.types.ts").Artist[]>;
7
+ searchPlaylists: (query: string) => Promise<import("../types/youtube-music.types.ts").Playlist[]>;
8
+ };
@@ -0,0 +1,76 @@
1
+ // Search hook
2
+ import { getSearchService } from "../services/youtube-music/search.service.js";
3
+ import { useState, useCallback } from 'react';
4
+ export function useSearch() {
5
+ const [isLoading, setIsLoading] = useState(false);
6
+ const [error, setError] = useState(null);
7
+ const searchService = getSearchService();
8
+ const searchSongs = useCallback(async (query) => {
9
+ setIsLoading(true);
10
+ setError(null);
11
+ try {
12
+ const results = await searchService.searchSongs(query);
13
+ return results;
14
+ }
15
+ catch (err) {
16
+ setError(err instanceof Error ? err.message : 'Search failed');
17
+ return [];
18
+ }
19
+ finally {
20
+ setIsLoading(false);
21
+ }
22
+ }, [searchService]);
23
+ const searchAlbums = useCallback(async (query) => {
24
+ setIsLoading(true);
25
+ setError(null);
26
+ try {
27
+ const results = await searchService.searchAlbums(query);
28
+ return results;
29
+ }
30
+ catch (err) {
31
+ setError(err instanceof Error ? err.message : 'Search failed');
32
+ return [];
33
+ }
34
+ finally {
35
+ setIsLoading(false);
36
+ }
37
+ }, [searchService]);
38
+ const searchArtists = useCallback(async (query) => {
39
+ setIsLoading(true);
40
+ setError(null);
41
+ try {
42
+ const results = await searchService.searchArtists(query);
43
+ return results;
44
+ }
45
+ catch (err) {
46
+ setError(err instanceof Error ? err.message : 'Search failed');
47
+ return [];
48
+ }
49
+ finally {
50
+ setIsLoading(false);
51
+ }
52
+ }, [searchService]);
53
+ const searchPlaylists = useCallback(async (query) => {
54
+ setIsLoading(true);
55
+ setError(null);
56
+ try {
57
+ const results = await searchService.searchPlaylists(query);
58
+ return results;
59
+ }
60
+ catch (err) {
61
+ setError(err instanceof Error ? err.message : 'Search failed');
62
+ return [];
63
+ }
64
+ finally {
65
+ setIsLoading(false);
66
+ }
67
+ }, [searchService]);
68
+ return {
69
+ isLoading,
70
+ error,
71
+ searchSongs,
72
+ searchAlbums,
73
+ searchArtists,
74
+ searchPlaylists,
75
+ };
76
+ }
@@ -0,0 +1,4 @@
1
+ export declare function useTerminalSize(): {
2
+ columns: number;
3
+ rows: number;
4
+ };
@@ -0,0 +1,24 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useStdout } from 'ink';
3
+ export function useTerminalSize() {
4
+ const { stdout } = useStdout();
5
+ const [size, setSize] = useState({
6
+ columns: stdout?.columns || 80,
7
+ rows: stdout?.rows || 24,
8
+ });
9
+ useEffect(() => {
10
+ if (!stdout)
11
+ return;
12
+ const onResize = () => {
13
+ setSize({
14
+ columns: stdout.columns,
15
+ rows: stdout.rows,
16
+ });
17
+ };
18
+ stdout.on('resize', onResize);
19
+ return () => {
20
+ stdout.off('resize', onResize);
21
+ };
22
+ }, [stdout]);
23
+ return size;
24
+ }
@@ -0,0 +1,6 @@
1
+ export declare function useTheme(): {
2
+ theme: import("../types/theme.types.ts").Theme;
3
+ themeName: string;
4
+ setTheme: (name: string) => void;
5
+ setCustomTheme: (theme: import("../types/theme.types.ts").Theme) => void;
6
+ };
@@ -0,0 +1,5 @@
1
+ // Theme management hook
2
+ import { useTheme as useThemeContext } from "../contexts/theme.context.js";
3
+ export function useTheme() {
4
+ return useThemeContext();
5
+ }
@@ -0,0 +1,11 @@
1
+ import type { SearchOptions, SearchResponse, Track, Album, Artist, Playlist } from '../types/youtube-music.types.ts';
2
+ export declare function useYouTubeMusic(): {
3
+ isLoading: boolean;
4
+ error: string | null;
5
+ search: (query: string, options?: SearchOptions) => Promise<SearchResponse | null>;
6
+ getTrack: (videoId: string) => Promise<Track | null>;
7
+ getAlbum: (albumId: string) => Promise<Album | null>;
8
+ getArtist: (artistId: string) => Promise<Artist | null>;
9
+ getPlaylist: (playlistId: string) => Promise<Playlist | null>;
10
+ getSuggestions: (trackId: string) => Promise<Track[]>;
11
+ };
@@ -0,0 +1,112 @@
1
+ import { getMusicService } from "../services/youtube-music/api.js";
2
+ import { useState, useCallback } from 'react';
3
+ export function useYouTubeMusic() {
4
+ const [isLoading, setIsLoading] = useState(false);
5
+ const [error, setError] = useState(null);
6
+ const musicService = getMusicService();
7
+ const search = useCallback(async (query, options = {}) => {
8
+ setIsLoading(true);
9
+ setError(null);
10
+ try {
11
+ const response = await musicService.search(query, options);
12
+ return response;
13
+ }
14
+ catch (err) {
15
+ setError(err instanceof Error ? err.message : 'Search failed');
16
+ return null;
17
+ }
18
+ finally {
19
+ setIsLoading(false);
20
+ }
21
+ }, [musicService]);
22
+ const getTrack = useCallback(async (videoId) => {
23
+ setIsLoading(true);
24
+ setError(null);
25
+ try {
26
+ const track = await musicService.getTrack(videoId);
27
+ return track;
28
+ }
29
+ catch (err) {
30
+ setError(err instanceof Error ? err.message : 'Failed to get track');
31
+ return null;
32
+ }
33
+ finally {
34
+ setIsLoading(false);
35
+ }
36
+ }, [musicService]);
37
+ const getAlbum = useCallback(async (albumId) => {
38
+ setIsLoading(true);
39
+ setError(null);
40
+ try {
41
+ const album = await musicService.getAlbum(albumId);
42
+ return album;
43
+ }
44
+ catch (err) {
45
+ setError(err instanceof Error ? err.message : 'Failed to get album');
46
+ return null;
47
+ }
48
+ finally {
49
+ setIsLoading(false);
50
+ }
51
+ }, [musicService]);
52
+ const getArtist = useCallback(async (artistId) => {
53
+ setIsLoading(true);
54
+ setError(null);
55
+ try {
56
+ const artist = await musicService.getArtist(artistId);
57
+ return artist;
58
+ }
59
+ catch (err) {
60
+ setError(err instanceof Error ? err.message : 'Failed to get artist');
61
+ return null;
62
+ }
63
+ finally {
64
+ setIsLoading(false);
65
+ }
66
+ }, [musicService]);
67
+ const getPlaylist = useCallback(async (playlistId) => {
68
+ setIsLoading(true);
69
+ setError(null);
70
+ try {
71
+ const playlist = await musicService.getPlaylist(playlistId);
72
+ return playlist;
73
+ }
74
+ catch (err) {
75
+ setError(err instanceof Error ? err.message : 'Failed to get playlist');
76
+ return null;
77
+ }
78
+ finally {
79
+ setIsLoading(false);
80
+ }
81
+ }, [musicService]);
82
+ const getSuggestions = useCallback(async (trackId) => {
83
+ setIsLoading(true);
84
+ setError(null);
85
+ try {
86
+ const suggestions = await musicService.getSuggestions(trackId);
87
+ return suggestions;
88
+ }
89
+ catch (err) {
90
+ // Suppress YouTubeJS parsing errors (library limitation with YouTube's changing API)
91
+ // These are not user-actionable and create noise in the UI
92
+ const errorMessage = err instanceof Error ? err.message : 'Failed to get suggestions';
93
+ if (!errorMessage.includes('ParsingError')) {
94
+ setError(errorMessage);
95
+ }
96
+ return [];
97
+ }
98
+ finally {
99
+ setIsLoading(false);
100
+ }
101
+ }, [musicService]);
102
+ return {
103
+ isLoading,
104
+ error,
105
+ search,
106
+ getTrack,
107
+ getAlbum,
108
+ getArtist,
109
+ getPlaylist,
110
+ getSuggestions,
111
+ };
112
+ }
@@ -0,0 +1,4 @@
1
+ import type { Flags } from './types/cli.types.ts';
2
+ export default function Main({ flags }: {
3
+ flags?: Flags;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Main application orchestrator
3
+ import { NavigationProvider } from "./stores/navigation.store.js";
4
+ import { PluginsProvider } from "./stores/plugins.store.js";
5
+ import MainLayout from "./components/layouts/MainLayout.js";
6
+ import { ThemeProvider } from "./contexts/theme.context.js";
7
+ import { PlayerProvider } from "./stores/player.store.js";
8
+ import { ErrorBoundary } from "./components/common/ErrorBoundary.js";
9
+ import { KeyboardManager } from "./hooks/useKeyboard.js";
10
+ import { Box, Text } from 'ink';
11
+ import { useEffect } from 'react';
12
+ import { useNavigation } from "./hooks/useNavigation.js";
13
+ import { usePlayer } from "./hooks/usePlayer.js";
14
+ import { useYouTubeMusic } from "./hooks/useYouTubeMusic.js";
15
+ import { VIEW } from "./utils/constants.js";
16
+ function Initializer({ flags }) {
17
+ const { dispatch } = useNavigation();
18
+ const { play } = usePlayer();
19
+ const { getTrack, getPlaylist } = useYouTubeMusic();
20
+ useEffect(() => {
21
+ if (flags?.showSuggestions) {
22
+ dispatch({ category: 'NAVIGATE', view: VIEW.SUGGESTIONS });
23
+ }
24
+ else if (flags?.searchQuery) {
25
+ dispatch({ category: 'NAVIGATE', view: VIEW.SEARCH });
26
+ dispatch({ category: 'SET_SEARCH_QUERY', query: flags.searchQuery });
27
+ }
28
+ else if (flags?.playTrack) {
29
+ void getTrack(flags.playTrack).then(track => {
30
+ if (track)
31
+ play(track);
32
+ });
33
+ }
34
+ else if (flags?.playPlaylist) {
35
+ dispatch({ category: 'NAVIGATE', view: VIEW.PLAYLISTS });
36
+ void getPlaylist(flags.playPlaylist).then(playlist => {
37
+ // For now just navigate, but we could auto-play
38
+ if (playlist) {
39
+ dispatch({ category: 'SET_SELECTED_PLAYLIST', index: 0 });
40
+ }
41
+ });
42
+ }
43
+ }, [flags, dispatch, play, getTrack, getPlaylist]);
44
+ return null;
45
+ }
46
+ function HeadlessLayout({ flags }) {
47
+ const { play, pause, resume, next, previous } = usePlayer();
48
+ const { getTrack } = useYouTubeMusic();
49
+ useEffect(() => {
50
+ if (flags?.playTrack) {
51
+ void getTrack(flags.playTrack).then(track => {
52
+ if (track)
53
+ play(track);
54
+ });
55
+ }
56
+ if (flags?.action === 'pause')
57
+ pause();
58
+ if (flags?.action === 'resume')
59
+ resume();
60
+ if (flags?.action === 'next')
61
+ next();
62
+ if (flags?.action === 'previous')
63
+ previous();
64
+ }, [flags, play, pause, resume, next, previous, getTrack]);
65
+ return (_jsx(Box, { padding: 1, children: _jsx(Text, { color: "green", children: "Headless mode active." }) }));
66
+ }
67
+ 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
+ }
@@ -0,0 +1,26 @@
1
+ import type { Config } from '../../types/config.types.ts';
2
+ import type { Theme } from '../../types/theme.types.ts';
3
+ declare class ConfigService {
4
+ private configPath;
5
+ private configDir;
6
+ private config;
7
+ constructor();
8
+ getDefaultConfig(): Config;
9
+ load(): Config | null;
10
+ save(): void;
11
+ get<K extends keyof Config>(key: K): Config[K];
12
+ set<K extends keyof Config>(key: K, value: Config[K]): void;
13
+ updateTheme(themeName: string): void;
14
+ getTheme(): Theme;
15
+ setCustomTheme(theme: Theme): void;
16
+ getKeybinding(action: string): string[] | undefined;
17
+ setKeybinding(action: string, keys: string[]): void;
18
+ addToHistory(trackId: string): void;
19
+ getHistory(): string[];
20
+ addFavorite(trackId: string): void;
21
+ removeFavorite(trackId: string): void;
22
+ isFavorite(trackId: string): boolean;
23
+ getFavorites(): string[];
24
+ }
25
+ export declare function getConfigService(): ConfigService;
26
+ export {};