@ciphore/radiocli 0.2.4 → 0.2.5

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 (134) hide show
  1. package/CHANGELOG.md +70 -2
  2. package/CONTRIBUTING.md +10 -0
  3. package/README.md +108 -253
  4. package/SECURITY.md +23 -1
  5. package/dist/agent/alarm-service.js +10 -7
  6. package/dist/agent/cli.js +16 -1
  7. package/dist/agent/command-schema.js +47 -0
  8. package/dist/agent/headless-host.js +226 -81
  9. package/dist/agent/launcher.js +2 -1
  10. package/dist/agent/mcp-server.js +10 -5
  11. package/dist/agent/service.js +75 -40
  12. package/dist/agent/session.js +214 -24
  13. package/dist/agent/tui-session.js +213 -0
  14. package/dist/alarms/cli.js +19 -3
  15. package/dist/alarms/occurrence-cache.js +25 -0
  16. package/dist/alarms/runner.js +87 -23
  17. package/dist/alarms/schedule.js +29 -1
  18. package/dist/alarms/scheduler.js +11 -3
  19. package/dist/alarms/system-volume-linux.js +160 -0
  20. package/dist/alarms/system-volume-ownership.js +76 -8
  21. package/dist/alarms/system-volume.js +44 -23
  22. package/dist/cli/arguments.js +212 -0
  23. package/dist/cli/completion.js +44 -0
  24. package/dist/cli/doctor.js +156 -0
  25. package/dist/cli/interactive.js +79 -0
  26. package/dist/cli/listener.js +227 -0
  27. package/dist/cli/output.js +121 -0
  28. package/dist/cli/render-tui.js +34 -0
  29. package/dist/cli.js +178 -199
  30. package/dist/platform/capabilities.js +8 -1
  31. package/dist/platform/desktop-media.js +61 -0
  32. package/dist/platform/diagnostics.js +273 -0
  33. package/dist/platform/json-worker.js +111 -0
  34. package/dist/platform/launch-command.js +9 -6
  35. package/dist/platform/mpris.js +356 -0
  36. package/dist/platform/network.js +69 -1
  37. package/dist/platform/omarchy.js +147 -0
  38. package/dist/platform/packages.js +7 -1
  39. package/dist/platform/runtime.js +1 -1
  40. package/dist/platform/terminals.js +24 -4
  41. package/dist/player/airplay-discovery.js +144 -61
  42. package/dist/player/airplay-policy.js +15 -0
  43. package/dist/player/airplay-process.js +63 -0
  44. package/dist/player/airplay-readiness.js +42 -0
  45. package/dist/player/airplay-sender-health.js +49 -49
  46. package/dist/player/airplay-sender-package.js +5 -0
  47. package/dist/player/airplay-sender-patch.js +102 -4
  48. package/dist/player/airplay-setup.js +51 -0
  49. package/dist/player/airplay-worker.js +97 -41
  50. package/dist/player/backend-install.js +9 -6
  51. package/dist/player/ffmpeg-input.js +37 -0
  52. package/dist/player/mpv-desktop-media.js +194 -0
  53. package/dist/player/mpv-ipc-client.js +51 -2
  54. package/dist/player/player-controller.js +390 -73
  55. package/dist/player/radio-engine.js +282 -0
  56. package/dist/playlists/playlist.js +18 -16
  57. package/dist/providers/cache.js +291 -75
  58. package/dist/providers/discovery-session.js +111 -0
  59. package/dist/providers/provider-manager.js +106 -28
  60. package/dist/providers/radio-browser.js +272 -41
  61. package/dist/providers/radio-garden.js +4 -2
  62. package/dist/setup-playback.js +152 -0
  63. package/dist/setup.js +88 -21
  64. package/dist/storage/file-lock.js +113 -0
  65. package/dist/storage/read-settings.js +33 -0
  66. package/dist/storage/replace-file.js +20 -0
  67. package/dist/storage/repository.js +247 -0
  68. package/dist/storage/station-identity.js +21 -0
  69. package/dist/storage/station-schema.js +27 -0
  70. package/dist/storage/store.js +249 -76
  71. package/dist/ui/AdaptiveContent.js +18 -10
  72. package/dist/ui/App.js +708 -454
  73. package/dist/ui/AppContent.js +5 -5
  74. package/dist/ui/airplay-settings.js +12 -5
  75. package/dist/ui/bidi-ranges.js +45 -0
  76. package/dist/ui/components/AdaptiveMarquee.js +16 -9
  77. package/dist/ui/format.js +37 -4
  78. package/dist/ui/help-content.js +5 -2
  79. package/dist/ui/rtl-output.js +137 -0
  80. package/dist/ui/screen-items.js +10 -4
  81. package/dist/ui/screens/AirPlaySettingsScreen.js +3 -3
  82. package/dist/ui/screens/AlarmsScreen.js +5 -2
  83. package/dist/ui/screens/HelpScreen.js +3 -3
  84. package/dist/ui/screens/SettingsScreen.js +16 -16
  85. package/dist/ui/screens/StatsScreen.js +1 -1
  86. package/dist/ui/text-direction.js +9 -0
  87. package/dist/ui/use-alarm-tui.js +24 -24
  88. package/dist/ui/use-app-input.js +42 -8
  89. package/dist/ui/use-command-executor.js +21 -14
  90. package/dist/ui/visualizers/gallop.js +1 -1
  91. package/dist/ui/visualizers/receiver-style-registry.js +2 -1
  92. package/dist/update-check.js +2 -1
  93. package/docs/THIRD_PARTY_NOTICES.md +6 -4
  94. package/package.json +34 -11
  95. package/scripts/build-airplay-native.mjs +22 -0
  96. package/vendor/node-airtunes2/LICENSE +208 -0
  97. package/vendor/node-airtunes2/RADIOCLI_PATCH.md +29 -0
  98. package/vendor/node-airtunes2/README.md +225 -0
  99. package/vendor/node-airtunes2/UPSTREAM.json +50 -0
  100. package/vendor/node-airtunes2/binding.gyp +41 -0
  101. package/vendor/node-airtunes2/lib/ap2_test.js +4 -0
  102. package/vendor/node-airtunes2/lib/atv.js +307 -0
  103. package/vendor/node-airtunes2/lib/atvAuthenticator.js +136 -0
  104. package/vendor/node-airtunes2/lib/audio_out.js +69 -0
  105. package/vendor/node-airtunes2/lib/circular_buffer.js +133 -0
  106. package/vendor/node-airtunes2/lib/config.js +26 -0
  107. package/vendor/node-airtunes2/lib/device_airtunes.js +532 -0
  108. package/vendor/node-airtunes2/lib/devices.js +266 -0
  109. package/vendor/node-airtunes2/lib/ed25519.js +26 -0
  110. package/vendor/node-airtunes2/lib/homekit/credentials.js +86 -0
  111. package/vendor/node-airtunes2/lib/homekit/encryption.js +69 -0
  112. package/vendor/node-airtunes2/lib/homekit/number.js +44 -0
  113. package/vendor/node-airtunes2/lib/homekit/tlv.js +95 -0
  114. package/vendor/node-airtunes2/lib/http.js +189 -0
  115. package/vendor/node-airtunes2/lib/index.d.ts +141 -0
  116. package/vendor/node-airtunes2/lib/index.js +127 -0
  117. package/vendor/node-airtunes2/lib/ntp.js +37 -0
  118. package/vendor/node-airtunes2/lib/num_util.js +21 -0
  119. package/vendor/node-airtunes2/lib/packet_pool.js +43 -0
  120. package/vendor/node-airtunes2/lib/rtsp.js +1807 -0
  121. package/vendor/node-airtunes2/lib/srp.js +158 -0
  122. package/vendor/node-airtunes2/lib/udp_servers.js +214 -0
  123. package/vendor/node-airtunes2/lib/util.js +12 -0
  124. package/vendor/node-airtunes2/package.json +31 -0
  125. package/vendor/node-airtunes2/src/CADebugMacros.h +257 -0
  126. package/vendor/node-airtunes2/src/CAHostTimeBase.cpp +105 -0
  127. package/vendor/node-airtunes2/src/CAHostTimeBase.h +224 -0
  128. package/vendor/node-airtunes2/src/aes_utils.c +864 -0
  129. package/vendor/node-airtunes2/src/aes_utils.h +24 -0
  130. package/vendor/node-airtunes2/src/base64.c +136 -0
  131. package/vendor/node-airtunes2/src/base64.h +42 -0
  132. package/vendor/node-airtunes2/src/bindings.cc +29 -0
  133. package/vendor/node-airtunes2/src/codec.cc +170 -0
  134. package/vendor/node-airtunes2/src/coreaudio.cc +332 -0
package/CHANGELOG.md CHANGED
@@ -2,8 +2,75 @@
2
2
 
3
3
  All notable changes to RadioCLI are documented in this file.
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+ ## [0.2.5] - 2026-10-18
6
+
7
+ ### Added
8
+
9
+ - Added direct shell playback, volume controls, station-name lookup, filtered
10
+ search, favorites, recents, named presets, and listening stats.
11
+ - Added a compact now-playing view with live track metadata and `--watch`,
12
+ readable terminal tables, JSON/plain output, and shell completion for Bash,
13
+ Zsh, Fish, and PowerShell.
14
+ - Added Linux desktop media controls for RadioCLI's station queue, metadata,
15
+ playback, and volume, with `radiocli media-controls auto|on|off` overrides.
16
+ - Added experimental Omarchy Quattro integration with preferred-terminal
17
+ launching and desktop/session diagnostics.
18
+ - Added experimental, opt-in Linux AirPlay with Avahi discovery, receiver
19
+ selection, passcode handling, and guided dependency setup.
20
+
21
+ ### Changed
22
+
23
+ - Simplified shell help around eight everyday commands, with `help --all` for
24
+ the full reference, `play` to resume, and favorites, recent, and popular queues.
25
+ - Expanded Doctor with separate terminal, desktop media, audio, Omarchy, and
26
+ AirPlay readiness checks.
27
+ - Included station presets in library backup, restore, and concurrent saves.
28
+ - Reduced Gallop's drawing scale by 10% while preserving its proportions,
29
+ centering, and animation timing.
30
+ - Removed the twelve preview receiver scenes, retaining the original 63 styles
31
+ and Ultracode default. Retired selections migrate without resetting saved data.
32
+
33
+ ### Fixed
34
+
35
+ - Kept TUI playback running after a terminal crash, recovering the active
36
+ station, queue, controls, and sleep timer when the terminal reopens.
37
+ - Recovered interrupted or stalled live mpv streams without restarting paused
38
+ sessions, finite recordings, or playback canceled by Stop.
39
+ - Prevented duplicate mpv desktop media players and kept desktop controls in
40
+ sync with the shared player, including live-edge resume and alarm transitions.
41
+ - Restored alarm volume to the original Linux output after default-device
42
+ changes and overlapping alarms, and kept imported alarm IDs distinct across
43
+ case-insensitive and Unicode-normalizing filesystems.
44
+ - Preserved AirPlay mute, volume, and station metadata during connection setup,
45
+ added legacy RAOP password authentication, and improved connection errors.
46
+ - Protected concurrent library saves and preserved the latest data in the safety
47
+ backup before a restore. Retried temporary Windows file-replacement conflicts.
48
+ - Preserved migrated custom-station IDs, corrected XSPF CDATA and Unicode URL
49
+ imports, and restored Windows drive-path playlist imports.
50
+ - Corrected mixed Arabic/Latin terminal layout and emoji clipping, padding, and
51
+ scrolling without changing plain output or screen-reader behavior.
52
+ - Expired future-dated provider cache entries after clock corrections and
53
+ preserved valid data when migrating older caches.
54
+ - Preserved package-manager paths during Unix updates and allowed slower
55
+ PowerShell startup when opening native Windows terminals.
56
+ - Fixed a documentation typecheck race and added a single required CI gate for
57
+ platform, playback, and documentation checks. Release checks now include
58
+ packed upgrades from 0.2.3 and 0.2.4 and native TUI crash recovery.
59
+
60
+ ### Security
61
+
62
+ - Removed the optional AirPlay sender's vulnerable elliptic dependency, using
63
+ Node's native Ed25519 while preserving pairing formats and optional installs.
64
+ - Enabled native stream certificate verification and restricted FFmpeg network
65
+ playlists that cannot safely carry TLS policy. Documented the remaining
66
+ native OpenSSL numeric-IP certificate identity limitation.
67
+ - Kept Doctor's probes read-only, bounded local service checks, and excluded
68
+ receiver codes and stream credentials from AirPlay diagnostics.
69
+ - Updated documentation and development dependencies to patched releases.
70
+
71
+ Quattro integration and Linux AirPlay remain experimental. Software playback
72
+ checks do not establish physical receiver, speaker, hardware-key, or suspend/wake
73
+ support; existing platform support tiers are unchanged.
7
74
 
8
75
  ## [0.2.4] - 2026-09-17
9
76
 
@@ -478,3 +545,4 @@ Initial public release.
478
545
  [0.1.3]: https://github.com/Ciphore/RadioCLI/releases/tag/v0.1.3
479
546
  [0.1.2]: https://github.com/Ciphore/RadioCLI/releases/tag/v0.1.2
480
547
  [0.1.1]: https://github.com/Ciphore/RadioCLI/releases/tag/v0.1.1
548
+ [0.2.5]: https://github.com/Ciphore/RadioCLI/releases/tag/v0.2.5
package/CONTRIBUTING.md CHANGED
@@ -51,6 +51,16 @@ a local WAV file; mpv uses null audio in deterministic package
51
51
  checks. These checks exercise the player process and controls, and do not prove
52
52
  speaker output. `npm run smoke:data` intentionally contacts public providers.
53
53
 
54
+ Also run `npm run smoke:network` for playback or transport changes. It uses
55
+ loopback HTTP/TLS fixtures with null/dummy audio to verify certificate rejection,
56
+ live EOF and stall recovery, finite-media EOF, Stop ownership, and installed
57
+ fallback readiness. Set `RADIOCLI_REQUIRE_FALLBACKS=1` when the test host should
58
+ have FFmpeg, ffplay and VLC; missing tools then fail instead of being skipped.
59
+
60
+ Run `npm run build && npm run smoke:cli` when changing shell playback or session
61
+ control. It uses an isolated library and silent local WAV with mpv's null audio
62
+ output to check background startup, separate CLI controls, presets, and shutdown.
63
+
54
64
  Live provider checks are useful before releases, but ordinary pull requests
55
65
  should stay deterministic. Run `npm run verify:release` for a full maintainer
56
66
  pre-release pass that includes live data, playback, packaging, and fresh-install
package/README.md CHANGED
@@ -1,39 +1,70 @@
1
- ![RadioCLI Now Playing visualizer demo](apps/docs/public/demo/radiocli-now-playing.gif)
2
-
3
1
  # RadioCLI
4
2
 
5
- [![CI](https://github.com/Ciphore/RadioCLI/actions/workflows/ci.yml/badge.svg)](https://github.com/Ciphore/RadioCLI/actions/workflows/ci.yml)
3
+ RadioCLI is an open-source internet radio player with a full-screen terminal
4
+ interface and a scriptable CLI. Discover and stream stations from around the
5
+ world without leaving your terminal. No account. No cloud library.
6
+
7
+ [![CI](https://github.com/Ciphore/RadioCLI/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Ciphore/RadioCLI/actions/workflows/ci.yml)
6
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
9
  [![Node.js](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](package.json)
8
10
 
9
- RadioCLI brings live radio to your terminal. Browse stations around the world,
10
- save favorites, and listen through `mpv`—without an account or cloud library.
11
-
12
- Built with [Ink](https://github.com/vadimdemedes/ink),
13
- [React](https://react.dev/), and the
14
- [Radio Browser](https://api.radio-browser.info/) directory.
15
-
16
- ## Highlights
17
-
18
- - Discover stations through Explore, Search, Countries, or approximate-location Nearby.
19
- - Listen with a receiver-style Now Playing screen, live metadata, sleep timer, diagnostics, and selectable visualizers.
20
- - Keep favorites, recents, imports, track history, and listening stats on your machine.
21
- - Create one-time or recurring radio alarms from the TUI, with native background
22
- scheduling, station fallback, missed-run grace, snooze, and optional Alarm Guard.
23
- - Move through the exact station list you tuned from with previous and next controls.
24
- - Use `mpv` for complete playback control, with `ffplay` and VLC as limited fallbacks.
25
- - Resize freely: full, compact, and micro layouts preserve navigation and essential controls; Explore retains its interactive world map at every usable size.
11
+ ![RadioCLI playing SomaFM while cycling through terminal receiver styles](apps/docs/public/demo/radiocli-now-playing.gif)
12
+
13
+ ## Features
14
+
15
+ - **Station discovery:** Search by name or genre, filter by country, language,
16
+ codec, and bitrate, or browse country lists. The interactive world map uses a
17
+ cached station atlas to find broadcasts around the map cursor and sort them by
18
+ distance. Cached directory results remain available during provider outages.
19
+ - **63 receiver styles:** Switch between braille animation, ASCII geometry,
20
+ waves, fire, cityscapes, and the Gallop display without interrupting
21
+ playback. Color themes are selected independently. Receivers animate during
22
+ active playback and return to a zero-signal display when audio is inactive.
23
+ - **Listening stats:** A 52-week activity graph, listening time, session counts,
24
+ active days, streaks, and your most-listened station. Stats are recorded locally
25
+ across TUI and CLI listening sessions.
26
+ - **Alarms (beta):** One-time and recurring schedules with timezones, independent
27
+ volume, fade-in, fallback stations, snooze, and auto-stop. Native OS scheduling
28
+ allows alarms to run after the terminal closes. See the
29
+ [alarm guide](apps/docs/content/docs/alarms.mdx) for setup and power requirements.
30
+ - **Agent control:** An optional Model Context Protocol (MCP) server exposes
31
+ typed tools for discovery, playback, favorites, stats, alarms, AirPlay, and
32
+ appearance. Coding assistants can operate RadioCLI directly; `radiocli agent`
33
+ provides the same controls for other local tools.
34
+ - **Terminal support:** Full, compact, and micro layouts adapt to window resizing.
35
+ Keyboard, mouse, and trackpad controls work alongside Unicode and ASCII
36
+ rendering, reduced motion, screen-reader output, transparent backgrounds, and
37
+ limited-color or `NO_COLOR` environments. Offline browsing and low-bandwidth
38
+ modes are also supported.
39
+ - **Playback:** Pause, volume, mute, and live track metadata through `mpv`, with
40
+ configurable media keys and a sleep timer. Previous and next follow the station
41
+ list you tuned from. Track history and stream diagnostics are available in
42
+ the TUI.
43
+ - **Nearby stations:** Discover stations around an approximate location and
44
+ browse them by distance. Location lookup runs when you open Nearby and can be
45
+ disabled in Settings.
46
+ - **Local library:** Save favorites, revisit recent stations, and manage imports
47
+ in one place. Settings, track history, and listening data stay on your machine,
48
+ with JSON backup and restore. No account is required.
49
+ - **AirPlay (experimental):** Discover network receivers and switch audio output
50
+ from the TUI, including passcode entry when required. Availability depends on
51
+ the platform and receiver; see [supported setups](apps/docs/content/docs/platforms.mdx).
52
+ - **Custom stations:** Add direct HTTP(S) stream URLs with automatic ICY metadata
53
+ lookup. Import M3U, PLS, or XSPF playlists and export an M3U library.
54
+ - **Shell commands:** Start background playback by station name, save named
55
+ presets, and watch the current track from your prompt. Shell completion and
56
+ JSON or plain output support interactive use and scripting.
26
57
 
27
58
  ## Quick start
28
59
 
29
- On macOS configurations supported by Homebrew:
60
+ **Homebrew**
30
61
 
31
62
  ```bash
32
63
  brew install ciphore/tap/radiocli
33
64
  radiocli
34
65
  ```
35
66
 
36
- macOS, Linux, or Windows with npm:
67
+ **npm · Node.js 22 or newer**
37
68
 
38
69
  ```bash
39
70
  npm install -g @ciphore/radiocli
@@ -41,260 +72,84 @@ radiocli setup
41
72
  radiocli
42
73
  ```
43
74
 
44
- RadioCLI requires Node.js 22 or newer. The Homebrew formula installs `mpv` and
45
- FFmpeg. After an npm install, `radiocli setup` detects the operating system and
46
- package manager, lets you choose `mpv`, FFmpeg, and VLC, installs the selected
47
- native tools with branded progress feedback, and checks that executables are
48
- available. Play a station to verify the actual audio path. Intel or older macOS
49
- installations should consult the platform matrix for alternative package routes.
50
-
51
- See the [installation guide](apps/docs/content/docs/getting-started/install.mdx)
52
- for Windows, Linux distributions, AirPlay prerequisites, and fallback players.
75
+ Setup helps install the playback tools for your system. See
76
+ [installation](apps/docs/content/docs/getting-started/install.mdx) for other
77
+ routes and [platform support](apps/docs/content/docs/platforms.mdx) for
78
+ macOS, Linux, Windows, and BSD details.
53
79
 
54
- ## Platform coverage
55
-
56
- RadioCLI separates core playback, storage, terminal rendering, desktop helpers,
57
- and native alarm services. A missing scheduler or clipboard helper leaves the
58
- other features available. `radiocli doctor --json` reports each capability,
59
- terminal and network policy, and runtime eligibility separately.
60
-
61
- The [platform matrix](apps/docs/content/docs/platforms.mdx) distinguishes required
62
- CI targets from verified installations, experimental community paths, and known
63
- runtime blockers. Node 22 and 24 are the reference test lines. BSD package plans,
64
- Termux, Haiku and illumos guidance do not imply that every OS/CPU combination has
65
- been exercised. AIX playback and Haiku's packaged Node 20 remain explicit gaps.
66
- Linux without a usable systemd user session can still run the player and TUI;
67
- reliable background alarms require a verified native adapter.
68
-
69
- Invocation preferences do not change saved settings:
70
-
71
- ```bash
72
- RADIOCLI_ASCII=1 radiocli # ASCII decoration; station names preserved
73
- NO_COLOR=1 radiocli # no color or background fills
74
- RADIOCLI_SCREEN_READER=1 radiocli # readable controls; no visualizer animation
75
- RADIOCLI_OFFLINE=1 radiocli # cached directories and saved stations
76
- RADIOCLI_LOW_BANDWIDTH=1 radiocli # cached atlas; no automatic receiver scan
77
- ```
80
+ Launch `radiocli`, open Search or Explore, select a station, and press `Enter`
81
+ to start playback.
78
82
 
79
- Offline mode disables directory, location, update, vote and receiver-discovery
80
- requests. An external player still needs a connection to listen to a live stream.
81
- For proxy, SSH, read-only storage and custom-player paths, see
82
- [troubleshooting](apps/docs/content/docs/troubleshooting.mdx).
83
-
84
- ## Visual tour
83
+ | Key | Action |
84
+ | --- | --- |
85
+ | `←` / `→` | Switch screens |
86
+ | `↑` / `↓`, then `Enter` | Select and tune a station |
87
+ | `space` | Pause or resume |
88
+ | `f` | Toggle a favorite |
89
+ | `v` / `t` | Change receiver style / color theme on Playing |
90
+ | `?` / `q` | Show shortcuts / quit |
85
91
 
86
- These recordings come from the built TUI. Generate them locally with
87
- `npm run demo:assets`.
92
+ The footer shows controls for the current screen. See the
93
+ [controls guide](apps/docs/content/docs/getting-started/tui-controls.mdx) for
94
+ filters, station skipping, media keys, and the command palette.
88
95
 
89
- ### Library
96
+ ## Screenshots
90
97
 
91
- ![Favorites and recent stations in the RadioCLI Library](apps/docs/public/demo/radiocli-library.gif)
98
+ **Explore:** Move the map cursor with WASD or click a location to list stations
99
+ by distance.
92
100
 
93
- ### Explore
101
+ ![Interactive world map and station discovery in RadioCLI](apps/docs/public/demo/radiocli-explore-map.gif)
94
102
 
95
- ![World map discovery in RadioCLI Explore](apps/docs/public/demo/radiocli-explore-map.gif)
103
+ **Search:** Browse results with station names, countries, codecs, and bitrates.
96
104
 
97
- ### Search
105
+ ![Searching internet radio stations in the terminal](apps/docs/public/demo/radiocli-search.gif)
98
106
 
99
- ![Station search results in RadioCLI](apps/docs/public/demo/radiocli-search.gif)
107
+ **Library:** Switch between favorites, recent stations, and imported streams.
100
108
 
101
- ### Nearby
109
+ ![Favorites and recent stations in the RadioCLI library](apps/docs/public/demo/radiocli-library.gif)
102
110
 
103
- ![Nearby stations in RadioCLI](apps/docs/public/demo/radiocli-nearby.gif)
111
+ **Nearby:** Browse stations around your approximate location.
104
112
 
105
- ### Stats
113
+ ![Nearby radio stations in RadioCLI](apps/docs/public/demo/radiocli-nearby.gif)
106
114
 
107
- ![Local listening stats with selectable display colors](apps/docs/public/demo/radiocli-stats-colors.gif)
115
+ **Stats:** Review listening activity and totals, with selectable display colors.
108
116
 
109
- ## Essential controls
117
+ ![Listening stats and the 52-week activity graph with selectable colors](apps/docs/public/demo/radiocli-stats-colors.gif)
110
118
 
111
- | Key | Action |
112
- | --- | --- |
113
- | `←` / `→` or `Tab` / `Shift+Tab` | Switch screens |
114
- | `↑` / `↓` or `n` / `p` | Move the selection |
115
- | `Enter` | Open or tune the selection |
116
- | `space` or `F8` | Pause; resume reconnects at the live edge with `mpv` |
117
- | `,` / `.` or `F7` / `F9` | Previous or next station |
118
- | `+` / `-` | Change volume |
119
- | `f` (`Ctrl+F` while typing in Search) | Save or remove a favorite |
120
- | `?` | Open all shortcuts and commands |
121
- | `q` or `Ctrl+C` twice | Quit cleanly |
122
-
123
- The footer always shows controls for the current screen. Press `:` to open the
124
- command palette. The [TUI controls guide](apps/docs/content/docs/getting-started/tui-controls.mdx)
125
- covers filters, playback, media-key learning, AirPlay, and every command.
126
-
127
- ## CLI
128
-
129
- RadioCLI can also be controlled by local coding agents. Run `radiocli setup`
130
- and opt into MCP, or use `radiocli setup --mcp`, to configure detected Codex,
131
- Claude, OpenCode, Cursor, Gemini, VS Code/Copilot, and Orca-backed runtimes
132
- automatically. From the TUI, open **Settings → Agent control & MCP** and turn on
133
- **Allow local agent control** for the same one-step setup—no terminal command is
134
- required. The same actions are
135
- available to non-MCP harnesses through `radiocli agent` and `radiocli alarm`:
136
- search and play stations, browse recents/favorites/nearby stations, favorite the
137
- current station, inspect listening stats, create and control alarms, control
138
- macOS AirPlay, change the receiver look, or run a user-configured completion
139
- preset. Codex Voice can invoke those same typed MCP tools. Agent playback opens
140
- the normal RadioCLI TUI in a terminal window by default on macOS, Windows, and
141
- Linux. Interactive setup explains that macOS may require the agent host to approve
142
- a one-time Automation prompt; users can explicitly opt into headless playback.
143
- Scheduled alarms remain isolated and always use local output rather than
144
- unattended AirPlay. See the
145
- [CLI guide](apps/docs/content/docs/getting-started/cli.mdx#local-agent-and-mcp-control).
119
+ ## CLI and agent control
146
120
 
147
121
  ```bash
148
- radiocli # Start the TUI
149
- radiocli check # Check providers, playback tools, and the local store
150
- radiocli doctor --json # Create a redacted support report
151
- radiocli search "japan hits"
152
- radiocli countries
153
- radiocli import https://example.com/live.mp3
154
- radiocli import stations.m3u
155
- radiocli export favorites.m3u
156
- radiocli alarm list
157
- radiocli alarm doctor
158
- radiocli update --install # Upgrade and repair enabled MCP registrations
122
+ radiocli play jazz
123
+ radiocli now
124
+ radiocli favorite
125
+ radiocli stop
159
126
  ```
160
127
 
161
- Existing installations keep their library and preferences when upgraded. Use
162
- `radiocli update` to inspect the detected Homebrew, npm, pnpm, or Bun command,
163
- or `radiocli update --install` to run it. MCP registrations use an
164
- upgrade-stable launcher so versioned package paths do not break them. On
165
- Windows this is a shell-free Node command; on Unix it uses the `radiocli` shim.
166
- Run `radiocli mcp status` to inspect registrations and `radiocli mcp repair`
167
- after moving an installation or when an agent client reports a stale server;
168
- fully quit and reopen agent clients after enabling, repairing, or upgrading.
169
- Until that restart, even a newly created task in an already-running client will
170
- not have RadioCLI's tools and may incorrectly fall back to browser playback.
171
-
172
- RadioCLI imports direct HTTP(S) station streams as well as `.m3u`, `.pls`, and
173
- `.xspf` playlists. Direct import is useful when a station cannot be found through
174
- an open radio directory—for example, an iHeartMedia station that is available
175
- only through its direct stream. Find the station's direct stream URL and run
176
- `radiocli import <stream-url> [station name]`, or enter
177
- `:import <stream-url> [station name]` in the TUI. RadioCLI reads available
178
- HTTP/ICY metadata to discover the name, codec, bitrate, and homepage; the name
179
- argument is optional and overrides the published name. Imported stations appear
180
- after favorites and before recently played stations. RadioCLI exports favorites
181
- and imported streams as `.m3u`.
182
-
183
- Alarms are experimental beta functionality and live under **Overview**,
184
- immediately before Settings. Saving an enabled
185
- alarm registers it with launchd on macOS, a systemd user timer on Linux, or Task
186
- Scheduler on Windows, so the terminal does not need to remain open. When it
187
- fires, RadioCLI reopens the saved supported terminal and shows the ringing
188
- screen. Enter transfers the station into normal interactive playback; Space
189
- stops it and snoozes for 10 minutes. If a
190
- RadioCLI TUI is already open, that instance shows the controls instead. The
191
- computer must still be powered on with a logged-in interactive audio session; wake timing
192
- depends on hardware and OS power policy. See the [CLI guide](apps/docs/content/docs/getting-started/cli.mdx)
193
- and [reliability notes](apps/docs/content/docs/reliability.mdx) before relying on
194
- an alarm for something critical. Use a secondary device as the primary alarm
195
- for safety-critical, medical, travel, or emergency timing. See the dedicated
196
- [Alarms guide](apps/docs/content/docs/alarms.mdx) for the full workflow.
197
-
198
- The **Verify alarm setup** row performs a disposable native-scheduler
199
- registration, an authenticated terminal/control handshake, sleep-inhibitor and
200
- system-volume checks, and a short sample from the next configured alarm. It
201
- cleans up the temporary job and reports hard blockers separately from hardware-
202
- or policy-dependent wake limitations.
203
-
204
- See the [CLI guide](apps/docs/content/docs/getting-started/cli.mdx) for all
205
- arguments and examples.
206
-
207
- ## Playback and reliability
208
-
209
- RadioCLI prefers `mpv` because it handles redirects, HLS, real-world codecs,
210
- ICY metadata, and interactive controls reliably. `ffplay` and VLC can keep a
211
- stream playing when `mpv` is unavailable, but the UI labels their controls as
212
- limited.
213
-
214
- Station providers use mirror fallback, bounded caches, and stale-cache recovery.
215
- Playback waits for backend readiness, applies tune timeouts, and can skip broken
216
- streams. Corrupt library and cache files are backed up instead of silently
217
- overwritten.
218
-
219
- Scheduled playback retries the primary station once, then tries an optional
220
- fallback. A missed-run grace window controls catch-up after sleep or logout.
221
- True fade-in requires `mpv`; `ffplay` and VLC start audibly at the configured
222
- target volume. Scheduled alarms always use local speakers, never unattended
223
- AirPlay.
224
-
225
- On macOS, an npm or source installation is executed by Node, so System Settings
226
- may identify its background item as `node`. Showing RadioCLI as the providing
227
- application requires a future signed macOS app bundle with a bundled helper;
228
- changing a launchd label cannot safely relabel the executable.
229
-
230
- Read the [reliability notes](apps/docs/content/docs/reliability.mdx) for failure
231
- handling and troubleshooting.
232
-
233
- ## Privacy
234
-
235
- RadioCLI does not require an account, proxy audio, or upload your listening
236
- history. Favorites, recents, imports, settings, alarms, track history, and
237
- activity stay in a local JSON library. Full JSON library backups include alarm
238
- definitions, while native scheduler registrations, runtime health, Alarm Guard
239
- state, and authenticated loopback-control tokens remain machine-local.
240
-
241
- Nearby contacts `ipapi.co` only when you open that screen, then uses an
242
- approximate location to sort the local station atlas. Disable the lookup with
243
- `l`, Settings, or `:location off`.
244
-
245
- Favoriting a Radio Browser station sends a best-effort public directory vote by
246
- default. Turn off **Share favorite votes with Radio Browser** in Settings to
247
- keep favorites local-only.
248
-
249
- See [Privacy and security](apps/docs/content/docs/privacy-security.mdx) for the
250
- complete data-flow description.
251
-
252
- ## Project structure
253
-
254
- - `src/ui` — screens, input, layout, and terminal rendering
255
- - `src/providers` — station directories, resolution, and caches
256
- - `src/player` — playback backends, metadata, and AirPlay
257
- - `src/platform` — host identity, independent capabilities, native command plans,
258
- paths, terminal policy, networking and runtime eligibility
259
- - `src/alarms` — schedules, native registration, Alarm Guard, and active controls
260
- - `src/storage` — local library persistence and migration
261
- - `apps/docs` — documentation website and manual
262
-
263
- More detail lives in the [architecture guide](apps/docs/content/docs/architecture.mdx)
264
- and [design notes](apps/docs/content/docs/design.mdx).
265
-
266
- ## Development
128
+ `play` starts background playback and returns to your prompt. If the TUI is
129
+ already open, commands control its player instead. Both interfaces use the same
130
+ library, metadata, and listening history. Use `radiocli help` for common commands
131
+ or `radiocli help --all` for the full reference.
267
132
 
268
- ```bash
269
- git clone https://github.com/Ciphore/RadioCLI.git
270
- cd RadioCLI
271
- npm ci
272
- npm run verify
273
- npm run dev
274
- ```
275
-
276
- `npm run verify` checks types, lint, tests, the production build, and live provider
277
- data. `npm run fresh:check -- --require-mpv` tests packed installs with and without
278
- optional dependencies using a local WAV, real mpv IPC controls and MCP. See
279
- [contribution checks](CONTRIBUTING.md) for documentation and packaging validation.
280
-
281
- ## Contributing
282
-
283
- Contributions are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md), and
284
- include `radiocli check` output with playback reports.
133
+ Run `radiocli setup --mcp` to configure the optional local MCP integration for
134
+ coding assistants. Ordinary CLI playback does not require MCP setup. The
135
+ [CLI and agent guide](apps/docs/content/docs/getting-started/cli.mdx) covers
136
+ commands, presets, shell completion, and integration details.
285
137
 
286
138
  ## Documentation
287
139
 
288
- - [Getting started](apps/docs/content/docs/index.mdx)
289
- - [Installation](apps/docs/content/docs/getting-started/install.mdx)
290
- - [Platform matrix](apps/docs/content/docs/platforms.mdx)
291
- - [Troubleshooting](apps/docs/content/docs/troubleshooting.mdx)
292
- - [Controls](apps/docs/content/docs/getting-started/tui-controls.mdx)
293
- - [Architecture](apps/docs/content/docs/architecture.mdx)
294
- - [Release packaging](apps/docs/content/docs/release-packaging.mdx)
140
+ [Getting started](apps/docs/content/docs/index.mdx) ·
141
+ [Controls](apps/docs/content/docs/getting-started/tui-controls.mdx) ·
142
+ [Alarms](apps/docs/content/docs/alarms.mdx) ·
143
+ [Troubleshooting](apps/docs/content/docs/troubleshooting.mdx) ·
144
+ [Architecture](apps/docs/content/docs/architecture.mdx)
145
+
146
+ Your library and listening history stay on your machine. Read
147
+ [Privacy and security](apps/docs/content/docs/privacy-security.mdx) for the
148
+ network requests and data flow.
295
149
 
296
- Run the documentation site locally with `npm run docs:dev`.
150
+ Want to contribute? [CONTRIBUTING.md](CONTRIBUTING.md) covers development and
151
+ checks. RadioCLI is [MIT licensed](LICENSE).
297
152
 
298
- ## License
153
+ ---
299
154
 
300
- [MIT](LICENSE)
155
+ ![Radio on internet, from Silicon Valley](apps/docs/public/demo/silicon-valley-radio-on-internet.gif)
package/SECURITY.md CHANGED
@@ -24,10 +24,32 @@ information, or unexpected command execution.
24
24
  - The app stores recents, favorites, settings, imports, and provider cache data
25
25
  locally under the user data directory.
26
26
  - RadioCLI does not proxy audio. It resolves public stream URLs and hands
27
- playback to `mpv` or `ffplay`.
27
+ playback to native players.
28
28
  - Favoriting a Radio Browser station sends a best-effort vote to that public
29
29
  directory by default; the Settings screen can disable vote sharing.
30
30
 
31
+ ## Stream Certificate Verification
32
+
33
+ RadioCLI enables mpv TLS verification, including redirected and nested streams,
34
+ and enables TLS verification for direct FFmpeg/ffplay network media. Native
35
+ trust stores and player certificate configuration apply. It never retries an
36
+ HTTPS input with certificate verification disabled.
37
+
38
+ FFmpeg releases in circulation do not propagate TLS policy to every resource
39
+ opened by network playlist demuxers. Those inputs are restricted in FFmpeg
40
+ fallbacks: use mpv for HLS/DASH/IMF playback, or a direct audio stream for AirPlay.
41
+ Local FFmpeg inputs cannot open network protocols. The plain HTTP compatibility
42
+ retry explicitly blocks both HTTPS and TLS, including changed redirects.
43
+
44
+ Known upstream limitation: tested FFmpeg/OpenSSL builds can accept a trusted
45
+ certificate with a mismatched **numeric IP address**, even with verification
46
+ enabled. This also affects mpv builds using that native TLS implementation.
47
+ DNS hostname mismatch fixtures are rejected. Prefer DNS station URLs. A
48
+ separate Node preflight would not secure the player's actual connection, so
49
+ RadioCLI does not present one as a fix. See the
50
+ [reliability reference](apps/docs/content/docs/reliability.mdx#playback-recovery-and-secure-streams)
51
+ and native smoke results for the precise guarantees and remaining limits.
52
+
31
53
  ## Supported Versions
32
54
 
33
55
  Until a tagged release cadence is established, security fixes target the active
@@ -1,4 +1,5 @@
1
- import { canonicalizeAlarmTime, canonicalizeIsoWeekdays, canonicalizeTimeZone, nextOccurrenceForAlarm } from '../alarms/schedule.js';
1
+ import { AlarmOccurrenceCache } from '../alarms/occurrence-cache.js';
2
+ import { canonicalizeAlarmTime, canonicalizeIsoWeekdays, canonicalizeTimeZone } from '../alarms/schedule.js';
2
3
  import { connectActiveAlarms } from '../alarms/active-session.js';
3
4
  import { createSchedulerService } from '../alarms/scheduler.js';
4
5
  export class AgentAlarmService {
@@ -6,6 +7,7 @@ export class AgentAlarmService {
6
7
  resolveStation;
7
8
  scheduler;
8
9
  handoffToInteractive;
10
+ occurrences = new AlarmOccurrenceCache();
9
11
  constructor(store, resolveStation, scheduler = createSchedulerService(), handoffToInteractive) {
10
12
  this.store = store;
11
13
  this.resolveStation = resolveStation;
@@ -45,7 +47,7 @@ export class AgentAlarmService {
45
47
  keepAwakeUntilAlarm: input.keepAwakeUntilAlarm ?? false
46
48
  }
47
49
  };
48
- const alarm = this.store.addAlarm(alarmInput);
50
+ const alarm = await this.store.addAlarm(alarmInput);
49
51
  await this.syncSaved(alarm);
50
52
  return this.describe(alarm);
51
53
  }
@@ -55,7 +57,7 @@ export class AgentAlarmService {
55
57
  const fallbackStation = input.clearFallback
56
58
  ? undefined
57
59
  : input.fallbackStationId ? await this.requiredStation(input.fallbackStationId) : alarm.playback.fallbackStation;
58
- const updated = this.store.updateAlarm(alarm.id, {
60
+ const updated = await this.store.updateAlarm(alarm.id, {
59
61
  label: input.label === undefined ? alarm.label : cleanLabel(input.label),
60
62
  enabled: input.enabled ?? alarm.enabled,
61
63
  station,
@@ -76,7 +78,7 @@ export class AgentAlarmService {
76
78
  return this.describe(updated);
77
79
  }
78
80
  async setEnabled(id, enabled) {
79
- const alarm = this.store.toggleAlarm(this.requiredAlarm(id).id, enabled);
81
+ const alarm = await this.store.toggleAlarm(this.requiredAlarm(id).id, enabled);
80
82
  await this.syncSaved(alarm);
81
83
  return this.describe(alarm);
82
84
  }
@@ -84,8 +86,9 @@ export class AgentAlarmService {
84
86
  if (!confirm)
85
87
  throw new Error('Alarm removal requires confirm=true. List alarms first and confirm the exact alarm ID with the user.');
86
88
  const alarm = this.requiredAlarm(id);
87
- await this.scheduler.remove(alarm.id);
88
- if (!this.store.removeAlarm(alarm.id))
89
+ const disabled = await this.store.toggleAlarm(alarm.id, false);
90
+ await this.scheduler.remove(disabled.id);
91
+ if (!await this.store.removeAlarm(alarm.id))
89
92
  throw new Error(`Alarm not found: ${id}`);
90
93
  return { ok: true, removed: alarm.id };
91
94
  }
@@ -117,7 +120,7 @@ export class AgentAlarmService {
117
120
  enabled: alarm.enabled,
118
121
  station: { id: `${alarm.station.provider}:${alarm.station.id}`, name: alarm.station.name },
119
122
  schedule: alarm.schedule,
120
- nextOccurrence: nextOccurrenceForAlarm(alarm, new Date())?.toISOString() ?? null,
123
+ nextOccurrence: this.occurrences.next(alarm, new Date())?.toISOString() ?? null,
121
124
  playback: {
122
125
  volume: alarm.playback.volume,
123
126
  fadeSeconds: alarm.playback.fadeSeconds,
package/dist/agent/cli.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { airPlayPolicy } from '../player/airplay-policy.js';
2
+ import { readSettingsWithoutMigration } from '../storage/read-settings.js';
3
+ import { defaultLibraryPath } from '../storage/store.js';
1
4
  import { AgentRadioService, stationForAgent } from './service.js';
2
5
  import { configureMcpIntegrations, mcpIntegrationReport, portableMcpConfig } from './mcp-install.js';
3
6
  import { runMcpServer } from './mcp-server.js';
@@ -64,7 +67,7 @@ export async function runAgentCliCommand(args, runtime) {
64
67
  else if (command === 'done')
65
68
  value = await service.runCompletionPreset();
66
69
  else
67
- throw new Error('Usage: radiocli agent <status|search|browse|play|pause|resume|stop|next|previous|volume|mute|unmute|favorite|unfavorite|stats|appearance|airplay|preset|done>');
70
+ throw new Error(agentCommandUsage());
68
71
  console.log(JSON.stringify(value, null, 2));
69
72
  }
70
73
  function parseBrowseKind(value) {
@@ -191,3 +194,15 @@ function assertMcpInstallSucceeded(results, action) {
191
194
  return;
192
195
  throw new Error(`RadioCLI was ${action} where possible, but ${failed.length} integration${failed.length === 1 ? '' : 's'} failed: ${failed.map(result => `${result.client} (${result.detail})`).join('; ')}`);
193
196
  }
197
+ export function agentCommandUsage() {
198
+ let enabled = airPlayPolicy().enabled;
199
+ if (process.platform === 'linux') {
200
+ try {
201
+ enabled = airPlayPolicy({ linuxAirPlayEnabled: readSettingsWithoutMigration(defaultLibraryPath())?.linuxAirPlayEnabled === true }).enabled;
202
+ }
203
+ catch {
204
+ enabled = false;
205
+ }
206
+ }
207
+ return `Usage: radiocli agent <status|search|browse|play|pause|resume|stop|next|previous|volume|mute|unmute|favorite|unfavorite|stats|appearance${enabled ? '|airplay' : ''}|preset|done>`;
208
+ }