@mkterswingman/5mghost-yonder 0.0.2 → 0.0.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/README.md +44 -3
- package/dist/auth/sharedAuth.d.ts +10 -0
- package/dist/auth/sharedAuth.js +24 -0
- package/dist/auth/tokenManager.d.ts +10 -1
- package/dist/auth/tokenManager.js +14 -22
- package/dist/cli/check.js +6 -3
- package/dist/cli/index.d.ts +15 -1
- package/dist/cli/index.js +74 -31
- package/dist/cli/runtime.d.ts +9 -0
- package/dist/cli/runtime.js +35 -0
- package/dist/cli/serve.js +3 -1
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.js +84 -68
- package/dist/cli/setupCookies.js +2 -2
- package/dist/cli/smoke.d.ts +27 -0
- package/dist/cli/smoke.js +108 -0
- package/dist/cli/uninstall.d.ts +1 -0
- package/dist/cli/uninstall.js +67 -0
- package/dist/download/downloader.d.ts +64 -0
- package/dist/download/downloader.js +264 -0
- package/dist/download/jobManager.d.ts +21 -0
- package/dist/download/jobManager.js +198 -0
- package/dist/download/types.d.ts +43 -0
- package/dist/download/types.js +1 -0
- package/dist/runtime/ffmpegRuntime.d.ts +13 -0
- package/dist/runtime/ffmpegRuntime.js +51 -0
- package/dist/runtime/installers.d.ts +12 -0
- package/dist/runtime/installers.js +45 -0
- package/dist/runtime/manifest.d.ts +18 -0
- package/dist/runtime/manifest.js +43 -0
- package/dist/runtime/playwrightRuntime.d.ts +13 -0
- package/dist/runtime/playwrightRuntime.js +37 -0
- package/dist/runtime/systemDeps.d.ts +3 -0
- package/dist/runtime/systemDeps.js +30 -0
- package/dist/runtime/ytdlpRuntime.d.ts +14 -0
- package/dist/runtime/ytdlpRuntime.js +58 -0
- package/dist/server.d.ts +3 -1
- package/dist/server.js +4 -1
- package/dist/tools/downloads.d.ts +11 -0
- package/dist/tools/downloads.js +220 -0
- package/dist/tools/subtitles.d.ts +25 -0
- package/dist/tools/subtitles.js +135 -47
- package/dist/utils/config.d.ts +28 -0
- package/dist/utils/config.js +40 -11
- package/dist/utils/ffmpeg.d.ts +5 -0
- package/dist/utils/ffmpeg.js +16 -0
- package/dist/utils/ffmpegPath.d.ts +8 -0
- package/dist/utils/ffmpegPath.js +21 -0
- package/dist/utils/formatters.d.ts +4 -0
- package/dist/utils/formatters.js +42 -0
- package/dist/utils/mediaPaths.d.ts +7 -0
- package/dist/utils/mediaPaths.js +10 -0
- package/dist/utils/openClaw.d.ts +17 -0
- package/dist/utils/openClaw.js +79 -0
- package/dist/utils/videoInput.js +3 -0
- package/dist/utils/videoMetadata.d.ts +11 -0
- package/dist/utils/videoMetadata.js +1 -0
- package/dist/utils/ytdlp.d.ts +17 -1
- package/dist/utils/ytdlp.js +89 -2
- package/dist/utils/ytdlpPath.d.ts +9 -2
- package/dist/utils/ytdlpPath.js +19 -20
- package/dist/utils/ytdlpProgress.d.ts +13 -0
- package/dist/utils/ytdlpProgress.js +77 -0
- package/package.json +5 -3
- package/scripts/download-ytdlp.mjs +1 -1
- package/scripts/install.ps1 +9 -0
- package/scripts/install.sh +15 -0
package/dist/utils/ytdlp.js
CHANGED
|
@@ -1,8 +1,63 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { PATHS } from "./config.js";
|
|
4
5
|
import { getYtDlpPath } from "./ytdlpPath.js";
|
|
5
|
-
export function
|
|
6
|
+
export function createYtDlpStderrLineSplitter(onLine) {
|
|
7
|
+
let buffer = "";
|
|
8
|
+
let pendingCarriageReturn = false;
|
|
9
|
+
const emit = () => {
|
|
10
|
+
if (buffer.length === 0) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
onLine(buffer);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Why: progress observers are best-effort and must not break downloads.
|
|
18
|
+
}
|
|
19
|
+
buffer = "";
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
push(chunk) {
|
|
23
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
24
|
+
for (const ch of text) {
|
|
25
|
+
if (pendingCarriageReturn) {
|
|
26
|
+
if (ch === "\n") {
|
|
27
|
+
pendingCarriageReturn = false;
|
|
28
|
+
emit();
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
pendingCarriageReturn = false;
|
|
32
|
+
emit();
|
|
33
|
+
}
|
|
34
|
+
if (ch === "\r") {
|
|
35
|
+
pendingCarriageReturn = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ch === "\n") {
|
|
39
|
+
emit();
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
buffer += ch;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
flush() {
|
|
46
|
+
if (pendingCarriageReturn) {
|
|
47
|
+
pendingCarriageReturn = false;
|
|
48
|
+
}
|
|
49
|
+
emit();
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function hasYtDlp(runSpawnSync = spawnSync) {
|
|
54
|
+
const result = runSpawnSync(getYtDlpPath(), ["--version"], {
|
|
55
|
+
stdio: "ignore",
|
|
56
|
+
timeout: 30_000,
|
|
57
|
+
});
|
|
58
|
+
return result.status === 0 && result.error == null;
|
|
59
|
+
}
|
|
60
|
+
export function runYtDlp(args, timeoutMs = 45_000, onStderrLine) {
|
|
6
61
|
return new Promise((resolve, reject) => {
|
|
7
62
|
const start = Date.now();
|
|
8
63
|
const finalArgs = ["--force-ipv4", "--no-warnings", ...args];
|
|
@@ -14,13 +69,23 @@ export function runYtDlp(args, timeoutMs = 45_000) {
|
|
|
14
69
|
});
|
|
15
70
|
const stdoutChunks = [];
|
|
16
71
|
const stderrChunks = [];
|
|
72
|
+
const stderrSplitter = onStderrLine
|
|
73
|
+
? createYtDlpStderrLineSplitter(onStderrLine)
|
|
74
|
+
: null;
|
|
17
75
|
proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
18
|
-
proc.stderr.on("data", (chunk) =>
|
|
76
|
+
proc.stderr.on("data", (chunk) => {
|
|
77
|
+
stderrChunks.push(chunk);
|
|
78
|
+
stderrSplitter?.push(chunk);
|
|
79
|
+
});
|
|
80
|
+
function flushStderrLineBuffer() {
|
|
81
|
+
stderrSplitter?.flush();
|
|
82
|
+
}
|
|
19
83
|
let settled = false;
|
|
20
84
|
const timer = setTimeout(() => {
|
|
21
85
|
if (!settled) {
|
|
22
86
|
settled = true;
|
|
23
87
|
proc.kill("SIGKILL");
|
|
88
|
+
flushStderrLineBuffer();
|
|
24
89
|
resolve({
|
|
25
90
|
exitCode: -1,
|
|
26
91
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
@@ -33,6 +98,7 @@ export function runYtDlp(args, timeoutMs = 45_000) {
|
|
|
33
98
|
clearTimeout(timer);
|
|
34
99
|
if (!settled) {
|
|
35
100
|
settled = true;
|
|
101
|
+
flushStderrLineBuffer();
|
|
36
102
|
resolve({
|
|
37
103
|
exitCode: code ?? 1,
|
|
38
104
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
@@ -50,3 +116,24 @@ export function runYtDlp(args, timeoutMs = 45_000) {
|
|
|
50
116
|
});
|
|
51
117
|
});
|
|
52
118
|
}
|
|
119
|
+
export async function runYtDlpJson(args, timeoutMs = 45_000) {
|
|
120
|
+
const result = await runYtDlp(args, timeoutMs);
|
|
121
|
+
if (result.exitCode !== 0) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
error: result.stderr.slice(0, 500) || `yt-dlp exited with ${result.exitCode}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
value: JSON.parse(result.stdout),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
error: "yt-dlp returned invalid JSON metadata.",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Priority:
|
|
5
5
|
* 1. YT_DLP_PATH env var (explicit override)
|
|
6
|
-
* 2.
|
|
6
|
+
* 2. Runtime-managed binary at ~/.yt-mcp/runtime/bin/yt-dlp
|
|
7
7
|
* 3. "yt-dlp" — fall back to system PATH
|
|
8
8
|
*/
|
|
9
|
+
export interface ResolveYtDlpPathOptions {
|
|
10
|
+
envPath?: string | null;
|
|
11
|
+
runtimePath?: string;
|
|
12
|
+
runtimeExists?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function getRuntimeYtDlpPath(runtimeBinDir?: string): string;
|
|
15
|
+
export declare function resolveYtDlpPath(options?: ResolveYtDlpPathOptions): string;
|
|
9
16
|
/**
|
|
10
17
|
* Returns the absolute path to the yt-dlp binary, or the bare command name
|
|
11
18
|
* "yt-dlp" if only available on system PATH.
|
|
@@ -13,7 +20,7 @@
|
|
|
13
20
|
export declare function getYtDlpPath(): string;
|
|
14
21
|
export interface YtDlpVersionInfo {
|
|
15
22
|
version: string;
|
|
16
|
-
source: "
|
|
23
|
+
source: "runtime" | "system" | "env";
|
|
17
24
|
}
|
|
18
25
|
/**
|
|
19
26
|
* Get the version string and source of the resolved yt-dlp binary.
|
package/dist/utils/ytdlpPath.js
CHANGED
|
@@ -3,35 +3,34 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Priority:
|
|
5
5
|
* 1. YT_DLP_PATH env var (explicit override)
|
|
6
|
-
* 2.
|
|
6
|
+
* 2. Runtime-managed binary at ~/.yt-mcp/runtime/bin/yt-dlp
|
|
7
7
|
* 3. "yt-dlp" — fall back to system PATH
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
// From dist/utils/ → package root is ../../
|
|
15
|
-
const pkgRoot = join(__dirname, "..", "..");
|
|
16
|
-
function bundledPath() {
|
|
11
|
+
import { PATHS, buildRuntimeBinaryPath } from "./config.js";
|
|
12
|
+
export function getRuntimeYtDlpPath(runtimeBinDir = PATHS.runtimeBinDir) {
|
|
17
13
|
const name = process.platform === "win32" ? "yt-dlp.exe" : "yt-dlp";
|
|
18
|
-
return
|
|
14
|
+
return buildRuntimeBinaryPath(runtimeBinDir, name);
|
|
15
|
+
}
|
|
16
|
+
export function resolveYtDlpPath(options = {}) {
|
|
17
|
+
const envPath = options.envPath ?? process.env.YT_DLP_PATH ?? null;
|
|
18
|
+
if (envPath) {
|
|
19
|
+
return envPath;
|
|
20
|
+
}
|
|
21
|
+
const runtimePath = options.runtimePath ?? getRuntimeYtDlpPath();
|
|
22
|
+
const runtimeExists = options.runtimeExists ?? existsSync(runtimePath);
|
|
23
|
+
if (runtimeExists) {
|
|
24
|
+
return runtimePath;
|
|
25
|
+
}
|
|
26
|
+
return "yt-dlp";
|
|
19
27
|
}
|
|
20
28
|
/**
|
|
21
29
|
* Returns the absolute path to the yt-dlp binary, or the bare command name
|
|
22
30
|
* "yt-dlp" if only available on system PATH.
|
|
23
31
|
*/
|
|
24
32
|
export function getYtDlpPath() {
|
|
25
|
-
|
|
26
|
-
const envPath = process.env.YT_DLP_PATH;
|
|
27
|
-
if (envPath && existsSync(envPath))
|
|
28
|
-
return envPath;
|
|
29
|
-
// 2. Bundled binary
|
|
30
|
-
const bundled = bundledPath();
|
|
31
|
-
if (existsSync(bundled))
|
|
32
|
-
return bundled;
|
|
33
|
-
// 3. System PATH
|
|
34
|
-
return "yt-dlp";
|
|
33
|
+
return resolveYtDlpPath();
|
|
35
34
|
}
|
|
36
35
|
/**
|
|
37
36
|
* Get the version string and source of the resolved yt-dlp binary.
|
|
@@ -47,8 +46,8 @@ export function getYtDlpVersion(binPath) {
|
|
|
47
46
|
if (process.env.YT_DLP_PATH && resolved === process.env.YT_DLP_PATH) {
|
|
48
47
|
source = "env";
|
|
49
48
|
}
|
|
50
|
-
else if (resolved ===
|
|
51
|
-
source = "
|
|
49
|
+
else if (resolved === getRuntimeYtDlpPath()) {
|
|
50
|
+
source = "runtime";
|
|
52
51
|
}
|
|
53
52
|
else {
|
|
54
53
|
source = "system";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ParsedYtDlpProgress {
|
|
2
|
+
progress: string;
|
|
3
|
+
downloaded_size?: string;
|
|
4
|
+
total_size?: string;
|
|
5
|
+
speed?: string;
|
|
6
|
+
eta?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Parse the canonical yt-dlp download progress line used by this integration.
|
|
10
|
+
* Narrow contract: intentionally supports only representative `[download] ...`
|
|
11
|
+
* lines with percent, total size, speed, and ETA fields.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseYtDlpProgressLine(line: string): ParsedYtDlpProgress | null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { formatBytes, formatEta, formatPercent, formatSpeed } from "./formatters.js";
|
|
2
|
+
const SIZE_UNITS = {
|
|
3
|
+
b: 1,
|
|
4
|
+
kb: 1000,
|
|
5
|
+
kib: 1024,
|
|
6
|
+
mb: 1000 ** 2,
|
|
7
|
+
mib: 1024 ** 2,
|
|
8
|
+
gb: 1000 ** 3,
|
|
9
|
+
gib: 1024 ** 3,
|
|
10
|
+
tb: 1000 ** 4,
|
|
11
|
+
tib: 1024 ** 4,
|
|
12
|
+
};
|
|
13
|
+
function parseSizeToBytes(text) {
|
|
14
|
+
const match = text.trim().match(/^(\d+(?:\.\d+)?)([kmgt]?i?b)$/i);
|
|
15
|
+
if (!match) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const value = Number(match[1]);
|
|
19
|
+
const unit = match[2].toLowerCase();
|
|
20
|
+
const multiplier = SIZE_UNITS[unit];
|
|
21
|
+
if (!Number.isFinite(value) || multiplier == null) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return value * multiplier;
|
|
25
|
+
}
|
|
26
|
+
function parseEtaToSeconds(text) {
|
|
27
|
+
const trimmed = text.trim();
|
|
28
|
+
if (!trimmed || trimmed === "N/A") {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const hms = trimmed.match(/^(\d+):(\d{2}):(\d{2})$/);
|
|
32
|
+
if (hms) {
|
|
33
|
+
return Number(hms[1]) * 3600 + Number(hms[2]) * 60 + Number(hms[3]);
|
|
34
|
+
}
|
|
35
|
+
const ms = trimmed.match(/^(\d{1,2}):(\d{2})$/);
|
|
36
|
+
if (ms) {
|
|
37
|
+
return Number(ms[1]) * 60 + Number(ms[2]);
|
|
38
|
+
}
|
|
39
|
+
const unitMatch = trimmed.match(/^(\d+(?:\.\d+)?)([smhd])$/i);
|
|
40
|
+
if (unitMatch) {
|
|
41
|
+
const value = Number(unitMatch[1]);
|
|
42
|
+
const unit = unitMatch[2].toLowerCase();
|
|
43
|
+
const multiplier = unit === "s" ? 1 : unit === "m" ? 60 : unit === "h" ? 3600 : 86400;
|
|
44
|
+
return value * multiplier;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse the canonical yt-dlp download progress line used by this integration.
|
|
50
|
+
* Narrow contract: intentionally supports only representative `[download] ...`
|
|
51
|
+
* lines with percent, total size, speed, and ETA fields.
|
|
52
|
+
*/
|
|
53
|
+
export function parseYtDlpProgressLine(line) {
|
|
54
|
+
const match = line.match(/^\[download\]\s+(?<progress>\d+(?:\.\d+)?)% of (?<total>\S+)(?: at (?<speed>\S+?\/s))?(?: ETA (?<eta>\S+))?(?: \((?<details>.+)\))?$/);
|
|
55
|
+
if (!match?.groups?.progress) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const progress = formatPercent(Number(match.groups.progress));
|
|
59
|
+
const totalBytes = match.groups.total ? parseSizeToBytes(match.groups.total) : null;
|
|
60
|
+
const speedBytes = match.groups.speed ? parseSizeToBytes(match.groups.speed.replace(/\/s$/i, "")) : null;
|
|
61
|
+
const etaSeconds = match.groups.eta ? parseEtaToSeconds(match.groups.eta) : null;
|
|
62
|
+
const parsed = { progress };
|
|
63
|
+
if (totalBytes != null) {
|
|
64
|
+
parsed.total_size = formatBytes(totalBytes);
|
|
65
|
+
}
|
|
66
|
+
if (speedBytes != null) {
|
|
67
|
+
parsed.speed = formatSpeed(speedBytes);
|
|
68
|
+
}
|
|
69
|
+
if (etaSeconds != null) {
|
|
70
|
+
parsed.eta = formatEta(etaSeconds);
|
|
71
|
+
}
|
|
72
|
+
if (totalBytes != null && Number.isFinite(Number(match.groups.progress))) {
|
|
73
|
+
const downloadedBytes = (totalBytes * Number(match.groups.progress)) / 100;
|
|
74
|
+
parsed.downloaded_size = formatBytes(downloadedBytes);
|
|
75
|
+
}
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mkterswingman/5mghost-yonder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Internal MCP client with local data tools and remote API proxy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc -p tsconfig.json",
|
|
14
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
14
15
|
"dev": "tsx src/cli/index.ts",
|
|
15
|
-
"start": "node dist/cli/index.js"
|
|
16
|
-
"postinstall": "node scripts/download-ytdlp.mjs"
|
|
16
|
+
"start": "node dist/cli/index.js"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"files": [
|
|
36
36
|
"dist/",
|
|
37
37
|
"scripts/download-ytdlp.mjs",
|
|
38
|
+
"scripts/install.sh",
|
|
39
|
+
"scripts/install.ps1",
|
|
38
40
|
"README.md"
|
|
39
41
|
],
|
|
40
42
|
"devDependencies": {
|
|
@@ -39,7 +39,7 @@ const PLATFORM_MAP = {
|
|
|
39
39
|
|
|
40
40
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
41
41
|
const pkgRoot = join(__dirname, "..");
|
|
42
|
-
const binDir = join(pkgRoot, "bin");
|
|
42
|
+
const binDir = process.env.YT_MCP_BINARY_DIR || join(pkgRoot, "bin");
|
|
43
43
|
|
|
44
44
|
function localName() {
|
|
45
45
|
return process.platform === "win32" ? "yt-dlp.exe" : "yt-dlp";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
5
|
+
if command -v brew >/dev/null 2>&1; then
|
|
6
|
+
brew install node
|
|
7
|
+
else
|
|
8
|
+
echo "Node.js is required. Install it first, then rerun this script." >&2
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
npm install -g @mkterswingman/5mghost-yonder
|
|
14
|
+
yt-mcp runtime install
|
|
15
|
+
yt-mcp setup
|