@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 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
@@ -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 !== currentVersion) {
56
+ if (compareVersions(currentVersion, latestVersion) < 0) {
43
57
  await deps.installLatestPackage();
44
58
  updated = true;
45
59
  }
@@ -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 cliCandidates = [
247
- // Claude Code: {bin} mcp add yt-mcp -- npx ... serve
248
- { bin: "claude-internal", label: "Claude Code (internal)",
249
- cmd: (b, launcher) => ({ file: b, args: ["mcp", "add", "-s", "user", "yt-mcp", "--", launcher.command, ...launcher.args] }) },
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>;
@@ -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 +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 {};
@@ -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 async function runUninstall() {
34
- console.log("\n🧹 yt-mcp uninstall\n");
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
- for (const candidate of cliCandidates) {
45
- if (!detectCli(candidate.bin) || !candidate.command)
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
- console.log(" ℹ️ If you installed globally, remove the package with: npm uninstall -g @mkterswingman/5mghost-yonder");
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 { 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() {
@@ -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
+ }
@@ -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.8",
4
4
  "description": "Internal MCP client with local data tools and remote API proxy",
5
5
  "type": "module",
6
6
  "bin": {