ytdl-rmcp 0.7.1

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/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # ytdl-rmcp
2
+
3
+ Node launcher for the `ytdl-rmcp` Rust MCP server and CLI binary.
4
+
5
+ ```bash
6
+ npx -y ytdl-rmcp
7
+ ```
8
+
9
+ Run the guided setup:
10
+
11
+ ```bash
12
+ npx -y ytdl-rmcp setup
13
+ ```
14
+
15
+ Install globally when you want the command on `PATH`:
16
+
17
+ ```bash
18
+ npm i -g ytdl-rmcp
19
+ ytdl-rmcp --version
20
+ ytdl-rmcp setup
21
+ ```
22
+
23
+ The package downloads the matching GitHub Release binary during `postinstall`.
24
+ The npm package version and the `ytdl-rmcp` release tag are expected to match.
25
+ Release automation publishes this package from the repository `v*` tag workflow;
26
+ the GitHub repository must have an `NPM_TOKEN` secret with publish access.
27
+
28
+ ## MCP stdio
29
+
30
+ Run without subcommands, `ytdl-rmcp` serves MCP over stdio. MCP clients can launch
31
+ it with:
32
+
33
+ ```json
34
+ {
35
+ "command": "npx",
36
+ "args": ["-y", "ytdl-rmcp"],
37
+ "env": {
38
+ "YTDLP_REMOTE": "tootie",
39
+ "YTDLP_REMOTE_PATH": "/media/music"
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Overrides
45
+
46
+ ```bash
47
+ YTDL_RMCP_BINARY_VERSION=v0.7.0 npm i -g ytdl-rmcp
48
+ YTDL_RMCP_RELEASE_BASE_URL=https://github.com/jmagar/ytdl-rmcp/releases/download npm i -g ytdl-rmcp
49
+ YTDL_RMCP_SKIP_DOWNLOAD=1 npm i -g ytdl-rmcp
50
+ ```
51
+
52
+ Supported binary targets are Linux x64 and Windows x64, matching the current
53
+ GitHub Release assets.
package/bin/rytdl.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const path = require("node:path");
6
+ const { spawnSync } = require("node:child_process");
7
+ const { binaryPath } = require("../lib/platform");
8
+
9
+ function fail(message) {
10
+ process.stderr.write(`ytdl-rmcp: ${message}\n`);
11
+ process.exit(1);
12
+ }
13
+
14
+ const binary = binaryPath();
15
+
16
+ if (!fs.existsSync(binary)) {
17
+ const installer = path.resolve(__dirname, "..", "scripts", "install.js");
18
+ const install = spawnSync(process.execPath, [installer], { stdio: "inherit" });
19
+ if (install.status !== 0) {
20
+ fail("binary is not installed; postinstall may have failed");
21
+ }
22
+ }
23
+
24
+ const child = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
25
+
26
+ if (child.error) {
27
+ fail(child.error.message);
28
+ }
29
+
30
+ if (child.signal) {
31
+ process.kill(process.pid, child.signal);
32
+ } else {
33
+ process.exit(child.status ?? 1);
34
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ const path = require("node:path");
4
+
5
+ function packageVersion() {
6
+ return require("../package.json").version;
7
+ }
8
+
9
+ function targetFor(platform = process.platform, arch = process.arch) {
10
+ if (platform === "linux" && arch === "x64") {
11
+ return {
12
+ asset: "rytdl-x86_64.tar.gz",
13
+ binary: "rytdl",
14
+ };
15
+ }
16
+
17
+ if (platform === "win32" && arch === "x64") {
18
+ return {
19
+ asset: "rytdl-windows-x86_64.tar.gz",
20
+ binary: "rytdl.exe",
21
+ };
22
+ }
23
+
24
+ throw new Error(`Unsupported platform ${platform}/${arch}. Supported targets: linux/x64, win32/x64.`);
25
+ }
26
+
27
+ function releaseVersion(env = process.env) {
28
+ const raw = env.YTDL_RMCP_BINARY_VERSION || packageVersion();
29
+ return raw.startsWith("v") ? raw : `v${raw}`;
30
+ }
31
+
32
+ function releaseBaseUrl(env = process.env) {
33
+ return env.YTDL_RMCP_RELEASE_BASE_URL || "https://github.com/jmagar/ytdl-rmcp/releases/download";
34
+ }
35
+
36
+ function downloadUrl(target, env = process.env) {
37
+ return `${releaseBaseUrl(env)}/${releaseVersion(env)}/${target.asset}`;
38
+ }
39
+
40
+ function installRoot() {
41
+ return path.resolve(__dirname, "..", "vendor");
42
+ }
43
+
44
+ function binaryPath(platform = process.platform, arch = process.arch) {
45
+ const target = targetFor(platform, arch);
46
+ return path.join(installRoot(), target.binary);
47
+ }
48
+
49
+ module.exports = {
50
+ binaryPath,
51
+ downloadUrl,
52
+ installRoot,
53
+ packageVersion,
54
+ releaseVersion,
55
+ targetFor,
56
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "ytdl-rmcp",
3
+ "version": "0.7.1",
4
+ "description": "Node launcher for the ytdl-rmcp Rust MCP server and CLI binary.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/jmagar/ytdl-rmcp.git",
9
+ "directory": "packages/ytdl-rmcp"
10
+ },
11
+ "bin": {
12
+ "ytdl-rmcp": "bin/rytdl.js",
13
+ "rytdl": "bin/rytdl.js"
14
+ },
15
+ "files": [
16
+ "bin/",
17
+ "lib/",
18
+ "scripts/",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "postinstall": "node scripts/install.js",
23
+ "test": "node --test",
24
+ "check": "node --check bin/rytdl.js && node --check scripts/install.js && node --check lib/platform.js"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ }
29
+ }
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const http = require("node:http");
6
+ const https = require("node:https");
7
+ const os = require("node:os");
8
+ const path = require("node:path");
9
+ const { spawnSync } = require("node:child_process");
10
+ const {
11
+ binaryPath,
12
+ downloadUrl,
13
+ installRoot,
14
+ releaseVersion,
15
+ targetFor,
16
+ } = require("../lib/platform");
17
+
18
+ function log(message) {
19
+ process.stderr.write(`ytdl-rmcp: ${message}\n`);
20
+ }
21
+
22
+ function download(url, destination) {
23
+ return new Promise((resolve, reject) => {
24
+ const client = url.startsWith("http:") ? http : https;
25
+ const request = client.get(url, (response) => {
26
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
27
+ response.resume();
28
+ download(response.headers.location, destination).then(resolve, reject);
29
+ return;
30
+ }
31
+
32
+ if (response.statusCode !== 200) {
33
+ response.resume();
34
+ reject(new Error(`download failed (${response.statusCode}) from ${url}`));
35
+ return;
36
+ }
37
+
38
+ const file = fs.createWriteStream(destination, { mode: 0o600 });
39
+ response.pipe(file);
40
+ file.on("finish", () => file.close(resolve));
41
+ file.on("error", reject);
42
+ });
43
+
44
+ request.on("error", reject);
45
+ });
46
+ }
47
+
48
+ function extract(archive, destination) {
49
+ fs.rmSync(destination, { recursive: true, force: true });
50
+ fs.mkdirSync(destination, { recursive: true });
51
+
52
+ const result = spawnSync("tar", ["-xzf", archive, "-C", destination], {
53
+ encoding: "utf8",
54
+ });
55
+
56
+ if (result.status !== 0) {
57
+ throw new Error((result.stderr || result.stdout || "tar extraction failed").trim());
58
+ }
59
+ }
60
+
61
+ async function main() {
62
+ if (process.env.YTDL_RMCP_SKIP_DOWNLOAD === "1") {
63
+ log("skipping binary download because YTDL_RMCP_SKIP_DOWNLOAD=1");
64
+ return;
65
+ }
66
+
67
+ const target = targetFor();
68
+ const destination = binaryPath();
69
+
70
+ if (fs.existsSync(destination)) {
71
+ log(`${path.basename(destination)} already installed for ${releaseVersion()}`);
72
+ return;
73
+ }
74
+
75
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ytdl-rmcp-install-"));
76
+ const archive = path.join(tempDir, target.asset);
77
+
78
+ try {
79
+ const url = downloadUrl(target);
80
+ log(`downloading ${url}`);
81
+ await download(url, archive);
82
+ extract(archive, installRoot());
83
+ fs.chmodSync(destination, 0o755);
84
+ log(`installed ${destination}`);
85
+ } finally {
86
+ fs.rmSync(tempDir, { recursive: true, force: true });
87
+ }
88
+ }
89
+
90
+ main().catch((error) => {
91
+ log(error.message);
92
+ process.exitCode = 1;
93
+ });