@mkterswingman/5mghost-yonder 0.0.37 → 0.0.39
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/dist/cli/check.js +6 -2
- package/dist/cli/index.js +14 -1
- package/dist/cli/installSkills.js +21 -12
- package/dist/cli/serve.js +7 -3
- package/dist/cli/setup.js +50 -32
- package/dist/cli/setupCookies.d.ts +1 -4
- package/dist/cli/setupCookies.js +15 -2
- package/dist/cli/uninstall.js +21 -18
- package/dist/contracts/youtubeToolContracts.d.ts +17 -0
- package/dist/contracts/youtubeToolContracts.js +189 -0
- package/dist/server.d.ts +1 -1
- package/dist/tools/downloads.d.ts +1 -1
- package/dist/tools/remote.d.ts +1 -1
- package/dist/tools/remote.js +32 -51
- package/dist/tools/subtitles/cookieSession.d.ts +22 -0
- package/dist/tools/subtitles/cookieSession.js +66 -0
- package/dist/tools/subtitles/download.d.ts +24 -0
- package/dist/tools/subtitles/download.js +169 -0
- package/dist/tools/subtitles/parse.d.ts +1 -0
- package/dist/tools/subtitles/parse.js +106 -0
- package/dist/tools/subtitles.d.ts +5 -26
- package/dist/tools/subtitles.js +7 -389
- package/dist/utils/codeBuddy.d.ts +8 -0
- package/dist/utils/codeBuddy.js +62 -0
- package/dist/utils/cookieRefresh.js +7 -0
- package/dist/utils/launcher.d.ts +6 -11
- package/dist/utils/launcher.js +11 -82
- package/dist/utils/workBuddy.d.ts +8 -0
- package/dist/utils/workBuddy.js +62 -0
- package/package.json +6 -1
- package/dist/auth/oauthFlow.d.ts +0 -9
- package/dist/auth/oauthFlow.js +0 -151
- package/dist/auth/sharedAuth.d.ts +0 -10
- package/dist/auth/sharedAuth.js +0 -31
- package/dist/auth/tokenManager.d.ts +0 -18
- package/dist/auth/tokenManager.js +0 -92
- package/dist/utils/codexInternal.d.ts +0 -9
- package/dist/utils/codexInternal.js +0 -60
- package/dist/utils/mcpRegistration.d.ts +0 -7
- package/dist/utils/mcpRegistration.js +0 -23
- package/dist/utils/openClaw.d.ts +0 -18
- package/dist/utils/openClaw.js +0 -82
- package/dist/utils/skills.d.ts +0 -16
- package/dist/utils/skills.js +0 -56
package/dist/utils/launcher.js
CHANGED
|
@@ -1,90 +1,19 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
1
|
import { PATHS } from "./config.js";
|
|
2
|
+
import { buildLauncherCommand as _buildLauncherCommand, buildLauncherSource as _buildLauncherSource, writeLauncherFile as _writeLauncherFile, isRepairableNpxFailure, } from "@mkterswingman/5mghost-shared-client/launcher";
|
|
3
|
+
export { isRepairableNpxFailure };
|
|
4
4
|
const DEFAULT_PACKAGE_SPEC = "@mkterswingman/5mghost-yonder@latest";
|
|
5
5
|
export function buildLauncherCommand(launcherPath = PATHS.launcherJs) {
|
|
6
|
-
return
|
|
7
|
-
command: "node",
|
|
8
|
-
args: [launcherPath, "serve"],
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
export function isRepairableNpxFailure(stderr) {
|
|
12
|
-
const lower = stderr.toLowerCase();
|
|
13
|
-
const referencesNpxDir = lower.includes("_npx/") || lower.includes("_npx\\");
|
|
14
|
-
return referencesNpxDir && (lower.includes("enotempty") || lower.includes("rename"));
|
|
6
|
+
return _buildLauncherCommand(launcherPath);
|
|
15
7
|
}
|
|
16
8
|
export function buildLauncherSource(options = {}) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import { join } from "node:path";
|
|
22
|
-
import { spawnSync } from "node:child_process";
|
|
23
|
-
|
|
24
|
-
const packageSpec = ${JSON.stringify(packageSpec)};
|
|
25
|
-
const npmCacheDir = ${JSON.stringify(npmCacheDir)};
|
|
26
|
-
const args = process.argv.slice(2);
|
|
27
|
-
const targetArgs = args.length > 0 ? args : ["serve"];
|
|
28
|
-
|
|
29
|
-
function isSkillInstallerMode(subArgs) {
|
|
30
|
-
return subArgs[0] === "install-skills";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isRepairableNpxFailure(stderr) {
|
|
34
|
-
const lower = stderr.toLowerCase();
|
|
35
|
-
return (lower.includes("_npx/") || lower.includes("_npx\\\\"))
|
|
36
|
-
&& (lower.includes("enotempty") || lower.includes("rename"));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function runNpx(subArgs, captureStdErrOnly = false) {
|
|
40
|
-
mkdirSync(npmCacheDir, { recursive: true });
|
|
41
|
-
const npxBin = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
42
|
-
return spawnSync(npxBin, ["--yes", packageSpec, ...subArgs], {
|
|
43
|
-
env: { ...process.env, npm_config_cache: npmCacheDir },
|
|
44
|
-
// Why: probe runs before MCP starts; stdout must stay silent or it will corrupt stdio transport.
|
|
45
|
-
// The skill installer is an explicit one-shot CLI path, so it can inherit stdio safely.
|
|
46
|
-
stdio: captureStdErrOnly ? ["ignore", "ignore", "pipe"] : "inherit",
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function rotateNpxDir() {
|
|
51
|
-
const npxDir = join(npmCacheDir, "_npx");
|
|
52
|
-
if (!existsSync(npxDir)) return false;
|
|
53
|
-
const backup = \`\${npxDir}.bad.\${new Date().toISOString().replace(/[^0-9]/g, "").slice(0, 14)}\`;
|
|
54
|
-
renameSync(npxDir, backup);
|
|
55
|
-
process.stderr.write(\`[yt-mcp launcher] repaired corrupted npx cache: \${backup}\\n\`);
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function ensurePackageReady() {
|
|
60
|
-
const first = runNpx(["version"], true);
|
|
61
|
-
if ((first.status ?? 1) === 0) return;
|
|
62
|
-
|
|
63
|
-
const stderr = first.stderr ? String(first.stderr) : "";
|
|
64
|
-
if (!isRepairableNpxFailure(stderr) || !rotateNpxDir()) {
|
|
65
|
-
if (stderr) process.stderr.write(stderr);
|
|
66
|
-
process.exit(first.status ?? 1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const second = runNpx(["version"], true);
|
|
70
|
-
const secondStderr = second.stderr ? String(second.stderr) : "";
|
|
71
|
-
if ((second.status ?? 1) !== 0) {
|
|
72
|
-
if (secondStderr) process.stderr.write(secondStderr);
|
|
73
|
-
process.exit(second.status ?? 1);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
ensurePackageReady();
|
|
78
|
-
if (isSkillInstallerMode(targetArgs)) {
|
|
79
|
-
process.env.YT_MCP_INSTALL_SKILLS = "1";
|
|
80
|
-
}
|
|
81
|
-
const finalRun = runNpx(targetArgs, false);
|
|
82
|
-
process.exit(finalRun.status ?? 0);
|
|
83
|
-
`;
|
|
9
|
+
return _buildLauncherSource({
|
|
10
|
+
packageSpec: options.packageSpec ?? DEFAULT_PACKAGE_SPEC,
|
|
11
|
+
npmCacheDir: options.npmCacheDir ?? PATHS.npmCacheDir,
|
|
12
|
+
});
|
|
84
13
|
}
|
|
85
14
|
export function writeLauncherFile(options = {}) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
15
|
+
return _writeLauncherFile({
|
|
16
|
+
packageSpec: options.packageSpec ?? DEFAULT_PACKAGE_SPEC,
|
|
17
|
+
npmCacheDir: options.npmCacheDir ?? PATHS.npmCacheDir,
|
|
18
|
+
}, PATHS.launcherJs);
|
|
90
19
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function getWorkBuddyConfigPath(homeDir?: string): string;
|
|
2
|
+
export declare function getWorkBuddySkillsDir(homeDir?: string): string;
|
|
3
|
+
export declare function isWorkBuddyInstallLikelyInstalled(configPath?: string): boolean;
|
|
4
|
+
export declare function writeWorkBuddyConfig(serverName: string, launcherCommand: {
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
}, configPath?: string): "created" | "updated";
|
|
8
|
+
export declare function removeWorkBuddyConfig(serverName: string, configPath?: string): "removed" | "missing";
|
|
@@ -0,0 +1,62 @@
|
|
|
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 getWorkBuddyConfigPath(homeDir = homedir()) {
|
|
5
|
+
return join(homeDir, ".workbuddy", "mcp.json");
|
|
6
|
+
}
|
|
7
|
+
export function getWorkBuddySkillsDir(homeDir = homedir()) {
|
|
8
|
+
return join(homeDir, ".workbuddy", "skills");
|
|
9
|
+
}
|
|
10
|
+
function parseConfig(raw) {
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
throw new Error("WorkBuddy config must be a JSON object");
|
|
16
|
+
}
|
|
17
|
+
export function isWorkBuddyInstallLikelyInstalled(configPath = getWorkBuddyConfigPath()) {
|
|
18
|
+
return existsSync(configPath) || existsSync(dirname(configPath));
|
|
19
|
+
}
|
|
20
|
+
export function writeWorkBuddyConfig(serverName, launcherCommand, configPath = getWorkBuddyConfigPath()) {
|
|
21
|
+
const existingText = existsSync(configPath) ? readFileSync(configPath, "utf8") : null;
|
|
22
|
+
const created = existingText === null;
|
|
23
|
+
const config = existingText ? parseConfig(existingText) : {};
|
|
24
|
+
const mcpServers = config.mcpServers && typeof config.mcpServers === "object"
|
|
25
|
+
? { ...config.mcpServers }
|
|
26
|
+
: {};
|
|
27
|
+
mcpServers[serverName] = {
|
|
28
|
+
command: launcherCommand.command,
|
|
29
|
+
args: [...launcherCommand.args],
|
|
30
|
+
};
|
|
31
|
+
const nextConfig = {
|
|
32
|
+
...config,
|
|
33
|
+
mcpServers,
|
|
34
|
+
};
|
|
35
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
36
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
37
|
+
return created ? "created" : "updated";
|
|
38
|
+
}
|
|
39
|
+
export function removeWorkBuddyConfig(serverName, configPath = getWorkBuddyConfigPath()) {
|
|
40
|
+
if (!existsSync(configPath)) {
|
|
41
|
+
return "missing";
|
|
42
|
+
}
|
|
43
|
+
const config = parseConfig(readFileSync(configPath, "utf8"));
|
|
44
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object" || !(serverName in config.mcpServers)) {
|
|
45
|
+
return "missing";
|
|
46
|
+
}
|
|
47
|
+
const nextMcpServers = { ...config.mcpServers };
|
|
48
|
+
delete nextMcpServers[serverName];
|
|
49
|
+
const nextConfig = { ...config };
|
|
50
|
+
if (Object.keys(nextMcpServers).length === 0) {
|
|
51
|
+
delete nextConfig.mcpServers;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
nextConfig.mcpServers = nextMcpServers;
|
|
55
|
+
}
|
|
56
|
+
if (Object.keys(nextConfig).length === 0) {
|
|
57
|
+
rmSync(configPath, { force: true });
|
|
58
|
+
return "removed";
|
|
59
|
+
}
|
|
60
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
61
|
+
return "removed";
|
|
62
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mkterswingman/5mghost-yonder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.39",
|
|
4
4
|
"description": "Internal MCP client with local data tools and remote API proxy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc -p tsconfig.json",
|
|
14
14
|
"test": "npm run build && node --test tests/*.test.mjs",
|
|
15
|
+
"test:unit": "npm test",
|
|
16
|
+
"test:tool": "npm run build && node --test tests/smoke.test.mjs tests/subtitleTools.test.mjs tests/downloadTools.test.mjs",
|
|
17
|
+
"test:smoke": "npm run build && node dist/cli/index.js --help && node dist/cli/index.js doctor",
|
|
18
|
+
"test:clean-install": "npm run build && node scripts/clean-install-smoke.mjs",
|
|
15
19
|
"prepublishOnly": "npm run build",
|
|
16
20
|
"dev": "tsx src/cli/index.ts",
|
|
17
21
|
"start": "node dist/cli/index.js"
|
|
18
22
|
},
|
|
19
23
|
"dependencies": {
|
|
24
|
+
"@mkterswingman/5mghost-shared-client": "^0.0.2",
|
|
20
25
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
21
26
|
"playwright": "^1.58.0",
|
|
22
27
|
"zod": "^4.3.6"
|
package/dist/auth/oauthFlow.d.ts
DELETED
package/dist/auth/oauthFlow.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
import { randomBytes, createHash } from "node:crypto";
|
|
3
|
-
import { URL } from "node:url";
|
|
4
|
-
import { buildBrowserOpenCommand } from "../utils/browserLaunch.js";
|
|
5
|
-
function base64url(buf) {
|
|
6
|
-
return buf
|
|
7
|
-
.toString("base64")
|
|
8
|
-
.replace(/\+/g, "-")
|
|
9
|
-
.replace(/\//g, "_")
|
|
10
|
-
.replace(/=+$/, "");
|
|
11
|
-
}
|
|
12
|
-
export async function runOAuthFlow(authUrl, options = {}) {
|
|
13
|
-
// 1. Generate PKCE + state
|
|
14
|
-
const codeVerifier = base64url(randomBytes(32));
|
|
15
|
-
const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
|
|
16
|
-
const state = base64url(randomBytes(32));
|
|
17
|
-
// 2. Start temp HTTP server to get the actual port for redirect_uri
|
|
18
|
-
const { server: httpServer, port } = await startCallbackServer();
|
|
19
|
-
const redirectUri = `http://127.0.0.1:${port}`;
|
|
20
|
-
// 3. DCR register client with actual redirect_uri (including port)
|
|
21
|
-
let clientId;
|
|
22
|
-
let clientSecret;
|
|
23
|
-
try {
|
|
24
|
-
const dcrRes = await fetch(`${authUrl}/oauth/register`, {
|
|
25
|
-
method: "POST",
|
|
26
|
-
headers: { "Content-Type": "application/json" },
|
|
27
|
-
body: JSON.stringify({
|
|
28
|
-
client_name: "yt-mcp-cli",
|
|
29
|
-
redirect_uris: [redirectUri],
|
|
30
|
-
}),
|
|
31
|
-
});
|
|
32
|
-
if (!dcrRes.ok) {
|
|
33
|
-
const text = await dcrRes.text().catch(() => "");
|
|
34
|
-
httpServer.close();
|
|
35
|
-
throw new Error(`DCR registration failed: ${dcrRes.status} ${text}`);
|
|
36
|
-
}
|
|
37
|
-
const dcrBody = (await dcrRes.json());
|
|
38
|
-
clientId = dcrBody.client_id;
|
|
39
|
-
clientSecret = dcrBody.client_secret;
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
httpServer.close();
|
|
43
|
-
throw err;
|
|
44
|
-
}
|
|
45
|
-
// 4. Wait for OAuth callback
|
|
46
|
-
return new Promise((resolve, reject) => {
|
|
47
|
-
const timeoutMs = options.timeoutMs ?? 5 * 60 * 1000;
|
|
48
|
-
const timeout = setTimeout(() => {
|
|
49
|
-
httpServer.close();
|
|
50
|
-
reject(new Error(`OAuth flow timed out after ${Math.round(timeoutMs / 1000)}s`));
|
|
51
|
-
}, timeoutMs);
|
|
52
|
-
function cleanup() {
|
|
53
|
-
clearTimeout(timeout);
|
|
54
|
-
httpServer.close();
|
|
55
|
-
}
|
|
56
|
-
httpServer.on("request", async (req, res) => {
|
|
57
|
-
try {
|
|
58
|
-
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
59
|
-
const code = url.searchParams.get("code");
|
|
60
|
-
const error = url.searchParams.get("error");
|
|
61
|
-
const returnedState = url.searchParams.get("state");
|
|
62
|
-
if (error) {
|
|
63
|
-
const safeError = error.replace(/[<>&"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
64
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
65
|
-
res.end(`<h1>Authorization failed</h1><p>${safeError}</p>`);
|
|
66
|
-
cleanup();
|
|
67
|
-
reject(new Error(`OAuth error: ${error}`));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (!code) {
|
|
71
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
72
|
-
res.end("<h1>Waiting for authorization...</h1>");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
// Verify state to prevent CSRF
|
|
76
|
-
if (returnedState !== state) {
|
|
77
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
78
|
-
res.end("<h1>Authorization failed</h1><p>State mismatch — possible CSRF attack.</p>");
|
|
79
|
-
cleanup();
|
|
80
|
-
reject(new Error("OAuth state mismatch"));
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
// Exchange code for tokens
|
|
84
|
-
const tokenBody = {
|
|
85
|
-
grant_type: "authorization_code",
|
|
86
|
-
code,
|
|
87
|
-
redirect_uri: redirectUri,
|
|
88
|
-
client_id: clientId,
|
|
89
|
-
code_verifier: codeVerifier,
|
|
90
|
-
};
|
|
91
|
-
if (clientSecret) {
|
|
92
|
-
tokenBody.client_secret = clientSecret;
|
|
93
|
-
}
|
|
94
|
-
const tokenRes = await fetch(`${authUrl}/oauth/token`, {
|
|
95
|
-
method: "POST",
|
|
96
|
-
headers: { "Content-Type": "application/json" },
|
|
97
|
-
body: JSON.stringify(tokenBody),
|
|
98
|
-
});
|
|
99
|
-
if (!tokenRes.ok) {
|
|
100
|
-
const text = await tokenRes.text().catch(() => "");
|
|
101
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
102
|
-
res.end(`<h1>Token exchange failed</h1><p>${text}</p>`);
|
|
103
|
-
cleanup();
|
|
104
|
-
reject(new Error(`Token exchange failed: ${tokenRes.status} ${text}`));
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const tokens = (await tokenRes.json());
|
|
108
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
109
|
-
res.end("<h1>Authorization successful!</h1><p>You can close this window.</p>");
|
|
110
|
-
cleanup();
|
|
111
|
-
resolve({
|
|
112
|
-
accessToken: tokens.access_token,
|
|
113
|
-
refreshToken: tokens.refresh_token,
|
|
114
|
-
expiresIn: tokens.expires_in,
|
|
115
|
-
clientId,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
res.writeHead(500);
|
|
120
|
-
res.end("Internal error");
|
|
121
|
-
cleanup();
|
|
122
|
-
reject(err);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
// Open browser
|
|
126
|
-
const authorizeUrl = `${authUrl}/oauth/authorize?response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${encodeURIComponent(codeChallenge)}&code_challenge_method=S256&state=${encodeURIComponent(state)}`;
|
|
127
|
-
console.log("\n\x1b[1mOpen this URL in your browser to authorize:\x1b[0m");
|
|
128
|
-
console.log(`\n ${authorizeUrl}\n`);
|
|
129
|
-
import("node:child_process").then(({ execFile }) => {
|
|
130
|
-
const command = buildBrowserOpenCommand(authorizeUrl);
|
|
131
|
-
execFile(command.file, command.args);
|
|
132
|
-
}).catch(() => {
|
|
133
|
-
// ignore — user can open manually
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
/** Start an HTTP server on a random port and return the server + port. */
|
|
138
|
-
async function startCallbackServer() {
|
|
139
|
-
const server = createServer();
|
|
140
|
-
return new Promise((resolve, reject) => {
|
|
141
|
-
server.listen(0, "127.0.0.1", () => {
|
|
142
|
-
const addr = server.address();
|
|
143
|
-
if (!addr || typeof addr === "string") {
|
|
144
|
-
server.close();
|
|
145
|
-
reject(new Error("Failed to start callback server"));
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
resolve({ server, port: addr.port });
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export interface SharedAuthData {
|
|
2
|
-
type: "jwt" | "pat";
|
|
3
|
-
access_token?: string;
|
|
4
|
-
refresh_token?: string;
|
|
5
|
-
expires_at?: number;
|
|
6
|
-
client_id?: string;
|
|
7
|
-
pat?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function readSharedAuth(authPath: string): SharedAuthData | null;
|
|
10
|
-
export declare function writeSharedAuth(authPath: string, data: SharedAuthData): void;
|
package/dist/auth/sharedAuth.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
export function readSharedAuth(authPath) {
|
|
4
|
-
if (!existsSync(authPath))
|
|
5
|
-
return null;
|
|
6
|
-
try {
|
|
7
|
-
return JSON.parse(readFileSync(authPath, "utf8"));
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
export function writeSharedAuth(authPath, data) {
|
|
14
|
-
const authDir = dirname(authPath);
|
|
15
|
-
mkdirSync(authDir, { recursive: true });
|
|
16
|
-
try {
|
|
17
|
-
chmodSync(authDir, 0o700);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
// Why: best-effort hardening; Windows may ignore POSIX-style directory modes.
|
|
21
|
-
}
|
|
22
|
-
const tempPath = `${authPath}.tmp`;
|
|
23
|
-
writeFileSync(tempPath, JSON.stringify(data, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
24
|
-
renameSync(tempPath, authPath);
|
|
25
|
-
try {
|
|
26
|
-
chmodSync(authPath, 0o600);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// Why: chmod is best-effort on Windows and should not block auth persistence.
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
interface TokenManagerOptions {
|
|
2
|
-
authPath?: string;
|
|
3
|
-
env?: NodeJS.ProcessEnv;
|
|
4
|
-
fetchImpl?: typeof fetch;
|
|
5
|
-
}
|
|
6
|
-
export declare class TokenManager {
|
|
7
|
-
private authUrl;
|
|
8
|
-
private authPath;
|
|
9
|
-
private env;
|
|
10
|
-
private fetchImpl;
|
|
11
|
-
constructor(authUrl: string, options?: TokenManagerOptions);
|
|
12
|
-
getValidToken(): Promise<string | null>;
|
|
13
|
-
saveTokens(accessToken: string, refreshToken: string, expiresIn: number, clientId?: string): Promise<void>;
|
|
14
|
-
savePAT(pat: string): Promise<void>;
|
|
15
|
-
private readAuth;
|
|
16
|
-
private refreshTokens;
|
|
17
|
-
}
|
|
18
|
-
export {};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { PATHS } from "../utils/config.js";
|
|
2
|
-
import { readSharedAuth, writeSharedAuth } from "./sharedAuth.js";
|
|
3
|
-
export class TokenManager {
|
|
4
|
-
authUrl;
|
|
5
|
-
authPath;
|
|
6
|
-
env;
|
|
7
|
-
fetchImpl;
|
|
8
|
-
constructor(authUrl, options = {}) {
|
|
9
|
-
this.authUrl = authUrl;
|
|
10
|
-
this.authPath = options.authPath ?? PATHS.sharedAuthJson;
|
|
11
|
-
this.env = options.env ?? process.env;
|
|
12
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
13
|
-
}
|
|
14
|
-
async getValidToken() {
|
|
15
|
-
// Check env var PAT first
|
|
16
|
-
const envPat = this.env.YT_MCP_TOKEN;
|
|
17
|
-
if (envPat)
|
|
18
|
-
return envPat;
|
|
19
|
-
const auth = this.readAuth();
|
|
20
|
-
if (!auth)
|
|
21
|
-
return null;
|
|
22
|
-
if (auth.type === "pat" && auth.pat) {
|
|
23
|
-
return auth.pat;
|
|
24
|
-
}
|
|
25
|
-
if (auth.type === "jwt") {
|
|
26
|
-
if (auth.access_token && auth.expires_at && Date.now() < auth.expires_at) {
|
|
27
|
-
return auth.access_token;
|
|
28
|
-
}
|
|
29
|
-
// Try refresh
|
|
30
|
-
if (auth.refresh_token) {
|
|
31
|
-
try {
|
|
32
|
-
const refreshed = await this.refreshTokens(auth.refresh_token, auth.client_id);
|
|
33
|
-
if (refreshed) {
|
|
34
|
-
await this.saveTokens(refreshed.access_token, refreshed.refresh_token, refreshed.expires_in, auth.client_id);
|
|
35
|
-
return refreshed.access_token;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
// refresh failed → AUTH_EXPIRED
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
async saveTokens(accessToken, refreshToken, expiresIn, clientId) {
|
|
47
|
-
const data = {
|
|
48
|
-
type: "jwt",
|
|
49
|
-
access_token: accessToken,
|
|
50
|
-
refresh_token: refreshToken,
|
|
51
|
-
expires_at: Date.now() + expiresIn * 1000,
|
|
52
|
-
client_id: clientId,
|
|
53
|
-
};
|
|
54
|
-
writeSharedAuth(this.authPath, data);
|
|
55
|
-
}
|
|
56
|
-
async savePAT(pat) {
|
|
57
|
-
const data = {
|
|
58
|
-
type: "pat",
|
|
59
|
-
pat,
|
|
60
|
-
};
|
|
61
|
-
writeSharedAuth(this.authPath, data);
|
|
62
|
-
}
|
|
63
|
-
readAuth() {
|
|
64
|
-
return readSharedAuth(this.authPath);
|
|
65
|
-
}
|
|
66
|
-
async refreshTokens(refreshToken, clientId) {
|
|
67
|
-
const url = `${this.authUrl}/oauth/token`;
|
|
68
|
-
const body = {
|
|
69
|
-
grant_type: "refresh_token",
|
|
70
|
-
refresh_token: refreshToken,
|
|
71
|
-
};
|
|
72
|
-
if (clientId) {
|
|
73
|
-
body.client_id = clientId;
|
|
74
|
-
}
|
|
75
|
-
const res = await this.fetchImpl(url, {
|
|
76
|
-
method: "POST",
|
|
77
|
-
headers: { "Content-Type": "application/json" },
|
|
78
|
-
body: JSON.stringify(body),
|
|
79
|
-
});
|
|
80
|
-
if (!res.ok)
|
|
81
|
-
return null;
|
|
82
|
-
try {
|
|
83
|
-
const body = (await res.json());
|
|
84
|
-
if (!body.access_token)
|
|
85
|
-
return null;
|
|
86
|
-
return body;
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -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,7 +0,0 @@
|
|
|
1
|
-
export declare const MCP_REGISTER_TIMEOUT_MS = 5000;
|
|
2
|
-
export type RegistrationFailureKind = "already_exists" | "authentication" | "timeout" | "other";
|
|
3
|
-
export interface RegistrationFailure {
|
|
4
|
-
kind: RegistrationFailureKind;
|
|
5
|
-
output: string;
|
|
6
|
-
}
|
|
7
|
-
export declare function classifyRegistrationFailure(err: unknown): RegistrationFailure;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export const MCP_REGISTER_TIMEOUT_MS = 5000;
|
|
2
|
-
function readOutput(err) {
|
|
3
|
-
if (!(err instanceof Error))
|
|
4
|
-
return "";
|
|
5
|
-
const stderr = "stderr" in err ? String(err.stderr ?? "") : "";
|
|
6
|
-
const stdout = "stdout" in err ? String(err.stdout ?? "") : "";
|
|
7
|
-
return `${stderr}${stdout}`;
|
|
8
|
-
}
|
|
9
|
-
export function classifyRegistrationFailure(err) {
|
|
10
|
-
const output = readOutput(err);
|
|
11
|
-
const lower = output.toLowerCase();
|
|
12
|
-
if (lower.includes("already exists")) {
|
|
13
|
-
return { kind: "already_exists", output };
|
|
14
|
-
}
|
|
15
|
-
if (lower.includes("waiting for authentication")) {
|
|
16
|
-
return { kind: "authentication", output };
|
|
17
|
-
}
|
|
18
|
-
if (err instanceof Error &&
|
|
19
|
-
("code" in err && err.code === "ETIMEDOUT")) {
|
|
20
|
-
return { kind: "timeout", output };
|
|
21
|
-
}
|
|
22
|
-
return { kind: "other", output };
|
|
23
|
-
}
|
package/dist/utils/openClaw.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { LauncherCommand } from "./launcher.js";
|
|
2
|
-
export interface OpenClawServerConfig {
|
|
3
|
-
transport: "stdio";
|
|
4
|
-
command: string;
|
|
5
|
-
args: string[];
|
|
6
|
-
env?: Record<string, string>;
|
|
7
|
-
}
|
|
8
|
-
export declare function getOpenClawConfigPath(homeDir?: string): string;
|
|
9
|
-
export declare function getOpenClawSkillsDir(homeDir?: string): string;
|
|
10
|
-
export declare function buildOpenClawServerConfig(launcherCommand: LauncherCommand): OpenClawServerConfig;
|
|
11
|
-
export declare function upsertOpenClawConfigText(currentText: string | null, serverName: string, launcherCommand: LauncherCommand): string;
|
|
12
|
-
export declare function removeOpenClawConfigEntryText(currentText: string | null, serverName: string): {
|
|
13
|
-
changed: boolean;
|
|
14
|
-
nextText: string | null;
|
|
15
|
-
};
|
|
16
|
-
export declare function isOpenClawInstallLikelyInstalled(detectBinary: (name: string) => boolean, configPath?: string): boolean;
|
|
17
|
-
export declare function writeOpenClawConfig(serverName: string, launcherCommand: LauncherCommand, configPath?: string): "created" | "updated";
|
|
18
|
-
export declare function removeOpenClawConfig(serverName: string, configPath?: string): "removed" | "missing";
|