16
16
xFlag = flag .Bool ("x" , false , "Return the song.link URL without surrounding <>" )
17
17
dFlag = flag .Bool ("d" , false , "Return the song.link URL surrounded by <> and the Spotify URL" )
18
18
sFlag = flag .Bool ("s" , false , "Return only the Spotify URL" )
19
+ hFlag = flag .Bool ("h" , false , "Show help information" )
20
+ helpFlag = flag .Bool ("help" , false , "Show help information" )
19
21
)
20
22
21
23
// Command represents a CLI command
@@ -53,6 +55,12 @@ func main() {
53
55
// Define base flags
54
56
flag .Parse ()
55
57
58
+ // Check for help flags
59
+ if * hFlag || * helpFlag {
60
+ printHelp ("" )
61
+ os .Exit (0 )
62
+ }
63
+
56
64
// Check if a subcommand is provided
57
65
args := flag .Args ()
58
66
if len (args ) > 0 {
@@ -70,9 +78,15 @@ func main() {
70
78
}
71
79
}
72
80
81
+ // Check if asking for help on a subcommand
82
+ if subcommand == "help" && len (args ) > 1 {
83
+ printHelp (args [1 ])
84
+ os .Exit (0 )
85
+ }
86
+
73
87
// If we get here, the subcommand wasn't recognized
74
88
fmt .Printf ("Unknown command: %s\n \n " , subcommand )
75
- printUsage ( )
89
+ printHelp ( "" )
76
90
os .Exit (1 )
77
91
}
78
92
@@ -91,12 +105,20 @@ func executeSearch(args []string) error {
91
105
typeFlag := searchCmd .String ("type" , "song" , "Type of search: song, album, or both (default: song)" )
92
106
outFlag := searchCmd .String ("out" , "downloads" , "Output directory for downloaded files" )
93
107
debugFlag := searchCmd .Bool ("debug" , false , "Enable debug logging during download" )
108
+ helpFlag := searchCmd .Bool ("help" , false , "Show help for search command" )
109
+ hFlag := searchCmd .Bool ("h" , false , "Show help for search command" )
94
110
95
111
// Parse search flags
96
112
if err := searchCmd .Parse (args ); err != nil {
97
113
return err
98
114
}
99
115
116
+ // Check for help
117
+ if * helpFlag || * hFlag {
118
+ printSearchHelp ()
119
+ os .Exit (0 )
120
+ }
121
+
100
122
// Get search query
101
123
searchArgs := searchCmd .Args ()
102
124
if len (searchArgs ) == 0 {
@@ -123,6 +145,22 @@ func executeSearch(args []string) error {
123
145
124
146
// executeConfig handles the config subcommand
125
147
func executeConfig (args []string ) error {
148
+ // Define config flags
149
+ configCmd := flag .NewFlagSet ("config" , flag .ExitOnError )
150
+ helpFlag := configCmd .Bool ("help" , false , "Show help for config command" )
151
+ hFlag := configCmd .Bool ("h" , false , "Show help for config command" )
152
+
153
+ // Parse flags
154
+ if err := configCmd .Parse (args ); err != nil {
155
+ return err
156
+ }
157
+
158
+ // Check for help
159
+ if * helpFlag || * hFlag {
160
+ printConfigHelp ()
161
+ os .Exit (0 )
162
+ }
163
+
126
164
fmt .Println ("Configuring Apple Music API credentials..." )
127
165
return RunOnboarding ()
128
166
}
@@ -135,11 +173,19 @@ func executeDownload(args []string) error {
135
173
formatFlag := downloadCmd .String ("format" , "mp3" , "Download format: mp3 or mp4 (default: mp3)" )
136
174
outFlag := downloadCmd .String ("out" , "downloads" , "Output directory for downloaded files" )
137
175
debugFlag := downloadCmd .Bool ("debug" , false , "Enable debug logging (show yt-dlp/ffmpeg output)" )
176
+ helpFlag := downloadCmd .Bool ("help" , false , "Show help for download command" )
177
+ hFlag := downloadCmd .Bool ("h" , false , "Show help for download command" )
138
178
139
179
// Parse flags
140
180
if err := downloadCmd .Parse (args ); err != nil {
141
181
return err
142
182
}
183
+
184
+ // Check for help
185
+ if * helpFlag || * hFlag {
186
+ printDownloadHelp ()
187
+ os .Exit (0 )
188
+ }
143
189
144
190
// Get search query
145
191
queryArgs := downloadCmd .Args ()
@@ -215,11 +261,19 @@ func executePlaylist(args []string) error {
215
261
concurrentFlag := playlistCmd .Int ("concurrent" , 3 , "Number of parallel downloads (default: 3)" )
216
262
metadataFlag := playlistCmd .Bool ("metadata" , false , "Save playlist metadata JSON" )
217
263
debugFlag := playlistCmd .Bool ("debug" , false , "Enable debug logging" )
264
+ helpFlag := playlistCmd .Bool ("help" , false , "Show help for playlist command" )
265
+ hFlag := playlistCmd .Bool ("h" , false , "Show help for playlist command" )
218
266
219
267
// Parse flags
220
268
if err := playlistCmd .Parse (args ); err != nil {
221
269
return err
222
270
}
271
+
272
+ // Check for help
273
+ if * helpFlag || * hFlag {
274
+ printPlaylistHelp ()
275
+ os .Exit (0 )
276
+ }
223
277
224
278
// Get URL argument
225
279
urlArgs := playlistCmd .Args ()
@@ -388,26 +442,218 @@ func runDefault() error {
388
442
return nil
389
443
}
390
444
391
- // printUsage prints usage information
392
- func printUsage () {
393
- fmt .Println ("Usage:" )
394
- fmt .Println (" songlink-cli [flags] Process URL from clipboard" )
395
- fmt .Println (" songlink-cli search [flags] <query> Search for a song or album" )
396
- fmt .Println (" songlink-cli config Configure Apple Music API credentials" )
397
- fmt .Println (" songlink-cli download [flags] <query> Download a song or album" )
398
- fmt .Println (" songlink-cli playlist [flags] <url> Download entire playlist/album" )
399
- fmt .Println ("\n Flags:" )
400
- fmt .Println (" -x Return the song.link URL without surrounding <>" )
401
- fmt .Println (" -d Return the song.link URL surrounded by <> and the Spotify URL" )
402
- fmt .Println (" -s Return only the Spotify URL" )
403
- fmt .Println ("\n Search Flags:" )
404
- fmt .Println (" -type=<type> Type of search: song, album, or both (default: song)" )
405
- fmt .Println ("\n Playlist Flags:" )
406
- fmt .Println (" --format=<fmt> Download format: mp3 or mp4 (default: mp3)" )
407
- fmt .Println (" --out=<dir> Output directory (default: downloads)" )
408
- fmt .Println (" --concurrent=<n> Number of parallel downloads (default: 3)" )
409
- fmt .Println (" --metadata Save playlist metadata JSON" )
410
- fmt .Println (" --debug Show debug output" )
445
+ // printHelp prints comprehensive help information
446
+ func printHelp (command string ) {
447
+ switch command {
448
+ case "search" :
449
+ printSearchHelp ()
450
+ case "download" :
451
+ printDownloadHelp ()
452
+ case "playlist" :
453
+ printPlaylistHelp ()
454
+ case "config" :
455
+ printConfigHelp ()
456
+ default :
457
+ printGeneralHelp ()
458
+ }
459
+ }
460
+
461
+ // printGeneralHelp prints the main help screen
462
+ func printGeneralHelp () {
463
+ fmt .Println ("Songlink CLI - A powerful tool for music sharing and downloading" )
464
+ fmt .Println ("" )
465
+ fmt .Println ("USAGE:" )
466
+ fmt .Println (" songlink-cli [flags] Process URL from clipboard" )
467
+ fmt .Println (" songlink-cli <command> [flags] <args> Run a specific command" )
468
+ fmt .Println (" songlink-cli help <command> Show help for a command" )
469
+ fmt .Println ("" )
470
+ fmt .Println ("COMMANDS:" )
471
+ fmt .Println (" search Search for songs/albums and get shareable links" )
472
+ fmt .Println (" download Search and download tracks as MP3 or MP4 files" )
473
+ fmt .Println (" playlist Download entire playlists or albums from Apple Music" )
474
+ fmt .Println (" config Configure Apple Music API credentials" )
475
+ fmt .Println ("" )
476
+ fmt .Println ("GLOBAL FLAGS:" )
477
+ fmt .Println (" -h, --help Show this help message" )
478
+ fmt .Println ("" )
479
+ fmt .Println ("URL PROCESSING FLAGS (when run without command):" )
480
+ fmt .Println (" -x Return song.link URL without <> brackets (for Twitter)" )
481
+ fmt .Println (" -d Return song.link URL with <> + Spotify URL (for Discord)" )
482
+ fmt .Println (" -s Return only the Spotify URL" )
483
+ fmt .Println ("" )
484
+ fmt .Println ("EXAMPLES:" )
485
+ fmt .Println (" # Process URL from clipboard (default)" )
486
+ fmt .Println (" songlink-cli" )
487
+ fmt .Println ("" )
488
+ fmt .Println (" # Get link for Twitter sharing" )
489
+ fmt .Println (" songlink-cli -x" )
490
+ fmt .Println ("" )
491
+ fmt .Println (" # Search for a song" )
492
+ fmt .Println (" songlink-cli search \" Bohemian Rhapsody\" " )
493
+ fmt .Println ("" )
494
+ fmt .Println (" # Download a track as MP4 with artwork" )
495
+ fmt .Println (" songlink-cli download -format=mp4 \" Purple Rain\" " )
496
+ fmt .Println ("" )
497
+ fmt .Println (" # Download an entire album" )
498
+ fmt .Println (" songlink-cli playlist \" https://music.apple.com/us/album/...\" " )
499
+ fmt .Println ("" )
500
+ fmt .Println ("For more information on a command, run:" )
501
+ fmt .Println (" songlink-cli help <command>" )
502
+ }
503
+
504
+ // printSearchHelp prints help for the search command
505
+ func printSearchHelp () {
506
+ fmt .Println ("songlink-cli search - Search for songs or albums and get shareable links" )
507
+ fmt .Println ("" )
508
+ fmt .Println ("USAGE:" )
509
+ fmt .Println (" songlink-cli search [flags] <query>" )
510
+ fmt .Println ("" )
511
+ fmt .Println ("DESCRIPTION:" )
512
+ fmt .Println (" Search Apple Music for songs or albums and interactively select a result." )
513
+ fmt .Println (" After selection, you can choose to:" )
514
+ fmt .Println (" 1) Copy shareable links to clipboard" )
515
+ fmt .Println (" 2) Download the track as MP3" )
516
+ fmt .Println (" 3) Download as MP4 video with album artwork" )
517
+ fmt .Println ("" )
518
+ fmt .Println ("FLAGS:" )
519
+ fmt .Println (" -type=<type> Search type: song, album, or both (default: song)" )
520
+ fmt .Println (" -out=<dir> Output directory for downloads (default: downloads)" )
521
+ fmt .Println (" -debug Enable debug logging during download" )
522
+ fmt .Println ("" )
523
+ fmt .Println ("GLOBAL FLAGS (when copying links):" )
524
+ fmt .Println (" -x Format link for Twitter (no brackets)" )
525
+ fmt .Println (" -d Format for Discord (with Spotify URL)" )
526
+ fmt .Println (" -s Copy only Spotify URL" )
527
+ fmt .Println ("" )
528
+ fmt .Println ("EXAMPLES:" )
529
+ fmt .Println (" # Search for a song" )
530
+ fmt .Println (" songlink-cli search \" Imagine\" " )
531
+ fmt .Println ("" )
532
+ fmt .Println (" # Search for albums only" )
533
+ fmt .Println (" songlink-cli search -type=album \" Dark Side of the Moon\" " )
534
+ fmt .Println ("" )
535
+ fmt .Println (" # Search and format for Discord" )
536
+ fmt .Println (" songlink-cli search -d \" Hotel California\" " )
537
+ fmt .Println ("" )
538
+ fmt .Println ("REQUIREMENTS:" )
539
+ fmt .Println (" - Apple Music API credentials (run 'songlink-cli config' to set up)" )
540
+ fmt .Println (" - For downloads: yt-dlp and ffmpeg must be installed" )
541
+ }
542
+
543
+ // printDownloadHelp prints help for the download command
544
+ func printDownloadHelp () {
545
+ fmt .Println ("songlink-cli download - Search and download tracks directly" )
546
+ fmt .Println ("" )
547
+ fmt .Println ("USAGE:" )
548
+ fmt .Println (" songlink-cli download [flags] <query>" )
549
+ fmt .Println ("" )
550
+ fmt .Println ("DESCRIPTION:" )
551
+ fmt .Println (" Search for a song or album and download it immediately as an audio" )
552
+ fmt .Println (" file (MP3) or video file with album artwork (MP4). This command" )
553
+ fmt .Println (" combines search and download into a single step." )
554
+ fmt .Println ("" )
555
+ fmt .Println ("FLAGS:" )
556
+ fmt .Println (" -type=<type> Search type: song or album (default: song)" )
557
+ fmt .Println (" -format=<fmt> Download format: mp3 or mp4 (default: mp3)" )
558
+ fmt .Println (" -out=<dir> Output directory (default: downloads)" )
559
+ fmt .Println (" -debug Show yt-dlp and ffmpeg output" )
560
+ fmt .Println ("" )
561
+ fmt .Println ("EXAMPLES:" )
562
+ fmt .Println (" # Download a song as MP3" )
563
+ fmt .Println (" songlink-cli download \" Stairway to Heaven\" " )
564
+ fmt .Println ("" )
565
+ fmt .Println (" # Download as MP4 with album artwork" )
566
+ fmt .Println (" songlink-cli download -format=mp4 \" Wonderwall\" " )
567
+ fmt .Println ("" )
568
+ fmt .Println (" # Download to custom directory" )
569
+ fmt .Println (" songlink-cli download -out=~/Music \" Yesterday\" " )
570
+ fmt .Println ("" )
571
+ fmt .Println ("REQUIREMENTS:" )
572
+ fmt .Println (" - Apple Music API credentials (run 'songlink-cli config' to set up)" )
573
+ fmt .Println (" - yt-dlp: For downloading audio from YouTube" )
574
+ fmt .Println (" - ffmpeg: For audio/video processing" )
575
+ fmt .Println ("" )
576
+ fmt .Println ("INSTALLATION (macOS):" )
577
+ fmt .Println (" brew install yt-dlp ffmpeg" )
578
+ }
579
+
580
+ // printPlaylistHelp prints help for the playlist command
581
+ func printPlaylistHelp () {
582
+ fmt .Println ("songlink-cli playlist - Download entire playlists or albums" )
583
+ fmt .Println ("" )
584
+ fmt .Println ("USAGE:" )
585
+ fmt .Println (" songlink-cli playlist [flags] <apple-music-url>" )
586
+ fmt .Println ("" )
587
+ fmt .Println ("DESCRIPTION:" )
588
+ fmt .Println (" Download all tracks from an Apple Music playlist or album URL." )
589
+ fmt .Println (" Supports parallel downloads and automatic retry on failures." )
590
+ fmt .Println ("" )
591
+ fmt .Println ("SUPPORTED CONTENT:" )
592
+ fmt .Println (" ✓ Public catalog albums" )
593
+ fmt .Println (" ✓ Public catalog playlists" )
594
+ fmt .Println (" ✗ Personal library playlists" )
595
+ fmt .Println (" ✗ User-created playlists" )
596
+ fmt .Println (" ✗ Radio stations" )
597
+ fmt .Println ("" )
598
+ fmt .Println ("FLAGS:" )
599
+ fmt .Println (" --format=<fmt> Download format: mp3 or mp4 (default: mp3)" )
600
+ fmt .Println (" --out=<dir> Output directory (default: downloads)" )
601
+ fmt .Println (" --concurrent=<n> Parallel downloads, 1-10 (default: 3)" )
602
+ fmt .Println (" --metadata Save playlist/album info as JSON" )
603
+ fmt .Println (" --debug Show detailed progress and errors" )
604
+ fmt .Println ("" )
605
+ fmt .Println ("EXAMPLES:" )
606
+ fmt .Println (" # Download an album" )
607
+ fmt .Println (" songlink-cli playlist \" https://music.apple.com/us/album/abbey-road/401469823\" " )
608
+ fmt .Println ("" )
609
+ fmt .Println (" # Download playlist with metadata" )
610
+ fmt .Println (" songlink-cli playlist --metadata \" https://music.apple.com/playlist/...\" " )
611
+ fmt .Println ("" )
612
+ fmt .Println (" # Fast download with 5 workers" )
613
+ fmt .Println (" songlink-cli playlist --concurrent=5 --format=mp4 \" https://...\" " )
614
+ fmt .Println ("" )
615
+ fmt .Println ("FEATURES:" )
616
+ fmt .Println (" - Progress tracking for each download" )
617
+ fmt .Println (" - Automatic retry with exponential backoff" )
618
+ fmt .Println (" - Saves metadata including track status" )
619
+ fmt .Println (" - Creates organized directory structure" )
620
+ fmt .Println ("" )
621
+ fmt .Println ("TROUBLESHOOTING:" )
622
+ fmt .Println (" 404 errors: Content may be region-locked, try different" )
623
+ fmt .Println (" storefront in URL (e.g., /us/, /gb/, /jp/)" )
624
+ }
625
+
626
+ // printConfigHelp prints help for the config command
627
+ func printConfigHelp () {
628
+ fmt .Println ("songlink-cli config - Configure Apple Music API credentials" )
629
+ fmt .Println ("" )
630
+ fmt .Println ("USAGE:" )
631
+ fmt .Println (" songlink-cli config" )
632
+ fmt .Println ("" )
633
+ fmt .Println ("DESCRIPTION:" )
634
+ fmt .Println (" Interactive setup wizard for Apple Music API credentials." )
635
+ fmt .Println (" Required for search, download, and playlist features." )
636
+ fmt .Println ("" )
637
+ fmt .Println ("WHAT YOU'LL NEED:" )
638
+ fmt .Println (" 1. Apple Developer account" )
639
+ fmt .Println (" 2. MusicKit-enabled API key" )
640
+ fmt .Println (" 3. Team ID and Key ID" )
641
+ fmt .Println (" 4. Private key (.p8 file)" )
642
+ fmt .Println ("" )
643
+ fmt .Println ("SETUP STEPS:" )
644
+ fmt .Println (" 1. Sign in to https://developer.apple.com" )
645
+ fmt .Println (" 2. Go to Certificates, Identifiers & Profiles" )
646
+ fmt .Println (" 3. Under Keys, create a new key" )
647
+ fmt .Println (" 4. Enable MusicKit service" )
648
+ fmt .Println (" 5. Download the .p8 private key file" )
649
+ fmt .Println (" 6. Note your Team ID and Key ID" )
650
+ fmt .Println ("" )
651
+ fmt .Println ("STORED LOCATION:" )
652
+ fmt .Println (" ~/.songlink-cli/config.json" )
653
+ fmt .Println ("" )
654
+ fmt .Println ("SECURITY:" )
655
+ fmt .Println (" Credentials are stored locally and never transmitted" )
656
+ fmt .Println (" except to Apple's API servers." )
411
657
}
412
658
413
659
func loadingIndicator (stop chan bool ) {
0 commit comments