@mkterswingman/5mghost-yonder 0.0.38 → 0.0.40

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
@@ -28,6 +28,7 @@ Commands:
28
28
  setup-cookies Refresh YouTube cookies using browser login
29
29
  runtime Manage required runtimes
30
30
  check Check auth, runtime, cookies, and connectivity
31
+ doctor Alias for check; diagnose local install and runtime health
31
32
  uninstall Remove MCP registrations and local yt-mcp config
32
33
  update Update the main package and required runtimes
33
34
  version Show current version
@@ -70,6 +71,13 @@ export async function runUnifiedUpdate(deps) {
70
71
  const command = process.argv[2];
71
72
  async function main() {
72
73
  switch (command) {
74
+ case undefined:
75
+ case "help":
76
+ case "--help":
77
+ case "-h": {
78
+ console.log(buildHelpText(getVersion()));
79
+ break;
80
+ }
73
81
  case "setup": {
74
82
  const { runSetup } = await import("./setup.js");
75
83
  await runSetup();
@@ -118,6 +126,11 @@ async function main() {
118
126
  await runCheck();
119
127
  break;
120
128
  }
129
+ case "doctor": {
130
+ const { runCheck } = await import("./check.js");
131
+ await runCheck();
132
+ break;
133
+ }
121
134
  case "update": {
122
135
  const current = getVersion();
123
136
  console.log(`Current version: ${current}`);
@@ -165,7 +178,7 @@ async function main() {
165
178
  }
166
179
  default:
167
180
  console.log(buildHelpText(getVersion()));
168
- process.exit(command ? 1 : 0);
181
+ process.exit(1);
169
182
  }
170
183
  }
171
184
  main().catch((err) => {
@@ -2,6 +2,8 @@ import { fileURLToPath } from "node:url";
2
2
  import { dirname, join } from "node:path";
3
3
  import { buildSkillInstallPlan, installSkillTarget, } from "@mkterswingman/5mghost-shared-client/registration";
4
4
  import { detectCli } from "@mkterswingman/5mghost-shared-client";
5
+ import { getWorkBuddySkillsDir, isWorkBuddyInstallLikelyInstalled } from "../utils/workBuddy.js";
6
+ import { getCodeBuddySkillsDir, isCodeBuddyInstallLikelyInstalled } from "../utils/codeBuddy.js";
5
7
  function resolvePackageRoot() {
6
8
  return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
7
9
  }
@@ -19,6 +21,22 @@ export async function runInstallSkills() {
19
21
  availableCliNames,
20
22
  includeOpenClaw: process.env.YT_MCP_INSTALL_OPENCLAW_SKILL === "1",
21
23
  });
24
+ if (isWorkBuddyInstallLikelyInstalled() && !plan.some((target) => target.client === "workbuddy")) {
25
+ plan.push({
26
+ client: "workbuddy",
27
+ label: "WorkBuddy",
28
+ sourceDir: join(packageRoot, "skills", "use-yt-mcp"),
29
+ targetDir: join(getWorkBuddySkillsDir(), "use-yt-mcp"),
30
+ });
31
+ }
32
+ if (isCodeBuddyInstallLikelyInstalled() && !plan.some((target) => target.client === "codebuddy")) {
33
+ plan.push({
34
+ client: "codebuddy",
35
+ label: "CodeBuddy",
36
+ sourceDir: join(packageRoot, "skills", "use-yt-mcp"),
37
+ targetDir: join(getCodeBuddySkillsDir(), "use-yt-mcp"),
38
+ });
39
+ }
22
40
  if (plan.length === 0) {
23
41
  console.log("[yt-mcp] No supported AI client detected for bundled skill install.");
24
42
  return;
package/dist/cli/setup.js CHANGED
@@ -7,6 +7,8 @@ import { runOAuthFlow } from "@mkterswingman/5mghost-shared-client/auth";
7
7
  import { hasSIDCookies } from "../utils/cookies.js";
8
8
  import { buildLauncherCommand, writeLauncherFile } from "../utils/launcher.js";
9
9
  import { buildBrowserOpenCommand } from "../utils/browserLaunch.js";
10
+ import { getWorkBuddyConfigPath, isWorkBuddyInstallLikelyInstalled, writeWorkBuddyConfig, } from "../utils/workBuddy.js";
11
+ import { getCodeBuddyConfigPath, isCodeBuddyInstallLikelyInstalled, writeCodeBuddyConfig, } from "../utils/codeBuddy.js";
10
12
  import { checkAll } from "../runtime/installers.js";
11
13
  import { runInstallSkills } from "./installSkills.js";
12
14
  import { registerCliCandidates, registerOpenClaw, } from "@mkterswingman/5mghost-shared-client/registration";
@@ -400,6 +402,28 @@ export async function runSetup() {
400
402
  console.log(` ⚠️ Codex CLI (internal) auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
401
403
  }
402
404
  }
405
+ if (isWorkBuddyInstallLikelyInstalled(getWorkBuddyConfigPath())) {
406
+ try {
407
+ const status = writeWorkBuddyConfig("yt-mcp", launcherCommand);
408
+ const suffix = status === "created" ? "created" : "updated";
409
+ console.log(` ✅ MCP registered in WorkBuddy (${suffix} ${getWorkBuddyConfigPath()})`);
410
+ registered = true;
411
+ }
412
+ catch (err) {
413
+ console.log(` ⚠️ WorkBuddy auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
414
+ }
415
+ }
416
+ if (isCodeBuddyInstallLikelyInstalled(getCodeBuddyConfigPath())) {
417
+ try {
418
+ const status = writeCodeBuddyConfig("yt-mcp", launcherCommand);
419
+ const suffix = status === "created" ? "created" : "updated";
420
+ console.log(` ✅ MCP registered in CodeBuddy (${suffix} ${getCodeBuddyConfigPath()})`);
421
+ registered = true;
422
+ }
423
+ catch (err) {
424
+ console.log(` ⚠️ CodeBuddy auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
425
+ }
426
+ }
403
427
  let skillsInstalled = false;
404
428
  try {
405
429
  process.env.YT_MCP_INSTALL_OPENCLAW_SKILL = isOpenClawInstallLikelyInstalled(detectCli) ? "1" : "0";
@@ -441,6 +465,8 @@ export async function runSetup() {
441
465
  console.log(" OpenClaw stdio CLI (recommended):");
442
466
  console.log(` mcporter config add yt-mcp --command node --arg ${JSON.stringify(PATHS.launcherJs)} --arg serve`);
443
467
  console.log(` OpenClaw uses ${PATHS.sharedAuthJson} for PAT/JWT, so env.YT_MCP_TOKEN is optional after setup.`);
468
+ console.log(` WorkBuddy MCP config: ${getWorkBuddyConfigPath()}`);
469
+ console.log(` CodeBuddy MCP config: ${getCodeBuddyConfigPath()}`);
444
470
  if (skillsInstalled) {
445
471
  console.log(" ✅ Installed bundled yt-mcp analysis skill for detected AI clients");
446
472
  }
@@ -76,9 +76,6 @@ export declare function readCdpCookiesUntilSession(client: CdpCookieClient, deps
76
76
  export declare function tryImportBrowserCookies(chromium: SetupCookiesChromium, deps: SetupCookiesDeps): Promise<boolean>;
77
77
  export declare function readImportedBrowserCookies(candidate: BrowserProfileCandidate, chromium: SetupCookiesChromium, deps: SetupCookiesDeps): Promise<PlaywrightCookie[] | null>;
78
78
  export declare function runManualCookieSetup(chromium: SetupCookiesChromium, deps: SetupCookiesDeps): Promise<void>;
79
- /**
80
- * Interactive cookie setup — opens a visible browser for user to log in.
81
- */
82
- export declare function parseSetupCookiesArgs(argv: string[]): void;
79
+ export declare function parseSetupCookiesArgs(argv: string[], warn?: (message: string) => void): void;
83
80
  export declare function runSetupCookies(overrides?: Partial<SetupCookiesDeps>): Promise<void>;
84
81
  export {};
@@ -412,9 +412,22 @@ export async function runManualCookieSetup(chromium, deps) {
412
412
  /**
413
413
  * Interactive cookie setup — opens a visible browser for user to log in.
414
414
  */
415
- export function parseSetupCookiesArgs(argv) {
415
+ const LEGACY_SETUP_COOKIES_FLAGS = new Set(["--import-only", "--headed"]);
416
+ export function parseSetupCookiesArgs(argv, warn = console.warn) {
417
+ const legacyFlags = [];
416
418
  for (const arg of argv) {
417
- throw new Error(`Unknown option for setup-cookies: ${arg}. setup-cookies accepts no options.`);
419
+ if (LEGACY_SETUP_COOKIES_FLAGS.has(arg)) {
420
+ if (!legacyFlags.includes(arg)) {
421
+ legacyFlags.push(arg);
422
+ }
423
+ continue;
424
+ }
425
+ throw new Error(`Unknown option for setup-cookies: ${arg}. Supported legacy flags: --import-only, --headed.`);
426
+ }
427
+ if (legacyFlags.length > 0) {
428
+ warn(`[DEPRECATED] setup-cookies now runs a single guided flow. ` +
429
+ `Ignoring legacy flags: ${legacyFlags.join(", ")}. ` +
430
+ `Please run: yt-mcp setup-cookies`);
418
431
  }
419
432
  }
420
433
  export async function runSetupCookies(overrides = {}) {
@@ -1,6 +1,8 @@
1
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
+ import { getWorkBuddyConfigPath, removeWorkBuddyConfig } from "../utils/workBuddy.js";
5
+ import { getCodeBuddyConfigPath, removeCodeBuddyConfig } from "../utils/codeBuddy.js";
4
6
  import { getOpenClawConfigPath, getCodexInternalConfigPath, removeCodexInternalConfig, unregisterOpenClaw, detectCli, } from "@mkterswingman/5mghost-shared-client";
5
7
  import { unregisterCliCandidates } from "@mkterswingman/5mghost-shared-client/registration";
6
8
  function tryRemoveMcp(command, label) {
@@ -72,6 +74,20 @@ export async function runUninstall() {
72
74
  console.log(" ℹ️ Codex CLI (internal) did not have yt-mcp registered");
73
75
  }
74
76
  }
77
+ const workBuddyStatus = removeWorkBuddyConfig("yt-mcp");
78
+ if (workBuddyStatus === "removed") {
79
+ console.log(` ✅ Removed MCP registration from WorkBuddy (${getWorkBuddyConfigPath()})`);
80
+ }
81
+ else {
82
+ console.log(" ℹ️ WorkBuddy did not have yt-mcp registered");
83
+ }
84
+ const codeBuddyStatus = removeCodeBuddyConfig("yt-mcp");
85
+ if (codeBuddyStatus === "removed") {
86
+ console.log(` ✅ Removed MCP registration from CodeBuddy (${getCodeBuddyConfigPath()})`);
87
+ }
88
+ else {
89
+ console.log(" ℹ️ CodeBuddy did not have yt-mcp registered");
90
+ }
75
91
  if (existsSync(PATHS.configDir)) {
76
92
  // Why: ~/.yt-mcp contains launcher, token cache, cookies, and npm cache owned by this package.
77
93
  rmSync(PATHS.configDir, { recursive: true, force: true });
@@ -0,0 +1,17 @@
1
+ export type YouTubeToolHttpMethod = "GET" | "POST";
2
+ export interface YouTubeToolAliasContract {
3
+ name: string;
4
+ remove_after_version: string;
5
+ remove_after_date: string;
6
+ note: string;
7
+ }
8
+ export interface YouTubeToolContract {
9
+ id: string;
10
+ remote_tool_name: string;
11
+ mcp_primary_name: string;
12
+ mcp_aliases: YouTubeToolAliasContract[];
13
+ http_path: string;
14
+ http_method: YouTubeToolHttpMethod;
15
+ }
16
+ export declare const YOUTUBE_TOOL_CONTRACTS: YouTubeToolContract[];
17
+ export declare function getYouTubeToolContractById(id: string): YouTubeToolContract;
@@ -0,0 +1,189 @@
1
+ // AUTO-GENERATED by scripts/sync-youtube-tool-contracts.mjs
2
+ // Source of truth: contracts/youtube-tool-contracts.json
3
+ // Do not edit manually.
4
+ export const YOUTUBE_TOOL_CONTRACTS = [
5
+ {
6
+ "id": "search_videos",
7
+ "remote_tool_name": "search_videos",
8
+ "mcp_primary_name": "search_videos",
9
+ "mcp_aliases": [],
10
+ "http_path": "/api/search",
11
+ "http_method": "POST"
12
+ },
13
+ {
14
+ "id": "get_video_stats",
15
+ "remote_tool_name": "get_video_stats",
16
+ "mcp_primary_name": "get_video_stats",
17
+ "mcp_aliases": [
18
+ {
19
+ "name": "get_video_stats_bulk_sync",
20
+ "remove_after_version": "0.2.0",
21
+ "remove_after_date": "2026-12-31",
22
+ "note": "Legacy name kept for backward compatibility."
23
+ }
24
+ ],
25
+ "http_path": "/api/video-stats/bulk",
26
+ "http_method": "POST"
27
+ },
28
+ {
29
+ "id": "start_video_stats_job",
30
+ "remote_tool_name": "start_video_stats_job",
31
+ "mcp_primary_name": "start_video_stats_job",
32
+ "mcp_aliases": [],
33
+ "http_path": "/api/video-stats/job/start",
34
+ "http_method": "POST"
35
+ },
36
+ {
37
+ "id": "poll_video_stats_job",
38
+ "remote_tool_name": "poll_video_stats_job",
39
+ "mcp_primary_name": "poll_video_stats_job",
40
+ "mcp_aliases": [],
41
+ "http_path": "/api/video-stats/job/poll",
42
+ "http_method": "POST"
43
+ },
44
+ {
45
+ "id": "resume_video_stats_job",
46
+ "remote_tool_name": "resume_video_stats_job",
47
+ "mcp_primary_name": "resume_video_stats_job",
48
+ "mcp_aliases": [],
49
+ "http_path": "/api/video-stats/job/resume",
50
+ "http_method": "POST"
51
+ },
52
+ {
53
+ "id": "get_trending",
54
+ "remote_tool_name": "get_trending",
55
+ "mcp_primary_name": "get_trending",
56
+ "mcp_aliases": [
57
+ {
58
+ "name": "get_trending_videos",
59
+ "remove_after_version": "0.2.0",
60
+ "remove_after_date": "2026-12-31",
61
+ "note": "Legacy name kept for backward compatibility."
62
+ }
63
+ ],
64
+ "http_path": "/api/trending",
65
+ "http_method": "POST"
66
+ },
67
+ {
68
+ "id": "search_channels",
69
+ "remote_tool_name": "search_channels",
70
+ "mcp_primary_name": "search_channels",
71
+ "mcp_aliases": [],
72
+ "http_path": "/api/channel/search",
73
+ "http_method": "POST"
74
+ },
75
+ {
76
+ "id": "get_channel_stats",
77
+ "remote_tool_name": "get_channel_stats",
78
+ "mcp_primary_name": "get_channel_stats",
79
+ "mcp_aliases": [],
80
+ "http_path": "/api/channel/stats",
81
+ "http_method": "POST"
82
+ },
83
+ {
84
+ "id": "list_channel_uploads",
85
+ "remote_tool_name": "list_channel_uploads",
86
+ "mcp_primary_name": "list_channel_uploads",
87
+ "mcp_aliases": [],
88
+ "http_path": "/api/channel/uploads",
89
+ "http_method": "POST"
90
+ },
91
+ {
92
+ "id": "get_comments",
93
+ "remote_tool_name": "get_comments",
94
+ "mcp_primary_name": "get_comments",
95
+ "mcp_aliases": [
96
+ {
97
+ "name": "get_video_comments_sync",
98
+ "remove_after_version": "0.2.0",
99
+ "remove_after_date": "2026-12-31",
100
+ "note": "Legacy name kept for backward compatibility."
101
+ }
102
+ ],
103
+ "http_path": "/api/comments/sync",
104
+ "http_method": "POST"
105
+ },
106
+ {
107
+ "id": "start_comments_job",
108
+ "remote_tool_name": "start_comments_job",
109
+ "mcp_primary_name": "start_comments_job",
110
+ "mcp_aliases": [
111
+ {
112
+ "name": "start_comments_export_job",
113
+ "remove_after_version": "0.2.0",
114
+ "remove_after_date": "2026-12-31",
115
+ "note": "Legacy name kept for backward compatibility."
116
+ }
117
+ ],
118
+ "http_path": "/api/comments/start",
119
+ "http_method": "POST"
120
+ },
121
+ {
122
+ "id": "poll_comments_job",
123
+ "remote_tool_name": "poll_comments_job",
124
+ "mcp_primary_name": "poll_comments_job",
125
+ "mcp_aliases": [
126
+ {
127
+ "name": "poll_comments_export_job",
128
+ "remove_after_version": "0.2.0",
129
+ "remove_after_date": "2026-12-31",
130
+ "note": "Legacy name kept for backward compatibility."
131
+ }
132
+ ],
133
+ "http_path": "/api/comments/poll",
134
+ "http_method": "POST"
135
+ },
136
+ {
137
+ "id": "resume_comments_job",
138
+ "remote_tool_name": "resume_comments_job",
139
+ "mcp_primary_name": "resume_comments_job",
140
+ "mcp_aliases": [
141
+ {
142
+ "name": "resume_comments_export_job",
143
+ "remove_after_version": "0.2.0",
144
+ "remove_after_date": "2026-12-31",
145
+ "note": "Legacy name kept for backward compatibility."
146
+ }
147
+ ],
148
+ "http_path": "/api/comments/resume",
149
+ "http_method": "POST"
150
+ },
151
+ {
152
+ "id": "get_comment_replies",
153
+ "remote_tool_name": "get_comment_replies",
154
+ "mcp_primary_name": "get_comment_replies",
155
+ "mcp_aliases": [
156
+ {
157
+ "name": "get_comment_replies_sync",
158
+ "remove_after_version": "0.2.0",
159
+ "remove_after_date": "2026-12-31",
160
+ "note": "Legacy name kept for backward compatibility."
161
+ }
162
+ ],
163
+ "http_path": "/api/comments/replies",
164
+ "http_method": "POST"
165
+ },
166
+ {
167
+ "id": "get_quota_usage",
168
+ "remote_tool_name": "get_quota_usage",
169
+ "mcp_primary_name": "get_quota_usage",
170
+ "mcp_aliases": [],
171
+ "http_path": "/api/quota",
172
+ "http_method": "POST"
173
+ },
174
+ {
175
+ "id": "get_patch_notes",
176
+ "remote_tool_name": "get_patch_notes",
177
+ "mcp_primary_name": "get_patch_notes",
178
+ "mcp_aliases": [],
179
+ "http_path": "/api/patch-notes",
180
+ "http_method": "GET"
181
+ }
182
+ ];
183
+ export function getYouTubeToolContractById(id) {
184
+ const match = YOUTUBE_TOOL_CONTRACTS.find((tool) => tool.id === id);
185
+ if (!match) {
186
+ throw new Error(`Unknown YouTube tool contract id: ${id}`);
187
+ }
188
+ return match;
189
+ }
package/dist/server.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { YtMcpConfig } from "./utils/config.js";
3
3
  import type { TokenManager } from "@mkterswingman/5mghost-shared-client/auth";
4
4
  import { DownloadJobManager } from "./download/jobManager.js";
5
5
  import type { DownloadToolDeps } from "./tools/downloads.js";
6
+ import { type ToolTelemetryDeps } from "./telemetry.js";
6
7
  /**
7
8
  * Creates the MCP server.
8
9
  *
@@ -20,4 +21,4 @@ import type { DownloadToolDeps } from "./tools/downloads.js";
20
21
  * ├─ PAT login URL (user clicks to get token)
21
22
  * └─ setup command (for full OAuth + cookies)
22
23
  */
23
- export declare function createServer(config: YtMcpConfig, tokenManager: TokenManager, downloadJobManager?: DownloadJobManager, downloadToolDeps?: DownloadToolDeps): Promise<McpServer>;
24
+ export declare function createServer(config: YtMcpConfig, tokenManager: TokenManager, downloadJobManager?: DownloadJobManager, downloadToolDeps?: DownloadToolDeps, telemetryDeps?: ToolTelemetryDeps): Promise<McpServer>;
package/dist/server.js CHANGED
@@ -3,6 +3,8 @@ import { DownloadJobManager } from "./download/jobManager.js";
3
3
  import { registerSubtitleTools } from "./tools/subtitles.js";
4
4
  import { registerDownloadTools } from "./tools/downloads.js";
5
5
  import { registerRemoteTools } from "./tools/remote.js";
6
+ import { ToolTelemetryClient } from "./telemetry.js";
7
+ import { buildBrowserOpenCommand } from "./utils/browserLaunch.js";
6
8
  /**
7
9
  * Creates the MCP server.
8
10
  *
@@ -20,7 +22,7 @@ import { registerRemoteTools } from "./tools/remote.js";
20
22
  * ├─ PAT login URL (user clicks to get token)
21
23
  * └─ setup command (for full OAuth + cookies)
22
24
  */
23
- export async function createServer(config, tokenManager, downloadJobManager = new DownloadJobManager(), downloadToolDeps = {}) {
25
+ export async function createServer(config, tokenManager, downloadJobManager = new DownloadJobManager(), downloadToolDeps = {}, telemetryDeps = {}) {
24
26
  const server = new McpServer({
25
27
  name: "@mkterswingman/yt-mcp",
26
28
  version: "0.1.0",
@@ -73,9 +75,29 @@ export async function createServer(config, tokenManager, downloadJobManager = ne
73
75
  return server;
74
76
  }
75
77
  // Authenticated — register all tools
76
- registerSubtitleTools(server, config, tokenManager);
77
- registerDownloadTools(server, config, tokenManager, downloadJobManager, downloadToolDeps);
78
- registerRemoteTools(server, config, tokenManager);
78
+ const telemetry = new ToolTelemetryClient(config, tokenManager, telemetryDeps);
79
+ const instrumentedServer = withToolTelemetry(server, telemetry);
80
+ registerSubtitleTools(instrumentedServer, config, tokenManager);
81
+ registerDownloadTools(instrumentedServer, config, tokenManager, downloadJobManager, downloadToolDeps);
82
+ registerRemoteTools(instrumentedServer, config, tokenManager);
79
83
  return server;
80
84
  }
81
- import { buildBrowserOpenCommand } from "./utils/browserLaunch.js";
85
+ function withToolTelemetry(server, telemetry) {
86
+ const originalRegisterTool = server.registerTool.bind(server);
87
+ const instrumented = Object.create(server);
88
+ instrumented.registerTool = ((name, config, handler) => {
89
+ return originalRegisterTool(name, config, async (...args) => {
90
+ const startedAtMs = Date.now();
91
+ try {
92
+ const result = await handler(...args);
93
+ telemetry.recordToolCall({ toolName: name, startedAtMs, result: result });
94
+ return result;
95
+ }
96
+ catch (error) {
97
+ telemetry.recordToolCall({ toolName: name, startedAtMs, thrown: error });
98
+ throw error;
99
+ }
100
+ });
101
+ });
102
+ return instrumented;
103
+ }
@@ -0,0 +1,64 @@
1
+ import type { TokenManager } from "@mkterswingman/5mghost-shared-client/auth";
2
+ import type { YtMcpConfig } from "./utils/config.js";
3
+ type ToolOutcome = "success" | "failure";
4
+ type ErrorKind = "validation" | "auth" | "upstream" | "rate_limit" | "internal";
5
+ export interface ToolTelemetryEvent {
6
+ schema_version: 1;
7
+ event_id: string;
8
+ occurred_at: string;
9
+ product: string;
10
+ product_version: string;
11
+ install_id: string;
12
+ event_type: "tool_call";
13
+ tool_name: string;
14
+ outcome: ToolOutcome;
15
+ duration_ms: number;
16
+ error_kind?: ErrorKind;
17
+ error_code?: string;
18
+ error_message?: string;
19
+ }
20
+ interface ToolResultLike {
21
+ isError?: boolean;
22
+ structuredContent?: unknown;
23
+ }
24
+ interface FetchLike {
25
+ (input: string | URL, init?: RequestInit): Promise<Response>;
26
+ }
27
+ interface TelemetryLogger {
28
+ warn(event: string, data?: Record<string, unknown>): void;
29
+ }
30
+ export interface ToolTelemetryDeps {
31
+ fetchImpl?: FetchLike;
32
+ installId?: string;
33
+ logger?: TelemetryLogger;
34
+ }
35
+ export declare class ToolTelemetryClient {
36
+ private readonly config;
37
+ private readonly tokenManager;
38
+ private readonly fetchImpl;
39
+ private readonly configuredInstallId;
40
+ private persistedInstallId;
41
+ private readonly logger;
42
+ constructor(config: Pick<YtMcpConfig, "auth_url" | "telemetry_enabled" | "telemetry_endpoint_url" | "telemetry_timeout_ms">, tokenManager: Pick<TokenManager, "getValidToken">, deps?: ToolTelemetryDeps);
43
+ recordToolCall(input: {
44
+ toolName: string;
45
+ startedAtMs: number;
46
+ result?: ToolResultLike;
47
+ thrown?: unknown;
48
+ }): void;
49
+ private getInstallId;
50
+ private send;
51
+ }
52
+ export declare function buildToolTelemetryEvent(input: {
53
+ toolName: string;
54
+ startedAtMs: number;
55
+ installId: string;
56
+ result?: ToolResultLike;
57
+ thrown?: unknown;
58
+ }): ToolTelemetryEvent;
59
+ export declare function classifyTelemetryError(result?: ToolResultLike, thrown?: unknown): {
60
+ kind: ErrorKind;
61
+ code: string;
62
+ message: string;
63
+ } | null;
64
+ export {};