@mkterswingman/5mghost-yonder 0.0.6 → 0.0.8
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 +5 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +15 -1
- package/dist/cli/setup.d.ts +11 -0
- package/dist/cli/setup.js +26 -28
- package/dist/cli/setupCookies.d.ts +18 -0
- package/dist/cli/setupCookies.js +40 -14
- package/dist/cli/uninstall.d.ts +15 -0
- package/dist/cli/uninstall.js +40 -8
- package/dist/runtime/playwrightRuntime.d.ts +4 -0
- package/dist/runtime/playwrightRuntime.js +15 -3
- package/dist/utils/codexInternal.d.ts +9 -0
- package/dist/utils/codexInternal.js +60 -0
- package/dist/utils/ytdlp.d.ts +4 -0
- package/dist/utils/ytdlp.js +10 -4
- package/package.json +1 -1
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
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface UnifiedUpdateDeps {
|
|
|
6
6
|
installLatestPackage(): Promise<void>;
|
|
7
7
|
updateRuntime(): Promise<import("../runtime/installers.js").RuntimeSummary>;
|
|
8
8
|
}
|
|
9
|
+
export declare function compareVersions(currentVersion: string, latestVersion: string): number;
|
|
9
10
|
export declare function runUnifiedUpdate(deps: UnifiedUpdateDeps): Promise<{
|
|
10
11
|
package: {
|
|
11
12
|
currentVersion: string;
|
package/dist/cli/index.js
CHANGED
|
@@ -35,11 +35,25 @@ Environment variables:
|
|
|
35
35
|
YT_MCP_TOKEN Personal Access Token (skips OAuth)
|
|
36
36
|
`;
|
|
37
37
|
}
|
|
38
|
+
export function compareVersions(currentVersion, latestVersion) {
|
|
39
|
+
const normalize = (value) => value.split(".").map((part) => Number.parseInt(part, 10)).map((part) => (Number.isFinite(part) ? part : 0));
|
|
40
|
+
const current = normalize(currentVersion);
|
|
41
|
+
const latest = normalize(latestVersion);
|
|
42
|
+
const max = Math.max(current.length, latest.length);
|
|
43
|
+
for (let index = 0; index < max; index += 1) {
|
|
44
|
+
const a = current[index] ?? 0;
|
|
45
|
+
const b = latest[index] ?? 0;
|
|
46
|
+
if (a === b)
|
|
47
|
+
continue;
|
|
48
|
+
return a < b ? -1 : 1;
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
38
52
|
export async function runUnifiedUpdate(deps) {
|
|
39
53
|
const currentVersion = deps.getCurrentVersion();
|
|
40
54
|
const latestVersion = await deps.getLatestVersion();
|
|
41
55
|
let updated = false;
|
|
42
|
-
if (latestVersion
|
|
56
|
+
if (compareVersions(currentVersion, latestVersion) < 0) {
|
|
43
57
|
await deps.installLatestPackage();
|
|
44
58
|
updated = true;
|
|
45
59
|
}
|
package/dist/cli/setup.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
interface CliCommand {
|
|
2
|
+
file: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
}
|
|
5
|
+
interface SetupCliCandidate {
|
|
6
|
+
bin: string;
|
|
7
|
+
label: string;
|
|
8
|
+
command: CliCommand;
|
|
9
|
+
}
|
|
10
|
+
export declare function buildSetupCliCandidates(launcherCommand: CliCommand): SetupCliCandidate[];
|
|
1
11
|
export declare function canOpenBrowserForEnv(input: {
|
|
2
12
|
platform: NodeJS.Platform;
|
|
3
13
|
env: NodeJS.ProcessEnv;
|
|
@@ -7,3 +17,4 @@ export declare function getNoBrowserSessionNotice(): string;
|
|
|
7
17
|
export declare function getNoBrowserPatHint(authUrl: string): string[];
|
|
8
18
|
export declare function getCookieSetupDeferredHint(): string[];
|
|
9
19
|
export declare function runSetup(): Promise<void>;
|
|
20
|
+
export {};
|
package/dist/cli/setup.js
CHANGED
|
@@ -7,6 +7,7 @@ import { hasSIDCookies } from "../utils/cookies.js";
|
|
|
7
7
|
import { buildLauncherCommand, writeLauncherFile } from "../utils/launcher.js";
|
|
8
8
|
import { checkAll } from "../runtime/installers.js";
|
|
9
9
|
import { getOpenClawConfigPath, isOpenClawInstallLikelyInstalled, writeOpenClawConfig, } from "../utils/openClaw.js";
|
|
10
|
+
import { getCodexInternalConfigPath, writeCodexInternalConfig, } from "../utils/codexInternal.js";
|
|
10
11
|
import { MCP_REGISTER_TIMEOUT_MS, classifyRegistrationFailure, } from "../utils/mcpRegistration.js";
|
|
11
12
|
function detectCli(name) {
|
|
12
13
|
try {
|
|
@@ -40,6 +41,16 @@ function tryRegisterMcp(cmd, label) {
|
|
|
40
41
|
return false;
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
export function buildSetupCliCandidates(launcherCommand) {
|
|
45
|
+
return [
|
|
46
|
+
{ bin: "claude-internal", label: "Claude Code (internal)", command: { file: "claude-internal", args: ["mcp", "add", "-s", "user", "yt-mcp", "--", launcherCommand.file, ...launcherCommand.args] } },
|
|
47
|
+
{ bin: "claude", label: "Claude Code", command: { file: "claude", args: ["mcp", "add", "-s", "user", "yt-mcp", "--", launcherCommand.file, ...launcherCommand.args] } },
|
|
48
|
+
{ bin: "codex", label: "Codex CLI / Codex App", command: { file: "codex", args: ["mcp", "add", "yt-mcp", "--", launcherCommand.file, ...launcherCommand.args] } },
|
|
49
|
+
{ bin: "gemini-internal", label: "Gemini CLI (internal)", command: { file: "gemini-internal", args: ["mcp", "add", "-s", "user", "yt-mcp", launcherCommand.file, ...launcherCommand.args] } },
|
|
50
|
+
{ bin: "gemini", label: "Gemini CLI", command: { file: "gemini", args: ["mcp", "add", "-s", "user", "yt-mcp", launcherCommand.file, ...launcherCommand.args] } },
|
|
51
|
+
{ bin: "opencode", label: "OpenCode", command: { file: "opencode", args: ["mcp", "add", "yt-mcp", "--", launcherCommand.file, ...launcherCommand.args] } },
|
|
52
|
+
];
|
|
53
|
+
}
|
|
43
54
|
export function canOpenBrowserForEnv(input) {
|
|
44
55
|
if (input.env.YT_MCP_NO_BROWSER === "1") {
|
|
45
56
|
return false;
|
|
@@ -243,36 +254,12 @@ export async function runSetup() {
|
|
|
243
254
|
console.log("Step 5/5: Registering MCP in AI clients...");
|
|
244
255
|
const launcherCommand = buildLauncherCommand();
|
|
245
256
|
let registered = false;
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
{ bin: "claude", label: "Claude Code",
|
|
251
|
-
cmd: (b, launcher) => ({ file: b, args: ["mcp", "add", "-s", "user", "yt-mcp", "--", launcher.command, ...launcher.args] }) },
|
|
252
|
-
// Codex (public): {bin} mcp add yt-mcp -- npx ... serve
|
|
253
|
-
{ bin: "codex", label: "Codex CLI / Codex App",
|
|
254
|
-
cmd: (b, launcher) => ({ file: b, args: ["mcp", "add", "yt-mcp", "--", launcher.command, ...launcher.args] }) },
|
|
255
|
-
// Codex-internal doesn't support mcp add — needs manual config
|
|
256
|
-
{ bin: "codex-internal", label: "Codex CLI (internal)",
|
|
257
|
-
cmd: () => null },
|
|
258
|
-
// Gemini: {bin} mcp add -s user yt-mcp node <launcher> serve (no --)
|
|
259
|
-
{ bin: "gemini-internal", label: "Gemini CLI (internal)",
|
|
260
|
-
cmd: (b, launcher) => ({ file: b, args: ["mcp", "add", "-s", "user", "yt-mcp", launcher.command, ...launcher.args] }) },
|
|
261
|
-
{ bin: "gemini", label: "Gemini CLI",
|
|
262
|
-
cmd: (b, launcher) => ({ file: b, args: ["mcp", "add", "-s", "user", "yt-mcp", launcher.command, ...launcher.args] }) },
|
|
263
|
-
// Others: assume Claude-style syntax
|
|
264
|
-
{ bin: "opencode", label: "OpenCode",
|
|
265
|
-
cmd: (b, launcher) => ({ file: b, args: ["mcp", "add", "yt-mcp", "--", launcher.command, ...launcher.args] }) },
|
|
266
|
-
];
|
|
267
|
-
for (const { bin, label, cmd } of cliCandidates) {
|
|
257
|
+
for (const { bin, label, command } of buildSetupCliCandidates({
|
|
258
|
+
file: launcherCommand.command,
|
|
259
|
+
args: launcherCommand.args,
|
|
260
|
+
})) {
|
|
268
261
|
if (!detectCli(bin))
|
|
269
262
|
continue;
|
|
270
|
-
const command = cmd(bin, launcherCommand);
|
|
271
|
-
if (!command) {
|
|
272
|
-
// CLI detected but doesn't support auto-registration
|
|
273
|
-
console.log(` ⚠️ ${label} detected but requires manual MCP config.`);
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
263
|
if (tryRegisterMcp(command, label)) {
|
|
277
264
|
registered = true;
|
|
278
265
|
}
|
|
@@ -288,6 +275,17 @@ export async function runSetup() {
|
|
|
288
275
|
console.log(` ⚠️ OpenClaw auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
289
276
|
}
|
|
290
277
|
}
|
|
278
|
+
if (detectCli("codex-internal")) {
|
|
279
|
+
try {
|
|
280
|
+
const status = writeCodexInternalConfig("yt-mcp", launcherCommand);
|
|
281
|
+
const suffix = status === "created" ? "created" : "updated";
|
|
282
|
+
console.log(` ✅ MCP registered in Codex CLI (internal) (${suffix} ${getCodexInternalConfigPath()})`);
|
|
283
|
+
registered = true;
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
console.log(` ⚠️ Codex CLI (internal) auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
291
289
|
if (!registered) {
|
|
292
290
|
console.log(" ℹ️ No supported CLI found. Add manually to your AI client:");
|
|
293
291
|
}
|
|
@@ -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>;
|
package/dist/cli/setupCookies.js
CHANGED
|
@@ -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
|
-
|
|
32
|
-
|
|
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
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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:
|
|
277
|
+
" If it fails, run: yt-mcp runtime install\n");
|
|
252
278
|
}
|
|
253
279
|
let context;
|
|
254
280
|
try {
|
package/dist/cli/uninstall.d.ts
CHANGED
|
@@ -1 +1,16 @@
|
|
|
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
|
+
};
|
|
1
15
|
export declare function runUninstall(): Promise<void>;
|
|
16
|
+
export {};
|
package/dist/cli/uninstall.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
2
2
|
import { existsSync, rmSync } from "node:fs";
|
|
3
3
|
import { PATHS } from "../utils/config.js";
|
|
4
4
|
import { getOpenClawConfigPath, removeOpenClawConfig } from "../utils/openClaw.js";
|
|
5
|
+
import { getCodexInternalConfigPath, removeCodexInternalConfig, } from "../utils/codexInternal.js";
|
|
5
6
|
function detectCli(name) {
|
|
6
7
|
try {
|
|
7
8
|
execFileSync(name, ["--version"], { stdio: "pipe" });
|
|
@@ -30,10 +31,8 @@ function tryRemoveMcp(command, label) {
|
|
|
30
31
|
return "failed";
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
|
-
export
|
|
34
|
-
|
|
35
|
-
console.log("Removing MCP client registrations...");
|
|
36
|
-
const cliCandidates = [
|
|
34
|
+
export function buildUninstallCliCandidates() {
|
|
35
|
+
return [
|
|
37
36
|
{ bin: "claude-internal", label: "Claude Code (internal)", command: { file: "claude-internal", args: ["mcp", "remove", "-s", "user", "yt-mcp"] } },
|
|
38
37
|
{ bin: "claude", label: "Claude Code", command: { file: "claude", args: ["mcp", "remove", "-s", "user", "yt-mcp"] } },
|
|
39
38
|
{ bin: "codex", label: "Codex CLI / Codex App", command: { file: "codex", args: ["mcp", "remove", "yt-mcp"] } },
|
|
@@ -41,8 +40,31 @@ export async function runUninstall() {
|
|
|
41
40
|
{ bin: "gemini", label: "Gemini CLI", command: { file: "gemini", args: ["mcp", "remove", "-s", "user", "yt-mcp"] } },
|
|
42
41
|
{ bin: "opencode", label: "OpenCode", command: { file: "opencode", args: ["mcp", "remove", "yt-mcp"] } },
|
|
43
42
|
];
|
|
44
|
-
|
|
45
|
-
|
|
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))
|
|
46
68
|
continue;
|
|
47
69
|
tryRemoveMcp(candidate.command, candidate.label);
|
|
48
70
|
}
|
|
@@ -53,6 +75,15 @@ export async function runUninstall() {
|
|
|
53
75
|
else {
|
|
54
76
|
console.log(" ℹ️ OpenClaw did not have yt-mcp registered");
|
|
55
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
|
+
}
|
|
56
87
|
if (existsSync(PATHS.configDir)) {
|
|
57
88
|
// Why: ~/.yt-mcp contains launcher, token cache, cookies, and npm cache owned by this package.
|
|
58
89
|
rmSync(PATHS.configDir, { recursive: true, force: true });
|
|
@@ -62,6 +93,7 @@ export async function runUninstall() {
|
|
|
62
93
|
console.log(` ℹ️ Local yt-mcp config already absent: ${PATHS.configDir}`);
|
|
63
94
|
}
|
|
64
95
|
console.log(` ℹ️ Preserved downloaded media: ${PATHS.subtitlesDir}`);
|
|
65
|
-
|
|
96
|
+
scheduleSelfUninstall();
|
|
97
|
+
console.log(" ✅ Scheduled global npm package removal");
|
|
66
98
|
console.log("");
|
|
67
99
|
}
|
|
@@ -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 {
|
|
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: "
|
|
39
|
+
message: "yt-mcp runtime install",
|
|
29
40
|
};
|
|
30
41
|
}
|
|
31
42
|
export async function installPlaywrightRuntime() {
|
|
32
|
-
|
|
43
|
+
const invocation = getPlaywrightInstallInvocation();
|
|
44
|
+
execFileSync(invocation.command, invocation.args, { stdio: "inherit" });
|
|
33
45
|
return checkPlaywrightRuntime();
|
|
34
46
|
}
|
|
35
47
|
export async function updatePlaywrightRuntime() {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LauncherCommand } from "./launcher.js";
|
|
2
|
+
export declare function getCodexInternalConfigPath(homeDir?: string): string;
|
|
3
|
+
export declare function upsertCodexInternalConfigText(currentText: string | null, serverName: string, launcherCommand: LauncherCommand): string;
|
|
4
|
+
export declare function removeCodexInternalConfigEntryText(currentText: string | null, serverName: string): {
|
|
5
|
+
changed: boolean;
|
|
6
|
+
nextText: string | null;
|
|
7
|
+
};
|
|
8
|
+
export declare function writeCodexInternalConfig(serverName: string, launcherCommand: LauncherCommand, configPath?: string): "created" | "updated";
|
|
9
|
+
export declare function removeCodexInternalConfig(serverName: string, configPath?: string): "removed" | "missing";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export function getCodexInternalConfigPath(homeDir = homedir()) {
|
|
5
|
+
return join(homeDir, ".codex-internal", "config.toml");
|
|
6
|
+
}
|
|
7
|
+
function formatCodexInternalSection(serverName, launcherCommand) {
|
|
8
|
+
const escapedName = serverName.replace(/"/g, '\\"');
|
|
9
|
+
const args = launcherCommand.args.map((arg) => JSON.stringify(arg)).join(", ");
|
|
10
|
+
return `[mcp_servers.${JSON.stringify(escapedName)}]
|
|
11
|
+
command = ${JSON.stringify(launcherCommand.command)}
|
|
12
|
+
args = [${args}]
|
|
13
|
+
`;
|
|
14
|
+
}
|
|
15
|
+
export function upsertCodexInternalConfigText(currentText, serverName, launcherCommand) {
|
|
16
|
+
const section = formatCodexInternalSection(serverName, launcherCommand).trimEnd();
|
|
17
|
+
const sectionPattern = new RegExp(String.raw `(?:^|\n)\[mcp_servers\.${JSON.stringify(serverName).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n(?:.+\n?)*?(?=\n\[|$)`, "m");
|
|
18
|
+
if (!currentText || currentText.trim() === "") {
|
|
19
|
+
return `${section}\n`;
|
|
20
|
+
}
|
|
21
|
+
if (sectionPattern.test(currentText)) {
|
|
22
|
+
return `${currentText.replace(sectionPattern, `\n${section}\n`).trim()}\n`;
|
|
23
|
+
}
|
|
24
|
+
return `${currentText.trimEnd()}\n\n${section}\n`;
|
|
25
|
+
}
|
|
26
|
+
export function removeCodexInternalConfigEntryText(currentText, serverName) {
|
|
27
|
+
if (!currentText) {
|
|
28
|
+
return { changed: false, nextText: null };
|
|
29
|
+
}
|
|
30
|
+
const sectionPattern = new RegExp(String.raw `(?:^|\n)\[mcp_servers\.${JSON.stringify(serverName).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\]\n(?:.+\n?)*?(?=\n\[|$)`, "m");
|
|
31
|
+
if (!sectionPattern.test(currentText)) {
|
|
32
|
+
return { changed: false, nextText: currentText };
|
|
33
|
+
}
|
|
34
|
+
const nextText = currentText.replace(sectionPattern, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
35
|
+
return {
|
|
36
|
+
changed: true,
|
|
37
|
+
nextText: nextText ? `${nextText}\n` : null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function writeCodexInternalConfig(serverName, launcherCommand, configPath = getCodexInternalConfigPath()) {
|
|
41
|
+
const existingText = existsSync(configPath) ? readFileSync(configPath, "utf8") : null;
|
|
42
|
+
const created = existingText === null;
|
|
43
|
+
const nextText = upsertCodexInternalConfigText(existingText, serverName, launcherCommand);
|
|
44
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
45
|
+
writeFileSync(configPath, nextText, "utf8");
|
|
46
|
+
return created ? "created" : "updated";
|
|
47
|
+
}
|
|
48
|
+
export function removeCodexInternalConfig(serverName, configPath = getCodexInternalConfigPath()) {
|
|
49
|
+
const existingText = existsSync(configPath) ? readFileSync(configPath, "utf8") : null;
|
|
50
|
+
const result = removeCodexInternalConfigEntryText(existingText, serverName);
|
|
51
|
+
if (!result.changed) {
|
|
52
|
+
return "missing";
|
|
53
|
+
}
|
|
54
|
+
if (!result.nextText) {
|
|
55
|
+
rmSync(configPath, { force: true });
|
|
56
|
+
return "removed";
|
|
57
|
+
}
|
|
58
|
+
writeFileSync(configPath, result.nextText, "utf8");
|
|
59
|
+
return "removed";
|
|
60
|
+
}
|
package/dist/utils/ytdlp.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/ytdlp.js
CHANGED
|
@@ -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 =
|
|
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
|
});
|