lushapp 2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pseudoshell
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,104 @@
1
+ # LUSH (`lushapp`)
2
+
3
+ > Hyper-aesthetic terminal radio & music player with 560+ live streams, real-time CAVA DSP visualizers, retro studio decks, and ambient soundscapes.
4
+
5
+ ```text
6
+ __ __ __ _____ __ __
7
+ / / / / / / / ___// / / /
8
+ / / / / / / \__ \/ /_/ / [ 560+ LIVE STATIONS ]
9
+ / /__/ /_/ / ___/ / __ / [ 19 CAVA VISUALIZERS ]
10
+ /_____/\____/ /____/_/ /_/ [ 37 RICED THEMES ]
11
+ ```
12
+
13
+ ---
14
+
15
+ ## ✨ Features
16
+
17
+ - **560+ Live Stations**: Lo-Fi, Synthwave, Cyberpunk, Ambient, Classical, ASMR, and curated Artist Discographies.
18
+ - **Real-Time CAVA DSP Visualizers**: 19 spectrum analyzers (up to 144Hz) and 36 audio-reactive shimmers.
19
+ - **Retro Studio Decks**: Kinetic Technics SL-1200 vinyl turntable and Nakamichi cassette deck animations.
20
+ - **Ambient Soundscapes**: Layer rain, vinyl crackle, campfire, or café sounds over any stream.
21
+ - **Lossless FLAC Recording**: Capture audio directly to `~/Music` in 24-bit FLAC with <kbd>Shift+R</kbd>.
22
+ - **Listening Diary**: 100% offline analytics with a 16-week GitHub-style heatmap and streak tracking.
23
+ - **Fuzzy Search**: Instant, typo-tolerant search across all stations with <kbd>/</kbd>.
24
+ - **37 Themes**: Highly customizable color palettes with live preview.
25
+
26
+ ---
27
+
28
+ ## 🚀 Quick Start
29
+
30
+ ### Run Instantly (No Install)
31
+ ```bash
32
+ npx lushapp
33
+ ```
34
+
35
+ ### Install via pip / npm
36
+ ```bash
37
+ pip install lushapp
38
+ # or
39
+ npm install -g lushapp
40
+
41
+ # Start with either command:
42
+ lush
43
+ # or
44
+ lushapp
45
+ ```
46
+
47
+ ### Clone & Run from Source
48
+ ```bash
49
+ git clone https://github.com/pseudoshell/lush.git
50
+ cd lush
51
+ ./bin/lush
52
+ ```
53
+
54
+ ---
55
+
56
+ ## 📦 Prerequisites
57
+
58
+ LUSH requires **Python 3.9+** and **mpv**. Install **cava** for live visualizers.
59
+
60
+ | System | Command |
61
+ | :--- | :--- |
62
+ | **Arch / Manjaro** | `sudo pacman -S mpv cava python` |
63
+ | **Ubuntu / Debian** | `sudo apt install mpv cava python3` |
64
+ | **Fedora** | `sudo dnf install mpv cava python3` |
65
+ | **macOS (Homebrew)** | `brew install mpv cava python` |
66
+
67
+ ---
68
+
69
+ ## 🕹️ Keybindings
70
+
71
+ | Key | Action |
72
+ | :--- | :--- |
73
+ | <kbd>Space</kbd> | Play / Pause active stream |
74
+ | <kbd>/</kbd> | Live fuzzy search |
75
+ | <kbd>1</kbd> – <kbd>5</kbd> | Jump to Music categories (Top Artists, Gen Z, Legends, New Age, All) |
76
+ | <kbd>0</kbd> | Jump to All Web Radios |
77
+ | <kbd>p</kbd> | Open Now Playing Studio deck |
78
+ | <kbd>d</kbd> | Switch deck style (Turntable ↔ Cassette) |
79
+ | <kbd>v</kbd> | Cycle CAVA visualizers |
80
+ | <kbd>t</kbd> / <kbd>T</kbd> | Cycle / Select theme |
81
+ | <kbd>e</kbd> / <kbd>E</kbd> | Toggle / Configure ambient sounds |
82
+ | <kbd>Shift</kbd> + <kbd>R</kbd> | Toggle lossless FLAC recording |
83
+ | <kbd>b</kbd> | Add / Remove favorite station |
84
+ | <kbd>s</kbd> | Open Settings menu |
85
+ | <kbd>?</kbd> | Help modal |
86
+ | <kbd>q</kbd> | Quit |
87
+
88
+ ---
89
+
90
+ ## 🧪 Diagnostics & Testing
91
+
92
+ Run the live streaming test suite:
93
+
94
+ ```bash
95
+ lush --test
96
+ # or
97
+ python3 tests/run_tests.py
98
+ ```
99
+
100
+ ---
101
+
102
+ ## 📄 License
103
+
104
+ MIT © 2026 pseudoshell
package/bin/lush ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+ import os
4
+ from pathlib import Path
5
+
6
+ # Ensure lush package paths are in sys.path
7
+ for path_cand in [
8
+ Path(__file__).resolve().parent.parent / 'src',
9
+ Path(__file__).resolve().parent.parent / 'lib',
10
+ Path.home() / '.local' / 'lib',
11
+ ]:
12
+ if (path_cand / 'lush').exists():
13
+ p_str = str(path_cand)
14
+ if p_str not in sys.path:
15
+ sys.path.insert(0, p_str)
16
+
17
+ import shutil
18
+ import subprocess
19
+
20
+ def ensure_audio_backend():
21
+ """Detects if an audio player backend is available; offers auto-install if missing."""
22
+ if shutil.which("mpv") or shutil.which("ffplay") or shutil.which("cvlc"):
23
+ return
24
+
25
+ # No backend detected
26
+ pkg_managers = [
27
+ ("pacman", "sudo pacman -S --noconfirm mpv cava"),
28
+ ("apt-get", "sudo apt-get update && sudo apt-get install -y mpv cava"),
29
+ ("dnf", "sudo dnf install -y mpv cava"),
30
+ ("brew", "brew install mpv cava"),
31
+ ("zypper", "sudo zypper install -y mpv cava"),
32
+ ("apk", "sudo apk add mpv cava"),
33
+ ("pkg", "pkg install -y mpv"),
34
+ ("winget", "winget install mpv.mpv")
35
+ ]
36
+
37
+ detected_cmd = None
38
+ for pm, cmd in pkg_managers:
39
+ if shutil.which(pm):
40
+ detected_cmd = cmd
41
+ break
42
+
43
+ if sys.stdin.isatty() and detected_cmd:
44
+ print("\033[1;95m┌────────────────────────────────────────────────────────────────────────┐\033[0m")
45
+ print("\033[1;95m│\033[0m \033[1;96mLUSH Setup:\033[0m Audio player backend (\033[1;93mmpv\033[0m) not detected. \033[1;95m│\033[0m")
46
+ print(f"\033[1;95m│\033[0m Auto-install via \033[1;92m{detected_cmd:<44}\033[0m \033[1;95m│\033[0m")
47
+ print("\033[1;95m└────────────────────────────────────────────────────────────────────────┘\033[0m")
48
+ try:
49
+ ans = input("Proceed with auto-install? [Y/n]: ").strip().lower()
50
+ if ans in ("", "y", "yes"):
51
+ print("\n\033[1;96mInstalling audio dependencies...\033[0m")
52
+ subprocess.run(detected_cmd, shell=True)
53
+ print("\033[1;92m✓ Installation complete! Launching LUSH...\033[0m\n")
54
+ except (KeyboardInterrupt, EOFError):
55
+ pass
56
+
57
+ ensure_audio_backend()
58
+
59
+ from lush.__main__ import main
60
+
61
+ if __name__ == '__main__':
62
+ main()
package/bin/lush.js ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn, execSync } = require('child_process');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ const binPath = path.join(__dirname, 'lush');
8
+
9
+ function getPythonExecutable() {
10
+ const candidates = process.platform === 'win32'
11
+ ? ['python3', 'python', 'py']
12
+ : ['python3', 'python'];
13
+
14
+ for (const cmd of candidates) {
15
+ try {
16
+ const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
17
+ execSync(checkCmd, { stdio: 'ignore' });
18
+ return cmd;
19
+ } catch {}
20
+ }
21
+ return 'python3';
22
+ }
23
+
24
+ const pythonCmd = getPythonExecutable();
25
+
26
+ const child = spawn(pythonCmd, [binPath, ...process.argv.slice(2)], {
27
+ stdio: 'inherit',
28
+ env: {
29
+ ...process.env,
30
+ PYTHONPATH: path.join(__dirname, '..', 'src')
31
+ }
32
+ });
33
+
34
+ child.on('error', (err) => {
35
+ if (err.code === 'ENOENT') {
36
+ console.error('\x1b[1;95m┌────────────────────────────────────────────────────────────────────────┐\x1b[0m');
37
+ console.error('\x1b[1;95m│\x1b[0m \x1b[1;91m[ERROR]\x1b[0m Python 3 is required to run LUSH. \x1b[1;95m│\x1b[0m');
38
+ console.error('\x1b[1;95m│\x1b[0m Install via: \x1b[1;96msudo apt install python3 mpv cava\x1b[0m (or \x1b[1;96mbrew install python mpv cava\x1b[0m) \x1b[1;95m│\x1b[0m');
39
+ console.error('\x1b[1;95m└────────────────────────────────────────────────────────────────────────┘\x1b[0m');
40
+ } else {
41
+ console.error('\x1b[31m[ERROR]\x1b[0m Failed to start LUSH:', err.message);
42
+ }
43
+ process.exit(1);
44
+ });
45
+
46
+ child.on('exit', (code, signal) => {
47
+ process.exit(code !== null ? code : (signal ? 1 : 0));
48
+ });
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "lushapp",
3
+ "version": "2.0.0",
4
+ "description": "A hyper-aesthetic terminal radio player with 560+ live stations, real-time CAVA DSP audio visualizers, ambient soundscapes, and artist discographies.",
5
+ "main": "bin/lush.js",
6
+ "bin": {
7
+ "lush": "bin/lush.js",
8
+ "lushapp": "bin/lush.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node bin/lush.js",
12
+ "dev": "node bin/lush.js",
13
+ "build": "python3 -m compileall -q src/lush && npm pack --dry-run",
14
+ "pack": "npm pack",
15
+ "diag": "node bin/lush.js --test",
16
+ "test": "python3 tests/run_tests.py",
17
+ "clean": "node -e \"const fs=require('fs');fs.readdirSync('.').filter(f=>f.endsWith('.tgz')).forEach(f=>fs.unlinkSync(f))\"",
18
+ "postinstall": "node scripts/postinstall.js",
19
+ "version": "node scripts/sync-version.js && git add pyproject.toml setup.py src/lush/__init__.py",
20
+ "prepack": "python3 tests/run_tests.py",
21
+ "prepublishOnly": "python3 tests/run_tests.py"
22
+ },
23
+ "keywords": [
24
+ "tui",
25
+ "radio",
26
+ "music",
27
+ "terminal",
28
+ "audio-player",
29
+ "visualizer",
30
+ "cava",
31
+ "cli",
32
+ "ambient",
33
+ "curses",
34
+ "python",
35
+ "lofi",
36
+ "synthwave"
37
+ ],
38
+ "author": "pseudoshell",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/pseudoshell/lush.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/pseudoshell/lush/issues"
46
+ },
47
+ "homepage": "https://github.com/pseudoshell/lush#readme",
48
+ "files": [
49
+ "bin/",
50
+ "scripts/",
51
+ "src/lush/*.py",
52
+ "src/lush/data/*",
53
+ "LICENSE",
54
+ "README.md",
55
+ "pyproject.toml",
56
+ "setup.py"
57
+ ],
58
+ "engines": {
59
+ "node": ">=14.0.0"
60
+ }
61
+ }
package/pyproject.toml ADDED
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "lushapp"
7
+ version = "2.0.0"
8
+ description = "A hyper-aesthetic terminal radio player with 560+ live stations, real-time CAVA DSP visualizers, and artist discographies."
9
+ readme = "README.md"
10
+ authors = [{ name = "pseudoshell" }]
11
+ license = { text = "MIT" }
12
+ requires-python = ">=3.8"
13
+ classifiers = [
14
+ "Development Status :: 5 - Production/Stable",
15
+ "Environment :: Console :: Curses",
16
+ "Intended Audience :: End Users/Desktop",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: POSIX :: Linux",
19
+ "Operating System :: MacOS",
20
+ "Operating System :: Microsoft :: Windows",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
+ "Topic :: Multimedia :: Sound/Audio :: Players",
30
+ ]
31
+ dependencies = [
32
+ "python-mpv>=0.5.2",
33
+ "pyperclip>=1.8.2",
34
+ "requests>=2.28.0",
35
+ "yt-dlp>=2024.1.0",
36
+ "windows-curses>=2.3.0; sys_platform == 'win32'",
37
+ ]
38
+
39
+ [project.scripts]
40
+ lush = "lush.__main__:main"
41
+ lushapp = "lush.__main__:main"
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/pseudoshell/lush"
45
+ Repository = "https://github.com/pseudoshell/lush.git"
46
+ Issues = "https://github.com/pseudoshell/lush/issues"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools.package-data]
52
+ lush = ["data/*.json", "data/*.conf"]
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ const { execSync } = require('child_process');
3
+
4
+ function which(cmd) {
5
+ try {
6
+ const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
7
+ execSync(checkCmd, { stdio: 'ignore' });
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ const missing = [];
15
+ if (!which('python3') && !which('python')) missing.push('python3');
16
+ if (!which('mpv')) missing.push('mpv');
17
+ if (!which('cava')) missing.push('cava');
18
+ if (!which('ffmpeg')) missing.push('ffmpeg');
19
+
20
+ if (missing.length === 0) {
21
+ console.log('\x1b[1;92m✓ [LUSH]\x1b[0m All system dependencies (python3, mpv, cava, ffmpeg) are verified.');
22
+ process.exit(0);
23
+ }
24
+
25
+ console.log(`\x1b[1;95m┌────────────────────────────────────────────────────────────────────────┐\x1b[0m`);
26
+ console.log(`\x1b[1;95m│\x1b[0m \x1b[1;96mLUSH Setup:\x1b[0m Missing dependencies detected: \x1b[1;93m${missing.join(', ')}\x1b[0m \x1b[1;95m│\x1b[0m`);
27
+ console.log(`\x1b[1;95m│\x1b[0m Attempting automated installation via system package manager... \x1b[1;95m│\x1b[0m`);
28
+ console.log(`\x1b[1;95m└────────────────────────────────────────────────────────────────────────┘\x1b[0m`);
29
+
30
+ let installCmd = null;
31
+ if (which('pacman')) {
32
+ installCmd = `sudo pacman -S --noconfirm --needed ${missing.join(' ')}`;
33
+ } else if (which('apt-get')) {
34
+ installCmd = `sudo apt-get update -qq && sudo apt-get install -y ${missing.join(' ')}`;
35
+ } else if (which('dnf')) {
36
+ installCmd = `sudo dnf install -y ${missing.join(' ')}`;
37
+ } else if (which('brew')) {
38
+ installCmd = `brew install ${missing.join(' ')}`;
39
+ } else if (which('zypper')) {
40
+ installCmd = `sudo zypper install -y ${missing.join(' ')}`;
41
+ } else if (which('apk')) {
42
+ installCmd = `sudo apk add ${missing.join(' ')}`;
43
+ } else if (which('winget')) {
44
+ installCmd = `winget install mpv.mpv`;
45
+ }
46
+
47
+ if (installCmd) {
48
+ try {
49
+ console.log(`\x1b[96m> Running: ${installCmd}\x1b[0m\n`);
50
+ execSync(installCmd, { stdio: 'inherit' });
51
+ console.log('\n\x1b[1;92m✓ [LUSH] Dependencies successfully installed! Ready to launch `lush`.\x1b[0m\n');
52
+ } catch (err) {
53
+ console.log(`\x1b[1;93m[NOTICE]\x1b[0m Automated installation required manual elevation.`);
54
+ console.log(`Run: \x1b[1;96m${installCmd}\x1b[0m to finalize setup.\n`);
55
+ }
56
+ } else {
57
+ console.log(`\x1b[1;93m[NOTICE]\x1b[0m Please install missing tools (\x1b[1;96m${missing.join(', ')}\x1b[0m) with your package manager.\n`);
58
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const rootDir = path.resolve(__dirname, '..');
6
+ const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
7
+ const newVer = pkg.version;
8
+
9
+ console.log(`\x1b[96m[LUSH Version Sync]\x1b[0m Syncing version \x1b[92mv${newVer}\x1b[0m across all manifests...`);
10
+
11
+ // 1. Update pyproject.toml
12
+ const pyprojectPath = path.join(rootDir, 'pyproject.toml');
13
+ if (fs.existsSync(pyprojectPath)) {
14
+ let content = fs.readFileSync(pyprojectPath, 'utf8');
15
+ content = content.replace(/^version\s*=\s*["'][^"']+["']/m, `version = "${newVer}"`);
16
+ fs.writeFileSync(pyprojectPath, content, 'utf8');
17
+ console.log(` ✓ Updated pyproject.toml -> ${newVer}`);
18
+ }
19
+
20
+ // 2. Update setup.py
21
+ const setupPath = path.join(rootDir, 'setup.py');
22
+ if (fs.existsSync(setupPath)) {
23
+ let content = fs.readFileSync(setupPath, 'utf8');
24
+ content = content.replace(/version\s*=\s*["'][^"']+["']/m, `version="${newVer}"`);
25
+ fs.writeFileSync(setupPath, content, 'utf8');
26
+ console.log(` ✓ Updated setup.py -> ${newVer}`);
27
+ }
28
+
29
+ // 3. Update src/lush/__init__.py
30
+ const initPath = path.join(rootDir, 'src', 'lush', '__init__.py');
31
+ if (fs.existsSync(initPath)) {
32
+ let content = fs.readFileSync(initPath, 'utf8');
33
+ content = content.replace(/__version__\s*=\s*["'][^"']+["']/m, `__version__ = "${newVer}"`);
34
+ fs.writeFileSync(initPath, content, 'utf8');
35
+ console.log(` ✓ Updated src/lush/__init__.py -> ${newVer}`);
36
+ }
37
+
38
+ console.log('\x1b[1;92m✓ All package versions synchronized successfully!\x1b[0m\n');
package/setup.py ADDED
@@ -0,0 +1,23 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="lushapp",
5
+ version="2.0.0",
6
+ package_dir={"": "src"},
7
+ packages=find_packages(where="src"),
8
+ include_package_data=True,
9
+ package_data={"lush": ["data/*.json", "data/*.conf"]},
10
+ entry_points={
11
+ "console_scripts": [
12
+ "lush = lush.__main__:main",
13
+ "lushapp = lush.__main__:main",
14
+ ],
15
+ },
16
+ install_requires=[
17
+ "python-mpv>=0.5.2",
18
+ "pyperclip>=1.8.2",
19
+ "requests>=2.28.0",
20
+ "yt-dlp>=2024.1.0",
21
+ "windows-curses>=2.3.0; sys_platform == 'win32'",
22
+ ],
23
+ )
@@ -0,0 +1,30 @@
1
+ import os
2
+ os.environ["ESCDELAY"] = "25"
3
+
4
+ __version__ = "2.0.0"
5
+
6
+ def run_app():
7
+ try:
8
+ import curses
9
+ except ImportError:
10
+ try:
11
+ import windows_curses as curses
12
+ except ImportError:
13
+ print("\033[1;91m[ERROR]\033[0m Python curses library is required to run LUSH.")
14
+ if os.name == "nt":
15
+ print("On Windows, install it via: \033[1;96mpip install windows-curses\033[0m")
16
+ return
17
+
18
+ import threading
19
+ from .state import PlayerState, metadata_loop
20
+ from .ui import curses_main
21
+
22
+ state = PlayerState()
23
+ t_meta = threading.Thread(target=metadata_loop, args=(state,), daemon=True)
24
+ t_meta.start()
25
+
26
+ curses.wrapper(curses_main, state)
27
+
28
+ if __name__ == "__main__":
29
+ run_app()
30
+
@@ -0,0 +1,42 @@
1
+ # LUSH - CLI Main Entrypoint
2
+ import sys
3
+ import argparse
4
+ from . import __version__, run_app
5
+
6
+ def main():
7
+ parser = argparse.ArgumentParser(
8
+ prog="lush",
9
+ description="LUSH - A hyper-aesthetic terminal radio player with 560+ live stations and real-time CAVA DSP visualizers.",
10
+ epilog="Keybindings: [Space] Play/Pause | [/] Search | [p] Now Playing | [d] Decks | [1-5] Music | [0] Radio | [v] Visualizers | [t] Themes | [e] Ambient | [Shift+R] Record | [q] Quit"
11
+ )
12
+ parser.add_argument("-v", "--version", action="version", version=f"LUSH v{__version__} (Audiophile Edition)")
13
+ parser.add_argument("--test", action="store_true", help="Run internal self-test diagnostics")
14
+
15
+ args, unknown = parser.parse_known_args()
16
+
17
+ if args.test:
18
+ from pathlib import Path
19
+ test_cands = [
20
+ Path(__file__).resolve().parent.parent.parent / "tests" / "run_tests.py",
21
+ Path.home() / "lush" / "tests" / "run_tests.py",
22
+ Path.cwd() / "tests" / "run_tests.py"
23
+ ]
24
+ runner_path = next((p for p in test_cands if p.exists()), None)
25
+ if runner_path:
26
+ import subprocess
27
+ res = subprocess.run([sys.executable, str(runner_path)])
28
+ sys.exit(res.returncode)
29
+ else:
30
+ from .state import PlayerState
31
+ state = PlayerState()
32
+ stations = state.get_filtered_stations()
33
+ print(f"LUSH v{__version__} Self-Test:")
34
+ print(f" [OK] Station Catalog: {len(stations)} live streams loaded.")
35
+ state.shutdown()
36
+ print(" [OK] All core subsystems verified successfully.")
37
+ sys.exit(0)
38
+
39
+ run_app()
40
+
41
+ if __name__ == "__main__":
42
+ main()