@ciphore/radiocli 0.2.2 → 0.2.4
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 +124 -1
- package/CONTRIBUTING.md +36 -6
- package/README.md +84 -10
- package/dist/agent/alarm-service.js +210 -0
- package/dist/agent/cli.js +193 -0
- package/dist/agent/headless-host.js +165 -0
- package/dist/agent/launcher.js +10 -0
- package/dist/agent/mcp-install.js +467 -0
- package/dist/agent/mcp-server.js +139 -0
- package/dist/agent/service.js +347 -0
- package/dist/agent/session.js +232 -0
- package/dist/alarms/active-session.js +9 -12
- package/dist/alarms/cli.js +4 -1
- package/dist/alarms/guard.js +79 -36
- package/dist/alarms/inhibitor.js +27 -22
- package/dist/alarms/power-guard-store.js +2 -10
- package/dist/alarms/runner.js +96 -48
- package/dist/alarms/schedule.js +9 -2
- package/dist/alarms/scheduler.js +194 -52
- package/dist/alarms/setup-verification.js +1 -2
- package/dist/alarms/system-volume-ownership.js +267 -0
- package/dist/alarms/system-volume.js +155 -14
- package/dist/alarms/terminal-launcher.js +76 -136
- package/dist/alarms/tui-presence.js +2 -4
- package/dist/cli.js +157 -35
- package/dist/platform/capabilities.js +53 -0
- package/dist/platform/desktop.js +36 -0
- package/dist/{player/command.js → platform/executables.js} +43 -9
- package/dist/platform/ipc.js +14 -0
- package/dist/platform/launch-command.js +135 -0
- package/dist/platform/loopback.js +44 -0
- package/dist/platform/network.js +312 -0
- package/dist/platform/packages.js +216 -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 +203 -0
- package/dist/player/airplay-discovery.js +4 -2
- package/dist/player/backend-install.js +13 -94
- package/dist/player/command-diagnostics.js +2 -2
- package/dist/player/mpv-ipc-client.js +2 -1
- package/dist/player/player-controller.js +220 -31
- package/dist/providers/cache.js +4 -26
- package/dist/providers/provider-manager.js +5 -0
- package/dist/providers/radio-browser.js +156 -54
- package/dist/providers/radio-garden.js +15 -22
- package/dist/setup.js +149 -152
- package/dist/storage/store.js +138 -53
- package/dist/streams/import-stream.js +163 -0
- package/dist/types.js +6 -0
- package/dist/ui/AdaptiveContent.js +54 -30
- package/dist/ui/App.js +466 -98
- package/dist/ui/AppContent.js +4 -4
- package/dist/ui/app-state.js +8 -1
- package/dist/ui/ascii.js +11 -2
- package/dist/ui/components/AdaptiveMarquee.js +6 -3
- 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 +8 -8
- package/dist/ui/components/TopTabs.js +1 -1
- package/dist/ui/components/VersionIndicator.js +19 -0
- package/dist/ui/display-context.js +8 -11
- package/dist/ui/help-content.js +5 -5
- package/dist/ui/layout.js +5 -2
- package/dist/ui/page-footer.js +5 -3
- package/dist/ui/screen-items.js +42 -11
- package/dist/ui/screen-meta.js +1 -1
- package/dist/ui/screens/AirPlayCodeScreen.js +6 -2
- package/dist/ui/screens/AirPlaySettingsScreen.js +7 -3
- package/dist/ui/screens/AlarmsScreen.js +2 -1
- package/dist/ui/screens/CountriesScreen.js +8 -5
- package/dist/ui/screens/ExploreScreen.js +2 -1
- package/dist/ui/screens/HelpScreen.js +5 -1
- package/dist/ui/screens/HomeScreen.js +7 -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 +80 -56
- 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/use-alarm-tui.js +40 -21
- package/dist/ui/use-app-input.js +69 -23
- package/dist/ui/use-command-executor.js +31 -7
- package/dist/ui/visualizers/gallop.js +118 -0
- package/dist/ui/visualizers/horse-stride.js +20 -0
- package/dist/ui/visualizers/receiver-style-registry.js +12 -2
- package/dist/ui/visualizers/receiver-visualizers.js +3 -0
- package/dist/ui/visualizers/retro-receivers.js +4 -0
- package/dist/ui/visualizers/terminal-receivers.js +57 -0
- package/dist/update-check.js +33 -23
- package/package.json +4 -1
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) {
|
|
@@ -40,17 +41,26 @@ export function shouldCheckForUpdate(updateCheck, now = Date.now()) {
|
|
|
40
41
|
const checkedAt = Date.parse(updateCheck.checkedAt);
|
|
41
42
|
return !Number.isFinite(checkedAt) || now - checkedAt >= UPDATE_CHECK_INTERVAL_MS;
|
|
42
43
|
}
|
|
43
|
-
export function
|
|
44
|
+
export function automaticUpdateChecksAllowed(enabled = true) {
|
|
45
|
+
return enabled && !networkPolicy().offline && process.env.RADIOCLI_DISABLE_UPDATE_CHECK !== '1' && process.env.CI !== 'true';
|
|
46
|
+
}
|
|
47
|
+
export function updateAvailableForVersion(updateCheck, currentVersion = updateCheck?.currentVersion) {
|
|
48
|
+
return Boolean(updateCheck?.updateAvailable &&
|
|
49
|
+
updateCheck.latestVersion &&
|
|
50
|
+
currentVersion &&
|
|
51
|
+
compareSemver(updateCheck.latestVersion, currentVersion) > 0);
|
|
52
|
+
}
|
|
53
|
+
export function updateStatusText(updateCheck, currentVersion) {
|
|
44
54
|
if (!updateCheck) {
|
|
45
55
|
return 'not checked yet';
|
|
46
56
|
}
|
|
47
|
-
if (updateCheck
|
|
57
|
+
if (updateAvailableForVersion(updateCheck, currentVersion) && updateCheck.latestVersion) {
|
|
48
58
|
return `v${updateCheck.latestVersion} available`;
|
|
49
59
|
}
|
|
50
60
|
if (updateCheck.error) {
|
|
51
61
|
return `check failed: ${updateCheck.error}`;
|
|
52
62
|
}
|
|
53
|
-
return updateCheck.latestVersion ? `current at v${updateCheck.latestVersion}` : 'not checked yet';
|
|
63
|
+
return updateCheck.latestVersion ? `current at v${currentVersion ?? updateCheck.latestVersion}` : 'not checked yet';
|
|
54
64
|
}
|
|
55
65
|
export function updateCommandForInstall(entryPath = process.argv[1]) {
|
|
56
66
|
const resolved = resolvePath(entryPath);
|
|
@@ -58,16 +68,21 @@ export function updateCommandForInstall(entryPath = process.argv[1]) {
|
|
|
58
68
|
if (/\/(?:opt\/homebrew|usr\/local)\/(?:Cellar|Homebrew)\//.test(haystack) || /\/\.linuxbrew\/(?:Cellar|Homebrew)\//.test(haystack)) {
|
|
59
69
|
return { method: 'homebrew', command: 'brew update && brew upgrade radiocli' };
|
|
60
70
|
}
|
|
61
|
-
if (
|
|
71
|
+
if (/[\\/](?:pnpm|\.pnpm-global)[\\/]/.test(haystack)) {
|
|
72
|
+
return { method: 'pnpm', command: 'pnpm add -g @ciphore/radiocli@latest' };
|
|
73
|
+
}
|
|
74
|
+
if (/[\\/]\.bun[\\/]install[\\/]global[\\/]/.test(haystack)) {
|
|
75
|
+
return { method: 'bun', command: 'bun add -g @ciphore/radiocli@latest' };
|
|
76
|
+
}
|
|
77
|
+
if (/[\\/]node_modules[\\/]@ciphore[\\/]radiocli[\\/]/.test(haystack)) {
|
|
62
78
|
return { method: 'npm', command: 'npm install -g @ciphore/radiocli@latest' };
|
|
63
79
|
}
|
|
64
80
|
return { method: 'unknown', command: 'npm install -g @ciphore/radiocli@latest' };
|
|
65
81
|
}
|
|
66
82
|
export function installUpdate(command = updateCommandForInstall().command) {
|
|
67
83
|
return new Promise(resolve => {
|
|
68
|
-
const shell = process.platform
|
|
69
|
-
const
|
|
70
|
-
const child = spawn(shell, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
84
|
+
const shell = updateShellForPlatform(process.platform, command);
|
|
85
|
+
const child = spawn(shell.command, shell.args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
71
86
|
const chunks = [];
|
|
72
87
|
child.stdout.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
|
73
88
|
child.stderr.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
|
@@ -80,6 +95,11 @@ export function installUpdate(command = updateCommandForInstall().command) {
|
|
|
80
95
|
});
|
|
81
96
|
});
|
|
82
97
|
}
|
|
98
|
+
export function updateShellForPlatform(platform, command) {
|
|
99
|
+
return platform === 'win32'
|
|
100
|
+
? { command: 'cmd.exe', args: ['/d', '/s', '/c', command] }
|
|
101
|
+
: { command: 'sh', args: ['-lc', command] };
|
|
102
|
+
}
|
|
83
103
|
export function compareSemver(left, right) {
|
|
84
104
|
const leftParts = semverParts(left);
|
|
85
105
|
const rightParts = semverParts(right);
|
|
@@ -94,16 +114,6 @@ export function compareSemver(left, right) {
|
|
|
94
114
|
function registryLatestUrl(packageName) {
|
|
95
115
|
return `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
96
116
|
}
|
|
97
|
-
async function fetchWithTimeout(url, fetchImpl, timeoutMs) {
|
|
98
|
-
const controller = new AbortController();
|
|
99
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
100
|
-
try {
|
|
101
|
-
return await fetchImpl(url, { signal: controller.signal });
|
|
102
|
-
}
|
|
103
|
-
finally {
|
|
104
|
-
clearTimeout(timer);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
117
|
function semverParts(version) {
|
|
108
118
|
const normalized = version.trim().replace(/^v/i, '').split(/[+-]/)[0] ?? '';
|
|
109
119
|
const [major = '0', minor = '0', patch = '0'] = normalized.split('.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciphore/radiocli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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",
|
|
@@ -61,13 +61,16 @@
|
|
|
61
61
|
"prepublishOnly": "npm run check && npm run lint && npm run test && npm run smoke:data && npm run fresh:check && npm run check:package",
|
|
62
62
|
"pack:check": "npm pack --dry-run",
|
|
63
63
|
"smoke:data": "tsx src/smoke/data-smoke.ts",
|
|
64
|
+
"smoke:mcp": "node scripts/mcp-smoke.mjs",
|
|
64
65
|
"smoke:playback": "tsx src/smoke/playback-smoke.ts",
|
|
65
66
|
"demo:script": "node scripts/demo-script.mjs",
|
|
66
67
|
"demo:assets": "node scripts/capture-demo-assets.mjs",
|
|
67
68
|
"fresh:check": "node scripts/fresh-check.mjs"
|
|
68
69
|
},
|
|
69
70
|
"dependencies": {
|
|
71
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
70
72
|
"ink": "^7.0.5",
|
|
73
|
+
"jsonc-parser": "^3.3.1",
|
|
71
74
|
"react": "^19.2.6",
|
|
72
75
|
"zod": "^4.4.3"
|
|
73
76
|
},
|