@mkterswingman/5mghost-yonder 0.0.17 → 0.0.19

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/index.js CHANGED
@@ -24,6 +24,7 @@ Commands:
24
24
  setup Run first-time setup (OAuth + cookies + MCP registration)
25
25
  serve Start the MCP server (stdio transport)
26
26
  smoke Run installer smoke checks
27
+ install-skills Install the bundled yt-mcp analysis skill into supported AI clients
27
28
  setup-cookies Refresh YouTube cookies using browser login
28
29
  runtime Manage required runtimes
29
30
  check Check auth, runtime, cookies, and connectivity
@@ -81,9 +82,14 @@ async function main() {
81
82
  await runSmoke(process.argv.slice(3));
82
83
  break;
83
84
  }
85
+ case "install-skills": {
86
+ const { runInstallSkills } = await import("./installSkills.js");
87
+ await runInstallSkills();
88
+ break;
89
+ }
84
90
  case "setup-cookies": {
85
- const { runSetupCookies } = await import("./setupCookies.js");
86
- await runSetupCookies();
91
+ const { runSetupCookies, parseSetupCookiesArgs } = await import("./setupCookies.js");
92
+ await runSetupCookies({}, parseSetupCookiesArgs(process.argv.slice(3)));
87
93
  break;
88
94
  }
89
95
  case "uninstall": {
@@ -0,0 +1 @@
1
+ export declare function runInstallSkills(): Promise<void>;
@@ -0,0 +1,39 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { dirname, join } from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { buildSkillInstallPlan, installSkillTarget, } from "../utils/skills.js";
5
+ function detectCli(name) {
6
+ try {
7
+ execFileSync(name, ["--version"], { stdio: "pipe" });
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ function resolvePackageRoot() {
15
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
16
+ }
17
+ export async function runInstallSkills() {
18
+ const packageRoot = resolvePackageRoot();
19
+ const availableCliNames = [
20
+ "claude-internal",
21
+ "claude",
22
+ "codex-internal",
23
+ "codex",
24
+ "gemini-internal",
25
+ "gemini",
26
+ ].filter(detectCli);
27
+ const plan = buildSkillInstallPlan(packageRoot, {
28
+ availableCliNames,
29
+ includeOpenClaw: process.env.YT_MCP_INSTALL_OPENCLAW_SKILL === "1",
30
+ });
31
+ if (plan.length === 0) {
32
+ console.log("[yt-mcp] No supported AI client detected for bundled skill install.");
33
+ return;
34
+ }
35
+ for (const target of plan) {
36
+ installSkillTarget(target);
37
+ console.log(`[yt-mcp] Installed bundled skill for ${target.label}: ${target.targetDir}`);
38
+ }
39
+ }
package/dist/cli/setup.js CHANGED
@@ -6,6 +6,7 @@ import { runOAuthFlow } from "../auth/oauthFlow.js";
6
6
  import { hasSIDCookies } from "../utils/cookies.js";
7
7
  import { buildLauncherCommand, writeLauncherFile } from "../utils/launcher.js";
8
8
  import { checkAll } from "../runtime/installers.js";
9
+ import { runInstallSkills } from "./installSkills.js";
9
10
  import { getOpenClawConfigPath, isOpenClawInstallLikelyInstalled, writeOpenClawConfig, } from "../utils/openClaw.js";
10
11
  import { getCodexInternalConfigPath, writeCodexInternalConfig, } from "../utils/codexInternal.js";
11
12
  import { MCP_REGISTER_TIMEOUT_MS, classifyRegistrationFailure, } from "../utils/mcpRegistration.js";
@@ -122,6 +123,9 @@ export function getCookieSetupDeferredHint() {
122
123
  ];
123
124
  }
124
125
  export async function runSetup() {
126
+ const installerMode = process.env.YT_MCP_INSTALLER_MODE === "1";
127
+ const skipCookieStep = installerMode || process.env.YT_MCP_SETUP_SKIP_COOKIES === "1";
128
+ const quietSummary = installerMode || process.env.YT_MCP_SETUP_QUIET_SUMMARY === "1";
125
129
  console.log("\nšŸš€ yt-mcp setup\n");
126
130
  ensureConfigDir();
127
131
  const hasBrowser = canOpenBrowser();
@@ -235,7 +239,10 @@ export async function runSetup() {
235
239
  }
236
240
  // ── Step 4: YouTube Cookies ──
237
241
  console.log("Step 4/5: YouTube cookies...");
238
- if (!hasBrowser) {
242
+ if (skipCookieStep) {
243
+ console.log(" ā„¹ļø Deferred to installer cookie flow");
244
+ }
245
+ else if (!hasBrowser) {
239
246
  for (const line of getCookieSetupDeferredHint()) {
240
247
  console.log(line);
241
248
  }
@@ -261,6 +268,7 @@ export async function runSetup() {
261
268
  console.log("Step 5/5: Registering MCP in AI clients...");
262
269
  const launcherCommand = buildLauncherCommand();
263
270
  let registered = false;
271
+ let skillsInstalled = false;
264
272
  for (const { bin, label, command } of buildSetupCliCandidates({
265
273
  file: launcherCommand.command,
266
274
  args: launcherCommand.args,
@@ -293,6 +301,14 @@ export async function runSetup() {
293
301
  console.log(` āš ļø Codex CLI (internal) auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
294
302
  }
295
303
  }
304
+ try {
305
+ process.env.YT_MCP_INSTALL_OPENCLAW_SKILL = isOpenClawInstallLikelyInstalled(detectCli) ? "1" : "0";
306
+ await runInstallSkills();
307
+ skillsInstalled = true;
308
+ }
309
+ catch (err) {
310
+ console.log(` āš ļø Bundled skill install failed: ${err instanceof Error ? err.message : String(err)}`);
311
+ }
296
312
  if (!registered) {
297
313
  console.log(" ā„¹ļø No supported CLI found. Add manually to your AI client:");
298
314
  }
@@ -323,18 +339,23 @@ export async function runSetup() {
323
339
  }
324
340
  `);
325
341
  console.log(` OpenClaw uses ${PATHS.sharedAuthJson} for PAT/JWT, so env.YT_MCP_TOKEN is optional after setup.`);
342
+ if (skillsInstalled) {
343
+ console.log(" āœ… Installed bundled yt-mcp analysis skill for detected AI clients");
344
+ }
326
345
  console.log(" Media downloads:");
327
346
  console.log(" - `start_download_job` / `poll_download_job` are job-based local tools");
328
347
  console.log(" - Batch limit: 5 YouTube videos per job");
329
348
  console.log(" - Output path: ~/Downloads/yt-mcp/YYYY-MM-DD_<video_id>");
330
349
  console.log(" - `ffmpeg` is required for video download modes");
331
350
  console.log("");
332
- console.log("āœ… Setup complete!");
333
- if (hasBrowser) {
334
- console.log(' Open your AI client and try: "搜瓢 Python 教程"');
335
- }
336
- else {
337
- console.log(" Set YT_MCP_TOKEN in your MCP env config, then restart your AI client.");
351
+ if (!quietSummary) {
352
+ console.log("āœ… Setup complete!");
353
+ if (hasBrowser) {
354
+ console.log(' Open your AI client and try: "搜瓢 Python 教程"');
355
+ }
356
+ else {
357
+ console.log(" Set YT_MCP_TOKEN in your MCP env config, then restart your AI client.");
358
+ }
359
+ console.log("");
338
360
  }
339
- console.log("");
340
361
  }
@@ -51,6 +51,9 @@ export interface SetupCookiesDeps {
51
51
  runManualCookieSetup: typeof runManualCookieSetup;
52
52
  log: (message: string) => void;
53
53
  }
54
+ export interface SetupCookiesOptions {
55
+ importOnly?: boolean;
56
+ }
54
57
  type SetupCookiesChromium = Awaited<ReturnType<SetupCookiesDeps["loadChromium"]>>;
55
58
  interface CdpCookie {
56
59
  name: string;
@@ -76,5 +79,6 @@ export declare function runManualCookieSetup(chromium: SetupCookiesChromium, dep
76
79
  /**
77
80
  * Interactive cookie setup — opens a visible browser for user to log in.
78
81
  */
79
- export declare function runSetupCookies(overrides?: Partial<SetupCookiesDeps>): Promise<void>;
82
+ export declare function parseSetupCookiesArgs(argv: string[]): SetupCookiesOptions;
83
+ export declare function runSetupCookies(overrides?: Partial<SetupCookiesDeps>, options?: SetupCookiesOptions): Promise<void>;
80
84
  export {};
@@ -361,7 +361,12 @@ export async function runManualCookieSetup(chromium, deps) {
361
361
  /**
362
362
  * Interactive cookie setup — opens a visible browser for user to log in.
363
363
  */
364
- export async function runSetupCookies(overrides = {}) {
364
+ export function parseSetupCookiesArgs(argv) {
365
+ return {
366
+ importOnly: argv.includes("--import-only"),
367
+ };
368
+ }
369
+ export async function runSetupCookies(overrides = {}, options = {}) {
365
370
  const deps = buildSetupCookiesDeps(overrides);
366
371
  deps.log("\nšŸŖ YouTube Cookie Setup\n");
367
372
  deps.ensureConfigDir();
@@ -376,5 +381,8 @@ export async function runSetupCookies(overrides = {}) {
376
381
  if (imported) {
377
382
  return;
378
383
  }
384
+ if (options.importOnly) {
385
+ throw new Error("No reusable YouTube session found in local Chrome/Edge profiles");
386
+ }
379
387
  await deps.runManualCookieSetup(chromium, deps);
380
388
  }
@@ -26,6 +26,10 @@ const npmCacheDir = ${JSON.stringify(npmCacheDir)};
26
26
  const args = process.argv.slice(2);
27
27
  const targetArgs = args.length > 0 ? args : ["serve"];
28
28
 
29
+ function isSkillInstallerMode(subArgs) {
30
+ return subArgs[0] === "install-skills";
31
+ }
32
+
29
33
  function isRepairableNpxFailure(stderr) {
30
34
  const lower = stderr.toLowerCase();
31
35
  return (lower.includes("_npx/") || lower.includes("_npx\\\\"))
@@ -38,6 +42,7 @@ function runNpx(subArgs, captureStdErrOnly = false) {
38
42
  return spawnSync(npxBin, ["--yes", packageSpec, ...subArgs], {
39
43
  env: { ...process.env, npm_config_cache: npmCacheDir },
40
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.
41
46
  stdio: captureStdErrOnly ? ["ignore", "ignore", "pipe"] : "inherit",
42
47
  });
43
48
  }
@@ -70,6 +75,9 @@ function ensurePackageReady() {
70
75
  }
71
76
 
72
77
  ensurePackageReady();
78
+ if (isSkillInstallerMode(targetArgs)) {
79
+ process.env.YT_MCP_INSTALL_SKILLS = "1";
80
+ }
73
81
  const finalRun = runNpx(targetArgs, false);
74
82
  process.exit(finalRun.status ?? 0);
75
83
  `;
@@ -6,6 +6,7 @@ export interface OpenClawServerConfig {
6
6
  env?: Record<string, string>;
7
7
  }
8
8
  export declare function getOpenClawConfigPath(homeDir?: string): string;
9
+ export declare function getOpenClawSkillsDir(homeDir?: string): string;
9
10
  export declare function buildOpenClawServerConfig(launcherCommand: LauncherCommand): OpenClawServerConfig;
10
11
  export declare function upsertOpenClawConfigText(currentText: string | null, serverName: string, launcherCommand: LauncherCommand): string;
11
12
  export declare function removeOpenClawConfigEntryText(currentText: string | null, serverName: string): {
@@ -4,6 +4,9 @@ import { dirname, join } from "node:path";
4
4
  export function getOpenClawConfigPath(homeDir = homedir()) {
5
5
  return join(homeDir, ".openclaw", "workspace", "config", "mcporter.json");
6
6
  }
7
+ export function getOpenClawSkillsDir(homeDir = homedir()) {
8
+ return join(homeDir, ".openclaw", "skills");
9
+ }
7
10
  export function buildOpenClawServerConfig(launcherCommand) {
8
11
  return {
9
12
  transport: "stdio",
@@ -0,0 +1,16 @@
1
+ import type { LauncherCommand } from "./launcher.js";
2
+ export interface SkillInstallTarget {
3
+ client: string;
4
+ label: string;
5
+ sourceDir: string;
6
+ targetDir: string;
7
+ }
8
+ export interface SkillInstallPlanOptions {
9
+ homeDir?: string;
10
+ availableCliNames?: string[];
11
+ includeOpenClaw?: boolean;
12
+ }
13
+ export declare function getSkillPackageSourcePath(packageRoot: string): string;
14
+ export declare function buildSkillLauncherCommand(launcherPath: string): LauncherCommand;
15
+ export declare function buildSkillInstallPlan(packageRoot: string, options?: SkillInstallPlanOptions): SkillInstallTarget[];
16
+ export declare function installSkillTarget(target: SkillInstallTarget): void;
@@ -0,0 +1,56 @@
1
+ import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { getOpenClawSkillsDir } from "./openClaw.js";
5
+ const SKILL_NAME = "use-yt-mcp";
6
+ export function getSkillPackageSourcePath(packageRoot) {
7
+ return join(packageRoot, "skills", SKILL_NAME);
8
+ }
9
+ export function buildSkillLauncherCommand(launcherPath) {
10
+ return {
11
+ command: "node",
12
+ args: [launcherPath, "install-skills"],
13
+ };
14
+ }
15
+ export function buildSkillInstallPlan(packageRoot, options = {}) {
16
+ const homeDir = options.homeDir ?? homedir();
17
+ const availableCliNames = new Set(options.availableCliNames ?? []);
18
+ const sourceDir = getSkillPackageSourcePath(packageRoot);
19
+ const plan = [];
20
+ const cliTargets = [
21
+ { client: "claude-internal", label: "Claude Code (internal)", dir: join(homeDir, ".claude-internal", "skills") },
22
+ { client: "claude", label: "Claude Code", dir: join(homeDir, ".claude", "skills") },
23
+ { client: "codex-internal", label: "Codex CLI (internal)", dir: join(homeDir, ".codex-internal", "skills") },
24
+ { client: "codex", label: "Codex CLI / Codex App", dir: join(homeDir, ".codex", "skills") },
25
+ { client: "gemini-internal", label: "Gemini CLI (internal)", dir: join(homeDir, ".gemini-internal", "skills") },
26
+ { client: "gemini", label: "Gemini CLI", dir: join(homeDir, ".gemini", "skills") },
27
+ ];
28
+ for (const target of cliTargets) {
29
+ if (!availableCliNames.has(target.client)) {
30
+ continue;
31
+ }
32
+ plan.push({
33
+ client: target.client,
34
+ label: target.label,
35
+ sourceDir,
36
+ targetDir: join(target.dir, SKILL_NAME),
37
+ });
38
+ }
39
+ if (options.includeOpenClaw) {
40
+ plan.push({
41
+ client: "openclaw",
42
+ label: "OpenClaw",
43
+ sourceDir,
44
+ targetDir: join(getOpenClawSkillsDir(homeDir), SKILL_NAME),
45
+ });
46
+ }
47
+ return plan;
48
+ }
49
+ export function installSkillTarget(target) {
50
+ if (!existsSync(target.sourceDir)) {
51
+ throw new Error(`Bundled skill not found: ${target.sourceDir}`);
52
+ }
53
+ mkdirSync(dirname(target.targetDir), { recursive: true });
54
+ rmSync(target.targetDir, { recursive: true, force: true });
55
+ cpSync(target.sourceDir, target.targetDir, { recursive: true });
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mkterswingman/5mghost-yonder",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Internal MCP client with local data tools and remote API proxy",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "files": [
36
36
  "dist/",
37
+ "skills/",
37
38
  "scripts/download-ytdlp.mjs",
38
39
  "scripts/install.sh",
39
40
  "scripts/install.ps1",
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: use-yt-mcp
3
+ preamble-tier: 3
4
+ version: 1.2.0
5
+ description: |
6
+ Use when the user wants YouTube analysis through yt-mcp: channel performance,
7
+ video stats, subtitles, comments, trending, or batch YouTube data work.
8
+ Also use when the user pastes YouTube links such as youtube.com/watch,
9
+ youtube.com/shorts, youtube.com/live, or youtu.be URLs.
10
+ Keywords: YouTube, 频道, 视频, 字幕, 评论, å‡ę’­, ę’­ę”¾é‡, trending, yt-mcp, youtube.com, youtu.be.
11
+ ---
12
+
13
+ # Use yt-mcp
14
+
15
+ Focus on YouTube analysis with yt-mcp MCP tools. Answer the analysis question first. Only switch into setup or troubleshooting when the user explicitly asks for it or a tool call fails.
16
+
17
+ ## 0. Hard Constraints
18
+
19
+ - For YouTube data tasks, always use `mcp__yt-mcp__*` tools first.
20
+ - Do not route YouTube queries to legacy MCPs or legacy setup skills such as `youtube-data`, `mcp_youtube_data`, `use-youtube-data-mcp`, or `Use YouTube Data MCP` when `yt-mcp` can answer.
21
+ - Do not fetch YouTube metrics, subtitles, comments, channel data, or trending data by browsing the web, scraping pages, or using generic web search. Use `yt-mcp` instead.
22
+ - If `yt-mcp` is unavailable or fails, stop and tell the user it needs setup or troubleshooting. Do not silently fall back to old MCPs or ad-hoc web lookup.
23
+
24
+ ## Trigger Hints
25
+
26
+ - Treat pasted YouTube links as an automatic trigger for this skill, even if the user does not explicitly say "YouTube".
27
+ - Common URL forms: `https://www.youtube.com/watch?v=...`, `https://youtu.be/...`, `https://www.youtube.com/shorts/...`, `https://www.youtube.com/live/...`.
28
+
29
+ ## 1. Intent -> Tool Routing
30
+
31
+ | User Intent | Tool(s) |
32
+ |---|---|
33
+ | Search videos by keyword | `search_videos` |
34
+ | Get video metrics in bulk | `get_video_stats` |
35
+ | Large video stats job | `start_video_stats_job` -> `poll_video_stats_job` |
36
+ | Get subtitles / transcript | `get_subtitles`, `batch_get_subtitles` |
37
+ | Check subtitle languages | `list_available_subtitles` |
38
+ | Get comments | `get_comments` |
39
+ | Large comments export | `start_comments_job` -> `poll_comments_job` |
40
+ | Get comment replies | `get_comment_replies` |
41
+ | Channel stats | `get_channel_stats` |
42
+ | List channel uploads | `list_channel_uploads` |
43
+ | Trending videos | `get_trending` |
44
+ | Local download | `start_download_job` -> `poll_download_job` |
45
+
46
+ All tools are prefixed `mcp__yt-mcp__` in actual calls.
47
+
48
+ ## 2. Analysis Rules
49
+
50
+ - Prefer batch-capable tools when comparing multiple videos or channels.
51
+ - `get_video_stats` accepts up to 1000 items. If the response returns `mode: "file"`, then inline `preview_items` is only a preview sample from a larger dataset and must not be used as the full basis of analysis. Tell the user the complete result is in `output_file` or `download_url`.
52
+ - For large video/comment analyses where completeness matters, prefer async jobs: `start_video_stats_job` -> `poll_video_stats_job`, or `start_comments_job` -> `poll_comments_job`.
53
+ - When using async jobs, poll until status is `completed`, and tell the user if the result is still partial or in progress.
54
+ - Use normalized response fields in analysis: `published_at`, `duration`, `view_count`, `like_count`, `comment_count`, `is_live_replay`.
55
+ - Default channel analysis window to 30 days unless the user specifies another range.
56
+ - Shorts heuristic: parsed ISO 8601 `duration` <= 90 seconds. Live replay: `is_live_replay === true`. Otherwise treat as long-form.
57
+ - Follow the user's language. Use thousand separators like `1,234,567`; avoid `K/M/B` unless the user asks.
58
+ - Do not dump raw JSON unless the user explicitly wants raw output.
59
+
60
+ ## 3. Common Workflows
61
+
62
+ ### Channel Average Views
63
+
64
+ 1. `list_channel_uploads(channel or channel_id, max_results=N)`
65
+ 2. For moderate sets, `get_video_stats(videos=[...ids])`; for large sets or when full coverage matters, `start_video_stats_job(videos=[...ids])` then `poll_video_stats_job(job_id)` until completed
66
+ 3. Filter by `published_at` within the requested window
67
+ 4. Segment into long-form, Shorts, live replay
68
+ 5. Report video count, total views, average views, max, min
69
+
70
+ Default output:
71
+
72
+ ```md
73
+ ## {频道名} å‡ę’­åˆ†ęžļ¼ˆčæ‘ {N} 天)
74
+
75
+ | ē±»åž‹ | 视频数 | 总播放 | å‡ę’­ | ęœ€é«˜ | ęœ€ä½Ž |
76
+ |---|---:|---:|---:|---:|---:|
77
+ | 长视频 | 12 | 1,234,567 | 102,880 | 456,789 | 12,345 |
78
+ | Shorts | 24 | 2,345,678 | 97,736 | 234,567 | 23,456 |
79
+ | ē›“ę’­å›žę”¾ | 3 | 345,678 | 115,226 | 200,000 | 45,678 |
80
+ ```
81
+
82
+ ### Video Summary
83
+
84
+ 1. `get_subtitles(video, format="csv")`
85
+ 2. Build a timestamped summary from the transcript
86
+ 3. Output section headers with timestamps, not a single long paragraph
87
+
88
+ ### Comment Analysis
89
+
90
+ 1. For normal analysis, `get_comments(video, max_comments=N, order="relevance")`; for larger exports, `start_comments_job` then `poll_comments_job`
91
+ 2. Cluster by sentiment or topic
92
+ 3. Quote representative comments and keep like counts when useful
93
+
94
+ ### Channel Stability
95
+
96
+ 1. Fetch uploads and stats
97
+ 2. Measure posting frequency, average views, and volatility
98
+ 3. Compare recent videos vs earlier videos to judge trend direction
99
+
100
+ ## 4. Output Rules
101
+
102
+ - Answer the user's actual question first, then show supporting metrics.
103
+ - For comparisons, prefer compact tables over long prose.
104
+ - State assumptions when the time window, sample size, or language is inferred.
105
+ - If data is partial, say so directly.
106
+
107
+ ## 5. Setup Boundary
108
+
109
+ Only discuss CLI when the user asks to install or troubleshoot yt-mcp, or when a tool call fails because of auth/cookies/runtime.
110
+
111
+ Useful commands:
112
+
113
+ - `yt-mcp setup`
114
+ - `yt-mcp setup-cookies`
115
+ - `yt-mcp check`
116
+
117
+ Do not turn a normal analysis request into a setup checklist.