@mkterswingman/5mghost-yonder 0.0.26 → 0.0.27

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/package.json +1 -1
  2. package/dist/auth/oauthFlow.d.ts +0 -9
  3. package/dist/auth/oauthFlow.js +0 -151
  4. package/dist/auth/sharedAuth.d.ts +0 -10
  5. package/dist/auth/sharedAuth.js +0 -31
  6. package/dist/auth/tokenManager.d.ts +0 -18
  7. package/dist/auth/tokenManager.js +0 -92
  8. package/dist/cli/check.d.ts +0 -4
  9. package/dist/cli/check.js +0 -90
  10. package/dist/cli/index.d.ts +0 -18
  11. package/dist/cli/index.js +0 -166
  12. package/dist/cli/installSkills.d.ts +0 -1
  13. package/dist/cli/installSkills.js +0 -39
  14. package/dist/cli/runtime.d.ts +0 -9
  15. package/dist/cli/runtime.js +0 -35
  16. package/dist/cli/serve.d.ts +0 -1
  17. package/dist/cli/serve.js +0 -26
  18. package/dist/cli/setup.d.ts +0 -33
  19. package/dist/cli/setup.js +0 -450
  20. package/dist/cli/setupCookies.d.ts +0 -88
  21. package/dist/cli/setupCookies.js +0 -431
  22. package/dist/cli/smoke.d.ts +0 -27
  23. package/dist/cli/smoke.js +0 -108
  24. package/dist/cli/uninstall.d.ts +0 -16
  25. package/dist/cli/uninstall.js +0 -99
  26. package/dist/download/downloader.d.ts +0 -67
  27. package/dist/download/downloader.js +0 -309
  28. package/dist/download/jobManager.d.ts +0 -22
  29. package/dist/download/jobManager.js +0 -211
  30. package/dist/download/types.d.ts +0 -44
  31. package/dist/download/types.js +0 -1
  32. package/dist/runtime/ffmpegRuntime.d.ts +0 -13
  33. package/dist/runtime/ffmpegRuntime.js +0 -51
  34. package/dist/runtime/installers.d.ts +0 -12
  35. package/dist/runtime/installers.js +0 -45
  36. package/dist/runtime/manifest.d.ts +0 -18
  37. package/dist/runtime/manifest.js +0 -43
  38. package/dist/runtime/playwrightRuntime.d.ts +0 -17
  39. package/dist/runtime/playwrightRuntime.js +0 -49
  40. package/dist/runtime/systemDeps.d.ts +0 -3
  41. package/dist/runtime/systemDeps.js +0 -30
  42. package/dist/runtime/ytdlpRuntime.d.ts +0 -14
  43. package/dist/runtime/ytdlpRuntime.js +0 -58
  44. package/dist/server.d.ts +0 -23
  45. package/dist/server.js +0 -81
  46. package/dist/tools/downloads.d.ts +0 -11
  47. package/dist/tools/downloads.js +0 -220
  48. package/dist/tools/remote.d.ts +0 -4
  49. package/dist/tools/remote.js +0 -239
  50. package/dist/tools/subtitles.d.ts +0 -29
  51. package/dist/tools/subtitles.js +0 -713
  52. package/dist/utils/browserLaunch.d.ts +0 -5
  53. package/dist/utils/browserLaunch.js +0 -22
  54. package/dist/utils/browserProfileImport.d.ts +0 -49
  55. package/dist/utils/browserProfileImport.js +0 -163
  56. package/dist/utils/codexInternal.d.ts +0 -9
  57. package/dist/utils/codexInternal.js +0 -60
  58. package/dist/utils/config.d.ts +0 -53
  59. package/dist/utils/config.js +0 -77
  60. package/dist/utils/cookieRefresh.d.ts +0 -18
  61. package/dist/utils/cookieRefresh.js +0 -70
  62. package/dist/utils/cookies.d.ts +0 -18
  63. package/dist/utils/cookies.js +0 -100
  64. package/dist/utils/ffmpeg.d.ts +0 -5
  65. package/dist/utils/ffmpeg.js +0 -16
  66. package/dist/utils/ffmpegPath.d.ts +0 -8
  67. package/dist/utils/ffmpegPath.js +0 -21
  68. package/dist/utils/formatters.d.ts +0 -4
  69. package/dist/utils/formatters.js +0 -42
  70. package/dist/utils/launcher.d.ts +0 -12
  71. package/dist/utils/launcher.js +0 -90
  72. package/dist/utils/mcpRegistration.d.ts +0 -7
  73. package/dist/utils/mcpRegistration.js +0 -23
  74. package/dist/utils/mediaPaths.d.ts +0 -7
  75. package/dist/utils/mediaPaths.js +0 -10
  76. package/dist/utils/openClaw.d.ts +0 -18
  77. package/dist/utils/openClaw.js +0 -82
  78. package/dist/utils/skills.d.ts +0 -16
  79. package/dist/utils/skills.js +0 -56
  80. package/dist/utils/videoInput.d.ts +0 -5
  81. package/dist/utils/videoInput.js +0 -58
  82. package/dist/utils/videoMetadata.d.ts +0 -11
  83. package/dist/utils/videoMetadata.js +0 -1
  84. package/dist/utils/ytdlp.d.ts +0 -28
  85. package/dist/utils/ytdlp.js +0 -205
  86. package/dist/utils/ytdlpFailures.d.ts +0 -3
  87. package/dist/utils/ytdlpFailures.js +0 -27
  88. package/dist/utils/ytdlpPath.d.ts +0 -33
  89. package/dist/utils/ytdlpPath.js +0 -77
  90. package/dist/utils/ytdlpProgress.d.ts +0 -13
  91. package/dist/utils/ytdlpProgress.js +0 -77
  92. package/dist/utils/ytdlpScheduler.d.ts +0 -25
  93. package/dist/utils/ytdlpScheduler.js +0 -78
@@ -1,431 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { writeFileSync } from "node:fs";
3
- import { createServer } from "node:net";
4
- import { PATHS, ensureConfigDir } from "../utils/config.js";
5
- import { cookiesToNetscape } from "../utils/cookies.js";
6
- import { getCookieSetupRecoveryHint } from "./setup.js";
7
- import { cleanupImportedBrowserWorkspace, findImportableBrowserProfileCandidates, prepareImportedBrowserWorkspace, } from "../utils/browserProfileImport.js";
8
- /**
9
- * Detect which browser channel is available on the system.
10
- * Prefers Chrome → Edge → falls back to bundled Chromium.
11
- */
12
- export async function detectBrowserChannel(chromium) {
13
- for (const channel of ["chrome", "msedge"]) {
14
- try {
15
- const browser = await chromium.launch({ channel, headless: true });
16
- await browser.close();
17
- return channel;
18
- }
19
- catch {
20
- // channel not available, try next
21
- }
22
- }
23
- return "chromium";
24
- }
25
- export const CHANNEL_LABELS = {
26
- chrome: "Google Chrome",
27
- msedge: "Microsoft Edge",
28
- chromium: "Playwright Chromium",
29
- };
30
- /** Check if YouTube SID cookies are present — the real signal of a logged-in session. */
31
- export function hasYouTubeSession(cookies) {
32
- const hasCookieTriplet = (domainFragment) => {
33
- const names = new Set(cookies
34
- .filter((cookie) => cookie.domain.includes(domainFragment))
35
- .map((cookie) => cookie.name));
36
- return names.has("SID") && names.has("HSID") && names.has("SSID");
37
- };
38
- if (hasCookieTriplet("youtube.com")) {
39
- return true;
40
- }
41
- const hasYouTubeLoginInfo = cookies.some((cookie) => cookie.name === "LOGIN_INFO" && cookie.domain.includes("youtube.com"));
42
- return hasYouTubeLoginInfo && hasCookieTriplet("google.");
43
- }
44
- /**
45
- * Save cookies to Netscape format file and close the browser context.
46
- */
47
- export async function saveCookiesAndClose(context, rawCookies, silent = false) {
48
- try {
49
- await context.close();
50
- }
51
- catch { /* already closed */ }
52
- const entries = rawCookies.map((c) => ({
53
- name: c.name,
54
- value: c.value,
55
- domain: c.domain,
56
- path: c.path,
57
- secure: c.secure,
58
- httpOnly: c.httpOnly,
59
- expires: c.expires,
60
- }));
61
- const netscape = cookiesToNetscape(entries);
62
- writeFileSync(PATHS.cookiesTxt, netscape, { mode: 0o600 });
63
- if (!silent) {
64
- const httpOnlyCount = entries.filter((c) => c.httpOnly).length;
65
- console.log(`✅ Cookies saved to ${PATHS.cookiesTxt}`);
66
- console.log(` ${entries.length} cookies extracted (${httpOnlyCount} httpOnly)\n`);
67
- }
68
- }
69
- /**
70
- * Save cookies to disk WITHOUT closing the context (caller manages lifecycle).
71
- */
72
- export function saveCookiesToDisk(rawCookies) {
73
- const entries = rawCookies.map((c) => ({
74
- name: c.name,
75
- value: c.value,
76
- domain: c.domain,
77
- path: c.path,
78
- secure: c.secure,
79
- httpOnly: c.httpOnly,
80
- expires: c.expires,
81
- }));
82
- const netscape = cookiesToNetscape(entries);
83
- writeFileSync(PATHS.cookiesTxt, netscape, { mode: 0o600 });
84
- }
85
- /**
86
- * Poll cookies at intervals until YouTube session cookies appear or timeout.
87
- * Returns the full cookie list on success, or null on timeout / browser closed.
88
- */
89
- async function waitForLogin(context, isClosed, timeoutMs, pollIntervalMs = 2000) {
90
- const deadline = Date.now() + timeoutMs;
91
- while (Date.now() < deadline) {
92
- if (isClosed())
93
- return null;
94
- try {
95
- const cookies = await context.cookies("https://www.youtube.com");
96
- if (hasYouTubeSession(cookies)) {
97
- await new Promise((r) => setTimeout(r, 2000));
98
- return await context.cookies("https://www.youtube.com");
99
- }
100
- }
101
- catch {
102
- return null;
103
- }
104
- await new Promise((r) => setTimeout(r, pollIntervalMs));
105
- }
106
- return null;
107
- }
108
- async function loadPlaywrightChromium() {
109
- const pw = await import("playwright");
110
- return pw.chromium;
111
- }
112
- export class BrowserProfileLockedError extends Error {
113
- constructor(message = "Chrome/Edge profile is locked by a running browser") {
114
- super(message);
115
- this.name = "BrowserProfileLockedError";
116
- }
117
- }
118
- function buildSetupCookiesDeps(overrides = {}) {
119
- return {
120
- ensureConfigDir,
121
- loadChromium: loadPlaywrightChromium,
122
- detectBrowserChannel,
123
- hasYouTubeSession,
124
- saveCookiesAndClose,
125
- waitForLogin,
126
- findImportableBrowserProfileCandidates,
127
- prepareImportedBrowserWorkspace,
128
- cleanupImportedBrowserWorkspace,
129
- readImportedBrowserCookies,
130
- tryImportBrowserCookies,
131
- runManualCookieSetup,
132
- log: console.log,
133
- ...overrides,
134
- };
135
- }
136
- async function allocateDebugPort() {
137
- const server = createServer();
138
- await new Promise((resolve, reject) => {
139
- server.once("error", reject);
140
- server.listen(0, "127.0.0.1", () => resolve());
141
- });
142
- const address = server.address();
143
- const port = typeof address === "object" && address ? address.port : 0;
144
- await new Promise((resolve, reject) => {
145
- server.close((err) => err ? reject(err) : resolve());
146
- });
147
- return port;
148
- }
149
- async function waitForCdpVersion(port, timeoutMs = 15_000) {
150
- const startedAt = Date.now();
151
- while (Date.now() - startedAt < timeoutMs) {
152
- try {
153
- const response = await fetch(`http://127.0.0.1:${port}/json/version`);
154
- if (response.ok) {
155
- return await response.json();
156
- }
157
- }
158
- catch {
159
- // Browser not ready yet.
160
- }
161
- await new Promise((resolve) => setTimeout(resolve, 250));
162
- }
163
- return null;
164
- }
165
- export async function readCdpCookiesUntilSession(client, deps, options = {}) {
166
- const attempts = options.attempts ?? 5;
167
- const delayMs = options.delayMs ?? 1_000;
168
- for (let attempt = 0; attempt < attempts; attempt += 1) {
169
- const result = await client.send("Network.getAllCookies");
170
- const cookies = (result.cookies ?? []).map((cookie) => ({
171
- name: cookie.name,
172
- value: cookie.value,
173
- domain: cookie.domain,
174
- path: cookie.path,
175
- secure: cookie.secure ?? false,
176
- httpOnly: cookie.httpOnly ?? false,
177
- expires: cookie.expires ?? -1,
178
- }));
179
- if (deps.hasYouTubeSession(cookies)) {
180
- return cookies;
181
- }
182
- if (attempt < attempts - 1) {
183
- await new Promise((resolve) => setTimeout(resolve, delayMs));
184
- }
185
- }
186
- return null;
187
- }
188
- async function terminateImportedBrowser(processHandle) {
189
- if (processHandle.exitCode !== null || processHandle.signalCode !== null) {
190
- return;
191
- }
192
- processHandle.kill("SIGTERM");
193
- await Promise.race([
194
- new Promise((resolve) => processHandle.once("exit", () => resolve())),
195
- new Promise((resolve) => setTimeout(resolve, 2_000)),
196
- ]);
197
- if (processHandle.exitCode === null && processHandle.signalCode === null) {
198
- processHandle.kill("SIGKILL");
199
- }
200
- }
201
- export async function tryImportBrowserCookies(chromium, deps) {
202
- const candidates = deps.findImportableBrowserProfileCandidates();
203
- let sawLockedProfile = false;
204
- if (candidates.length === 0) {
205
- return false;
206
- }
207
- for (let index = 0; index < candidates.length; index += 1) {
208
- const candidate = candidates[index];
209
- const isLastCandidate = index === candidates.length - 1;
210
- deps.log(`Trying to import existing YouTube login from ${candidate.label} (${candidate.profileLabel})...`);
211
- let importedCookies = null;
212
- try {
213
- importedCookies = await deps.readImportedBrowserCookies(candidate, chromium, deps);
214
- }
215
- catch (err) {
216
- if (err instanceof BrowserProfileLockedError) {
217
- sawLockedProfile = true;
218
- deps.log(`${candidate.label} (${candidate.profileLabel}) profile is locked by a running browser.`);
219
- }
220
- else {
221
- throw err;
222
- }
223
- }
224
- if (importedCookies) {
225
- await deps.saveCookiesAndClose({ close: async () => { } }, importedCookies, true);
226
- deps.log(`✅ Imported YouTube session from ${candidate.label} (${candidate.profileLabel})\n`);
227
- return true;
228
- }
229
- if (!isLastCandidate) {
230
- const nextCandidate = candidates[index + 1];
231
- deps.log(`${candidate.label} (${candidate.profileLabel}) import unavailable, trying ${nextCandidate.label} (${nextCandidate.profileLabel})...`);
232
- }
233
- }
234
- if (sawLockedProfile) {
235
- throw new BrowserProfileLockedError("Chrome/Edge profile is locked by a running browser. Close Chrome/Edge and rerun `yt-mcp setup-cookies`, or continue with headed browser login.");
236
- }
237
- deps.log("Browser profile import failed, falling back to manual login...\n");
238
- return false;
239
- }
240
- export async function readImportedBrowserCookies(candidate, chromium, deps) {
241
- let workspace = null;
242
- let browser = null;
243
- let browserProcess = null;
244
- try {
245
- workspace = deps.prepareImportedBrowserWorkspace(candidate);
246
- const port = await allocateDebugPort();
247
- browserProcess = spawn(workspace.executablePath, [
248
- `--user-data-dir=${workspace.workspaceDir}`,
249
- `--profile-directory=${workspace.profileName}`,
250
- `--remote-debugging-port=${port}`,
251
- "--headless=new",
252
- "--no-first-run",
253
- "--no-default-browser-check",
254
- "https://www.youtube.com",
255
- ], {
256
- detached: true,
257
- stdio: "ignore",
258
- });
259
- browserProcess.unref();
260
- const version = await waitForCdpVersion(port);
261
- if (!version?.webSocketDebuggerUrl) {
262
- return null;
263
- }
264
- browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
265
- const context = browser.contexts()[0];
266
- if (!context) {
267
- return null;
268
- }
269
- const page = context.pages()[0] ?? await context.newPage();
270
- const client = await context.newCDPSession(page);
271
- const cookies = await readCdpCookiesUntilSession(client, deps, {
272
- attempts: 6,
273
- delayMs: 1_000,
274
- });
275
- if (!cookies) {
276
- return null;
277
- }
278
- return cookies;
279
- }
280
- catch (err) {
281
- const message = err instanceof Error ? err.message : String(err);
282
- const code = typeof err === "object" && err && "code" in err ? String(err.code ?? "") : "";
283
- if (code === "EBUSY" ||
284
- code === "EPERM" ||
285
- /resource busy|being used by another process|used by another process|device or resource busy|operation not permitted/i.test(message)) {
286
- throw new BrowserProfileLockedError();
287
- }
288
- return null;
289
- }
290
- finally {
291
- if (browser) {
292
- await browser.close().catch(() => { });
293
- }
294
- if (browserProcess) {
295
- await terminateImportedBrowser(browserProcess);
296
- }
297
- if (workspace) {
298
- deps.cleanupImportedBrowserWorkspace(workspace);
299
- }
300
- }
301
- }
302
- export async function runManualCookieSetup(chromium, deps) {
303
- const channel = await deps.detectBrowserChannel(chromium);
304
- deps.log(`Using browser: ${CHANNEL_LABELS[channel] ?? channel}`);
305
- if (channel === "chromium") {
306
- deps.log("⚠️ No system Chrome or Edge found. Using bundled Chromium.\n" +
307
- " If it fails, run: yt-mcp runtime install\n");
308
- }
309
- let context;
310
- try {
311
- context = await chromium.launchPersistentContext(PATHS.browserProfile, {
312
- headless: false,
313
- channel,
314
- args: ["--disable-blink-features=AutomationControlled"],
315
- });
316
- }
317
- catch (err) {
318
- const msg = err instanceof Error ? err.message : String(err);
319
- if (channel === "chromium" && msg.includes("Executable doesn't exist")) {
320
- throw new Error("Chromium browser runtime not found.\nRun: yt-mcp runtime install");
321
- }
322
- throw err;
323
- }
324
- let browserClosed = false;
325
- context.on("close", () => { browserClosed = true; });
326
- const page = context.pages()[0] ?? (await context.newPage());
327
- page.on("close", () => {
328
- if (context.pages().length === 0) {
329
- browserClosed = true;
330
- }
331
- });
332
- try {
333
- await page.goto("https://www.youtube.com");
334
- await page.waitForLoadState("domcontentloaded");
335
- }
336
- catch {
337
- if (browserClosed) {
338
- deps.log("\n⚠️ Browser was closed. Setup cancelled.\n");
339
- return;
340
- }
341
- throw new Error("Failed to navigate to YouTube");
342
- }
343
- const existingCookies = await context.cookies("https://www.youtube.com");
344
- if (deps.hasYouTubeSession(existingCookies)) {
345
- deps.log("✅ Already logged in to YouTube!\n");
346
- await deps.saveCookiesAndClose(context, existingCookies);
347
- return;
348
- }
349
- try {
350
- const signInBtn = page.locator('a[href*="accounts.google.com/ServiceLogin"], ' +
351
- 'tp-yt-paper-button#button:has-text("Sign in"), ' +
352
- 'a:has-text("Sign in"), ' +
353
- 'ytd-button-renderer a:has-text("Sign in")').first();
354
- await signInBtn.waitFor({ state: "visible", timeout: 5000 });
355
- await signInBtn.click();
356
- deps.log("🔑 Opened sign-in page. Please log in to your Google account.\n");
357
- }
358
- catch {
359
- try {
360
- await page.goto("https://accounts.google.com/ServiceLogin?continue=https://www.youtube.com");
361
- deps.log("🔑 Please log in to your Google account in the browser window.\n");
362
- }
363
- catch {
364
- if (browserClosed) {
365
- deps.log("\n⚠️ Browser was closed. Setup cancelled.\n");
366
- return;
367
- }
368
- throw new Error("Failed to navigate to Google login");
369
- }
370
- }
371
- const LOGIN_TIMEOUT_MS = 2 * 60 * 1000;
372
- deps.log("⏳ Waiting for login (up to 2 minutes)...");
373
- deps.log(" Login will be detected automatically once you sign in.\n");
374
- for (const line of getCookieSetupRecoveryHint()) {
375
- deps.log(line);
376
- }
377
- deps.log("");
378
- const finalCookies = await deps.waitForLogin(context, () => browserClosed, LOGIN_TIMEOUT_MS);
379
- if (browserClosed) {
380
- deps.log("\n⚠️ Browser was closed before login completed.");
381
- for (const line of getCookieSetupRecoveryHint()) {
382
- deps.log(line);
383
- }
384
- deps.log("");
385
- return;
386
- }
387
- if (!finalCookies) {
388
- try {
389
- await context.close();
390
- }
391
- catch { /* already closed */ }
392
- deps.log("\n⏰ Login timed out (2 minutes).");
393
- for (const line of getCookieSetupRecoveryHint()) {
394
- deps.log(line);
395
- }
396
- deps.log("");
397
- return;
398
- }
399
- await deps.saveCookiesAndClose(context, finalCookies);
400
- }
401
- /**
402
- * Interactive cookie setup — opens a visible browser for user to log in.
403
- */
404
- export function parseSetupCookiesArgs(argv) {
405
- return {
406
- importOnly: argv.includes("--import-only"),
407
- headed: argv.includes("--headed"),
408
- };
409
- }
410
- export async function runSetupCookies(overrides = {}, options = {}) {
411
- const deps = buildSetupCookiesDeps(overrides);
412
- deps.log("\n🍪 YouTube Cookie Setup\n");
413
- deps.ensureConfigDir();
414
- let chromium;
415
- try {
416
- chromium = await deps.loadChromium();
417
- }
418
- catch {
419
- throw new Error("Playwright runtime is not installed.\nRun: yt-mcp runtime install");
420
- }
421
- if (!options.headed) {
422
- const imported = await deps.tryImportBrowserCookies(chromium, deps);
423
- if (imported) {
424
- return;
425
- }
426
- }
427
- if (options.importOnly && !options.headed) {
428
- throw new Error("No reusable YouTube session found in local Chrome/Edge profiles");
429
- }
430
- await deps.runManualCookieSetup(chromium, deps);
431
- }
@@ -1,27 +0,0 @@
1
- export interface SmokeOptions {
2
- subtitles: boolean;
3
- }
4
- export interface SmokeSummary {
5
- remote: {
6
- video_id: string;
7
- title: string;
8
- };
9
- subtitles?: {
10
- valid: true;
11
- };
12
- }
13
- interface SmokeClient {
14
- listTools(): Promise<{
15
- tools: Array<{
16
- name: string;
17
- }>;
18
- }>;
19
- callTool(params: {
20
- name: string;
21
- arguments: Record<string, unknown>;
22
- }): Promise<Record<string, unknown>>;
23
- }
24
- export declare function parseSmokeArgs(argv: string[]): SmokeOptions;
25
- export declare function runSmokeChecks(client: SmokeClient, options: SmokeOptions): Promise<SmokeSummary>;
26
- export declare function runSmoke(argv: string[]): Promise<void>;
27
- export {};
package/dist/cli/smoke.js DELETED
@@ -1,108 +0,0 @@
1
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
- function toSmokeToolResult(value) {
4
- return value;
5
- }
6
- export function parseSmokeArgs(argv) {
7
- return {
8
- subtitles: argv.includes("--subtitles"),
9
- };
10
- }
11
- function extractPayload(result) {
12
- if (result.structuredContent && typeof result.structuredContent === "object") {
13
- return result.structuredContent;
14
- }
15
- const text = result.content?.find((item) => item.type === "text" && typeof item.text === "string")?.text;
16
- if (!text) {
17
- throw new Error("Tool returned no readable payload");
18
- }
19
- try {
20
- return JSON.parse(text);
21
- }
22
- catch {
23
- throw new Error(`Tool returned non-JSON text: ${text.slice(0, 200)}`);
24
- }
25
- }
26
- function requireTool(tools, name) {
27
- if (!tools.some((tool) => tool.name === name)) {
28
- throw new Error(`Required tool not registered: ${name}`);
29
- }
30
- }
31
- export async function runSmokeChecks(client, options) {
32
- const listed = await client.listTools();
33
- requireTool(listed.tools, "search_videos");
34
- if (options.subtitles) {
35
- requireTool(listed.tools, "validate_cookies");
36
- }
37
- const remoteResult = await client.callTool({
38
- name: "search_videos",
39
- arguments: { query: "OpenAI", max_results: 1 },
40
- });
41
- const remotePayload = extractPayload(remoteResult);
42
- const remoteItems = Array.isArray(remotePayload.items) ? remotePayload.items : [];
43
- const firstItem = remoteItems[0];
44
- if (!firstItem || typeof firstItem !== "object") {
45
- throw new Error("Remote smoke returned no videos");
46
- }
47
- const videoId = typeof firstItem.video_id === "string" ? firstItem.video_id : null;
48
- const title = typeof firstItem.title === "string" ? firstItem.title : null;
49
- if (!videoId || !title) {
50
- throw new Error("Remote smoke returned malformed video data");
51
- }
52
- const summary = {
53
- remote: {
54
- video_id: videoId,
55
- title,
56
- },
57
- };
58
- if (options.subtitles) {
59
- const subtitleResult = await client.callTool({
60
- name: "validate_cookies",
61
- arguments: {},
62
- });
63
- const subtitlePayload = extractPayload(subtitleResult);
64
- if (subtitlePayload.valid !== true) {
65
- const error = typeof subtitlePayload.error === "string"
66
- ? subtitlePayload.error
67
- : "Subtitle validation failed";
68
- throw new Error(error);
69
- }
70
- summary.subtitles = { valid: true };
71
- }
72
- return summary;
73
- }
74
- export async function runSmoke(argv) {
75
- const options = parseSmokeArgs(argv);
76
- const stderrChunks = [];
77
- const transport = new StdioClientTransport({
78
- command: process.execPath,
79
- args: [process.argv[1], "serve"],
80
- stderr: "pipe",
81
- env: Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] === "string")),
82
- });
83
- if (transport.stderr) {
84
- transport.stderr.on("data", (chunk) => {
85
- stderrChunks.push(String(chunk));
86
- });
87
- }
88
- const client = new Client({ name: "yt-mcp-smoke", version: "1.0.0" }, { capabilities: {} });
89
- try {
90
- await client.connect(transport);
91
- const summary = await runSmokeChecks({
92
- listTools: () => client.listTools(),
93
- callTool: async (params) => toSmokeToolResult(await client.callTool(params)),
94
- }, options);
95
- console.log(`✅ MCP smoke passed: ${summary.remote.video_id} — ${summary.remote.title}`);
96
- if (summary.subtitles?.valid) {
97
- console.log("✅ Subtitle smoke passed via validate_cookies");
98
- }
99
- }
100
- catch (error) {
101
- const message = error instanceof Error ? error.message : String(error);
102
- const stderr = stderrChunks.join("").trim();
103
- throw new Error(stderr ? `${message}\n${stderr}` : message);
104
- }
105
- finally {
106
- await transport.close().catch(() => undefined);
107
- }
108
- }
@@ -1,16 +0,0 @@
1
- interface CliCommand {
2
- file: string;
3
- args: string[];
4
- }
5
- interface UninstallCliCandidate {
6
- bin: string;
7
- label: string;
8
- command: CliCommand;
9
- }
10
- export declare function buildUninstallCliCandidates(): UninstallCliCandidate[];
11
- export declare function buildSelfUninstallInvocation(): {
12
- command: string;
13
- args: string[];
14
- };
15
- export declare function runUninstall(): Promise<void>;
16
- export {};
@@ -1,99 +0,0 @@
1
- import { execFileSync, spawn } from "node:child_process";
2
- import { existsSync, rmSync } from "node:fs";
3
- import { PATHS } from "../utils/config.js";
4
- import { getOpenClawConfigPath, removeOpenClawConfig } from "../utils/openClaw.js";
5
- import { getCodexInternalConfigPath, removeCodexInternalConfig, } from "../utils/codexInternal.js";
6
- function detectCli(name) {
7
- try {
8
- execFileSync(name, ["--version"], { stdio: "pipe" });
9
- return true;
10
- }
11
- catch {
12
- return false;
13
- }
14
- }
15
- function tryRemoveMcp(command, label) {
16
- try {
17
- execFileSync(command.file, command.args, { stdio: "pipe" });
18
- console.log(` ✅ Removed MCP registration from ${label}`);
19
- return "removed";
20
- }
21
- catch (err) {
22
- const details = err && typeof err === "object" && "stderr" in err
23
- ? String(err.stderr ?? "")
24
- : "";
25
- const lower = details.toLowerCase();
26
- if (lower.includes("not found") || lower.includes("no server") || lower.includes("unknown")) {
27
- console.log(` ℹ️ ${label} did not have yt-mcp registered`);
28
- return "missing";
29
- }
30
- console.log(` ⚠️ Failed to remove MCP registration from ${label}`);
31
- return "failed";
32
- }
33
- }
34
- export function buildUninstallCliCandidates() {
35
- return [
36
- { bin: "claude-internal", label: "Claude Code (internal)", command: { file: "claude-internal", args: ["mcp", "remove", "-s", "user", "yt-mcp"] } },
37
- { bin: "claude", label: "Claude Code", command: { file: "claude", args: ["mcp", "remove", "-s", "user", "yt-mcp"] } },
38
- { bin: "codex", label: "Codex CLI / Codex App", command: { file: "codex", args: ["mcp", "remove", "yt-mcp"] } },
39
- { bin: "gemini-internal", label: "Gemini CLI (internal)", command: { file: "gemini-internal", args: ["mcp", "remove", "-s", "user", "yt-mcp"] } },
40
- { bin: "gemini", label: "Gemini CLI", command: { file: "gemini", args: ["mcp", "remove", "-s", "user", "yt-mcp"] } },
41
- { bin: "opencode", label: "OpenCode", command: { file: "opencode", args: ["mcp", "remove", "yt-mcp"] } },
42
- ];
43
- }
44
- export function buildSelfUninstallInvocation() {
45
- const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
46
- const helper = process.platform === "win32"
47
- ? `setTimeout(() => { require("node:child_process").spawnSync(${JSON.stringify(npmBin)}, ["uninstall","-g","@mkterswingman/5mghost-yonder"], { stdio: "ignore", shell: false }); }, 1500);`
48
- : `setTimeout(() => { require("node:child_process").spawnSync(${JSON.stringify(npmBin)}, ["uninstall","-g","@mkterswingman/5mghost-yonder"], { stdio: "ignore" }); }, 1500);`;
49
- return {
50
- command: process.execPath,
51
- args: ["-e", helper],
52
- };
53
- }
54
- function scheduleSelfUninstall() {
55
- const invocation = buildSelfUninstallInvocation();
56
- const child = spawn(invocation.command, invocation.args, {
57
- detached: true,
58
- stdio: "ignore",
59
- windowsHide: true,
60
- });
61
- child.unref();
62
- }
63
- export async function runUninstall() {
64
- console.log("\n🧹 yt-mcp uninstall\n");
65
- console.log("Removing MCP client registrations...");
66
- for (const candidate of buildUninstallCliCandidates()) {
67
- if (!detectCli(candidate.bin))
68
- continue;
69
- tryRemoveMcp(candidate.command, candidate.label);
70
- }
71
- const openClawStatus = removeOpenClawConfig("yt-mcp");
72
- if (openClawStatus === "removed") {
73
- console.log(` ✅ Removed MCP registration from OpenClaw (${getOpenClawConfigPath()})`);
74
- }
75
- else {
76
- console.log(" ℹ️ OpenClaw did not have yt-mcp registered");
77
- }
78
- if (detectCli("codex-internal")) {
79
- const codexInternalStatus = removeCodexInternalConfig("yt-mcp");
80
- if (codexInternalStatus === "removed") {
81
- console.log(` ✅ Removed MCP registration from Codex CLI (internal) (${getCodexInternalConfigPath()})`);
82
- }
83
- else {
84
- console.log(" ℹ️ Codex CLI (internal) did not have yt-mcp registered");
85
- }
86
- }
87
- if (existsSync(PATHS.configDir)) {
88
- // Why: ~/.yt-mcp contains launcher, token cache, cookies, and npm cache owned by this package.
89
- rmSync(PATHS.configDir, { recursive: true, force: true });
90
- console.log(` ✅ Removed local yt-mcp config: ${PATHS.configDir}`);
91
- }
92
- else {
93
- console.log(` ℹ️ Local yt-mcp config already absent: ${PATHS.configDir}`);
94
- }
95
- console.log(` ℹ️ Preserved downloaded media: ${PATHS.subtitlesDir}`);
96
- scheduleSelfUninstall();
97
- console.log(" ✅ Scheduled global npm package removal");
98
- console.log("");
99
- }