yt-cli-terminal 1.4.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ping-Phantom39
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,442 @@
1
+ # ⚑ YouTube Terminal Suite (yt-song-cli)
2
+ [![SS of yt-player engine](demo.png)](https://youtu.be/aq0VeB1LSt8?si=epWLrQFFAEb2CtD4)
3
+
4
+
5
+ [![Go Version](https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat-square&logo=go)](https://golang.org/)
6
+ [![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)
7
+ [![Platform Support](https://img.shields.io/badge/platform-linux%20%7C%20macos-lightgrey?style=flat-square)](https://github.com/Ping-Phantom39/yt-cli)
8
+ [![Built with Bubble Tea](https://img.shields.io/badge/built%20with-Bubble%20Tea-magenta?style=flat-square)](https://github.com/charmbracelet/bubbletea)
9
+
10
+ ## πŸ–₯️ Supported Operating Systems
11
+
12
+ The tools are written in Go and rely on native audio/video back‑ends. They have been tested and are officially supported on:
13
+
14
+ - **Linux** (Ubuntu, Debian, Fedora, etc.) – requires ALSA/PulseAudio/pipewire libraries.
15
+ - **macOS** – uses CoreAudio via the `beep` library.
16
+ - **Windows** – builds with CGO and uses WASAPI; however the current CI and packaging focus on Linux/macOS.
17
+
18
+ For other platforms you may need to install the required audio/video dependencies manually.
19
+
20
+ Welcome to the **YouTube Terminal Suite** (`yt-song-cli`), a professional monorepo housing two high-performance, keyboard-driven Go CLI applications styled with a sleek cyberpunk aesthetic. The suite features a video streaming player (`ytplayer`) and an offline audio player/downloader (`ytmusic`), providing the ultimate terminal-native YouTube media experience.
21
+
22
+ ---
23
+
24
+ ## 🧭 Monorepo Overview
25
+
26
+ This repository contains two decoupled, highly modular Go utilities:
27
+
28
+ | Component | Target Directory | Binary Name | Description | Key Technologies |
29
+ |---|---|---|---|---|
30
+ | **Video Client** | [`/yt-player`](yt-player) | `ytplayer` | Cyberpunk terminal video searcher, downloader, and mpv-backed streaming player. | `yt-dlp`, Bubble Tea, `mpv` |
31
+ | **Audio Client** | [`/yt-song`](yt-song) | `ytmusic` | Highly optimized music player with low-level audio streaming and local MP3 caching. | `yt-dlp`, Bubble Tea, `gopxl/beep` |
32
+
33
+ ---
34
+
35
+ ## 🎨 System Architecture
36
+
37
+ The following diagram illustrates how the CLI tools leverage standard Go layouts and external binaries for media scraping, multiplexing, transcoding, and low-level playback:
38
+
39
+
40
+ ```mermaid
41
+ graph TD
42
+ subgraph Monorepo["yt-song-cli Workspace"]
43
+ direction TB
44
+ subgraph YTPlayer["ytplayer (Video Client)"]
45
+ VP_Cmd["Cobra CLI Bootstrapper"] --> VP_UI["Bubble Tea UI"]
46
+ VP_UI --> VP_DL["Downloader Engine"]
47
+ VP_UI --> VP_Exec["mpv Exec Process"]
48
+ end
49
+
50
+ subgraph YTMusic["ytmusic (Audio Client)"]
51
+ MU_Cmd["Cobra CLI Bootstrapper"] --> MU_UI["Bubble Tea UI"]
52
+ MU_UI --> MU_DL["Downloader Engine"]
53
+ MU_UI --> MU_Play["Beep Audio Engine"]
54
+ end
55
+ end
56
+
57
+ %% External Processes
58
+ VP_DL -->|exec.Command| YTDLP["yt-dlp Binary"]
59
+ MU_DL -->|exec.Command| YTDLP
60
+ YTDLP -->|Raw Streams| FFMPEG["ffmpeg Transcoder"]
61
+ FFMPEG -->|Merge MP4| VP_Local["Local downloads / Cache"]
62
+ FFMPEG -->|Extract MP3| MU_Local["Local downloads / Cache"]
63
+
64
+ VP_Exec -->|Stream Video/Audio| MPV["mpv Engine"]
65
+ MU_Play -->|Volume & Resampling| Speaker["gopxl/beep Speaker"]
66
+ Speaker -->|Audio Output| Output["OS Sound Output"]
67
+ ```
68
+
69
+ ### βš™οΈ Deep-Dive Engine Architecture (Under the Hood)
70
+
71
+ #### 1. πŸš€ Cobra CLI Bootstrapper (`cmd/`)
72
+ * **Flag & Argument Parsing**: Built on top of `spf13/cobra`, the bootstrapper parses runtime configuration parameters (`--limit`, `--cookies`, `--cookies-from-browser`, `--vo`, `--check`) and environment overrides.
73
+ * **Environment & Dependency Check**: Executes pre-flight scans verifying that external binaries (`yt-dlp`, `ffmpeg`, `mpv`) and CGO audio headers exist on the host system before spawning the interface.
74
+ * **TUI Lifecyle Management**: Initializes application state structs and bootstraps the main event loop.
75
+
76
+ #### 2. πŸ–₯️ Bubble Tea Reactive TUI Engine (`internal/ui/`)
77
+ * **Model-View-Update (MVU) Loop**: Powered by `charmbracelet/bubbletea` (Elm architecture pattern). User keystrokes are received as `tea.Msg` events, processed in pure `Update()` functions, and rendered deterministically in `View()`.
78
+ * **Asynchronous Command Orchestration**: Non-blocking network queries and media downloads run in isolated goroutines, dispatching `tea.Cmd` response messages back into the main event loop without freezing user interface animations.
79
+ * **Styling & Layout Rendering**: Uses `charmbracelet/lipgloss` for cyberpunk neon borders, HSL gradient text, and dynamic viewport calculation, alongside `charmbracelet/bubbles` for text input inputs and progress bar components.
80
+
81
+ #### 3. πŸ“₯ Asynchronous Downloader Engine (`internal/downloader/`)
82
+ * **Metadata Extraction**: Spawns sub-processes of `yt-dlp` using Go's `os/exec.Command` with flags (`--dump-json`, `--flat-playlist`). Reads standard output streams in real-time to parse JSON video metadata (ID, title, channel, duration, view count) into strongly-typed Go structs.
83
+ * **Transcoding & Multiplexing Pipe**:
84
+ * **Video (`ytplayer`)**: Invokes `yt-dlp` to download best-quality separate video and audio streams, piping them to `ffmpeg` for container multiplexing into high-definition `.mp4` files inside `downloads/`.
85
+ * **Audio (`ytmusic`)**: Triggers `yt-dlp` audio extraction and uses `ffmpeg` to transcode streams into `.mp3` files formatted specifically for low-latency PCM decoding.
86
+ * **Real-time Progress Parsing**: Intercepts `stdout` lines matching percentage regex patterns (`[download] XX.X%`), emitting real-time progress update messages to the TUI progress bar component.
87
+
88
+ #### 4. 🎬 `mpv` Video Process Controller (`yt-player/internal/player/`)
89
+ * **Terminal Handover Protocol**: Uses Bubble Tea's `tea.ExecProcess` wrapper. When a user streams a video, the TUI loop is cleanly suspended, handing standard input/output over to the native `mpv` binary.
90
+ * **Hardware & Terminal Video Drivers**: Leverages `mpv`'s video output (`--vo`) capabilities, allowing seamless switching between external GUI windows (`gpu`, `x11`) and terminal graphics rendering (`tct`, `sixel`, `kitty`).
91
+ * **Auto-Resume**: Upon `mpv` exit or termination signal, the terminal buffer is restored, and the Bubble Tea TUI seamlessly resumes playback state.
92
+
93
+ #### 5. πŸ”Š Beep Audio Engine & CGO Speaker (`yt-song/internal/player/`)
94
+ * **PCM Buffer Decoding**: Built using `gopxl/beep`. Reads local `.mp3` audio files and streams them into floating-point PCM audio buffers.
95
+ * **Logarithmic Volume Control**: Rather than simple linear volume scaling, volume adjustments use a logarithmic scale (`math.Pow`) to match natural human auditory perception curves.
96
+ * **Low-Level OS Sound Integration**: Uses CGO bindings to interface directly with OS native sound system APIs:
97
+ * **Linux**: ALSA (`libasound2`) / PulseAudio / PipeWire.
98
+ * **macOS**: CoreAudio.
99
+ * **Windows**: WASAPI.
100
+
101
+ ---
102
+
103
+ ## πŸš€ System Requirements & Setup
104
+
105
+ Before compiling, make sure your operating system has the necessary external libraries and executable tools installed.
106
+
107
+ ### 1. External Media CLI Utilities
108
+ Both applications require **`yt-dlp`** and **`ffmpeg`**. In addition, `ytplayer` requires **`mpv`**.
109
+
110
+ #### **Debian/Ubuntu**
111
+ ```bash
112
+ sudo apt-get update
113
+ sudo apt-get install -y mpv ffmpeg nodejs
114
+ ```
115
+
116
+ #### **macOS (via Homebrew)**
117
+ ```bash
118
+ brew install mpv ffmpeg nodejs
119
+ ```
120
+
121
+ > [!TIP]
122
+ > Node.js or Deno are optional but highly recommended to help `yt-dlp` bypass YouTube's signature bot blocks.
123
+
124
+ #### **Installing/Updating `yt-dlp` (Recommended)**
125
+ Ensure you have the latest `yt-dlp` build to handle changing YouTube API formats. The tools will check `./bin/yt-dlp` first, falling back to your system `$PATH`.
126
+ ```bash
127
+ sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
128
+ sudo chmod a+rx /usr/local/bin/yt-dlp
129
+ ```
130
+
131
+ ### 2. Audio Library Headers (Linux Only - Required for compilation)
132
+ `ytmusic` compiles low-level Go bindings to talk directly to your system's ALSA speakers:
133
+
134
+ * **Debian/Ubuntu**:
135
+ ```bash
136
+ sudo apt-get install -y libasound2-dev
137
+ ```
138
+ * **Fedora/CentOS/RHEL**:
139
+ ```bash
140
+ sudo dnf install alsa-lib-devel
141
+ ```
142
+ * **macOS**: No additional headers needed (native CoreAudio support).
143
+
144
+ ---
145
+
146
+ ## πŸ› οΈ Compilation & Installation
147
+
148
+ ### πŸ“¦ Download via Ubuntu PPA
149
+
150
+ ```bash
151
+ sudo add-apt-repository ppa:kamalchad/ytmusic
152
+ sudo apt update
153
+ sudo apt install ytmusic
154
+ ```
155
+
156
+ > [!WARNING]
157
+ > **Disclaimer**: Installing via PPA automatically includes all necessary packages (`yt-dlp`, `mpv`, `ffmpeg`, `libasound2-dev`) along with the binary, which may be **600 – 700 MB** in total download size.
158
+
159
+ #### ⚠️ Possible Errors & Troubleshooting
160
+
161
+ * **Outdated `yt-dlp` Version (`403 Forbidden` / Bot Block Errors)**:
162
+ System or PPA repositories may package an older version of `yt-dlp` that causes playback/search failures with YouTube API changes. Upgrade `yt-dlp` to the latest release using pip:
163
+ ```bash
164
+ python3 -m pip install -U yt-dlp
165
+ ```
166
+ or update directly via official GitHub binary:
167
+ ```bash
168
+ sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
169
+ sudo chmod a+rx /usr/local/bin/yt-dlp
170
+ ```
171
+
172
+ * **Missing Audio Backend Libraries**:
173
+ If audio output or CGO bindings fail to launch, ensure sound packages are installed:
174
+ ```bash
175
+ sudo apt-get install -y libasound2-dev alsa-utils
176
+ ```
177
+
178
+ ---
179
+
180
+ ### πŸ“¦ Download via npm
181
+
182
+ ```bash
183
+ npm install -g yt-cli-terminal
184
+ ytplayer --check
185
+ ```
186
+
187
+ This installs the `ytplayer`/`ytcli` (video) and `ytmusic`/`ytsong` (audio) commands. The postinstall step downloads the prebuilt binaries for your platform from the matching GitHub release and verifies their SHA-256 checksums. Supported platforms follow the release matrix (Linux amd64, macOS amd64/arm64, Windows amd64); on Debian/Ubuntu arm64 use the `.deb` asset instead.
188
+
189
+ > [!NOTE]
190
+ > npm only delivers the two Go binaries. The runtime dependencies must still be on your `PATH`: **`mpv`** (video playback), **`ffmpeg`** and **`yt-dlp`** (both tools) β€” see [System Requirements](#-system-requirements--setup). Run `ytplayer --check` (or `ytmusic --check`) to verify.
191
+
192
+ ---
193
+
194
+ ### 🎡 `ytmusic` (Audio Client via Script)
195
+ ```bash
196
+ curl -fsSL https://raw.githubusercontent.com/Ping-Phantom39/yt-cli/main/yt-song/scripts/install.sh | bash
197
+ ```
198
+
199
+ ---
200
+
201
+ ### 🎬 `ytplayer` (Video Client via Script)
202
+ ```bash
203
+ curl -fsSL https://raw.githubusercontent.com/Ping-Phantom39/yt-cli/main/yt-player/scripts/install.sh | bash
204
+ ```
205
+
206
+ ### Manual Compilation from Source
207
+ Ensure you have **Go 1.22+** installed on your system. Navigate to the desired module directory to build:
208
+
209
+ #### Build `ytplayer`
210
+ ```bash
211
+ cd yt-player
212
+ go build -o ../bin/ytplayer main.go
213
+ ```
214
+
215
+ #### Build `ytmusic`
216
+ ```bash
217
+ cd yt-song
218
+ go build -o ../bin/ytmusic main.go
219
+ ```
220
+
221
+ ---
222
+
223
+ ## πŸ•ΉοΈ Interactive Controls & Usage
224
+
225
+ ### Run ytmusic from anywhere(global access)
226
+
227
+
228
+ ### 1. `ytplayer` (Video Client)
229
+
230
+ Start the player and launch the interactive terminal interface:
231
+ ```bash
232
+ ./bin/ytplayer
233
+ ```
234
+ Or search directly from startup:
235
+ ```bash
236
+ ./bin/ytplayer "cyberpunk synthwave lofi"
237
+ ```
238
+
239
+ #### **Options and Flags**
240
+ * `-l, --limit int`: Maximum search results to query (default: `15`).
241
+ * `-q, --quality string`: Max playback/download quality (e.g. `best`, `1080`, `720`, `480`, `360`, `audio`) (default: `best`).
242
+ * `-m, --local`: Start directly in Local Offline Video mode.
243
+ * `--cookies string`: Custom file path containing exported session cookies.
244
+ * `--cookies-from-browser string`: Extract session cookies directly from a specific browser (e.g. `chrome`, `firefox`, `safari`, `brave`, `edge`).
245
+ * `--vo string`: Override the `mpv` video output driver (e.g., `tct`, `sixel`, `kitty`, `gpu`). Useful for streaming inside headless/SSH sessions.
246
+ * `--check`: Quick check of local media tools and dependency status.
247
+
248
+ #### **TUI Keyboard Shortcuts**
249
+ * `[/]` — Focus the search bar to enter queries or filter local files.
250
+ * `[m]` — Toggle between **Online YouTube Mode** and **Local Offline Video Mode**.
251
+ * `[v]` or `[Tab]` — Cycle through video quality presets (**Best/4K** βž” **1080p** βž” **720p** βž” **480p** βž” **360p** βž” **Audio Only 🎡**).
252
+ * `[1] - [6]` — Quick jump to quality preset (`1: 4K/Best`, `2: 1080p`, `3: 720p`, `4: 480p`, `5: 360p`, `6: Audio Only`).
253
+ * `[Enter]` — Stream the highlighted video using `mpv` with the selected quality preset.
254
+ * `[d]` — Background download the video as high-quality `.mp4` into `downloads/`.
255
+ * `[Esc]` — Unfocus search bar and return to result browsing.
256
+ * `[q]` or `[Ctrl+C]` — Quit the application.
257
+
258
+ ---
259
+
260
+ ### 2. `ytmusic` (Audio Client)
261
+
262
+ Start the music player:
263
+ ```bash
264
+ ./bin/ytmusic
265
+ ```
266
+ Or start with a search query:
267
+ ```bash
268
+ ./bin/ytmusic "lofi beats for studying"
269
+ ```
270
+
271
+ #### **Options and Flags**
272
+ * `-l, --limit int`: Maximum search results to query (default: `15`).
273
+ * `-m, --local`: Start directly in Local Offline Music mode.
274
+ * `-v, --volume float`: Starting volume level (0.0 to 1.0) (default: `0.8`).
275
+ * `--cookies string`: Custom Netscape format cookies file path.
276
+ * `--cookies-from-browser string`: Load cookies from a browser profile to avoid bot bans.
277
+ * `--check`: Verify local configuration, FFmpeg, yt-dlp, and audio device capability.
278
+
279
+ #### **TUI Keyboard Shortcuts**
280
+ * `[/]` — Focus the search bar to search online tracks or filter local library.
281
+ * `[m]` — Toggle between **Online YouTube Mode** and **Local Offline Music Mode**.
282
+ * `[Enter]` (on result) — Download/buffer and stream audio directly via system speaker.
283
+ * `[d]` &mdash; Download song permanently to `./downloads/<Song_Title>.mp3`.
284
+ * `[Space]` &mdash; Pause/Resume playback.
285
+ * `[s]` &mdash; Stop playback.
286
+ * `[Left]` / `[Right]` or `[h]` / `[l]` &mdash; Seek backward/forward by 5 seconds.
287
+ * `[` / `]` &mdash; Adjust volume down/up by 5% (Logarithmic scale).
288
+ * `[Esc]` &mdash; Unfocus search input.
289
+ * `[q]` or `[Ctrl+C]` &mdash; Stop playback, cancel active downloads, and exit.
290
+
291
+ ---
292
+
293
+ ## πŸͺ Bypassing Anti-Bot Blocking
294
+
295
+ When running these tools on servers (VPS, Cloud environments) or restricted subnets, YouTube may throw HTTP `403 Forbidden` or CAPTCHA errors. Use the following integration options to supply personal session cookies:
296
+
297
+ 1. **Browser Extraction (Local Client Only)**:
298
+ Instruct the downloader to pull cookies directly from your active browser profile:
299
+ ```bash
300
+ ./bin/ytmusic --cookies-from-browser chrome "vaporwave"
301
+ ```
302
+ 2. **Netscape Cookies File (Server/Remote)**:
303
+ Export cookies using a browser extension (such as *Get cookies.txt LOCALLY*) to a text file (e.g., `cookies.txt`). Place it in the app directory or pass it as a flag:
304
+ ```bash
305
+ ./bin/ytplayer --cookies ./cookies.txt "synthwave"
306
+ ```
307
+
308
+ ---
309
+
310
+ ## πŸ“ Codebase Layout
311
+
312
+ ```text
313
+ yt-song-cli/
314
+ β”œβ”€β”€ yt-player/ # Video TUI Application (ytplayer module)
315
+ β”‚ β”œβ”€β”€ cmd/ # Cobra commands & flags definitions
316
+ β”‚ β”œβ”€β”€ internal/ # Core business logic
317
+ β”‚ β”œβ”€β”€ go.mod # Module requirements
318
+ β”‚ └── README.md # Video project documentation
319
+ β”‚
320
+ └── yt-song/ # Audio TUI Application (ytmusic module)
321
+ β”œβ”€β”€ cmd/ # Cobra CLI commands & flags
322
+ β”œβ”€β”€ internal/ # Core business logic
323
+ β”œβ”€β”€ go.mod # Module requirements
324
+ └── README.md # Audio project documentation
325
+ ```
326
+
327
+ ### πŸ”— Quick Navigation
328
+
329
+ * **Video Player (`yt-player`):**
330
+ * Entry point: [yt-player/main.go](yt-player/main.go)
331
+ * Readme: [yt-player/README.md](yt-player/README.md)
332
+ * **Audio Player (`yt-song`):**
333
+ * Entry point: [yt-song/main.go](yt-song/main.go)
334
+ * Readme: [yt-song/README.md](yt-song/README.md)
335
+
336
+ ---
337
+
338
+ # Run `ytmusic` Globally Using a Wrapper Script(Possible Encounter Issue)
339
+
340
+ If `ytmusic` requires a local `--cookies.txt` file, creating a symbolic link with `ln -s` is **not enough**. A symbolic link only points to the executableβ€”it **does not change the current working directory**.
341
+
342
+ As a result, when you run:
343
+
344
+ ```bash
345
+ ytmusic
346
+ ```
347
+
348
+ from another directory, the application searches for `--cookies.txt` in your **current working directory** instead of the directory containing the binary.
349
+
350
+ ## Solution
351
+
352
+ Create a **wrapper script** in `/usr/local/bin` that changes to the directory containing the binary before executing it.
353
+
354
+ ### Step 1: Create the wrapper script
355
+
356
+ ```bash
357
+ sudo nano /usr/local/bin/ytmusic
358
+ ```
359
+
360
+ ### Step 2: Add the following contents
361
+
362
+ ```bash
363
+ #!/bin/bash
364
+
365
+ cd /home/codespace/ || exit 1
366
+ exec ./ytmusic "$@"
367
+ ```
368
+
369
+ > **Note:** Replace `/home/codespace/` with the directory where your `ytmusic` binary and `.env` file are located.
370
+
371
+ ### Step 3: Make the wrapper executable
372
+
373
+ ```bash
374
+ sudo chmod +x /usr/local/bin/ytmusic
375
+ ```
376
+
377
+ ### Step 4: Run the application
378
+
379
+ Now you can run the application from **any directory**:
380
+
381
+ ```bash
382
+ ytmusic
383
+ ```
384
+
385
+ ## How It Works
386
+
387
+ The wrapper script performs the following steps:
388
+
389
+ 1. Changes the current working directory to the directory containing the binary.
390
+ 2. Executes the `ytmusic` binary.
391
+ 3. Forwards any command-line arguments to the application using `"$@"`.
392
+
393
+ Because the working directory is correct, the application can successfully locate the local `.env` file.
394
+
395
+ ## Why Not Use a Symbolic Link?
396
+
397
+ For example:
398
+
399
+ ```bash
400
+ sudo ln -s /home/codespace/ytmusic /usr/local/bin/ytmusic
401
+ ```
402
+
403
+ Although this allows the command to be found in your `PATH`, it **does not** change the working directory.
404
+
405
+ If the Go application loads `.env` like this:
406
+
407
+ ```go
408
+ godotenv.Load()
409
+ ```
410
+
411
+ or
412
+
413
+ ```go
414
+ os.Open(".env")
415
+ ```
416
+
417
+ it searches for:
418
+
419
+ ```
420
+ <current-working-directory>/.env
421
+ ```
422
+
423
+ instead of:
424
+
425
+ ```
426
+ /home/codespace/.env
427
+ ```
428
+
429
+ Therefore, a wrapper script is the simplest solution when you do not want to modify the Go source code.
430
+
431
+ ## Similar work for the ytplayer binary file too
432
+
433
+ ## Summary
434
+
435
+ - βœ… Works globally from any directory.
436
+ - βœ… No changes to the Go application are required.
437
+ - βœ… Ensures `.env` is always loaded from the correct location.
438
+ - βœ… Passes all command-line arguments to the application.
439
+
440
+ ## πŸ“„ License
441
+
442
+ This project is licensed under the MIT License. See individual directories for any localized licenses or dependency notes.
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+ // Shared launcher for the npm bin shims. Spawns the postinstall-downloaded
3
+ // binary with inherited stdio so the Bubble Tea TUI works normally.
4
+ const { spawnSync } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ function run(binary) {
9
+ const exe = process.platform === 'win32' ? `${binary}.exe` : binary;
10
+ const bin = path.join(__dirname, '..', 'vendor', exe);
11
+ if (!fs.existsSync(bin)) {
12
+ console.error(
13
+ `[yt-cli-terminal] ${exe} not found.\n` +
14
+ 'The postinstall download probably failed (no network, or no prebuilt\n' +
15
+ 'binary for this platform). Reinstall with network access:\n' +
16
+ ' npm install -g yt-cli-terminal\n' +
17
+ 'You still need the runtime dependencies on PATH: mpv (ytplayer),\n' +
18
+ 'ffmpeg and yt-dlp (both tools). Run `ytplayer --check` to verify.\n' +
19
+ 'See https://github.com/Ping-Phantom39/yt-cli#readme for alternatives.'
20
+ );
21
+ process.exit(1);
22
+ }
23
+ const result = spawnSync(bin, process.argv.slice(2), { stdio: 'inherit' });
24
+ if (result.error) {
25
+ console.error(`[yt-cli-terminal] failed to launch ${exe}: ${result.error.message}`);
26
+ process.exit(1);
27
+ }
28
+ process.exit(result.status === null ? 1 : result.status);
29
+ }
30
+
31
+ module.exports = { run };
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ require('./_run').run('ytplayer');
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ require('./_run').run('ytmusic');
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ require('./_run').run('ytplayer');
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ require('./_run').run('ytmusic');
package/npm/install.js ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /*
4
+ * postinstall downloader for the `yt-cli-terminal` npm package.
5
+ *
6
+ * Fetches the prebuilt `ytplayer` / `ytmusic` binaries for the current
7
+ * platform from the GitHub release matching this package's version and
8
+ * places them in ./vendor. No runtime dependencies, Node >= 16 only.
9
+ *
10
+ * Environment overrides:
11
+ * YT_CLI_SKIP_POSTINSTALL=1 skip downloading (leave vendor/ empty)
12
+ * YT_CLI_VERSION=x.y.z release tag to download (default: package.json version)
13
+ * YT_CLI_REPO=owner/repo GitHub repo (default: Ping-Phantom39/yt-cli)
14
+ * YT_CLI_BASE_URL=<url> override release base URL (trailing /v<tag>/<asset> appended)
15
+ * YT_CLI_VENDOR_DIR=<dir> override download directory (default: ./vendor)
16
+ * YT_CLI_PLATFORM / YT_CLI_ARCH override platform detection (testing)
17
+ */
18
+
19
+ const crypto = require('crypto');
20
+ const fs = require('fs');
21
+ const http = require('http');
22
+ const https = require('https');
23
+ const path = require('path');
24
+
25
+ const REPO = process.env.YT_CLI_REPO || 'Ping-Phantom39/yt-cli';
26
+ const PKG_VERSION = (process.env.YT_CLI_VERSION || require('../package.json').version).replace(/^v/, '');
27
+ const TAG = `v${PKG_VERSION}`;
28
+ const BASE_URL = (process.env.YT_CLI_BASE_URL || `https://github.com/${REPO}/releases/download`).replace(/\/+$/, '');
29
+ const VENDOR_DIR = process.env.YT_CLI_VENDOR_DIR || path.join(__dirname, 'vendor');
30
+
31
+ const BINARIES = ['ytplayer', 'ytmusic'];
32
+
33
+ // Release matrix in .github/workflows/release.yml ships raw binaries for:
34
+ const SUPPORTED = new Set(['linux/amd64', 'darwin/amd64', 'darwin/arm64', 'windows/amd64']);
35
+
36
+ function currentPlatform() {
37
+ return {
38
+ platform: process.env.YT_CLI_PLATFORM || process.platform,
39
+ arch: process.env.YT_CLI_ARCH || process.arch,
40
+ };
41
+ }
42
+
43
+ function targetTriple() {
44
+ const { platform, arch } = currentPlatform();
45
+ let osPart;
46
+ if (platform === 'linux') osPart = 'linux';
47
+ else if (platform === 'darwin') osPart = 'darwin';
48
+ else if (platform === 'win32') osPart = 'windows';
49
+ else {
50
+ throw new Error(
51
+ `Unsupported platform "${platform}" (arch "${arch}"). ` +
52
+ `Prebuilt binaries exist for: ${[...SUPPORTED].join(', ')}. ` +
53
+ 'See https://github.com/Ping-Phantom39/yt-cli#readme for other install methods.'
54
+ );
55
+ }
56
+ let archPart;
57
+ if (arch === 'x64') archPart = 'amd64';
58
+ else if (arch === 'arm64') archPart = 'arm64';
59
+ else {
60
+ throw new Error(
61
+ `Unsupported architecture "${arch}" on "${platform}". ` +
62
+ `Prebuilt binaries exist for: ${[...SUPPORTED].join(', ')}.`
63
+ );
64
+ }
65
+ const triple = `${osPart}/${archPart}`;
66
+ if (!SUPPORTED.has(triple)) {
67
+ throw new Error(
68
+ `No prebuilt binaries for ${triple}. ` +
69
+ 'On Debian/Ubuntu arm64 use the .deb release asset, otherwise build from source: ' +
70
+ 'https://github.com/Ping-Phantom39/yt-cli#readme'
71
+ );
72
+ }
73
+ return { osPart, archPart, isWindows: osPart === 'windows' };
74
+ }
75
+
76
+ // Release asset filename, e.g. ytplayer-linux-amd64 / ytmusic-windows-amd64.exe
77
+ function assetName(binary) {
78
+ const { osPart, archPart, isWindows } = targetTriple();
79
+ return `${binary}-${osPart}-${archPart}${isWindows ? '.exe' : ''}`;
80
+ }
81
+
82
+ // On-disk filename inside vendor/, e.g. ytplayer / ytmusic.exe
83
+ function vendorName(binary) {
84
+ return binary + (targetTriple().isWindows ? '.exe' : '');
85
+ }
86
+
87
+ function get(url, redirects = 5) {
88
+ return new Promise((resolve, reject) => {
89
+ const lib = url.startsWith('https:') ? https : http;
90
+ const req = lib.get(
91
+ url,
92
+ { headers: { 'User-Agent': 'yt-cli-terminal-installer' } },
93
+ (res) => {
94
+ if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location && redirects > 0) {
95
+ res.resume();
96
+ resolve(get(new URL(res.headers.location, url).toString(), redirects - 1));
97
+ return;
98
+ }
99
+ if (res.statusCode !== 200) {
100
+ res.resume();
101
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
102
+ return;
103
+ }
104
+ resolve(res);
105
+ }
106
+ );
107
+ req.on('error', reject);
108
+ });
109
+ }
110
+
111
+ async function downloadFile(url, dest) {
112
+ const res = await get(url);
113
+ await fs.promises.mkdir(path.dirname(dest), { recursive: true });
114
+ const tmp = `${dest}.part`;
115
+ try {
116
+ await new Promise((resolve, reject) => {
117
+ const out = fs.createWriteStream(tmp, { mode: 0o755 });
118
+ out.on('finish', resolve);
119
+ out.on('error', reject);
120
+ res.on('error', reject);
121
+ res.pipe(out);
122
+ });
123
+ await fs.promises.rename(tmp, dest);
124
+ if (process.platform !== 'win32') {
125
+ await fs.promises.chmod(dest, 0o755);
126
+ }
127
+ } finally {
128
+ await fs.promises.rm(tmp, { force: true });
129
+ }
130
+ }
131
+
132
+ async function fetchText(url) {
133
+ const res = await get(url);
134
+ const chunks = [];
135
+ for await (const chunk of res) chunks.push(chunk);
136
+ return Buffer.concat(chunks).toString('utf8');
137
+ }
138
+
139
+ function sha256File(file) {
140
+ return new Promise((resolve, reject) => {
141
+ const hash = crypto.createHash('sha256');
142
+ const stream = fs.createReadStream(file);
143
+ stream.on('error', reject);
144
+ stream.on('data', (d) => hash.update(d));
145
+ stream.on('end', () => resolve(hash.digest('hex')));
146
+ });
147
+ }
148
+
149
+ // Strictly verify against the release SHA256SUMS.txt when available;
150
+ // older tags predate it, so only warn if the sums file itself is missing.
151
+ async function verifyChecksums(pairs) {
152
+ let sums;
153
+ try {
154
+ sums = await fetchText(`${BASE_URL}/${TAG}/SHA256SUMS.txt`);
155
+ } catch (err) {
156
+ console.warn(`[yt-cli-terminal] warning: no SHA256SUMS.txt for ${TAG} (${err.message}); skipping checksum verification`);
157
+ return;
158
+ }
159
+ const expected = new Map();
160
+ for (const line of sums.split('\n')) {
161
+ const m = line.match(/^([0-9a-f]{64})\s+\*?(\S+)\s*$/i);
162
+ if (m) expected.set(m[2], m[1].toLowerCase());
163
+ }
164
+ for (const { asset, file } of pairs) {
165
+ const sum = expected.get(asset);
166
+ if (!sum) {
167
+ console.warn(`[yt-cli-terminal] warning: no checksum entry for ${asset}; skipping`);
168
+ continue;
169
+ }
170
+ const actual = await sha256File(file);
171
+ if (actual !== sum) {
172
+ throw new Error(
173
+ `Checksum mismatch for ${asset} (expected ${sum}, got ${actual}). ` +
174
+ 'Delete node_modules and reinstall, or report an issue at ' +
175
+ 'https://github.com/Ping-Phantom39/yt-cli/issues'
176
+ );
177
+ }
178
+ console.log(`[yt-cli-terminal] checksum OK: ${asset}`);
179
+ }
180
+ }
181
+
182
+ async function main() {
183
+ if (process.env.YT_CLI_SKIP_POSTINSTALL) {
184
+ console.log('[yt-cli-terminal] YT_CLI_SKIP_POSTINSTALL set; skipping binary download');
185
+ return;
186
+ }
187
+ await fs.promises.mkdir(VENDOR_DIR, { recursive: true });
188
+ const pairs = [];
189
+ for (const binary of BINARIES) {
190
+ const asset = assetName(binary); // throws on unsupported platform
191
+ const url = `${BASE_URL}/${TAG}/${asset}`;
192
+ const file = path.join(VENDOR_DIR, vendorName(binary));
193
+ console.log(`[yt-cli-terminal] downloading ${asset} ...`);
194
+ await downloadFile(url, file);
195
+ pairs.push({ asset, file });
196
+ }
197
+ await verifyChecksums(pairs);
198
+ console.log(`[yt-cli-terminal] installed ytplayer + ytmusic ${TAG} to ${VENDOR_DIR}`);
199
+ console.log('[yt-cli-terminal] note: ytplayer needs mpv, and both tools need ffmpeg + yt-dlp on PATH (try `ytplayer --check`)');
200
+ }
201
+
202
+ if (require.main === module) {
203
+ main().catch((err) => {
204
+ console.error(`[yt-cli-terminal] install failed: ${err.message}`);
205
+ process.exit(1);
206
+ });
207
+ }
208
+
209
+ module.exports = { assetName, vendorName, targetTriple, TAG, BASE_URL, VENDOR_DIR, BINARIES };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "yt-cli-terminal",
3
+ "version": "1.4.0",
4
+ "description": "Cyberpunk terminal YouTube video player (ytplayer) and music player (ytmusic). Installs prebuilt binaries from GitHub releases.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Ping-Phantom39/yt-cli.git"
9
+ },
10
+ "homepage": "https://github.com/Ping-Phantom39/yt-cli#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/Ping-Phantom39/yt-cli/issues"
13
+ },
14
+ "keywords": [
15
+ "youtube",
16
+ "terminal",
17
+ "tui",
18
+ "mpv",
19
+ "music",
20
+ "video",
21
+ "cli",
22
+ "yt-dlp"
23
+ ],
24
+ "engines": {
25
+ "node": ">=16"
26
+ },
27
+ "bin": {
28
+ "ytplayer": "./npm/bin/ytplayer.js",
29
+ "ytcli": "./npm/bin/ytcli.js",
30
+ "ytmusic": "./npm/bin/ytmusic.js",
31
+ "ytsong": "./npm/bin/ytsong.js"
32
+ },
33
+ "scripts": {
34
+ "postinstall": "node ./npm/install.js",
35
+ "test": "node --test npm/"
36
+ },
37
+ "files": [
38
+ "npm/bin/",
39
+ "npm/install.js",
40
+ "README.md",
41
+ "LICENSE"
42
+ ]
43
+ }