@ciphore/radiocli 0.2.3 → 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.
- package/CHANGELOG.md +147 -2
- package/CONTRIBUTING.md +46 -6
- package/README.md +108 -209
- package/SECURITY.md +23 -1
- package/dist/agent/alarm-service.js +10 -7
- package/dist/agent/cli.js +16 -1
- package/dist/agent/command-schema.js +47 -0
- package/dist/agent/headless-host.js +234 -67
- package/dist/agent/launcher.js +7 -67
- package/dist/agent/mcp-install.js +15 -15
- package/dist/agent/mcp-server.js +10 -5
- package/dist/agent/service.js +74 -39
- package/dist/agent/session.js +227 -53
- package/dist/agent/tui-session.js +213 -0
- package/dist/alarms/active-session.js +9 -12
- package/dist/alarms/cli.js +19 -3
- package/dist/alarms/guard.js +79 -36
- package/dist/alarms/inhibitor.js +27 -22
- package/dist/alarms/occurrence-cache.js +25 -0
- package/dist/alarms/power-guard-store.js +2 -10
- package/dist/alarms/runner.js +140 -45
- package/dist/alarms/schedule.js +37 -2
- package/dist/alarms/scheduler.js +202 -52
- package/dist/alarms/setup-verification.js +1 -2
- package/dist/alarms/system-volume-linux.js +160 -0
- package/dist/alarms/system-volume-ownership.js +335 -0
- package/dist/alarms/system-volume.js +188 -26
- package/dist/alarms/terminal-launcher.js +76 -136
- package/dist/alarms/tui-presence.js +2 -4
- package/dist/cli/arguments.js +212 -0
- package/dist/cli/completion.js +44 -0
- package/dist/cli/doctor.js +156 -0
- package/dist/cli/interactive.js +79 -0
- package/dist/cli/listener.js +227 -0
- package/dist/cli/output.js +121 -0
- package/dist/cli/render-tui.js +34 -0
- package/dist/cli.js +198 -154
- package/dist/platform/capabilities.js +60 -0
- package/dist/platform/desktop-media.js +61 -0
- package/dist/platform/desktop.js +36 -0
- package/dist/platform/diagnostics.js +273 -0
- package/dist/{player/command.js → platform/executables.js} +43 -9
- package/dist/platform/ipc.js +14 -0
- package/dist/platform/json-worker.js +111 -0
- package/dist/platform/launch-command.js +138 -0
- package/dist/platform/loopback.js +44 -0
- package/dist/platform/mpris.js +356 -0
- package/dist/platform/network.js +380 -0
- package/dist/platform/omarchy.js +147 -0
- package/dist/platform/packages.js +222 -0
- package/dist/platform/paths.js +40 -0
- package/dist/platform/runtime.js +67 -0
- package/dist/platform/shell.js +24 -0
- package/dist/platform/storage.js +48 -0
- package/dist/platform/support.js +214 -0
- package/dist/platform/terminal.js +63 -0
- package/dist/platform/terminals.js +223 -0
- package/dist/player/airplay-discovery.js +145 -60
- package/dist/player/airplay-policy.js +15 -0
- package/dist/player/airplay-process.js +63 -0
- package/dist/player/airplay-readiness.js +42 -0
- package/dist/player/airplay-sender-health.js +49 -49
- package/dist/player/airplay-sender-package.js +5 -0
- package/dist/player/airplay-sender-patch.js +102 -4
- package/dist/player/airplay-setup.js +51 -0
- package/dist/player/airplay-worker.js +97 -41
- package/dist/player/backend-install.js +17 -95
- package/dist/player/command-diagnostics.js +2 -2
- package/dist/player/ffmpeg-input.js +37 -0
- package/dist/player/mpv-desktop-media.js +194 -0
- package/dist/player/mpv-ipc-client.js +52 -2
- package/dist/player/player-controller.js +569 -82
- package/dist/player/radio-engine.js +282 -0
- package/dist/playlists/playlist.js +18 -16
- package/dist/providers/cache.js +293 -99
- package/dist/providers/discovery-session.js +111 -0
- package/dist/providers/provider-manager.js +106 -28
- package/dist/providers/radio-browser.js +418 -89
- package/dist/providers/radio-garden.js +18 -23
- package/dist/setup-playback.js +152 -0
- package/dist/setup.js +163 -168
- package/dist/storage/file-lock.js +113 -0
- package/dist/storage/read-settings.js +33 -0
- package/dist/storage/replace-file.js +20 -0
- package/dist/storage/repository.js +247 -0
- package/dist/storage/station-identity.js +21 -0
- package/dist/storage/station-schema.js +27 -0
- package/dist/storage/store.js +333 -107
- package/dist/streams/import-stream.js +163 -0
- package/dist/ui/AdaptiveContent.js +49 -32
- package/dist/ui/App.js +822 -481
- package/dist/ui/AppContent.js +5 -5
- package/dist/ui/airplay-settings.js +12 -5
- package/dist/ui/app-state.js +8 -1
- package/dist/ui/ascii.js +11 -2
- package/dist/ui/bidi-ranges.js +45 -0
- package/dist/ui/components/AdaptiveMarquee.js +20 -10
- package/dist/ui/components/Logo.js +5 -2
- package/dist/ui/components/Menu.js +2 -2
- package/dist/ui/components/ScreenHeader.js +1 -1
- package/dist/ui/components/StationList.js +6 -4
- package/dist/ui/components/TopTabs.js +1 -1
- package/dist/ui/display-context.js +8 -11
- package/dist/ui/format.js +37 -4
- package/dist/ui/help-content.js +9 -6
- package/dist/ui/layout.js +5 -2
- package/dist/ui/page-footer.js +3 -3
- package/dist/ui/rtl-output.js +137 -0
- package/dist/ui/screen-items.js +12 -6
- package/dist/ui/screen-meta.js +1 -1
- package/dist/ui/screens/AirPlayCodeScreen.js +6 -2
- package/dist/ui/screens/AirPlaySettingsScreen.js +10 -6
- package/dist/ui/screens/AlarmsScreen.js +5 -2
- package/dist/ui/screens/ExploreScreen.js +2 -1
- package/dist/ui/screens/HelpScreen.js +8 -4
- package/dist/ui/screens/HomeScreen.js +5 -1
- package/dist/ui/screens/MapScreen.js +1 -1
- package/dist/ui/screens/NowPlayingScreen.js +4 -4
- package/dist/ui/screens/SearchScreen.js +3 -1
- package/dist/ui/screens/SettingsScreen.js +28 -21
- package/dist/ui/screens/StatsScreen.js +3 -1
- package/dist/ui/system-actions.js +80 -52
- package/dist/ui/terminal-renderer.js +16 -0
- package/dist/ui/text-direction.js +9 -0
- package/dist/ui/use-alarm-tui.js +57 -42
- package/dist/ui/use-app-input.js +84 -30
- package/dist/ui/use-command-executor.js +50 -19
- package/dist/ui/visualizers/gallop.js +1 -1
- package/dist/ui/visualizers/receiver-style-registry.js +2 -1
- package/dist/update-check.js +10 -18
- package/docs/THIRD_PARTY_NOTICES.md +6 -4
- package/package.json +34 -11
- package/scripts/build-airplay-native.mjs +22 -0
- package/vendor/node-airtunes2/LICENSE +208 -0
- package/vendor/node-airtunes2/RADIOCLI_PATCH.md +29 -0
- package/vendor/node-airtunes2/README.md +225 -0
- package/vendor/node-airtunes2/UPSTREAM.json +50 -0
- package/vendor/node-airtunes2/binding.gyp +41 -0
- package/vendor/node-airtunes2/lib/ap2_test.js +4 -0
- package/vendor/node-airtunes2/lib/atv.js +307 -0
- package/vendor/node-airtunes2/lib/atvAuthenticator.js +136 -0
- package/vendor/node-airtunes2/lib/audio_out.js +69 -0
- package/vendor/node-airtunes2/lib/circular_buffer.js +133 -0
- package/vendor/node-airtunes2/lib/config.js +26 -0
- package/vendor/node-airtunes2/lib/device_airtunes.js +532 -0
- package/vendor/node-airtunes2/lib/devices.js +266 -0
- package/vendor/node-airtunes2/lib/ed25519.js +26 -0
- package/vendor/node-airtunes2/lib/homekit/credentials.js +86 -0
- package/vendor/node-airtunes2/lib/homekit/encryption.js +69 -0
- package/vendor/node-airtunes2/lib/homekit/number.js +44 -0
- package/vendor/node-airtunes2/lib/homekit/tlv.js +95 -0
- package/vendor/node-airtunes2/lib/http.js +189 -0
- package/vendor/node-airtunes2/lib/index.d.ts +141 -0
- package/vendor/node-airtunes2/lib/index.js +127 -0
- package/vendor/node-airtunes2/lib/ntp.js +37 -0
- package/vendor/node-airtunes2/lib/num_util.js +21 -0
- package/vendor/node-airtunes2/lib/packet_pool.js +43 -0
- package/vendor/node-airtunes2/lib/rtsp.js +1807 -0
- package/vendor/node-airtunes2/lib/srp.js +158 -0
- package/vendor/node-airtunes2/lib/udp_servers.js +214 -0
- package/vendor/node-airtunes2/lib/util.js +12 -0
- package/vendor/node-airtunes2/package.json +31 -0
- package/vendor/node-airtunes2/src/CADebugMacros.h +257 -0
- package/vendor/node-airtunes2/src/CAHostTimeBase.cpp +105 -0
- package/vendor/node-airtunes2/src/CAHostTimeBase.h +224 -0
- package/vendor/node-airtunes2/src/aes_utils.c +864 -0
- package/vendor/node-airtunes2/src/aes_utils.h +24 -0
- package/vendor/node-airtunes2/src/base64.c +136 -0
- package/vendor/node-airtunes2/src/base64.h +42 -0
- package/vendor/node-airtunes2/src/bindings.cc +29 -0
- package/vendor/node-airtunes2/src/codec.cc +170 -0
- package/vendor/node-airtunes2/src/coreaudio.cc +332 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { favoriteTarget, normalizeMediaKeyBindings, parseMediaActionName } from './app-state.js';
|
|
3
|
-
|
|
3
|
+
import { importStreamUrl } from '../streams/import-stream.js';
|
|
4
|
+
export function useCommandExecutor({ beginLearningTransportKey, cancelPendingTune, countries, go, loadCountry, openAirPlaySettings, openAirPlayCode, openLibrary, persistLibrary, player, playingStation, providers, resetLearnedTransportKeys, runSearch, screen, screenRef, selectedStation, setCountries, setFilters, setLibrary, setMessage, setSearchQuery, setSleepUntil, setVolume, settingsRef, store, toggleFavorite, toggleMute, updateCommand, updateSettings }) {
|
|
4
5
|
return useCallback(async (rawCommand) => {
|
|
5
6
|
const trimmed = rawCommand.trim();
|
|
6
7
|
if (!trimmed) {
|
|
@@ -19,16 +20,18 @@ export function useCommandExecutor({ beginLearningTransportKey, countries, go, l
|
|
|
19
20
|
setMessage('Usage: :country <name or code>');
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
23
|
+
const origin = screenRef.current;
|
|
22
24
|
const availableCountries = countries.length > 0 ? countries : await providers.countries();
|
|
23
25
|
if (countries.length === 0) {
|
|
24
26
|
setCountries(availableCountries);
|
|
25
27
|
}
|
|
26
28
|
const match = availableCountries.find(country => `${country.code} ${country.name}`.toLowerCase().includes(value.toLowerCase()));
|
|
27
29
|
if (!match) {
|
|
28
|
-
|
|
30
|
+
if (screenRef.current === origin)
|
|
31
|
+
setMessage(`Country not found: ${value}`);
|
|
29
32
|
return;
|
|
30
33
|
}
|
|
31
|
-
await loadCountry(match);
|
|
34
|
+
await loadCountry(match, screenRef.current === origin);
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
37
|
if (name === 'codec') {
|
|
@@ -62,20 +65,20 @@ export function useCommandExecutor({ beginLearningTransportKey, countries, go, l
|
|
|
62
65
|
}
|
|
63
66
|
if (name === 'location') {
|
|
64
67
|
const enabled = value === 'on' || value === 'true' || value === '1';
|
|
65
|
-
updateSettings({ enableNearbyLocation: enabled });
|
|
68
|
+
await updateSettings({ enableNearbyLocation: enabled });
|
|
66
69
|
setMessage(`Nearby location lookup ${enabled ? 'enabled' : 'disabled'}.`);
|
|
67
70
|
return;
|
|
68
71
|
}
|
|
69
72
|
if (name === 'timeout') {
|
|
70
73
|
const seconds = Number(value);
|
|
71
74
|
if (Number.isFinite(seconds)) {
|
|
72
|
-
updateSettings({ tuneTimeoutSeconds: Math.min(45, Math.max(3, seconds)) });
|
|
75
|
+
await updateSettings({ tuneTimeoutSeconds: Math.min(45, Math.max(3, seconds)) });
|
|
73
76
|
}
|
|
74
77
|
return;
|
|
75
78
|
}
|
|
76
79
|
if (name === 'skip') {
|
|
77
80
|
const enabled = value !== 'off' && value !== 'false' && value !== '0';
|
|
78
|
-
updateSettings({ skipBrokenStreams: enabled });
|
|
81
|
+
await updateSettings({ skipBrokenStreams: enabled });
|
|
79
82
|
return;
|
|
80
83
|
}
|
|
81
84
|
if (name === 'learn' || name === 'bind' || name === 'key') {
|
|
@@ -98,10 +101,10 @@ export function useCommandExecutor({ beginLearningTransportKey, countries, go, l
|
|
|
98
101
|
}
|
|
99
102
|
if (name === 'airplay-code' || name === 'airplay-passcode') {
|
|
100
103
|
if (!value.trim()) {
|
|
101
|
-
|
|
104
|
+
openAirPlayCode();
|
|
102
105
|
return;
|
|
103
106
|
}
|
|
104
|
-
const result = player.submitAirPlayPasscode(value.trim());
|
|
107
|
+
const result = await player.submitAirPlayPasscode(value.trim());
|
|
105
108
|
if (result.message) {
|
|
106
109
|
setMessage(result.message);
|
|
107
110
|
}
|
|
@@ -138,7 +141,7 @@ export function useCommandExecutor({ beginLearningTransportKey, countries, go, l
|
|
|
138
141
|
}
|
|
139
142
|
if (name === 'export' || name === 'backup') {
|
|
140
143
|
try {
|
|
141
|
-
const backupPath = store.exportBackup(value || undefined);
|
|
144
|
+
const backupPath = await store.exportBackup(value || undefined);
|
|
142
145
|
setMessage(`Backup exported: ${backupPath}`);
|
|
143
146
|
}
|
|
144
147
|
catch (error) {
|
|
@@ -146,21 +149,43 @@ export function useCommandExecutor({ beginLearningTransportKey, countries, go, l
|
|
|
146
149
|
}
|
|
147
150
|
return;
|
|
148
151
|
}
|
|
149
|
-
if (name === 'import'
|
|
152
|
+
if (name === 'import') {
|
|
150
153
|
if (!value.trim()) {
|
|
151
|
-
setMessage('Usage: :import <
|
|
154
|
+
setMessage('Usage: :import <stream-url> [station name]');
|
|
152
155
|
return;
|
|
153
156
|
}
|
|
154
157
|
try {
|
|
155
|
-
const
|
|
158
|
+
const [url = '', ...nameParts] = rest;
|
|
159
|
+
setMessage('Reading stream metadata...');
|
|
160
|
+
const result = await importStreamUrl(url, nameParts.join(' ') || undefined);
|
|
161
|
+
const nextLibrary = await persistLibrary(() => store.addImported([result.station]));
|
|
162
|
+
if (!nextLibrary)
|
|
163
|
+
return;
|
|
164
|
+
openLibrary();
|
|
165
|
+
setMessage(result.warning
|
|
166
|
+
? `Imported ${result.station.name}. ${result.warning}`
|
|
167
|
+
: `Imported ${result.station.name}${result.station.codec ? ` · ${result.station.codec}` : ''}${result.station.bitrate ? ` · ${result.station.bitrate} kbps` : ''}.`);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
setMessage(error instanceof Error ? error.message : 'Could not import the radio stream.');
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (name === 'restore') {
|
|
175
|
+
if (!value.trim()) {
|
|
176
|
+
setMessage('Usage: :restore <backup.json>');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const result = await store.importBackup(value);
|
|
156
181
|
settingsRef.current = result.state.settings;
|
|
157
182
|
setLibrary(result.state);
|
|
158
183
|
setMessage(result.safetyBackupPath
|
|
159
|
-
? `Backup
|
|
160
|
-
: `Backup
|
|
184
|
+
? `Backup restored. Previous library saved to ${result.safetyBackupPath}`
|
|
185
|
+
: `Backup restored: ${result.sourcePath}`);
|
|
161
186
|
}
|
|
162
187
|
catch (error) {
|
|
163
|
-
setMessage(error instanceof Error ? error.message : 'Could not
|
|
188
|
+
setMessage(error instanceof Error ? error.message : 'Could not restore the RadioCLI backup.');
|
|
164
189
|
}
|
|
165
190
|
return;
|
|
166
191
|
}
|
|
@@ -169,7 +194,8 @@ export function useCommandExecutor({ beginLearningTransportKey, countries, go, l
|
|
|
169
194
|
return;
|
|
170
195
|
}
|
|
171
196
|
if (name === 'stop') {
|
|
172
|
-
|
|
197
|
+
cancelPendingTune();
|
|
198
|
+
persistLibrary(() => store.finishActiveListeningSession());
|
|
173
199
|
await player.stop();
|
|
174
200
|
return;
|
|
175
201
|
}
|
|
@@ -190,25 +216,30 @@ export function useCommandExecutor({ beginLearningTransportKey, countries, go, l
|
|
|
190
216
|
: name === 'motion'
|
|
191
217
|
? 'reduceMotion'
|
|
192
218
|
: 'transparentBackground';
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
219
|
+
const saved = await updateSettings(value === ''
|
|
220
|
+
? current => ({ [key]: !current[key] })
|
|
221
|
+
: { [key]: value === 'on' || value === 'true' || value === '1' });
|
|
222
|
+
setMessage(`${name} ${saved.settings[key] ? 'on' : 'off'}.`);
|
|
196
223
|
return;
|
|
197
224
|
}
|
|
198
225
|
setMessage(`Unknown command: ${name}`);
|
|
199
226
|
}, [
|
|
200
227
|
beginLearningTransportKey,
|
|
228
|
+
cancelPendingTune,
|
|
201
229
|
countries,
|
|
202
230
|
go,
|
|
203
231
|
loadCountry,
|
|
204
232
|
openAirPlaySettings,
|
|
233
|
+
openAirPlayCode,
|
|
205
234
|
openLibrary,
|
|
235
|
+
persistLibrary,
|
|
206
236
|
player,
|
|
207
237
|
playingStation,
|
|
208
238
|
providers,
|
|
209
239
|
resetLearnedTransportKeys,
|
|
210
240
|
runSearch,
|
|
211
241
|
screen,
|
|
242
|
+
screenRef,
|
|
212
243
|
selectedStation,
|
|
213
244
|
setCountries,
|
|
214
245
|
setFilters,
|
|
@@ -11,7 +11,7 @@ export function buildGallop(pulse, width, height, theme) {
|
|
|
11
11
|
const frame = frames[frameIndex];
|
|
12
12
|
const usableW = w > 8 ? w - 4 : w;
|
|
13
13
|
const usableH = h > 4 ? h - 2 : h;
|
|
14
|
-
const scale = Math.min(usableW * 2 / horseStrideWidth, usableH * 4 / horseStrideHeight);
|
|
14
|
+
const scale = Math.min(usableW * 2 / horseStrideWidth, usableH * 4 / horseStrideHeight) * 0.9;
|
|
15
15
|
const dotsW = Math.max(1, Math.floor(horseStrideWidth * scale));
|
|
16
16
|
const dotsH = Math.max(1, Math.floor(horseStrideHeight * scale));
|
|
17
17
|
const left = Math.floor((w - Math.ceil(dotsW / 2)) / 2) * 2;
|
|
@@ -65,9 +65,10 @@ export const receiverStyleRegistry = [
|
|
|
65
65
|
{ id: 'crossfade', maxRows: 14, minRows: 7 },
|
|
66
66
|
];
|
|
67
67
|
export const receiverStyleNames = receiverStyleRegistry.map(style => style.id);
|
|
68
|
-
export const defaultReceiverStyle = '
|
|
68
|
+
export const defaultReceiverStyle = 'ultracode';
|
|
69
69
|
export const receiverStyleMetadata = Object.fromEntries(receiverStyleRegistry.map(style => [style.id, style]));
|
|
70
70
|
const retiredReceiverStyleNames = [
|
|
71
|
+
'synthwave-sun', 'neon-alley', 'safari-dusk', 'hot-air-balloons', 'deep-sea-whale', 'meteor-shower', 'volcano', 'desert-highway', 'firefly-grove', 'cherry-blossom', 'event-horizon', 'lighthouse',
|
|
71
72
|
'duplex-wave', 'color-drift', 'undertow', 'cube-tide', 'parallax-slabs', 'voxel-swell',
|
|
72
73
|
'dot-current', 'flip-matrix', 'raster-harp',
|
|
73
74
|
'spin-lattice', 'packet-storm', 'spark-chamber',
|
package/dist/update-check.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { appVersion } from './version.js';
|
|
4
|
+
import { networkPolicy, withExternalResponse } from './platform/network.js';
|
|
4
5
|
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
5
6
|
const UPDATE_PACKAGE_NAME = '@ciphore/radiocli';
|
|
6
7
|
export async function checkForUpdate({ currentVersion = appVersion(), fetchImpl = fetch, now = new Date(), packageName = UPDATE_PACKAGE_NAME, timeoutMs = 3000 } = {}) {
|
|
7
8
|
try {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const parsed = await withExternalResponse(registryLatestUrl(packageName), { fetchImpl, timeoutMs }, async (response) => {
|
|
10
|
+
if (!response.ok)
|
|
11
|
+
throw new Error(`npm registry returned ${response.status}`);
|
|
12
|
+
return await response.json();
|
|
13
|
+
});
|
|
13
14
|
const latestVersion = typeof parsed.version === 'string' ? parsed.version : undefined;
|
|
14
15
|
if (!latestVersion) {
|
|
15
16
|
throw new Error('npm registry response did not include a version');
|
|
@@ -31,7 +32,7 @@ export async function checkForUpdate({ currentVersion = appVersion(), fetchImpl
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
export function shouldCheckForUpdate(updateCheck, now = Date.now()) {
|
|
34
|
-
if (
|
|
35
|
+
if (!automaticUpdateChecksAllowed()) {
|
|
35
36
|
return false;
|
|
36
37
|
}
|
|
37
38
|
if (!updateCheck?.checkedAt) {
|
|
@@ -41,7 +42,7 @@ export function shouldCheckForUpdate(updateCheck, now = Date.now()) {
|
|
|
41
42
|
return !Number.isFinite(checkedAt) || now - checkedAt >= UPDATE_CHECK_INTERVAL_MS;
|
|
42
43
|
}
|
|
43
44
|
export function automaticUpdateChecksAllowed(enabled = true) {
|
|
44
|
-
return enabled && process.env.RADIOCLI_DISABLE_UPDATE_CHECK !== '1' && process.env.CI !== 'true';
|
|
45
|
+
return enabled && !networkPolicy().offline && process.env.RADIOCLI_DISABLE_UPDATE_CHECK !== '1' && process.env.CI !== 'true';
|
|
45
46
|
}
|
|
46
47
|
export function updateAvailableForVersion(updateCheck, currentVersion = updateCheck?.currentVersion) {
|
|
47
48
|
return Boolean(updateCheck?.updateAvailable &&
|
|
@@ -95,9 +96,10 @@ export function installUpdate(command = updateCommandForInstall().command) {
|
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
export function updateShellForPlatform(platform, command) {
|
|
99
|
+
// Login profiles can replace PATH and select a different package-manager installation.
|
|
98
100
|
return platform === 'win32'
|
|
99
101
|
? { command: 'cmd.exe', args: ['/d', '/s', '/c', command] }
|
|
100
|
-
: { command: 'sh', args: ['-
|
|
102
|
+
: { command: 'sh', args: ['-c', command] };
|
|
101
103
|
}
|
|
102
104
|
export function compareSemver(left, right) {
|
|
103
105
|
const leftParts = semverParts(left);
|
|
@@ -113,16 +115,6 @@ export function compareSemver(left, right) {
|
|
|
113
115
|
function registryLatestUrl(packageName) {
|
|
114
116
|
return `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
115
117
|
}
|
|
116
|
-
async function fetchWithTimeout(url, fetchImpl, timeoutMs) {
|
|
117
|
-
const controller = new AbortController();
|
|
118
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
119
|
-
try {
|
|
120
|
-
return await fetchImpl(url, { signal: controller.signal });
|
|
121
|
-
}
|
|
122
|
-
finally {
|
|
123
|
-
clearTimeout(timer);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
118
|
function semverParts(version) {
|
|
127
119
|
const normalized = version.trim().replace(/^v/i, '').split(/[+-]/)[0] ?? '';
|
|
128
120
|
const [major = '0', minor = '0', patch = '0'] = normalized.split('.');
|
|
@@ -34,7 +34,9 @@ Earth vector data is public domain.
|
|
|
34
34
|
|
|
35
35
|
## node-airtunes2
|
|
36
36
|
|
|
37
|
-
AirPlay support
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
AirPlay support uses a source copy of `node-airtunes2` 2.5.0, distributed under
|
|
38
|
+
the GNU Affero General Public License v3, with RadioCLI's native Ed25519 security
|
|
39
|
+
fix. The original attribution, license, complete native source, changes and
|
|
40
|
+
archive provenance ship in `vendor/node-airtunes2/`. Runtime dependencies remain
|
|
41
|
+
optional. See `vendor/node-airtunes2/RADIOCLI_PATCH.md` and the original project
|
|
42
|
+
at <https://github.com/bertrandda/node-airtunes2>.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciphore/radiocli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "A terminal-first world radio receiver built with Ink, mpv, and resilient public-radio providers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,16 @@
|
|
|
33
33
|
"SECURITY.md",
|
|
34
34
|
"CONTRIBUTING.md",
|
|
35
35
|
"CODE_OF_CONDUCT.md",
|
|
36
|
-
"docs"
|
|
36
|
+
"docs",
|
|
37
|
+
"scripts/build-airplay-native.mjs",
|
|
38
|
+
"vendor/node-airtunes2/lib",
|
|
39
|
+
"vendor/node-airtunes2/src",
|
|
40
|
+
"vendor/node-airtunes2/package.json",
|
|
41
|
+
"vendor/node-airtunes2/binding.gyp",
|
|
42
|
+
"vendor/node-airtunes2/LICENSE",
|
|
43
|
+
"vendor/node-airtunes2/README.md",
|
|
44
|
+
"vendor/node-airtunes2/RADIOCLI_PATCH.md",
|
|
45
|
+
"vendor/node-airtunes2/UPSTREAM.json"
|
|
37
46
|
],
|
|
38
47
|
"bin": {
|
|
39
48
|
"radiocli": "dist/cli.js"
|
|
@@ -47,7 +56,7 @@
|
|
|
47
56
|
"check:whitespace": "node scripts/whitespace-check.mjs",
|
|
48
57
|
"check:exports": "knip",
|
|
49
58
|
"check:package": "publint",
|
|
50
|
-
"audit:runtime": "node scripts/audit-ci.mjs --omit=dev --
|
|
59
|
+
"audit:runtime": "node scripts/audit-ci.mjs --omit=dev --include=optional --audit-level=low",
|
|
51
60
|
"audit:docs": "node scripts/audit-ci.mjs --prefix=apps/docs --omit=dev --audit-level=high",
|
|
52
61
|
"lint": "npm run check:unused && npm run check:whitespace && npm run check:exports",
|
|
53
62
|
"docs:dev": "npm --prefix apps/docs run dev",
|
|
@@ -56,27 +65,29 @@
|
|
|
56
65
|
"start": "node dist/cli.js",
|
|
57
66
|
"test": "vitest run",
|
|
58
67
|
"verify": "npm run check && npm run lint && npm run test && npm run build && npm run smoke:data",
|
|
59
|
-
"verify:release": "
|
|
68
|
+
"verify:release": "node scripts/verify-release.mjs",
|
|
60
69
|
"prepack": "npm run build",
|
|
61
|
-
"prepublishOnly": "npm run
|
|
70
|
+
"prepublishOnly": "npm run verify:release",
|
|
62
71
|
"pack:check": "npm pack --dry-run",
|
|
63
72
|
"smoke:data": "tsx src/smoke/data-smoke.ts",
|
|
64
73
|
"smoke:mcp": "node scripts/mcp-smoke.mjs",
|
|
65
74
|
"smoke:playback": "tsx src/smoke/playback-smoke.ts",
|
|
75
|
+
"smoke:network": "tsx src/smoke/network-playback-smoke.ts",
|
|
76
|
+
"smoke:cli": "tsx src/smoke/cli-smoke.ts",
|
|
66
77
|
"demo:script": "node scripts/demo-script.mjs",
|
|
67
78
|
"demo:assets": "node scripts/capture-demo-assets.mjs",
|
|
68
|
-
"fresh:check": "node scripts/fresh-check.mjs"
|
|
79
|
+
"fresh:check": "node scripts/fresh-check.mjs",
|
|
80
|
+
"postinstall": "node scripts/build-airplay-native.mjs"
|
|
69
81
|
},
|
|
70
82
|
"dependencies": {
|
|
83
|
+
"@homebridge/dbus-native": "^0.7.9",
|
|
71
84
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
72
85
|
"ink": "^7.0.5",
|
|
73
86
|
"jsonc-parser": "^3.3.1",
|
|
74
87
|
"react": "^19.2.6",
|
|
88
|
+
"string-width": "8.2.1",
|
|
75
89
|
"zod": "^4.4.3"
|
|
76
90
|
},
|
|
77
|
-
"overrides": {
|
|
78
|
-
"protobufjs": "^7.5.8"
|
|
79
|
-
},
|
|
80
91
|
"devDependencies": {
|
|
81
92
|
"@types/node": "^22.15.0",
|
|
82
93
|
"@types/react": "^19.2.15",
|
|
@@ -85,12 +96,24 @@
|
|
|
85
96
|
"publint": "^0.3.21",
|
|
86
97
|
"tsx": "^4.22.3",
|
|
87
98
|
"typescript": "^6.0.3",
|
|
88
|
-
"vitest": "^4.1.
|
|
99
|
+
"vitest": "^4.1.11"
|
|
89
100
|
},
|
|
90
101
|
"engines": {
|
|
91
102
|
"node": ">=22"
|
|
92
103
|
},
|
|
93
104
|
"optionalDependencies": {
|
|
94
|
-
"
|
|
105
|
+
"@noble/ed25519": "1.7.5",
|
|
106
|
+
"async": "3.2.6",
|
|
107
|
+
"axlsign": "git+https://github.com/ciderapp/curve25519-js.git#cae68782564b83ffa1c7c757e40a3944cb140290",
|
|
108
|
+
"big-integer": "1.6.52",
|
|
109
|
+
"bplist-creator": "0.1.1",
|
|
110
|
+
"bplist-parser": "0.3.2",
|
|
111
|
+
"chacha-js": "git+https://github.com/ciderapp/chacha20poly1305.git#f85941b8f5c62843a1459758cc3dbabad9f849cb",
|
|
112
|
+
"curve25519-js": "0.0.4",
|
|
113
|
+
"fast-srp-hap": "2.0.4",
|
|
114
|
+
"js-sha1": "0.7.0",
|
|
115
|
+
"lodash": "4.18.1",
|
|
116
|
+
"python-struct": "1.1.3",
|
|
117
|
+
"node-gyp": "12.3.0"
|
|
95
118
|
}
|
|
96
119
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {createRequire} from 'node:module';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
|
|
7
|
+
/** Optional Linux optimization. The sender retains its upstream JS fallback. */
|
|
8
|
+
export function buildAirPlayNative({platform = process.platform, env = process.env, resolve = name => require.resolve(name), execute = spawnSync} = {}) {
|
|
9
|
+
const omitted = Object.entries(env).find(([name]) => name.toLowerCase() === 'npm_config_omit')?.[1] ?? '';
|
|
10
|
+
if (platform !== 'linux' || omitted.split(/[\s,]+/).includes('optional')) return 'skipped';
|
|
11
|
+
let entry;
|
|
12
|
+
try { entry = resolve('node-gyp/bin/node-gyp.js'); } catch { return 'unavailable'; }
|
|
13
|
+
try {
|
|
14
|
+
const result = execute(process.execPath, [entry, 'rebuild'], {
|
|
15
|
+
cwd: fileURLToPath(new URL('../vendor/node-airtunes2/', import.meta.url)), env,
|
|
16
|
+
shell: false, windowsHide: true, timeout: 120_000, stdio: 'ignore'
|
|
17
|
+
});
|
|
18
|
+
return result.status === 0 && !result.error ? 'built' : 'fallback';
|
|
19
|
+
} catch { return 'fallback'; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) buildAirPlayNative();
|