yarr-mcp 0.4.0

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