@involvex/youtube-music-cli 0.0.45 → 0.0.47
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 +8 -0
- package/dist/cli.js.map +1004 -0
- package/dist/package.json +1 -1
- package/dist/source/components/common/ShortcutsBar.js +11 -2
- package/dist/source/hooks/usePlayer.d.ts +1 -0
- package/dist/source/services/player-state/player-state.service.d.ts +1 -0
- package/dist/source/services/player-state/player-state.service.js +1 -0
- package/dist/source/services/web/web-server-manager.js +1 -0
- package/dist/source/services/web/web-streaming.service.js +3 -1
- package/dist/source/stores/player.store.d.ts +1 -0
- package/dist/source/stores/player.store.js +106 -7
- package/dist/source/types/actions.d.ts +4 -0
- package/dist/source/types/player.types.d.ts +3 -2
- package/dist/source/utils/constants.d.ts +1 -0
- package/dist/source/utils/constants.js +1 -0
- package/dist/source/utils/icons.d.ts +1 -0
- package/dist/source/utils/icons.js +2 -0
- package/dist/youtube-music-cli +0 -0
- package/package.json +1 -1
- package/readme.md +23 -0
package/dist/package.json
CHANGED
|
@@ -10,7 +10,7 @@ import { ICONS } from "../../utils/icons.js";
|
|
|
10
10
|
const FLASH_DURATION_MS = 300;
|
|
11
11
|
export default function ShortcutsBar() {
|
|
12
12
|
const { theme } = useTheme();
|
|
13
|
-
const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, volumeFineUp, volumeFineDown, toggleShuffle, toggleRepeat, } = usePlayer();
|
|
13
|
+
const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, volumeFineUp, volumeFineDown, toggleShuffle, toggleRepeat, toggleAutoplay, } = usePlayer();
|
|
14
14
|
const [flashState, setFlashState] = useState({});
|
|
15
15
|
const flash = (key) => {
|
|
16
16
|
setFlashState(prev => ({ ...prev, [key]: true }));
|
|
@@ -62,6 +62,10 @@ export default function ShortcutsBar() {
|
|
|
62
62
|
flash('repeat');
|
|
63
63
|
toggleRepeat();
|
|
64
64
|
});
|
|
65
|
+
useKeyBinding(KEYBINDINGS.AUTOPLAY_TOGGLE, () => {
|
|
66
|
+
flash('autoplay');
|
|
67
|
+
toggleAutoplay();
|
|
68
|
+
});
|
|
65
69
|
// Note: SETTINGS keybinding handled by MainLayout to avoid double-dispatch
|
|
66
70
|
const shuffleColor = flashState['shuffle']
|
|
67
71
|
? theme.colors.success
|
|
@@ -76,5 +80,10 @@ export default function ShortcutsBar() {
|
|
|
76
80
|
const volumeColor = flashState['volume']
|
|
77
81
|
? theme.colors.success
|
|
78
82
|
: theme.colors.primary;
|
|
79
|
-
|
|
83
|
+
const autoplayColor = flashState['autoplay']
|
|
84
|
+
? theme.colors.success
|
|
85
|
+
: playerState.autoplay
|
|
86
|
+
? theme.colors.primary
|
|
87
|
+
: theme.colors.dim;
|
|
88
|
+
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: shortcutColor('playPause'), children: [playerState.isPlaying ? ICONS.PAUSE : ICONS.PLAY_PAUSE_ON, " [Space]"] }), ' ', "| ", _jsxs(Text, { color: shortcutColor('prev'), children: [ICONS.PREV, " [B/\u2190]"] }), " |", ' ', _jsxs(Text, { color: shortcutColor('next'), children: [ICONS.NEXT, " [N/\u2192]"] }), " |", ' ', _jsxs(Text, { color: shuffleColor, children: [ICONS.SHUFFLE, " [Shift+S]"] }), " |", ' ', _jsxs(Text, { color: repeatColor, children: [playerState.repeat === 'one' ? ICONS.REPEAT_ONE : ICONS.REPEAT_ALL, ' ', "[R]"] }), ' ', "| ", _jsxs(Text, { color: autoplayColor, children: [ICONS.AUTOPLAY, " [Shift+A]"] }), " |", ' ', _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: shuffleColor, children: ICONS.SHUFFLE }), ' ', _jsx(Text, { color: repeatColor, children: playerState.repeat === 'one' ? ICONS.REPEAT_ONE : ICONS.REPEAT_ALL }), ' ', _jsx(Text, { color: autoplayColor, children: ICONS.AUTOPLAY }), ' ', _jsxs(Text, { color: theme.colors.dim, children: [ICONS.VOLUME, " [+/-]"] }), ' ', _jsxs(Text, { color: volumeColor, children: [playerState.volume, "%"] })] })] }));
|
|
80
89
|
}
|
|
@@ -17,6 +17,7 @@ export declare function usePlayer(): {
|
|
|
17
17
|
volumeFineDown: () => void;
|
|
18
18
|
toggleShuffle: () => void;
|
|
19
19
|
toggleRepeat: () => void;
|
|
20
|
+
toggleAutoplay: () => void;
|
|
20
21
|
setQueue: (queue: Track[]) => void;
|
|
21
22
|
addToQueue: (track: Track) => void;
|
|
22
23
|
removeFromQueue: (index: number) => void;
|
|
@@ -164,6 +164,7 @@ class WebStreamingService {
|
|
|
164
164
|
'queuePosition',
|
|
165
165
|
'repeat',
|
|
166
166
|
'shuffle',
|
|
167
|
+
'autoplay',
|
|
167
168
|
'isLoading',
|
|
168
169
|
'error',
|
|
169
170
|
];
|
|
@@ -186,9 +187,10 @@ class WebStreamingService {
|
|
|
186
187
|
* Update and broadcast player state (throttled)
|
|
187
188
|
*/
|
|
188
189
|
onStateChange(state) {
|
|
189
|
-
this.prevState = { ...state };
|
|
190
190
|
const now = Date.now();
|
|
191
|
+
// Compute delta against OLD prevState BEFORE updating it
|
|
191
192
|
const delta = this.computeDelta(state);
|
|
193
|
+
this.prevState = { ...state };
|
|
192
194
|
// Skip if no changes
|
|
193
195
|
if (Object.keys(delta).length === 0) {
|
|
194
196
|
return;
|
|
@@ -18,6 +18,7 @@ type PlayerContextValue = {
|
|
|
18
18
|
volumeFineDown: () => void;
|
|
19
19
|
toggleShuffle: () => void;
|
|
20
20
|
toggleRepeat: () => void;
|
|
21
|
+
toggleAutoplay: () => void;
|
|
21
22
|
setQueue: (queue: Track[]) => void;
|
|
22
23
|
addToQueue: (track: Track) => void;
|
|
23
24
|
removeFromQueue: (index: number) => void;
|
|
@@ -21,6 +21,7 @@ const initialState = {
|
|
|
21
21
|
queuePosition: 0,
|
|
22
22
|
repeat: 'off',
|
|
23
23
|
shuffle: false,
|
|
24
|
+
autoplay: true,
|
|
24
25
|
isLoading: false,
|
|
25
26
|
error: null,
|
|
26
27
|
playRequestId: 0,
|
|
@@ -150,6 +151,8 @@ export function playerReducer(state, action) {
|
|
|
150
151
|
}
|
|
151
152
|
case 'TOGGLE_SHUFFLE':
|
|
152
153
|
return { ...state, shuffle: !state.shuffle };
|
|
154
|
+
case 'TOGGLE_AUTOPLAY':
|
|
155
|
+
return { ...state, autoplay: !state.autoplay };
|
|
153
156
|
case 'TOGGLE_REPEAT':
|
|
154
157
|
const repeatModes = ['off', 'all', 'one'];
|
|
155
158
|
const currentIndex = repeatModes.indexOf(state.repeat);
|
|
@@ -225,6 +228,7 @@ export function playerReducer(state, action) {
|
|
|
225
228
|
volume: action.volume,
|
|
226
229
|
shuffle: action.shuffle,
|
|
227
230
|
repeat: action.repeat,
|
|
231
|
+
autoplay: action.autoplay ?? true,
|
|
228
232
|
isPlaying: false, // Don't auto-play restored state
|
|
229
233
|
};
|
|
230
234
|
default:
|
|
@@ -251,6 +255,7 @@ function PlayerManager() {
|
|
|
251
255
|
}, [dispatch]);
|
|
252
256
|
// Register event handler for mpv IPC events
|
|
253
257
|
const eofTimestampRef = useRef(0);
|
|
258
|
+
const lastAutoNextRef = useRef(0);
|
|
254
259
|
useEffect(() => {
|
|
255
260
|
let lastProgressUpdate = 0;
|
|
256
261
|
const PROGRESS_THROTTLE_MS = 1000; // Update progress max once per second
|
|
@@ -269,8 +274,10 @@ function PlayerManager() {
|
|
|
269
274
|
if (event.eof) {
|
|
270
275
|
// Track ended — record timestamp so we can suppress mpv's spurious
|
|
271
276
|
// pause event that immediately follows EOF (idle state).
|
|
272
|
-
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
eofTimestampRef.current = now;
|
|
273
279
|
next();
|
|
280
|
+
lastAutoNextRef.current = now;
|
|
274
281
|
}
|
|
275
282
|
if (event.paused !== undefined) {
|
|
276
283
|
// mpv sends pause=true when a track ends and it enters idle mode.
|
|
@@ -505,14 +512,101 @@ function PlayerManager() {
|
|
|
505
512
|
config.set('volume', state.volume);
|
|
506
513
|
}, [state.volume]);
|
|
507
514
|
// Handle track completion
|
|
515
|
+
const autoAdvanceRef = useRef(false);
|
|
508
516
|
useEffect(() => {
|
|
509
|
-
if (state.duration
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
517
|
+
if (state.duration <= 0) {
|
|
518
|
+
autoAdvanceRef.current = false;
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (state.progress < state.duration) {
|
|
522
|
+
autoAdvanceRef.current = false;
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (state.repeat === 'one') {
|
|
526
|
+
dispatch({ category: 'SEEK', position: 0 });
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const hasNextTrack = state.queue.length > 0 &&
|
|
530
|
+
(state.repeat === 'all' ||
|
|
531
|
+
state.queuePosition < state.queue.length - 1 ||
|
|
532
|
+
(state.shuffle && state.queue.length > 1));
|
|
533
|
+
if (!hasNextTrack) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const now = Date.now();
|
|
537
|
+
if (now - lastAutoNextRef.current < 1500) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (!autoAdvanceRef.current) {
|
|
541
|
+
autoAdvanceRef.current = true;
|
|
542
|
+
lastAutoNextRef.current = now;
|
|
543
|
+
dispatch({ category: 'NEXT' });
|
|
544
|
+
}
|
|
545
|
+
}, [
|
|
546
|
+
state.duration,
|
|
547
|
+
state.progress,
|
|
548
|
+
state.repeat,
|
|
549
|
+
state.queue.length,
|
|
550
|
+
state.queuePosition,
|
|
551
|
+
state.shuffle,
|
|
552
|
+
dispatch,
|
|
553
|
+
]);
|
|
554
|
+
// Smart autoplay: fetch suggestions when near end of queue
|
|
555
|
+
const fetchedForRef = useRef(null);
|
|
556
|
+
const isFetchingAutoplayRef = useRef(false);
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
if (!state.autoplay || !state.currentTrack || !state.isPlaying) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
// Already looping — no need to feed the queue
|
|
562
|
+
if (state.repeat === 'all' || (state.shuffle && state.queue.length > 1)) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
// Still enough tracks ahead — wait until we're close to the end
|
|
566
|
+
const tracksAhead = state.queue.length - state.queuePosition - 1;
|
|
567
|
+
if (tracksAhead > 5)
|
|
568
|
+
return;
|
|
569
|
+
// Already fetched for this track or a fetch is in flight
|
|
570
|
+
if (fetchedForRef.current === state.currentTrack.videoId ||
|
|
571
|
+
isFetchingAutoplayRef.current) {
|
|
572
|
+
return;
|
|
514
573
|
}
|
|
515
|
-
|
|
574
|
+
const trackId = state.currentTrack.videoId;
|
|
575
|
+
const trackTitle = state.currentTrack.title;
|
|
576
|
+
isFetchingAutoplayRef.current = true;
|
|
577
|
+
fetchedForRef.current = trackId;
|
|
578
|
+
musicService
|
|
579
|
+
.getSuggestions(trackId)
|
|
580
|
+
.then(suggestions => {
|
|
581
|
+
for (const track of suggestions) {
|
|
582
|
+
dispatch({ category: 'ADD_TO_QUEUE', track });
|
|
583
|
+
}
|
|
584
|
+
logger.info('PlayerManager', 'Autoplay: added suggestions', {
|
|
585
|
+
count: suggestions.length,
|
|
586
|
+
basedOn: trackTitle,
|
|
587
|
+
});
|
|
588
|
+
})
|
|
589
|
+
.catch((error) => {
|
|
590
|
+
isFetchingAutoplayRef.current = false;
|
|
591
|
+
fetchedForRef.current = null; // Allow retry on next track change
|
|
592
|
+
logger.warn('PlayerManager', 'Autoplay: failed to fetch suggestions', {
|
|
593
|
+
error: error instanceof Error ? error.message : String(error),
|
|
594
|
+
});
|
|
595
|
+
})
|
|
596
|
+
.finally(() => {
|
|
597
|
+
isFetchingAutoplayRef.current = false;
|
|
598
|
+
});
|
|
599
|
+
}, [
|
|
600
|
+
state.autoplay,
|
|
601
|
+
state.currentTrack,
|
|
602
|
+
state.isPlaying,
|
|
603
|
+
state.repeat,
|
|
604
|
+
state.shuffle,
|
|
605
|
+
state.queue.length,
|
|
606
|
+
state.queuePosition,
|
|
607
|
+
musicService,
|
|
608
|
+
dispatch,
|
|
609
|
+
]);
|
|
516
610
|
return null;
|
|
517
611
|
}
|
|
518
612
|
export function PlayerProvider({ children }) {
|
|
@@ -540,6 +634,7 @@ export function PlayerProvider({ children }) {
|
|
|
540
634
|
volume: persistedState.volume,
|
|
541
635
|
shuffle: persistedState.shuffle,
|
|
542
636
|
repeat: persistedState.repeat,
|
|
637
|
+
autoplay: persistedState.autoplay ?? true,
|
|
543
638
|
});
|
|
544
639
|
}
|
|
545
640
|
});
|
|
@@ -562,6 +657,7 @@ export function PlayerProvider({ children }) {
|
|
|
562
657
|
volume: state.volume,
|
|
563
658
|
shuffle: state.shuffle,
|
|
564
659
|
repeat: state.repeat,
|
|
660
|
+
autoplay: state.autoplay,
|
|
565
661
|
});
|
|
566
662
|
},
|
|
567
663
|
// Debounce progress updates (5s), immediate for track/queue changes
|
|
@@ -579,6 +675,7 @@ export function PlayerProvider({ children }) {
|
|
|
579
675
|
state.volume,
|
|
580
676
|
state.shuffle,
|
|
581
677
|
state.repeat,
|
|
678
|
+
state.autoplay,
|
|
582
679
|
]);
|
|
583
680
|
// Save immediately on unmount/quit
|
|
584
681
|
useEffect(() => {
|
|
@@ -593,6 +690,7 @@ export function PlayerProvider({ children }) {
|
|
|
593
690
|
volume: currentState.volume,
|
|
594
691
|
shuffle: currentState.shuffle,
|
|
595
692
|
repeat: currentState.repeat,
|
|
693
|
+
autoplay: currentState.autoplay,
|
|
596
694
|
});
|
|
597
695
|
};
|
|
598
696
|
process.on('beforeExit', handleExit);
|
|
@@ -661,6 +759,7 @@ export function PlayerProvider({ children }) {
|
|
|
661
759
|
},
|
|
662
760
|
toggleShuffle: () => dispatch({ category: 'TOGGLE_SHUFFLE' }),
|
|
663
761
|
toggleRepeat: () => dispatch({ category: 'TOGGLE_REPEAT' }),
|
|
762
|
+
toggleAutoplay: () => dispatch({ category: 'TOGGLE_AUTOPLAY' }),
|
|
664
763
|
setQueue: (queue) => dispatch({ category: 'SET_QUEUE', queue }),
|
|
665
764
|
addToQueue: (track) => dispatch({ category: 'ADD_TO_QUEUE', track }),
|
|
666
765
|
removeFromQueue: (index) => dispatch({ category: 'REMOVE_FROM_QUEUE', index }),
|
|
@@ -44,6 +44,9 @@ export interface ToggleShuffleAction {
|
|
|
44
44
|
export interface ToggleRepeatAction {
|
|
45
45
|
readonly category: 'TOGGLE_REPEAT';
|
|
46
46
|
}
|
|
47
|
+
export interface ToggleAutoplayAction {
|
|
48
|
+
readonly category: 'TOGGLE_AUTOPLAY';
|
|
49
|
+
}
|
|
47
50
|
export interface SetQueueAction {
|
|
48
51
|
readonly category: 'SET_QUEUE';
|
|
49
52
|
queue: Track[];
|
|
@@ -91,6 +94,7 @@ export interface RestoreStateAction {
|
|
|
91
94
|
volume: number;
|
|
92
95
|
shuffle: boolean;
|
|
93
96
|
repeat: 'off' | 'all' | 'one';
|
|
97
|
+
autoplay?: boolean;
|
|
94
98
|
}
|
|
95
99
|
export interface SetSpeedAction {
|
|
96
100
|
readonly category: 'SET_SPEED';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlayAction, PauseAction, ResumeAction, StopAction, NextAction, PreviousAction, SeekAction, SetVolumeAction, VolumeUpAction, VolumeDownAction, VolumeFineUpAction, VolumeFineDownAction, ToggleShuffleAction, ToggleRepeatAction, SetQueueAction, AddToQueueAction, RemoveFromQueueAction, ClearQueueAction, SetQueuePositionAction, UpdateProgressAction, SetDurationAction, TickAction, SetLoadingAction, SetErrorAction, RestoreStateAction, SetSpeedAction } from './actions.ts';
|
|
1
|
+
import type { PlayAction, PauseAction, ResumeAction, StopAction, NextAction, PreviousAction, SeekAction, SetVolumeAction, VolumeUpAction, VolumeDownAction, VolumeFineUpAction, VolumeFineDownAction, ToggleShuffleAction, ToggleRepeatAction, ToggleAutoplayAction, SetQueueAction, AddToQueueAction, RemoveFromQueueAction, ClearQueueAction, SetQueuePositionAction, UpdateProgressAction, SetDurationAction, TickAction, SetLoadingAction, SetErrorAction, RestoreStateAction, SetSpeedAction } from './actions.ts';
|
|
2
2
|
import type { Track } from './youtube-music.types.ts';
|
|
3
3
|
export interface PlayerState {
|
|
4
4
|
currentTrack: Track | null;
|
|
@@ -11,8 +11,9 @@ export interface PlayerState {
|
|
|
11
11
|
queuePosition: number;
|
|
12
12
|
repeat: 'off' | 'all' | 'one';
|
|
13
13
|
shuffle: boolean;
|
|
14
|
+
autoplay: boolean;
|
|
14
15
|
isLoading: boolean;
|
|
15
16
|
error: string | null;
|
|
16
17
|
playRequestId: number;
|
|
17
18
|
}
|
|
18
|
-
export type PlayerAction = PlayAction | PauseAction | ResumeAction | StopAction | NextAction | PreviousAction | SeekAction | SetVolumeAction | VolumeUpAction | VolumeDownAction | VolumeFineUpAction | VolumeFineDownAction | ToggleShuffleAction | ToggleRepeatAction | SetQueueAction | AddToQueueAction | RemoveFromQueueAction | ClearQueueAction | SetQueuePositionAction | UpdateProgressAction | SetDurationAction | TickAction | SetLoadingAction | SetErrorAction | RestoreStateAction | SetSpeedAction;
|
|
19
|
+
export type PlayerAction = PlayAction | PauseAction | ResumeAction | StopAction | NextAction | PreviousAction | SeekAction | SetVolumeAction | VolumeUpAction | VolumeDownAction | VolumeFineUpAction | VolumeFineDownAction | ToggleShuffleAction | ToggleRepeatAction | ToggleAutoplayAction | SetQueueAction | AddToQueueAction | RemoveFromQueueAction | ClearQueueAction | SetQueuePositionAction | UpdateProgressAction | SetDurationAction | TickAction | SetLoadingAction | SetErrorAction | RestoreStateAction | SetSpeedAction;
|
|
@@ -49,6 +49,7 @@ export declare const KEYBINDINGS: {
|
|
|
49
49
|
readonly VOLUME_FINE_DOWN: readonly ["shift+-"];
|
|
50
50
|
readonly SHUFFLE: readonly ["shift+s"];
|
|
51
51
|
readonly REPEAT: readonly ["r"];
|
|
52
|
+
readonly AUTOPLAY_TOGGLE: readonly ["shift+a"];
|
|
52
53
|
readonly GAPLESS_TOGGLE: readonly ["shift+g"];
|
|
53
54
|
readonly CROSSFADE_CYCLE: readonly ["shift+c"];
|
|
54
55
|
readonly EQUALIZER_CYCLE: readonly ["shift+e"];
|
package/dist/youtube-music-cli
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -27,6 +27,7 @@ A powerful Terminal User Interface (TUI) music player for YouTube Music
|
|
|
27
27
|
- 🖥️ **Headless Mode** - Run without TUI for scripting
|
|
28
28
|
- 💾 **Downloads** - Save tracks/playlists/artists with `Shift+D`
|
|
29
29
|
- 🏷️ **Metadata Tagging** - Auto-tag title/artist/album with optional cover art
|
|
30
|
+
- ⚡️ **Shell Completions** - `ymc completions <bash|zsh|powershell|fish>` emits scripts you can source or save so the CLI (also available as `ymc`) tab-completes subcommands and flags
|
|
30
31
|
|
|
31
32
|
## Roadmap
|
|
32
33
|
|
|
@@ -161,6 +162,28 @@ youtube-music-cli skip
|
|
|
161
162
|
youtube-music-cli back
|
|
162
163
|
```
|
|
163
164
|
|
|
165
|
+
### Shell completions
|
|
166
|
+
|
|
167
|
+
Generate shell completion helpers through the lightweight `ymc` alias that ships with the CLI. Run `ymc completions <bash|zsh|powershell|fish>` to print the completion script for your shell, then source it or persist it in your profile:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Bash
|
|
171
|
+
source <(ymc completions bash)
|
|
172
|
+
ymc completions bash >> ~/.bash_completion
|
|
173
|
+
|
|
174
|
+
# Zsh
|
|
175
|
+
source <(ymc completions zsh)
|
|
176
|
+
|
|
177
|
+
# PowerShell
|
|
178
|
+
ymc completions powershell | Out-File -Encoding utf8 $PROFILE
|
|
179
|
+
Invoke-Expression (ymc completions powershell)
|
|
180
|
+
|
|
181
|
+
# Fish
|
|
182
|
+
ymc completions fish > ~/.config/fish/completions/ymc.fish
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
If you installed the CLI globally with an alias or script name, make sure `ymc` points at the same binary before generating completions so that the script matches your install path.
|
|
186
|
+
|
|
164
187
|
### Options
|
|
165
188
|
|
|
166
189
|
| Flag | Short | Description |
|