@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,5 +0,0 @@
1
- export interface BrowserOpenCommand {
2
- file: string;
3
- args: string[];
4
- }
5
- export declare function buildBrowserOpenCommand(url: string, platform?: NodeJS.Platform): BrowserOpenCommand;
@@ -1,22 +0,0 @@
1
- function escapePowerShellSingleQuoted(value) {
2
- return value.replace(/'/g, "''");
3
- }
4
- export function buildBrowserOpenCommand(url, platform = process.platform) {
5
- if (platform === "darwin") {
6
- return { file: "open", args: [url] };
7
- }
8
- if (platform === "win32") {
9
- return {
10
- file: "powershell",
11
- args: [
12
- "-NoProfile",
13
- "-NonInteractive",
14
- "-ExecutionPolicy",
15
- "Bypass",
16
- "-Command",
17
- `Start-Process '${escapePowerShellSingleQuoted(url)}'`,
18
- ],
19
- };
20
- }
21
- return { file: "xdg-open", args: [url] };
22
- }
@@ -1,49 +0,0 @@
1
- export type SupportedBrowser = "chrome" | "msedge";
2
- export interface BrowserInstallationCandidate {
3
- browser: SupportedBrowser;
4
- label: string;
5
- rootDir: string;
6
- localStatePath: string;
7
- executablePathCandidates: string[];
8
- }
9
- export interface BrowserProfileCandidate extends BrowserInstallationCandidate {
10
- profileName: string;
11
- profileLabel: string;
12
- profileDir: string;
13
- executablePath: string;
14
- }
15
- export interface ImportedBrowserWorkspace {
16
- browser: SupportedBrowser;
17
- label: string;
18
- profileName: string;
19
- profileLabel: string;
20
- workspaceDir: string;
21
- localStatePath: string;
22
- profileDir: string;
23
- executablePath: string;
24
- }
25
- interface LocalStateProfileMeta {
26
- name?: string;
27
- user_name?: string;
28
- gaia_name?: string;
29
- active_time?: number;
30
- }
31
- export declare function listSupportedBrowserInstallations(input?: {
32
- platform?: NodeJS.Platform;
33
- env?: NodeJS.ProcessEnv;
34
- homeDir?: string;
35
- }): BrowserInstallationCandidate[];
36
- export declare function parseBrowserProfilesFromLocalState(localStateText: string): {
37
- orderedProfileNames: string[];
38
- infoCache: Record<string, LocalStateProfileMeta>;
39
- };
40
- export declare function findImportableBrowserProfileCandidates(input?: {
41
- platform?: NodeJS.Platform;
42
- env?: NodeJS.ProcessEnv;
43
- homeDir?: string;
44
- exists?: (path: string) => boolean;
45
- readFile?: (path: string) => string;
46
- }): BrowserProfileCandidate[];
47
- export declare function prepareImportedBrowserWorkspace(candidate: BrowserProfileCandidate, tempRootDir?: string): ImportedBrowserWorkspace;
48
- export declare function cleanupImportedBrowserWorkspace(workspace: ImportedBrowserWorkspace): void;
49
- export {};
@@ -1,163 +0,0 @@
1
- import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, } from "node:fs";
2
- import { homedir, tmpdir } from "node:os";
3
- import { basename, join } from "node:path";
4
- const CACHE_DIR_NAMES = new Set([
5
- "Cache",
6
- "Code Cache",
7
- "GPUCache",
8
- "GrShaderCache",
9
- "ShaderCache",
10
- "IndexedDB",
11
- "Storage",
12
- "WebStorage",
13
- "Service Worker",
14
- "blob_storage",
15
- "File System",
16
- "SharedStorage",
17
- "DawnGraphiteCache",
18
- "GraphiteDawnCache",
19
- "segmentation_platform",
20
- "commerce_subscription_db",
21
- ]);
22
- export function listSupportedBrowserInstallations(input) {
23
- const platform = input?.platform ?? process.platform;
24
- const env = input?.env ?? process.env;
25
- const homeDir = input?.homeDir ?? homedir();
26
- if (platform === "win32") {
27
- const localAppData = env.LOCALAPPDATA?.trim();
28
- const programFiles = env.ProgramFiles?.trim() ?? "C:/Program Files";
29
- const programFilesX86 = env["ProgramFiles(x86)"]?.trim() ?? "C:/Program Files (x86)";
30
- if (!localAppData) {
31
- return [];
32
- }
33
- return [
34
- buildInstallation("chrome", "Google Chrome", join(localAppData, "Google", "Chrome", "User Data"), [
35
- join(programFiles, "Google", "Chrome", "Application", "chrome.exe"),
36
- join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
37
- ]),
38
- buildInstallation("msedge", "Microsoft Edge", join(localAppData, "Microsoft", "Edge", "User Data"), [
39
- join(programFiles, "Microsoft", "Edge", "Application", "msedge.exe"),
40
- join(programFilesX86, "Microsoft", "Edge", "Application", "msedge.exe"),
41
- ]),
42
- ];
43
- }
44
- if (platform === "darwin") {
45
- return [
46
- buildInstallation("chrome", "Google Chrome", join(homeDir, "Library", "Application Support", "Google", "Chrome"), ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"]),
47
- buildInstallation("msedge", "Microsoft Edge", join(homeDir, "Library", "Application Support", "Microsoft Edge"), ["/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"]),
48
- ];
49
- }
50
- return [];
51
- }
52
- function buildInstallation(browser, label, rootDir, executablePathCandidates) {
53
- return {
54
- browser,
55
- label,
56
- rootDir,
57
- localStatePath: join(rootDir, "Local State"),
58
- executablePathCandidates,
59
- };
60
- }
61
- export function parseBrowserProfilesFromLocalState(localStateText) {
62
- const parsed = JSON.parse(localStateText);
63
- const profile = parsed.profile ?? {};
64
- const infoCache = profile.info_cache ?? {};
65
- const orderedNames = [];
66
- const pushUnique = (name) => {
67
- if (!name || orderedNames.includes(name)) {
68
- return;
69
- }
70
- orderedNames.push(name);
71
- };
72
- for (const name of profile.last_active_profiles ?? []) {
73
- pushUnique(name);
74
- }
75
- pushUnique(profile.last_used);
76
- for (const name of profile.profiles_order ?? []) {
77
- pushUnique(name);
78
- }
79
- const remaining = Object.keys(infoCache)
80
- .filter((name) => !orderedNames.includes(name))
81
- .sort((left, right) => ((infoCache[right]?.active_time ?? 0) - (infoCache[left]?.active_time ?? 0)));
82
- for (const name of remaining) {
83
- pushUnique(name);
84
- }
85
- if (orderedNames.length === 0 && infoCache.Default) {
86
- orderedNames.push("Default");
87
- }
88
- return {
89
- orderedProfileNames: orderedNames,
90
- infoCache,
91
- };
92
- }
93
- function buildProfileLabel(profileName, meta) {
94
- return meta?.name?.trim() || meta?.user_name?.trim() || meta?.gaia_name?.trim() || profileName;
95
- }
96
- function hasReusableCookieStore(profileDir, exists) {
97
- // Why: recent Chromium builds often moved the SQLite cookie DB under Network/Cookies.
98
- return exists(join(profileDir, "Cookies")) || exists(join(profileDir, "Network", "Cookies"));
99
- }
100
- export function findImportableBrowserProfileCandidates(input) {
101
- const exists = input?.exists ?? existsSync;
102
- const readFile = input?.readFile ?? ((path) => readFileSync(path, "utf8"));
103
- const candidates = [];
104
- for (const installation of listSupportedBrowserInstallations(input)) {
105
- if (!exists(installation.rootDir) || !exists(installation.localStatePath)) {
106
- continue;
107
- }
108
- const executablePath = installation.executablePathCandidates.find((path) => exists(path));
109
- if (!executablePath) {
110
- continue;
111
- }
112
- let parsedState;
113
- try {
114
- parsedState = parseBrowserProfilesFromLocalState(readFile(installation.localStatePath));
115
- }
116
- catch {
117
- continue;
118
- }
119
- for (const profileName of parsedState.orderedProfileNames) {
120
- const profileDir = join(installation.rootDir, profileName);
121
- if (!exists(profileDir) || !hasReusableCookieStore(profileDir, exists)) {
122
- continue;
123
- }
124
- candidates.push({
125
- ...installation,
126
- executablePath,
127
- profileName,
128
- profileLabel: buildProfileLabel(profileName, parsedState.infoCache[profileName]),
129
- profileDir,
130
- });
131
- }
132
- }
133
- return candidates;
134
- }
135
- function shouldCopyProfilePath(sourcePath) {
136
- return !sourcePath
137
- .split(/[\\/]+/)
138
- .filter(Boolean)
139
- .some((segment) => CACHE_DIR_NAMES.has(segment));
140
- }
141
- export function prepareImportedBrowserWorkspace(candidate, tempRootDir = tmpdir()) {
142
- const workspaceDir = mkdtempSync(join(tempRootDir, `yt-mcp-${candidate.browser}-profile-`));
143
- const localStatePath = join(workspaceDir, "Local State");
144
- const profileDir = join(workspaceDir, candidate.profileName);
145
- cpSync(candidate.localStatePath, localStatePath);
146
- cpSync(candidate.profileDir, profileDir, {
147
- recursive: true,
148
- filter: (sourcePath) => shouldCopyProfilePath(sourcePath.replace(candidate.profileDir, basename(candidate.profileDir))),
149
- });
150
- return {
151
- browser: candidate.browser,
152
- label: candidate.label,
153
- profileName: candidate.profileName,
154
- profileLabel: candidate.profileLabel,
155
- workspaceDir,
156
- localStatePath,
157
- profileDir,
158
- executablePath: candidate.executablePath,
159
- };
160
- }
161
- export function cleanupImportedBrowserWorkspace(workspace) {
162
- rmSync(workspace.workspaceDir, { recursive: true, force: true });
163
- }
@@ -1,9 +0,0 @@
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";
@@ -1,60 +0,0 @@
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
- }
@@ -1,53 +0,0 @@
1
- export declare function getConfigDir(homeDir?: string): string;
2
- export declare function getSharedAuthDir(homeDir?: string): string;
3
- export declare const MEDIA_ROOT_DIR: string;
4
- export declare function buildRuntimeBinaryPath(runtimeBinDir: string, binaryName: string): string;
5
- export declare function buildPaths(homeDir?: string): {
6
- configDir: string;
7
- authJson: string;
8
- cookiesTxt: string;
9
- logsDir: string;
10
- browserProfile: string;
11
- launcherJs: string;
12
- npmCacheDir: string;
13
- subtitlesDir: string;
14
- configJson: string;
15
- runtimeRootDir: string;
16
- runtimeBinDir: string;
17
- runtimeManifestJson: string;
18
- sharedAuthDir: string;
19
- sharedAuthJson: string;
20
- };
21
- export declare function buildSharedPaths(homeDir?: string): {
22
- sharedAuthDir: string;
23
- sharedAuthJson: string;
24
- };
25
- export declare const PATHS: {
26
- configDir: string;
27
- authJson: string;
28
- cookiesTxt: string;
29
- logsDir: string;
30
- browserProfile: string;
31
- launcherJs: string;
32
- npmCacheDir: string;
33
- subtitlesDir: string;
34
- configJson: string;
35
- runtimeRootDir: string;
36
- runtimeBinDir: string;
37
- runtimeManifestJson: string;
38
- sharedAuthDir: string;
39
- sharedAuthJson: string;
40
- };
41
- export interface YtMcpConfig {
42
- /** Auth gateway root URL — for OAuth, PAT, introspection (e.g., https://mkterswingman.com) */
43
- auth_url: string;
44
- /** v2 API URL — for data tools (e.g., https://mkterswingman.com/mcp/yt) */
45
- api_url: string;
46
- default_languages: string[];
47
- batch_sleep_min_ms: number;
48
- batch_sleep_max_ms: number;
49
- batch_max_size: number;
50
- }
51
- export declare function ensureConfigDir(): void;
52
- export declare function loadConfig(): YtMcpConfig;
53
- export declare function saveConfig(config: Partial<YtMcpConfig>): void;
@@ -1,77 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- export function getConfigDir(homeDir = homedir()) {
5
- return join(homeDir, ".yt-mcp");
6
- }
7
- export function getSharedAuthDir(homeDir = homedir()) {
8
- return join(homeDir, ".mkterswingman");
9
- }
10
- export const MEDIA_ROOT_DIR = join(homedir(), "Downloads", "yt-mcp");
11
- export function buildRuntimeBinaryPath(runtimeBinDir, binaryName) {
12
- return join(runtimeBinDir, binaryName);
13
- }
14
- export function buildPaths(homeDir = homedir()) {
15
- const configDir = getConfigDir(homeDir);
16
- const runtimeRootDir = join(configDir, "runtime");
17
- const runtimeBinDir = join(runtimeRootDir, "bin");
18
- const sharedAuthDir = getSharedAuthDir(homeDir);
19
- return {
20
- configDir,
21
- authJson: join(configDir, "auth.json"),
22
- cookiesTxt: join(configDir, "cookies.txt"),
23
- logsDir: join(configDir, "logs"),
24
- browserProfile: join(configDir, "browser-profile"),
25
- launcherJs: join(configDir, "launcher.mjs"),
26
- npmCacheDir: join(configDir, "npm-cache"),
27
- subtitlesDir: MEDIA_ROOT_DIR,
28
- configJson: join(configDir, "config.json"),
29
- runtimeRootDir,
30
- runtimeBinDir,
31
- runtimeManifestJson: join(runtimeRootDir, "runtime.json"),
32
- sharedAuthDir,
33
- sharedAuthJson: join(sharedAuthDir, "auth.json"),
34
- };
35
- }
36
- export function buildSharedPaths(homeDir = homedir()) {
37
- const sharedAuthDir = getSharedAuthDir(homeDir);
38
- return {
39
- sharedAuthDir,
40
- sharedAuthJson: join(sharedAuthDir, "auth.json"),
41
- };
42
- }
43
- export const PATHS = {
44
- ...buildPaths(),
45
- };
46
- const DEFAULTS = {
47
- auth_url: "https://mkterswingman.com",
48
- api_url: "https://mkterswingman.com/mcp/yt",
49
- // Fixed list, not probed: --list-subs costs ~10s extra and YouTube has no "source language" concept
50
- default_languages: ["en", "zh-Hans"],
51
- batch_sleep_min_ms: 3000,
52
- batch_sleep_max_ms: 8000,
53
- batch_max_size: 10,
54
- };
55
- export function ensureConfigDir() {
56
- mkdirSync(PATHS.configDir, { recursive: true });
57
- }
58
- export function loadConfig() {
59
- ensureConfigDir();
60
- if (!existsSync(PATHS.configJson)) {
61
- return { ...DEFAULTS };
62
- }
63
- try {
64
- const raw = readFileSync(PATHS.configJson, "utf8");
65
- const parsed = JSON.parse(raw);
66
- return { ...DEFAULTS, ...parsed };
67
- }
68
- catch {
69
- return { ...DEFAULTS };
70
- }
71
- }
72
- export function saveConfig(config) {
73
- ensureConfigDir();
74
- const existing = loadConfig();
75
- const merged = { ...existing, ...config };
76
- writeFileSync(PATHS.configJson, JSON.stringify(merged, null, 2), "utf8");
77
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Headless cookie auto-refresh.
3
- *
4
- * Uses Playwright to open YouTube in a headless browser with the existing
5
- * browser-profile. If Google session is still valid, YouTube cookies are
6
- * extracted and saved automatically — no user interaction needed.
7
- *
8
- * If Google session has expired too, returns false so the caller can
9
- * prompt the user for interactive login.
10
- */
11
- /**
12
- * Attempt to refresh YouTube cookies headlessly using existing browser profile.
13
- *
14
- * @returns true if cookies were refreshed, false if Google session expired
15
- * (needs interactive login).
16
- * @throws if Playwright is not installed or browser can't launch.
17
- */
18
- export declare function tryHeadlessRefresh(): Promise<boolean>;
@@ -1,70 +0,0 @@
1
- /**
2
- * Headless cookie auto-refresh.
3
- *
4
- * Uses Playwright to open YouTube in a headless browser with the existing
5
- * browser-profile. If Google session is still valid, YouTube cookies are
6
- * extracted and saved automatically — no user interaction needed.
7
- *
8
- * If Google session has expired too, returns false so the caller can
9
- * prompt the user for interactive login.
10
- */
11
- import { existsSync } from "node:fs";
12
- import { PATHS, ensureConfigDir } from "./config.js";
13
- import { detectBrowserChannel, hasYouTubeSession, saveCookiesToDisk, } from "../cli/setupCookies.js";
14
- const HEADLESS_TIMEOUT_MS = 30_000;
15
- /**
16
- * Attempt to refresh YouTube cookies headlessly using existing browser profile.
17
- *
18
- * @returns true if cookies were refreshed, false if Google session expired
19
- * (needs interactive login).
20
- * @throws if Playwright is not installed or browser can't launch.
21
- */
22
- export async function tryHeadlessRefresh() {
23
- // No browser profile = never logged in = can't refresh
24
- if (!existsSync(PATHS.browserProfile)) {
25
- return false;
26
- }
27
- ensureConfigDir();
28
- // Dynamic import to keep serve startup fast
29
- let chromium;
30
- try {
31
- const pw = await import("playwright");
32
- chromium = pw.chromium;
33
- }
34
- catch {
35
- return false; // Playwright not installed — can't refresh
36
- }
37
- const channel = await detectBrowserChannel(chromium);
38
- let context;
39
- try {
40
- context = await chromium.launchPersistentContext(PATHS.browserProfile, {
41
- headless: true,
42
- channel,
43
- args: ["--disable-blink-features=AutomationControlled"],
44
- });
45
- }
46
- catch {
47
- return false; // Browser launch failed
48
- }
49
- try {
50
- const page = context.pages()[0] ?? (await context.newPage());
51
- await page.goto("https://www.youtube.com", { timeout: HEADLESS_TIMEOUT_MS });
52
- await page.waitForLoadState("domcontentloaded");
53
- const cookies = await context.cookies("https://www.youtube.com");
54
- if (!hasYouTubeSession(cookies)) {
55
- // Google session expired — can't auto-refresh
56
- await context.close().catch(() => { });
57
- return false;
58
- }
59
- // Wait a moment for all cookies to settle
60
- await new Promise((r) => setTimeout(r, 1500));
61
- const finalCookies = await context.cookies("https://www.youtube.com");
62
- saveCookiesToDisk(finalCookies);
63
- await context.close().catch(() => { });
64
- return true;
65
- }
66
- catch {
67
- await context.close().catch(() => { });
68
- return false;
69
- }
70
- }
@@ -1,18 +0,0 @@
1
- export interface CookieEntry {
2
- name: string;
3
- value: string;
4
- domain: string;
5
- path: string;
6
- secure: boolean;
7
- httpOnly: boolean;
8
- expires: number;
9
- }
10
- export declare function cookiesToNetscape(cookies: CookieEntry[]): string;
11
- export declare function hasSIDCookies(cookiesPath: string): boolean;
12
- /**
13
- * Check if YouTube session cookies have expired by parsing Netscape cookie
14
- * file timestamps (field 5, Unix epoch seconds).
15
- *
16
- * Returns true if: file missing, no session cookies, or ALL session cookies expired.
17
- */
18
- export declare function areCookiesExpired(cookiesPath: string): boolean;
@@ -1,100 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- export function cookiesToNetscape(cookies) {
3
- const lines = [
4
- "# Netscape HTTP Cookie File",
5
- "# https://curl.haxx.se/rfc/cookie_spec.html",
6
- "# This is a generated file! Do not edit.",
7
- "",
8
- ];
9
- for (const c of cookies) {
10
- const domain = c.domain.startsWith(".") ? c.domain : c.domain;
11
- const includeSubdomains = c.domain.startsWith(".") ? "TRUE" : "FALSE";
12
- const secure = c.secure ? "TRUE" : "FALSE";
13
- const expiry = Math.floor(c.expires);
14
- lines.push(`${domain}\t${includeSubdomains}\t${c.path}\t${secure}\t${expiry}\t${c.name}\t${c.value}`);
15
- }
16
- return lines.join("\n") + "\n";
17
- }
18
- function parseCookieRows(content) {
19
- const rows = [];
20
- for (const line of content.split("\n")) {
21
- if (line.startsWith("#") || !line.trim())
22
- continue;
23
- const fields = line.split("\t");
24
- if (fields.length < 7)
25
- continue;
26
- rows.push({
27
- domain: fields[0],
28
- expires: Number(fields[4]),
29
- name: fields[5],
30
- });
31
- }
32
- return rows;
33
- }
34
- function hasCookieTriplet(rows, domainFragment) {
35
- const names = new Set(rows
36
- .filter((row) => row.domain.includes(domainFragment))
37
- .map((row) => row.name));
38
- return names.has("SID") && names.has("HSID") && names.has("SSID");
39
- }
40
- export function hasSIDCookies(cookiesPath) {
41
- if (!existsSync(cookiesPath))
42
- return false;
43
- try {
44
- const content = readFileSync(cookiesPath, "utf8");
45
- const rows = parseCookieRows(content);
46
- if (hasCookieTriplet(rows, "youtube.com")) {
47
- return true;
48
- }
49
- const hasYouTubeLoginInfo = rows.some((row) => row.name === "LOGIN_INFO" && row.domain.includes("youtube.com"));
50
- return hasYouTubeLoginInfo && hasCookieTriplet(rows, "google.");
51
- }
52
- catch {
53
- return false;
54
- }
55
- }
56
- /**
57
- * Cookie names that indicate a valid YouTube session.
58
- * If at least one of these is present and not expired, cookies are valid.
59
- */
60
- const SESSION_COOKIE_PATTERNS = [
61
- "SID", "HSID", "SSID", "APISID", "SAPISID",
62
- "__Secure-1PSID", "__Secure-3PSID",
63
- "__Secure-1PSIDTS", "__Secure-3PSIDTS",
64
- "LOGIN_INFO",
65
- ];
66
- /**
67
- * Check if YouTube session cookies have expired by parsing Netscape cookie
68
- * file timestamps (field 5, Unix epoch seconds).
69
- *
70
- * Returns true if: file missing, no session cookies, or ALL session cookies expired.
71
- */
72
- export function areCookiesExpired(cookiesPath) {
73
- if (!existsSync(cookiesPath))
74
- return true;
75
- try {
76
- const content = readFileSync(cookiesPath, "utf8");
77
- const nowSec = Date.now() / 1000;
78
- const rows = parseCookieRows(content);
79
- let foundAnySession = false;
80
- let foundAnyValid = false;
81
- for (const row of rows) {
82
- if (!SESSION_COOKIE_PATTERNS.includes(row.name))
83
- continue;
84
- foundAnySession = true;
85
- const expiry = row.expires;
86
- // expiry 0 = session cookie (no expiration) — treat as valid
87
- if (expiry === 0 || expiry >= nowSec) {
88
- foundAnyValid = true;
89
- break; // At least one valid session cookie — not expired
90
- }
91
- }
92
- // No session cookies at all = need login; all expired = need refresh
93
- if (!foundAnySession)
94
- return true;
95
- return !foundAnyValid;
96
- }
97
- catch {
98
- return true;
99
- }
100
- }
@@ -1,5 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- type SpawnSyncFn = typeof spawnSync;
3
- export declare function hasFfmpeg(runSpawnSync?: SpawnSyncFn): boolean;
4
- export declare function isRuntimeMissingMessage(binary: "ffmpeg" | "yt-dlp"): string;
5
- export {};
@@ -1,16 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- export function hasFfmpeg(runSpawnSync = spawnSync) {
3
- return hasBinary("ffmpeg", runSpawnSync);
4
- }
5
- function hasBinary(binary, runSpawnSync) {
6
- const result = runSpawnSync(binary, ["-version"], {
7
- stdio: "ignore",
8
- timeout: 30_000,
9
- });
10
- // Why: runtime probing should fail closed when the binary is absent,
11
- // exits non-zero, or times out instead of hanging the caller.
12
- return result.status === 0 && result.error == null;
13
- }
14
- export function isRuntimeMissingMessage(binary) {
15
- return `${binary} is required for media download.`;
16
- }