@mkterswingman/5mghost-yonder 0.0.27 → 0.0.29

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.
Files changed (93) hide show
  1. package/dist/auth/oauthFlow.d.ts +9 -0
  2. package/dist/auth/oauthFlow.js +151 -0
  3. package/dist/auth/sharedAuth.d.ts +10 -0
  4. package/dist/auth/sharedAuth.js +31 -0
  5. package/dist/auth/tokenManager.d.ts +18 -0
  6. package/dist/auth/tokenManager.js +92 -0
  7. package/dist/cli/check.d.ts +4 -0
  8. package/dist/cli/check.js +90 -0
  9. package/dist/cli/index.d.ts +18 -0
  10. package/dist/cli/index.js +166 -0
  11. package/dist/cli/installSkills.d.ts +1 -0
  12. package/dist/cli/installSkills.js +39 -0
  13. package/dist/cli/runtime.d.ts +9 -0
  14. package/dist/cli/runtime.js +35 -0
  15. package/dist/cli/serve.d.ts +1 -0
  16. package/dist/cli/serve.js +26 -0
  17. package/dist/cli/setup.d.ts +33 -0
  18. package/dist/cli/setup.js +450 -0
  19. package/dist/cli/setupCookies.d.ts +88 -0
  20. package/dist/cli/setupCookies.js +440 -0
  21. package/dist/cli/smoke.d.ts +27 -0
  22. package/dist/cli/smoke.js +108 -0
  23. package/dist/cli/uninstall.d.ts +16 -0
  24. package/dist/cli/uninstall.js +99 -0
  25. package/dist/download/downloader.d.ts +67 -0
  26. package/dist/download/downloader.js +309 -0
  27. package/dist/download/jobManager.d.ts +22 -0
  28. package/dist/download/jobManager.js +211 -0
  29. package/dist/download/types.d.ts +44 -0
  30. package/dist/download/types.js +1 -0
  31. package/dist/runtime/ffmpegRuntime.d.ts +13 -0
  32. package/dist/runtime/ffmpegRuntime.js +51 -0
  33. package/dist/runtime/installers.d.ts +12 -0
  34. package/dist/runtime/installers.js +45 -0
  35. package/dist/runtime/manifest.d.ts +18 -0
  36. package/dist/runtime/manifest.js +43 -0
  37. package/dist/runtime/playwrightRuntime.d.ts +17 -0
  38. package/dist/runtime/playwrightRuntime.js +49 -0
  39. package/dist/runtime/systemDeps.d.ts +3 -0
  40. package/dist/runtime/systemDeps.js +30 -0
  41. package/dist/runtime/ytdlpRuntime.d.ts +14 -0
  42. package/dist/runtime/ytdlpRuntime.js +58 -0
  43. package/dist/server.d.ts +23 -0
  44. package/dist/server.js +81 -0
  45. package/dist/tools/downloads.d.ts +11 -0
  46. package/dist/tools/downloads.js +220 -0
  47. package/dist/tools/remote.d.ts +4 -0
  48. package/dist/tools/remote.js +239 -0
  49. package/dist/tools/subtitles.d.ts +29 -0
  50. package/dist/tools/subtitles.js +713 -0
  51. package/dist/utils/browserLaunch.d.ts +5 -0
  52. package/dist/utils/browserLaunch.js +22 -0
  53. package/dist/utils/browserProfileImport.d.ts +49 -0
  54. package/dist/utils/browserProfileImport.js +163 -0
  55. package/dist/utils/codexInternal.d.ts +9 -0
  56. package/dist/utils/codexInternal.js +60 -0
  57. package/dist/utils/config.d.ts +53 -0
  58. package/dist/utils/config.js +77 -0
  59. package/dist/utils/cookieRefresh.d.ts +18 -0
  60. package/dist/utils/cookieRefresh.js +70 -0
  61. package/dist/utils/cookies.d.ts +18 -0
  62. package/dist/utils/cookies.js +100 -0
  63. package/dist/utils/ffmpeg.d.ts +5 -0
  64. package/dist/utils/ffmpeg.js +16 -0
  65. package/dist/utils/ffmpegPath.d.ts +8 -0
  66. package/dist/utils/ffmpegPath.js +21 -0
  67. package/dist/utils/formatters.d.ts +4 -0
  68. package/dist/utils/formatters.js +42 -0
  69. package/dist/utils/launcher.d.ts +12 -0
  70. package/dist/utils/launcher.js +90 -0
  71. package/dist/utils/mcpRegistration.d.ts +7 -0
  72. package/dist/utils/mcpRegistration.js +23 -0
  73. package/dist/utils/mediaPaths.d.ts +7 -0
  74. package/dist/utils/mediaPaths.js +10 -0
  75. package/dist/utils/openClaw.d.ts +18 -0
  76. package/dist/utils/openClaw.js +82 -0
  77. package/dist/utils/skills.d.ts +16 -0
  78. package/dist/utils/skills.js +56 -0
  79. package/dist/utils/videoInput.d.ts +5 -0
  80. package/dist/utils/videoInput.js +58 -0
  81. package/dist/utils/videoMetadata.d.ts +11 -0
  82. package/dist/utils/videoMetadata.js +1 -0
  83. package/dist/utils/ytdlp.d.ts +28 -0
  84. package/dist/utils/ytdlp.js +205 -0
  85. package/dist/utils/ytdlpFailures.d.ts +3 -0
  86. package/dist/utils/ytdlpFailures.js +27 -0
  87. package/dist/utils/ytdlpPath.d.ts +33 -0
  88. package/dist/utils/ytdlpPath.js +77 -0
  89. package/dist/utils/ytdlpProgress.d.ts +13 -0
  90. package/dist/utils/ytdlpProgress.js +77 -0
  91. package/dist/utils/ytdlpScheduler.d.ts +25 -0
  92. package/dist/utils/ytdlpScheduler.js +78 -0
  93. package/package.json +2 -1
@@ -0,0 +1,9 @@
1
+ export interface OAuthFlowOptions {
2
+ timeoutMs?: number;
3
+ }
4
+ export declare function runOAuthFlow(authUrl: string, options?: OAuthFlowOptions): Promise<{
5
+ accessToken: string;
6
+ refreshToken: string;
7
+ expiresIn: number;
8
+ clientId: string;
9
+ }>;
@@ -0,0 +1,151 @@
1
+ import { createServer } from "node:http";
2
+ import { randomBytes, createHash } from "node:crypto";
3
+ import { URL } from "node:url";
4
+ import { buildBrowserOpenCommand } from "../utils/browserLaunch.js";
5
+ function base64url(buf) {
6
+ return buf
7
+ .toString("base64")
8
+ .replace(/\+/g, "-")
9
+ .replace(/\//g, "_")
10
+ .replace(/=+$/, "");
11
+ }
12
+ export async function runOAuthFlow(authUrl, options = {}) {
13
+ // 1. Generate PKCE + state
14
+ const codeVerifier = base64url(randomBytes(32));
15
+ const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
16
+ const state = base64url(randomBytes(32));
17
+ // 2. Start temp HTTP server to get the actual port for redirect_uri
18
+ const { server: httpServer, port } = await startCallbackServer();
19
+ const redirectUri = `http://127.0.0.1:${port}`;
20
+ // 3. DCR register client with actual redirect_uri (including port)
21
+ let clientId;
22
+ let clientSecret;
23
+ try {
24
+ const dcrRes = await fetch(`${authUrl}/oauth/register`, {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify({
28
+ client_name: "yt-mcp-cli",
29
+ redirect_uris: [redirectUri],
30
+ }),
31
+ });
32
+ if (!dcrRes.ok) {
33
+ const text = await dcrRes.text().catch(() => "");
34
+ httpServer.close();
35
+ throw new Error(`DCR registration failed: ${dcrRes.status} ${text}`);
36
+ }
37
+ const dcrBody = (await dcrRes.json());
38
+ clientId = dcrBody.client_id;
39
+ clientSecret = dcrBody.client_secret;
40
+ }
41
+ catch (err) {
42
+ httpServer.close();
43
+ throw err;
44
+ }
45
+ // 4. Wait for OAuth callback
46
+ return new Promise((resolve, reject) => {
47
+ const timeoutMs = options.timeoutMs ?? 5 * 60 * 1000;
48
+ const timeout = setTimeout(() => {
49
+ httpServer.close();
50
+ reject(new Error(`OAuth flow timed out after ${Math.round(timeoutMs / 1000)}s`));
51
+ }, timeoutMs);
52
+ function cleanup() {
53
+ clearTimeout(timeout);
54
+ httpServer.close();
55
+ }
56
+ httpServer.on("request", async (req, res) => {
57
+ try {
58
+ const url = new URL(req.url ?? "/", `http://127.0.0.1`);
59
+ const code = url.searchParams.get("code");
60
+ const error = url.searchParams.get("error");
61
+ const returnedState = url.searchParams.get("state");
62
+ if (error) {
63
+ const safeError = error.replace(/[<>&"']/g, (c) => `&#${c.charCodeAt(0)};`);
64
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
65
+ res.end(`<h1>Authorization failed</h1><p>${safeError}</p>`);
66
+ cleanup();
67
+ reject(new Error(`OAuth error: ${error}`));
68
+ return;
69
+ }
70
+ if (!code) {
71
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
72
+ res.end("<h1>Waiting for authorization...</h1>");
73
+ return;
74
+ }
75
+ // Verify state to prevent CSRF
76
+ if (returnedState !== state) {
77
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
78
+ res.end("<h1>Authorization failed</h1><p>State mismatch — possible CSRF attack.</p>");
79
+ cleanup();
80
+ reject(new Error("OAuth state mismatch"));
81
+ return;
82
+ }
83
+ // Exchange code for tokens
84
+ const tokenBody = {
85
+ grant_type: "authorization_code",
86
+ code,
87
+ redirect_uri: redirectUri,
88
+ client_id: clientId,
89
+ code_verifier: codeVerifier,
90
+ };
91
+ if (clientSecret) {
92
+ tokenBody.client_secret = clientSecret;
93
+ }
94
+ const tokenRes = await fetch(`${authUrl}/oauth/token`, {
95
+ method: "POST",
96
+ headers: { "Content-Type": "application/json" },
97
+ body: JSON.stringify(tokenBody),
98
+ });
99
+ if (!tokenRes.ok) {
100
+ const text = await tokenRes.text().catch(() => "");
101
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
102
+ res.end(`<h1>Token exchange failed</h1><p>${text}</p>`);
103
+ cleanup();
104
+ reject(new Error(`Token exchange failed: ${tokenRes.status} ${text}`));
105
+ return;
106
+ }
107
+ const tokens = (await tokenRes.json());
108
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
109
+ res.end("<h1>Authorization successful!</h1><p>You can close this window.</p>");
110
+ cleanup();
111
+ resolve({
112
+ accessToken: tokens.access_token,
113
+ refreshToken: tokens.refresh_token,
114
+ expiresIn: tokens.expires_in,
115
+ clientId,
116
+ });
117
+ }
118
+ catch (err) {
119
+ res.writeHead(500);
120
+ res.end("Internal error");
121
+ cleanup();
122
+ reject(err);
123
+ }
124
+ });
125
+ // Open browser
126
+ const authorizeUrl = `${authUrl}/oauth/authorize?response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${encodeURIComponent(codeChallenge)}&code_challenge_method=S256&state=${encodeURIComponent(state)}`;
127
+ console.log("\n\x1b[1mOpen this URL in your browser to authorize:\x1b[0m");
128
+ console.log(`\n ${authorizeUrl}\n`);
129
+ import("node:child_process").then(({ execFile }) => {
130
+ const command = buildBrowserOpenCommand(authorizeUrl);
131
+ execFile(command.file, command.args);
132
+ }).catch(() => {
133
+ // ignore — user can open manually
134
+ });
135
+ });
136
+ }
137
+ /** Start an HTTP server on a random port and return the server + port. */
138
+ async function startCallbackServer() {
139
+ const server = createServer();
140
+ return new Promise((resolve, reject) => {
141
+ server.listen(0, "127.0.0.1", () => {
142
+ const addr = server.address();
143
+ if (!addr || typeof addr === "string") {
144
+ server.close();
145
+ reject(new Error("Failed to start callback server"));
146
+ return;
147
+ }
148
+ resolve({ server, port: addr.port });
149
+ });
150
+ });
151
+ }
@@ -0,0 +1,10 @@
1
+ export interface SharedAuthData {
2
+ type: "jwt" | "pat";
3
+ access_token?: string;
4
+ refresh_token?: string;
5
+ expires_at?: number;
6
+ client_id?: string;
7
+ pat?: string;
8
+ }
9
+ export declare function readSharedAuth(authPath: string): SharedAuthData | null;
10
+ export declare function writeSharedAuth(authPath: string, data: SharedAuthData): void;
@@ -0,0 +1,31 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ export function readSharedAuth(authPath) {
4
+ if (!existsSync(authPath))
5
+ return null;
6
+ try {
7
+ return JSON.parse(readFileSync(authPath, "utf8"));
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ export function writeSharedAuth(authPath, data) {
14
+ const authDir = dirname(authPath);
15
+ mkdirSync(authDir, { recursive: true });
16
+ try {
17
+ chmodSync(authDir, 0o700);
18
+ }
19
+ catch {
20
+ // Why: best-effort hardening; Windows may ignore POSIX-style directory modes.
21
+ }
22
+ const tempPath = `${authPath}.tmp`;
23
+ writeFileSync(tempPath, JSON.stringify(data, null, 2), { encoding: "utf8", mode: 0o600 });
24
+ renameSync(tempPath, authPath);
25
+ try {
26
+ chmodSync(authPath, 0o600);
27
+ }
28
+ catch {
29
+ // Why: chmod is best-effort on Windows and should not block auth persistence.
30
+ }
31
+ }
@@ -0,0 +1,18 @@
1
+ interface TokenManagerOptions {
2
+ authPath?: string;
3
+ env?: NodeJS.ProcessEnv;
4
+ fetchImpl?: typeof fetch;
5
+ }
6
+ export declare class TokenManager {
7
+ private authUrl;
8
+ private authPath;
9
+ private env;
10
+ private fetchImpl;
11
+ constructor(authUrl: string, options?: TokenManagerOptions);
12
+ getValidToken(): Promise<string | null>;
13
+ saveTokens(accessToken: string, refreshToken: string, expiresIn: number, clientId?: string): Promise<void>;
14
+ savePAT(pat: string): Promise<void>;
15
+ private readAuth;
16
+ private refreshTokens;
17
+ }
18
+ export {};
@@ -0,0 +1,92 @@
1
+ import { PATHS } from "../utils/config.js";
2
+ import { readSharedAuth, writeSharedAuth } from "./sharedAuth.js";
3
+ export class TokenManager {
4
+ authUrl;
5
+ authPath;
6
+ env;
7
+ fetchImpl;
8
+ constructor(authUrl, options = {}) {
9
+ this.authUrl = authUrl;
10
+ this.authPath = options.authPath ?? PATHS.sharedAuthJson;
11
+ this.env = options.env ?? process.env;
12
+ this.fetchImpl = options.fetchImpl ?? fetch;
13
+ }
14
+ async getValidToken() {
15
+ // Check env var PAT first
16
+ const envPat = this.env.YT_MCP_TOKEN;
17
+ if (envPat)
18
+ return envPat;
19
+ const auth = this.readAuth();
20
+ if (!auth)
21
+ return null;
22
+ if (auth.type === "pat" && auth.pat) {
23
+ return auth.pat;
24
+ }
25
+ if (auth.type === "jwt") {
26
+ if (auth.access_token && auth.expires_at && Date.now() < auth.expires_at) {
27
+ return auth.access_token;
28
+ }
29
+ // Try refresh
30
+ if (auth.refresh_token) {
31
+ try {
32
+ const refreshed = await this.refreshTokens(auth.refresh_token, auth.client_id);
33
+ if (refreshed) {
34
+ await this.saveTokens(refreshed.access_token, refreshed.refresh_token, refreshed.expires_in, auth.client_id);
35
+ return refreshed.access_token;
36
+ }
37
+ }
38
+ catch {
39
+ // refresh failed → AUTH_EXPIRED
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ return null;
45
+ }
46
+ async saveTokens(accessToken, refreshToken, expiresIn, clientId) {
47
+ const data = {
48
+ type: "jwt",
49
+ access_token: accessToken,
50
+ refresh_token: refreshToken,
51
+ expires_at: Date.now() + expiresIn * 1000,
52
+ client_id: clientId,
53
+ };
54
+ writeSharedAuth(this.authPath, data);
55
+ }
56
+ async savePAT(pat) {
57
+ const data = {
58
+ type: "pat",
59
+ pat,
60
+ };
61
+ writeSharedAuth(this.authPath, data);
62
+ }
63
+ readAuth() {
64
+ return readSharedAuth(this.authPath);
65
+ }
66
+ async refreshTokens(refreshToken, clientId) {
67
+ const url = `${this.authUrl}/oauth/token`;
68
+ const body = {
69
+ grant_type: "refresh_token",
70
+ refresh_token: refreshToken,
71
+ };
72
+ if (clientId) {
73
+ body.client_id = clientId;
74
+ }
75
+ const res = await this.fetchImpl(url, {
76
+ method: "POST",
77
+ headers: { "Content-Type": "application/json" },
78
+ body: JSON.stringify(body),
79
+ });
80
+ if (!res.ok)
81
+ return null;
82
+ try {
83
+ const body = (await res.json());
84
+ if (!body.access_token)
85
+ return null;
86
+ return body;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * `yt-mcp check` — verify token, cookies, yt-dlp, and remote API connectivity.
3
+ */
4
+ export declare function runCheck(): Promise<void>;
@@ -0,0 +1,90 @@
1
+ import { existsSync } from "node:fs";
2
+ import { PATHS, loadConfig } from "../utils/config.js";
3
+ import { TokenManager } from "../auth/tokenManager.js";
4
+ import { hasSIDCookies, areCookiesExpired } from "../utils/cookies.js";
5
+ import { getYtDlpVersion } from "../utils/ytdlpPath.js";
6
+ import { runRuntimeCommand } from "./runtime.js";
7
+ /**
8
+ * `yt-mcp check` — verify token, cookies, yt-dlp, and remote API connectivity.
9
+ */
10
+ export async function runCheck() {
11
+ const config = loadConfig();
12
+ const tm = new TokenManager(config.auth_url);
13
+ console.log("yt-mcp check\n");
14
+ // 1. Token
15
+ const token = await tm.getValidToken();
16
+ if (token) {
17
+ const masked = token.slice(0, 8) + "...";
18
+ const source = process.env.YT_MCP_TOKEN ? "env" : PATHS.sharedAuthJson;
19
+ console.log(`🔑 Shared auth: ✅ present (${masked}) [${source}]`);
20
+ // Server verification
21
+ try {
22
+ const res = await fetch(`${config.auth_url}/api/me`, {
23
+ headers: { Authorization: `Bearer ${token}` },
24
+ signal: AbortSignal.timeout(10_000),
25
+ });
26
+ if (res.ok) {
27
+ console.log("🔐 Server verification: ✅ active");
28
+ }
29
+ else {
30
+ console.log(`🔐 Server verification: ❌ ${res.status} ${res.statusText}`);
31
+ console.log(" Run `yt-mcp setup` to re-authenticate.");
32
+ }
33
+ }
34
+ catch (err) {
35
+ const msg = err instanceof Error ? err.message : String(err);
36
+ console.log(`🔐 Server verification: ⚠️ unreachable (${msg})`);
37
+ }
38
+ }
39
+ else {
40
+ console.log("🔑 Shared auth: ❌ not configured");
41
+ console.log(" Run `yt-mcp setup` or set YT_MCP_TOKEN env var");
42
+ }
43
+ console.log("");
44
+ console.log(await runRuntimeCommand("check"));
45
+ // 2. Cookies
46
+ if (existsSync(PATHS.cookiesTxt)) {
47
+ const hasSID = hasSIDCookies(PATHS.cookiesTxt);
48
+ const expired = areCookiesExpired(PATHS.cookiesTxt);
49
+ if (hasSID && !expired) {
50
+ console.log("🍪 Cookies: ✅ valid (SID present, not expired)");
51
+ }
52
+ else if (hasSID && expired) {
53
+ console.log("🍪 Cookies: ❌ expired");
54
+ console.log(" Run `yt-mcp setup-cookies` to refresh");
55
+ }
56
+ else {
57
+ console.log("🍪 Cookies: ❌ no YouTube session cookies found");
58
+ console.log(" Run `yt-mcp setup-cookies`");
59
+ }
60
+ }
61
+ else {
62
+ console.log("🍪 Cookies: ❌ not found");
63
+ console.log(" Run `yt-mcp setup-cookies`");
64
+ }
65
+ // 3. yt-dlp
66
+ const ytdlp = getYtDlpVersion();
67
+ if (ytdlp) {
68
+ console.log(`🎬 yt-dlp: ✅ ${ytdlp.version} (${ytdlp.source})`);
69
+ }
70
+ else {
71
+ console.log("🎬 yt-dlp: ❌ not found");
72
+ console.log(" Install: brew install yt-dlp (or pip install yt-dlp)");
73
+ }
74
+ // 4. Remote API
75
+ try {
76
+ const res = await fetch(`${config.api_url}/healthz`, {
77
+ signal: AbortSignal.timeout(10_000),
78
+ });
79
+ if (res.ok) {
80
+ console.log(`🌐 Remote API: ✅ reachable (${config.api_url})`);
81
+ }
82
+ else {
83
+ console.log(`🌐 Remote API: ⚠️ ${res.status} ${res.statusText}`);
84
+ }
85
+ }
86
+ catch (err) {
87
+ const msg = err instanceof Error ? err.message : String(err);
88
+ console.log(`🌐 Remote API: ❌ unreachable (${msg})`);
89
+ }
90
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ export declare function buildHelpText(version: string): string;
3
+ export interface UnifiedUpdateDeps {
4
+ getCurrentVersion(): string;
5
+ getLatestVersion(): Promise<string>;
6
+ installLatestPackage(): Promise<void>;
7
+ updateRuntime(): Promise<import("../runtime/installers.js").RuntimeSummary>;
8
+ postUpdate?: () => Promise<void>;
9
+ }
10
+ export declare function compareVersions(currentVersion: string, latestVersion: string): number;
11
+ export declare function runUnifiedUpdate(deps: UnifiedUpdateDeps): Promise<{
12
+ package: {
13
+ currentVersion: string;
14
+ latestVersion: string;
15
+ updated: boolean;
16
+ };
17
+ runtime: import("../runtime/installers.js").RuntimeSummary;
18
+ }>;
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from "node:child_process";
3
+ import { readFileSync } from "node:fs";
4
+ import { join, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const pkgPath = join(__dirname, "..", "..", "package.json");
8
+ function getVersion() {
9
+ try {
10
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
11
+ return pkg.version ?? "unknown";
12
+ }
13
+ catch {
14
+ return "unknown";
15
+ }
16
+ }
17
+ export function buildHelpText(version) {
18
+ return `
19
+ yt-mcp v${version}
20
+
21
+ Usage: yt-mcp <command>
22
+
23
+ Commands:
24
+ setup Run first-time setup (OAuth + cookies + MCP registration)
25
+ serve Start the MCP server (stdio transport)
26
+ smoke Run installer smoke checks
27
+ install-skills Install the bundled yt-mcp analysis skill into supported AI clients
28
+ setup-cookies Refresh YouTube cookies using browser login
29
+ runtime Manage required runtimes
30
+ check Check auth, runtime, cookies, and connectivity
31
+ uninstall Remove MCP registrations and local yt-mcp config
32
+ update Update the main package and required runtimes
33
+ version Show current version
34
+
35
+ Environment variables:
36
+ YT_MCP_TOKEN Personal Access Token (skips OAuth)
37
+ `;
38
+ }
39
+ export function compareVersions(currentVersion, latestVersion) {
40
+ const normalize = (value) => value.split(".").map((part) => Number.parseInt(part, 10)).map((part) => (Number.isFinite(part) ? part : 0));
41
+ const current = normalize(currentVersion);
42
+ const latest = normalize(latestVersion);
43
+ const max = Math.max(current.length, latest.length);
44
+ for (let index = 0; index < max; index += 1) {
45
+ const a = current[index] ?? 0;
46
+ const b = latest[index] ?? 0;
47
+ if (a === b)
48
+ continue;
49
+ return a < b ? -1 : 1;
50
+ }
51
+ return 0;
52
+ }
53
+ export async function runUnifiedUpdate(deps) {
54
+ const currentVersion = deps.getCurrentVersion();
55
+ const latestVersion = await deps.getLatestVersion();
56
+ let updated = false;
57
+ if (compareVersions(currentVersion, latestVersion) < 0) {
58
+ await deps.installLatestPackage();
59
+ updated = true;
60
+ }
61
+ const runtime = await deps.updateRuntime();
62
+ if (deps.postUpdate) {
63
+ await deps.postUpdate();
64
+ }
65
+ return {
66
+ package: { currentVersion, latestVersion, updated },
67
+ runtime,
68
+ };
69
+ }
70
+ const command = process.argv[2];
71
+ async function main() {
72
+ switch (command) {
73
+ case "setup": {
74
+ const { runSetup } = await import("./setup.js");
75
+ await runSetup();
76
+ break;
77
+ }
78
+ case "serve": {
79
+ const { runServe } = await import("./serve.js");
80
+ await runServe();
81
+ break;
82
+ }
83
+ case "smoke": {
84
+ const { runSmoke } = await import("./smoke.js");
85
+ await runSmoke(process.argv.slice(3));
86
+ break;
87
+ }
88
+ case "install-skills": {
89
+ const { runInstallSkills } = await import("./installSkills.js");
90
+ await runInstallSkills();
91
+ break;
92
+ }
93
+ case "setup-cookies": {
94
+ const { runSetupCookies, parseSetupCookiesArgs } = await import("./setupCookies.js");
95
+ await runSetupCookies({}, parseSetupCookiesArgs(process.argv.slice(3)));
96
+ break;
97
+ }
98
+ case "uninstall": {
99
+ const { runUninstall } = await import("./uninstall.js");
100
+ await runUninstall();
101
+ break;
102
+ }
103
+ case "runtime": {
104
+ const { runRuntimeCli } = await import("./runtime.js");
105
+ await runRuntimeCli(process.argv[3]);
106
+ break;
107
+ }
108
+ case "check": {
109
+ const { runCheck } = await import("./check.js");
110
+ await runCheck();
111
+ break;
112
+ }
113
+ case "update": {
114
+ const current = getVersion();
115
+ console.log(`Current version: ${current}`);
116
+ console.log("Checking for updates...\n");
117
+ try {
118
+ const { updateAll } = await import("../runtime/installers.js");
119
+ const { runInstallSkills } = await import("./installSkills.js");
120
+ const summary = await runUnifiedUpdate({
121
+ getCurrentVersion: getVersion,
122
+ getLatestVersion: async () => execSync("npm view @mkterswingman/5mghost-yonder version", {
123
+ encoding: "utf8",
124
+ stdio: ["ignore", "pipe", "ignore"],
125
+ }).trim(),
126
+ installLatestPackage: async () => {
127
+ execSync("npm install -g @mkterswingman/5mghost-yonder@latest", {
128
+ stdio: "inherit",
129
+ });
130
+ },
131
+ updateRuntime: updateAll,
132
+ postUpdate: runInstallSkills,
133
+ });
134
+ if (summary.package.updated) {
135
+ console.log(`\n✅ Updated package to ${summary.package.latestVersion}`);
136
+ }
137
+ else {
138
+ console.log(`✅ Already on the latest version (${summary.package.currentVersion})`);
139
+ }
140
+ console.log("");
141
+ const { formatRuntimeSummary } = await import("./runtime.js");
142
+ console.log(formatRuntimeSummary(summary.runtime));
143
+ }
144
+ catch (err) {
145
+ const msg = err instanceof Error ? err.message : String(err);
146
+ console.error(`❌ Update failed: ${msg}`);
147
+ console.error("Try manually: npm install -g @mkterswingman/5mghost-yonder@latest");
148
+ process.exit(1);
149
+ }
150
+ break;
151
+ }
152
+ case "version":
153
+ case "--version":
154
+ case "-v": {
155
+ console.log(getVersion());
156
+ break;
157
+ }
158
+ default:
159
+ console.log(buildHelpText(getVersion()));
160
+ process.exit(command ? 1 : 0);
161
+ }
162
+ }
163
+ main().catch((err) => {
164
+ console.error("Fatal error:", err);
165
+ process.exit(1);
166
+ });
@@ -0,0 +1 @@
1
+ export declare function runInstallSkills(): Promise<void>;
@@ -0,0 +1,39 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { dirname, join } from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { buildSkillInstallPlan, installSkillTarget, } from "../utils/skills.js";
5
+ function detectCli(name) {
6
+ try {
7
+ execFileSync(name, ["--version"], { stdio: "pipe" });
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ function resolvePackageRoot() {
15
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
16
+ }
17
+ export async function runInstallSkills() {
18
+ const packageRoot = resolvePackageRoot();
19
+ const availableCliNames = [
20
+ "claude-internal",
21
+ "claude",
22
+ "codex-internal",
23
+ "codex",
24
+ "gemini-internal",
25
+ "gemini",
26
+ ].filter(detectCli);
27
+ const plan = buildSkillInstallPlan(packageRoot, {
28
+ availableCliNames,
29
+ includeOpenClaw: process.env.YT_MCP_INSTALL_OPENCLAW_SKILL === "1",
30
+ });
31
+ if (plan.length === 0) {
32
+ console.log("[yt-mcp] No supported AI client detected for bundled skill install.");
33
+ return;
34
+ }
35
+ for (const target of plan) {
36
+ installSkillTarget(target);
37
+ console.log(`[yt-mcp] Installed bundled skill for ${target.label}: ${target.targetDir}`);
38
+ }
39
+ }
@@ -0,0 +1,9 @@
1
+ import { type RuntimeSummary } from "../runtime/installers.js";
2
+ export interface RuntimeCommandDeps {
3
+ installAll(): Promise<RuntimeSummary>;
4
+ updateAll(): Promise<RuntimeSummary>;
5
+ checkAll(): Promise<RuntimeSummary>;
6
+ }
7
+ export declare function formatRuntimeSummary(summary: RuntimeSummary): string;
8
+ export declare function runRuntimeCommand(action: "install" | "update" | "check", deps?: RuntimeCommandDeps): Promise<string>;
9
+ export declare function runRuntimeCli(action?: string): Promise<void>;