@mkterswingman/5mghost-yonder 0.0.6 → 0.0.7

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 CHANGED
@@ -56,3 +56,8 @@ Media download runtime expectations:
56
56
  - `uninstall` — remove MCP registrations and local `~/.yt-mcp` config
57
57
  - `update` — update the main package and required runtimes
58
58
  - `version` — print the installed version
59
+
60
+ Runtime notes:
61
+
62
+ - Playwright browser installs are managed through `yt-mcp runtime install`, not a raw `npx playwright install ...` call
63
+ - `yt-dlp` is invoked with `--js-runtimes node`, so no extra Deno install is required
@@ -52,6 +52,24 @@ export interface SetupCookiesDeps {
52
52
  log: (message: string) => void;
53
53
  }
54
54
  type SetupCookiesChromium = Awaited<ReturnType<SetupCookiesDeps["loadChromium"]>>;
55
+ interface CdpCookie {
56
+ name: string;
57
+ value: string;
58
+ domain: string;
59
+ path: string;
60
+ secure?: boolean;
61
+ httpOnly?: boolean;
62
+ expires?: number;
63
+ }
64
+ interface CdpCookieClient {
65
+ send(method: "Network.getAllCookies"): Promise<{
66
+ cookies?: CdpCookie[];
67
+ }>;
68
+ }
69
+ export declare function readCdpCookiesUntilSession(client: CdpCookieClient, deps: Pick<SetupCookiesDeps, "hasYouTubeSession">, options?: {
70
+ attempts?: number;
71
+ delayMs?: number;
72
+ }): Promise<PlaywrightCookie[] | null>;
55
73
  export declare function tryImportBrowserCookies(chromium: SetupCookiesChromium, deps: SetupCookiesDeps): Promise<boolean>;
56
74
  export declare function readImportedBrowserCookies(candidate: BrowserProfileCandidate, chromium: SetupCookiesChromium, deps: SetupCookiesDeps): Promise<PlaywrightCookie[] | null>;
57
75
  export declare function runManualCookieSetup(chromium: SetupCookiesChromium, deps: SetupCookiesDeps): Promise<void>;
@@ -28,8 +28,17 @@ export const CHANNEL_LABELS = {
28
28
  };
29
29
  /** Check if YouTube SID cookies are present — the real signal of a logged-in session. */
30
30
  export function hasYouTubeSession(cookies) {
31
- return cookies.some((c) => (c.name === "SID" || c.name === "HSID" || c.name === "SSID") &&
32
- c.domain.includes("youtube.com"));
31
+ const hasCookieTriplet = (domainFragment) => {
32
+ const names = new Set(cookies
33
+ .filter((cookie) => cookie.domain.includes(domainFragment))
34
+ .map((cookie) => cookie.name));
35
+ return names.has("SID") && names.has("HSID") && names.has("SSID");
36
+ };
37
+ if (hasCookieTriplet("youtube.com")) {
38
+ return true;
39
+ }
40
+ const hasYouTubeLoginInfo = cookies.some((cookie) => cookie.name === "LOGIN_INFO" && cookie.domain.includes("youtube.com"));
41
+ return hasYouTubeLoginInfo && hasCookieTriplet("google.");
33
42
  }
34
43
  /**
35
44
  * Save cookies to Netscape format file and close the browser context.
@@ -146,6 +155,29 @@ async function waitForCdpVersion(port, timeoutMs = 15_000) {
146
155
  }
147
156
  return null;
148
157
  }
158
+ export async function readCdpCookiesUntilSession(client, deps, options = {}) {
159
+ const attempts = options.attempts ?? 5;
160
+ const delayMs = options.delayMs ?? 1_000;
161
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
162
+ const result = await client.send("Network.getAllCookies");
163
+ const cookies = (result.cookies ?? []).map((cookie) => ({
164
+ name: cookie.name,
165
+ value: cookie.value,
166
+ domain: cookie.domain,
167
+ path: cookie.path,
168
+ secure: cookie.secure ?? false,
169
+ httpOnly: cookie.httpOnly ?? false,
170
+ expires: cookie.expires ?? -1,
171
+ }));
172
+ if (deps.hasYouTubeSession(cookies)) {
173
+ return cookies;
174
+ }
175
+ if (attempt < attempts - 1) {
176
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
177
+ }
178
+ }
179
+ return null;
180
+ }
149
181
  async function terminateImportedBrowser(processHandle) {
150
182
  if (processHandle.exitCode !== null || processHandle.signalCode !== null) {
151
183
  return;
@@ -213,17 +245,11 @@ export async function readImportedBrowserCookies(candidate, chromium, deps) {
213
245
  }
214
246
  const page = context.pages()[0] ?? await context.newPage();
215
247
  const client = await context.newCDPSession(page);
216
- const result = await client.send("Network.getAllCookies");
217
- const cookies = (result.cookies ?? []).map((cookie) => ({
218
- name: cookie.name,
219
- value: cookie.value,
220
- domain: cookie.domain,
221
- path: cookie.path,
222
- secure: cookie.secure ?? false,
223
- httpOnly: cookie.httpOnly ?? false,
224
- expires: cookie.expires ?? -1,
225
- }));
226
- if (!deps.hasYouTubeSession(cookies)) {
248
+ const cookies = await readCdpCookiesUntilSession(client, deps, {
249
+ attempts: 6,
250
+ delayMs: 1_000,
251
+ });
252
+ if (!cookies) {
227
253
  return null;
228
254
  }
229
255
  return cookies;
@@ -248,7 +274,7 @@ export async function runManualCookieSetup(chromium, deps) {
248
274
  deps.log(`Using browser: ${CHANNEL_LABELS[channel] ?? channel}`);
249
275
  if (channel === "chromium") {
250
276
  deps.log("⚠️ No system Chrome or Edge found. Using bundled Chromium.\n" +
251
- " If it fails, run: npx playwright install chromium\n");
277
+ " If it fails, run: yt-mcp runtime install\n");
252
278
  }
253
279
  let context;
254
280
  try {
@@ -1,4 +1,8 @@
1
1
  import type { RuntimeComponentState } from "./manifest.js";
2
+ export declare function getPlaywrightInstallInvocation(): {
3
+ command: string;
4
+ args: string[];
5
+ };
2
6
  export declare function checkPlaywrightRuntime(): Promise<RuntimeComponentState & {
3
7
  name: "playwright";
4
8
  message?: string;
@@ -1,5 +1,16 @@
1
- import { execSync } from "node:child_process";
1
+ import { execFileSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { createRequire } from "node:module";
5
+ const require = createRequire(import.meta.url);
6
+ export function getPlaywrightInstallInvocation() {
7
+ const packageJsonPath = require.resolve("playwright/package.json");
8
+ const cliPath = join(dirname(packageJsonPath), "cli.js");
9
+ return {
10
+ command: process.execPath,
11
+ args: [cliPath, "install", "chromium"],
12
+ };
13
+ }
3
14
  export async function checkPlaywrightRuntime() {
4
15
  try {
5
16
  const { chromium } = await import("playwright");
@@ -25,11 +36,12 @@ export async function checkPlaywrightRuntime() {
25
36
  source: "runtime",
26
37
  installed_at: null,
27
38
  binary_path: null,
28
- message: "npx playwright install chromium",
39
+ message: "yt-mcp runtime install",
29
40
  };
30
41
  }
31
42
  export async function installPlaywrightRuntime() {
32
- execSync("npx playwright install chromium", { stdio: "inherit" });
43
+ const invocation = getPlaywrightInstallInvocation();
44
+ execFileSync(invocation.command, invocation.args, { stdio: "inherit" });
33
45
  return checkPlaywrightRuntime();
34
46
  }
35
47
  export async function updatePlaywrightRuntime() {
@@ -12,6 +12,10 @@ export interface YtDlpStderrLineSplitter {
12
12
  type SpawnSyncFn = typeof spawnSync;
13
13
  export declare function createYtDlpStderrLineSplitter(onLine: (line: string) => void): YtDlpStderrLineSplitter;
14
14
  export declare function hasYtDlp(runSpawnSync?: SpawnSyncFn): boolean;
15
+ export declare function buildYtDlpArgs(args: string[], options?: {
16
+ cookiesPath?: string;
17
+ cookiesExist?: boolean;
18
+ }): string[];
15
19
  export declare function runYtDlp(args: string[], timeoutMs?: number, onStderrLine?: (line: string) => void): Promise<YtDlpResult>;
16
20
  export declare function runYtDlpJson<T>(args: string[], timeoutMs?: number): Promise<{
17
21
  ok: true;
@@ -57,13 +57,19 @@ export function hasYtDlp(runSpawnSync = spawnSync) {
57
57
  });
58
58
  return result.status === 0 && result.error == null;
59
59
  }
60
+ export function buildYtDlpArgs(args, options = {}) {
61
+ const cookiesPath = options.cookiesPath ?? PATHS.cookiesTxt;
62
+ const cookiesExist = options.cookiesExist ?? existsSync(cookiesPath);
63
+ const finalArgs = ["--force-ipv4", "--no-warnings", "--js-runtimes", "node", ...args];
64
+ if (cookiesExist) {
65
+ finalArgs.push("--cookies", cookiesPath);
66
+ }
67
+ return finalArgs;
68
+ }
60
69
  export function runYtDlp(args, timeoutMs = 45_000, onStderrLine) {
61
70
  return new Promise((resolve, reject) => {
62
71
  const start = Date.now();
63
- const finalArgs = ["--force-ipv4", "--no-warnings", ...args];
64
- if (existsSync(PATHS.cookiesTxt)) {
65
- finalArgs.push("--cookies", PATHS.cookiesTxt);
66
- }
72
+ const finalArgs = buildYtDlpArgs(args);
67
73
  const proc = spawn(getYtDlpPath(), finalArgs, {
68
74
  stdio: ["ignore", "pipe", "pipe"],
69
75
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mkterswingman/5mghost-yonder",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Internal MCP client with local data tools and remote API proxy",
5
5
  "type": "module",
6
6
  "bin": {