@ciphore/radiocli 0.2.4 → 0.2.6

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 (145) hide show
  1. package/CHANGELOG.md +103 -2
  2. package/CONTRIBUTING.md +28 -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 +182 -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 +130 -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 +314 -76
  71. package/dist/types.js +22 -1
  72. package/dist/ui/AdaptiveContent.js +21 -24
  73. package/dist/ui/App.js +708 -454
  74. package/dist/ui/AppContent.js +5 -5
  75. package/dist/ui/airplay-settings.js +12 -5
  76. package/dist/ui/bidi-ranges.js +45 -0
  77. package/dist/ui/components/AdaptiveMarquee.js +16 -9
  78. package/dist/ui/components/ReceiverVisualRow.js +39 -0
  79. package/dist/ui/format.js +37 -4
  80. package/dist/ui/help-content.js +5 -2
  81. package/dist/ui/rtl-output.js +137 -0
  82. package/dist/ui/screen-items.js +10 -4
  83. package/dist/ui/screens/AirPlaySettingsScreen.js +3 -3
  84. package/dist/ui/screens/AlarmsScreen.js +5 -2
  85. package/dist/ui/screens/HelpScreen.js +3 -3
  86. package/dist/ui/screens/NowPlayingScreen.js +2 -21
  87. package/dist/ui/screens/SettingsScreen.js +16 -16
  88. package/dist/ui/screens/StatsScreen.js +1 -1
  89. package/dist/ui/text-direction.js +9 -0
  90. package/dist/ui/theme.js +46 -0
  91. package/dist/ui/use-alarm-tui.js +24 -24
  92. package/dist/ui/use-app-input.js +42 -8
  93. package/dist/ui/use-command-executor.js +21 -14
  94. package/dist/ui/visualizers/boat-lake-kernel.js +325 -0
  95. package/dist/ui/visualizers/boat-lake.js +161 -0
  96. package/dist/ui/visualizers/gallop.js +2 -2
  97. package/dist/ui/visualizers/receiver-style-registry.js +3 -1
  98. package/dist/ui/visualizers/receiver-visualizers.js +6 -108
  99. package/dist/ui/visualizers/sumi-ocean.js +183 -0
  100. package/dist/ui/visualizers/terminal-cell.js +45 -0
  101. package/dist/update-check.js +2 -1
  102. package/docs/THIRD_PARTY_NOTICES.md +6 -4
  103. package/docs/boat-lake.md +88 -0
  104. package/docs/sumi-ocean.md +80 -0
  105. package/package.json +36 -12
  106. package/scripts/build-airplay-native.mjs +22 -0
  107. package/vendor/node-airtunes2/LICENSE +208 -0
  108. package/vendor/node-airtunes2/RADIOCLI_PATCH.md +29 -0
  109. package/vendor/node-airtunes2/README.md +225 -0
  110. package/vendor/node-airtunes2/UPSTREAM.json +50 -0
  111. package/vendor/node-airtunes2/binding.gyp +41 -0
  112. package/vendor/node-airtunes2/lib/ap2_test.js +4 -0
  113. package/vendor/node-airtunes2/lib/atv.js +307 -0
  114. package/vendor/node-airtunes2/lib/atvAuthenticator.js +136 -0
  115. package/vendor/node-airtunes2/lib/audio_out.js +69 -0
  116. package/vendor/node-airtunes2/lib/circular_buffer.js +133 -0
  117. package/vendor/node-airtunes2/lib/config.js +26 -0
  118. package/vendor/node-airtunes2/lib/device_airtunes.js +532 -0
  119. package/vendor/node-airtunes2/lib/devices.js +266 -0
  120. package/vendor/node-airtunes2/lib/ed25519.js +26 -0
  121. package/vendor/node-airtunes2/lib/homekit/credentials.js +86 -0
  122. package/vendor/node-airtunes2/lib/homekit/encryption.js +69 -0
  123. package/vendor/node-airtunes2/lib/homekit/number.js +44 -0
  124. package/vendor/node-airtunes2/lib/homekit/tlv.js +95 -0
  125. package/vendor/node-airtunes2/lib/http.js +189 -0
  126. package/vendor/node-airtunes2/lib/index.d.ts +141 -0
  127. package/vendor/node-airtunes2/lib/index.js +127 -0
  128. package/vendor/node-airtunes2/lib/ntp.js +37 -0
  129. package/vendor/node-airtunes2/lib/num_util.js +21 -0
  130. package/vendor/node-airtunes2/lib/packet_pool.js +43 -0
  131. package/vendor/node-airtunes2/lib/rtsp.js +1807 -0
  132. package/vendor/node-airtunes2/lib/srp.js +158 -0
  133. package/vendor/node-airtunes2/lib/udp_servers.js +214 -0
  134. package/vendor/node-airtunes2/lib/util.js +12 -0
  135. package/vendor/node-airtunes2/package.json +31 -0
  136. package/vendor/node-airtunes2/src/CADebugMacros.h +257 -0
  137. package/vendor/node-airtunes2/src/CAHostTimeBase.cpp +105 -0
  138. package/vendor/node-airtunes2/src/CAHostTimeBase.h +224 -0
  139. package/vendor/node-airtunes2/src/aes_utils.c +864 -0
  140. package/vendor/node-airtunes2/src/aes_utils.h +24 -0
  141. package/vendor/node-airtunes2/src/base64.c +136 -0
  142. package/vendor/node-airtunes2/src/base64.h +42 -0
  143. package/vendor/node-airtunes2/src/bindings.cc +29 -0
  144. package/vendor/node-airtunes2/src/codec.cc +170 -0
  145. package/vendor/node-airtunes2/src/coreaudio.cc +332 -0
@@ -5,9 +5,9 @@ import { computeListeningStats } from '../activity/stats.js';
5
5
  import { playbackBackendLabel } from '../player/backend-install.js';
6
6
  import { visibleWindow } from './list-window.js';
7
7
  import { displayWidth, padDisplayEnd, stationLocation, stationTags, stationTech, truncate } from './format.js';
8
- import { homeItems, settingsGroup, settingsGroups, settingsItemsForPage } from './screen-items.js';
8
+ import { homeItems, settingsGroup, settingsGroupsForAirPlay, settingsItemsForPage } from './screen-items.js';
9
9
  import { screenTitle } from './screen-meta.js';
10
- import { keyHelpSections, commandHelp } from './help-content.js';
10
+ import { keyHelpSections, commandHelpForAirPlay } from './help-content.js';
11
11
  import { settingLabel, settingValue } from './screens/SettingsScreen.js';
12
12
  import { exploreMapLand, mapMarker, panelBorder, textDim, textMuted, themeAccent } from './theme.js';
13
13
  import { panelBorderStyle, useDisplay } from './display-context.js';
@@ -19,6 +19,7 @@ import { formatExploreCursor } from './app-state.js';
19
19
  import { buildCosmoWorldMap } from './cosmo-world-map.js';
20
20
  import { adaptiveExploreFrameMetrics, computeAdaptiveExploreLayout } from './adaptive-explore-layout.js';
21
21
  import { AdaptiveMarquee } from './components/AdaptiveMarquee.js';
22
+ import { ReceiverVisualRow } from './components/ReceiverVisualRow.js';
22
23
  export function AdaptiveContent(props) {
23
24
  return _jsx(AdaptiveContentBody, { ...props });
24
25
  }
@@ -55,9 +56,11 @@ function AdaptiveContentBody(props) {
55
56
  width,
56
57
  mode,
57
58
  ascii,
58
- airPlaySupported
59
+ airPlaySupported,
60
+ platform: props.platform
59
61
  });
60
62
  const empty = adaptiveEmptyState({
63
+ airPlaySetupRequired: props.platform === 'linux' && !backends.includes('airplay'),
61
64
  screen,
62
65
  searchQuery,
63
66
  editingSearch,
@@ -105,9 +108,11 @@ function AdaptiveContentBody(props) {
105
108
  width,
106
109
  mode,
107
110
  ascii,
108
- airPlaySupported
111
+ airPlaySupported,
112
+ platform: props.platform
109
113
  });
110
114
  const empty = adaptiveEmptyState({
115
+ airPlaySetupRequired: props.platform === 'linux' && !backends.includes('airplay'),
111
116
  screen,
112
117
  searchQuery,
113
118
  editingSearch,
@@ -221,7 +226,7 @@ function StaticRows({ rows, width, theme }) {
221
226
  }
222
227
  function AdaptiveNowPlaying({ mode, station, playback, metadata, width, height, ascii, theme, receiverStyle, reduceMotion }) {
223
228
  const pulse = useReceiverPulse();
224
- const { screenReader } = useDisplay();
229
+ const { screenReader, app: appBackground } = useDisplay();
225
230
  const accent = themeAccent(theme);
226
231
  const stationName = station?.name ?? 'No station tuned';
227
232
  const showMetadata = Boolean(metadata?.title) && (screenReader || mode === 'compact' && height >= 9);
@@ -237,19 +242,7 @@ function AdaptiveNowPlaying({ mode, station, playback, metadata, width, height,
237
242
  const status = `${stationName} ${playback.state}${playback.muted ? ' · muted' : ` · vol ${playback.volume}`}`;
238
243
  return (_jsxs(Box, { flexDirection: "column", height: height, width: width, overflow: "hidden", children: [_jsx(Text, { color: accent, bold: true, "aria-label": mode === 'micro' ? `Now playing. Station: ${stationName}. Playback: ${playback.state}, ${playback.muted ? 'muted' : `volume ${playback.volume}`}` : undefined, children: mode === 'micro'
239
244
  ? _jsx(AdaptiveMarquee, { text: header, width: width, active: true, reduceMotion: reduceMotion })
240
- : truncate(header, width) }), mode === 'compact' ? _jsx(Text, { color: textMuted, "aria-label": `Station: ${stationName}. Playback: ${playback.state}, ${playback.muted ? 'muted' : `volume ${playback.volume}`}`, children: ascii ? toAsciiSafe(truncate(status, width)) : truncate(status, width) }) : null, gapRows ? _jsx(Box, { height: gapRows, flexShrink: 0 }) : null, !screenReader ? _jsx(Box, { flexDirection: "column", height: availableVisualRows, overflow: "hidden", "aria-hidden": true, children: visualRows.map((row, index) => (_jsx(Text, { color: row.segments ? undefined : row.color, children: row.segments
241
- ? renderAdaptiveSegments(row.segments, ascii)
242
- : ascii
243
- ? toAsciiSafe(row.text)
244
- : row.text }, index))) }) : null, showMetadata ? (_jsx(Text, { color: accent, "aria-label": `Track: ${metadata?.title ?? ''}`, children: _jsx(AdaptiveMarquee, { text: metadata?.title ?? '', width: width, active: true, reduceMotion: reduceMotion }) })) : null, gapRows ? _jsx(Box, { height: gapRows, flexShrink: 0 }) : null] }));
245
- }
246
- function renderAdaptiveSegments(segments, ascii) {
247
- let offset = 0;
248
- return segments.map(segment => {
249
- const key = offset;
250
- offset += segment.text.length;
251
- return (_jsx(Text, { color: segment.color, backgroundColor: segment.backgroundColor, bold: segment.bold, children: ascii ? toAsciiSafe(segment.text) : segment.text }, key));
252
- });
245
+ : truncate(header, width) }), mode === 'compact' ? _jsx(Text, { color: textMuted, "aria-label": `Station: ${stationName}. Playback: ${playback.state}, ${playback.muted ? 'muted' : `volume ${playback.volume}`}`, children: ascii ? toAsciiSafe(truncate(status, width)) : truncate(status, width) }) : null, gapRows ? _jsx(Box, { height: gapRows, flexShrink: 0 }) : null, !screenReader ? _jsx(Box, { flexDirection: "column", height: availableVisualRows, overflow: "hidden", "aria-hidden": true, children: visualRows.map((row, index) => (_jsx(ReceiverVisualRow, { row: row, width: width, ascii: ascii, backgroundColor: appBackground }, index))) }) : null, showMetadata ? (_jsx(Text, { color: accent, "aria-label": `Track: ${metadata?.title ?? ''}`, children: _jsx(AdaptiveMarquee, { text: metadata?.title ?? '', width: width, active: true, reduceMotion: reduceMotion }) })) : null, gapRows ? _jsx(Box, { height: gapRows, flexShrink: 0 }) : null] }));
253
246
  }
254
247
  function AdaptiveSearch({ mode, query, editing, loading, rows, empty, selected, height, width, theme, ascii, reduceMotion }) {
255
248
  const accent = themeAccent(theme);
@@ -273,15 +266,15 @@ function adaptiveRows(input) {
273
266
  }));
274
267
  }
275
268
  if (screen === 'settings') {
276
- const pageItems = settingsItemsForPage(input.settingsPage);
269
+ const pageItems = settingsItemsForPage(input.settingsPage, input.airPlaySupported);
277
270
  const labels = pageItems.map(item => settingLabel(item, updateCheck, input.appVersion, input.airPlaySupported));
278
271
  const labelWidth = pairedColumnWidth(labels, width, mode);
279
272
  return pageItems.map((item, index) => ({
280
273
  key: item,
281
274
  label: padDisplayEnd(truncate(settingLabel(item, updateCheck, input.appVersion, input.airPlaySupported), labelWidth), labelWidth),
282
275
  detail: input.settingsPage === 'root'
283
- ? adaptiveSettingsRootValue(item)
284
- : settingValue(item, library.settings, diagnostics, backends, airPlayDevices, updateCheck, input.appVersion),
276
+ ? adaptiveSettingsRootValue(item, input.airPlaySupported)
277
+ : settingValue(item, library.settings, diagnostics, backends, airPlayDevices, updateCheck, input.appVersion, input.platform),
285
278
  separator: ' ',
286
279
  index
287
280
  }));
@@ -312,7 +305,7 @@ function adaptiveRows(input) {
312
305
  }
313
306
  }
314
307
  rows.push({ key: 'section-commands', label: 'Commands', heading: true });
315
- for (const command of commandHelp) {
308
+ for (const command of commandHelpForAirPlay(input.airPlaySupported)) {
316
309
  rows.push({ key: `command-${command.name}`, label: `:${command.name}${command.args ? ` ${command.args}` : ''}`, detail: command.description, index });
317
310
  index += 1;
318
311
  }
@@ -320,8 +313,8 @@ function adaptiveRows(input) {
320
313
  }
321
314
  return stationAdaptiveRows(stations, favoriteKeys, selected, width, ascii);
322
315
  }
323
- function adaptiveSettingsRootValue(item) {
324
- const group = settingsGroups.find(candidate => candidate.label === item);
316
+ function adaptiveSettingsRootValue(item, airPlayVisible) {
317
+ const group = settingsGroupsForAirPlay(airPlayVisible).find(candidate => candidate.label === item);
325
318
  return group ? `${group.items.length} settings ›` : undefined;
326
319
  }
327
320
  function stationAdaptiveRows(stations, favoriteKeys, selected, width, ascii) {
@@ -378,6 +371,8 @@ function adaptiveEmptyState(input) {
378
371
  return [{ key: 'error', label: stationError }, { key: 'hint', label: 'Try again later or reopen this view.' }];
379
372
  }
380
373
  if (screen === 'airplay-settings') {
374
+ if (input.airPlaySetupRequired)
375
+ return [{ key: 'empty', label: 'Setup required' }, { key: 'hint', label: 'radiocli setup' }, { key: 'option', label: '--airplay' }];
381
376
  return [{ key: 'empty', label: 'No AirPlay receivers found.' }, { key: 'hint', label: 'Press r to scan again.' }];
382
377
  }
383
378
  return [{ key: 'empty', label: `No stations in ${stationTitle.toLowerCase()}.` }, { key: 'hint', label: 'Try another view or clear filters.' }];
@@ -415,6 +410,8 @@ function adaptiveStatus(props) {
415
410
  return playingStation ? playingStation.name : 'Receiver authentication';
416
411
  }
417
412
  if (screen === 'airplay-settings') {
413
+ if (props.platform === 'linux' && !props.backends.includes('airplay'))
414
+ return 'Setup required';
418
415
  return `${props.airPlayDevices.length} receivers · ${playbackBackendLabel(playback.backend)}`;
419
416
  }
420
417
  return `${stations.length.toLocaleString()} stations${playingStation ? ` · playing ${playingStation.name}` : ''}`;