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 +34 -0
- package/bin/yarr.js +34 -0
- package/lib/platform.js +56 -0
- package/package.json +40 -0
- package/scripts/install.js +91 -0
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
|
+
}
|
package/lib/platform.js
ADDED
|
@@ -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
|
+
});
|