@mweinbach/apple-docs-mcp 1.0.0

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 (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +560 -0
  3. package/dist/constants/browser-headers.d.ts +48 -0
  4. package/dist/constants/browser-headers.d.ts.map +1 -0
  5. package/dist/constants/browser-headers.js +166 -0
  6. package/dist/constants/browser-headers.js.map +1 -0
  7. package/dist/index.d.ts +83 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +215 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/schemas/analyze-api.schema.d.ts +42 -0
  12. package/dist/schemas/analyze-api.schema.d.ts.map +1 -0
  13. package/dist/schemas/analyze-api.schema.js +23 -0
  14. package/dist/schemas/analyze-api.schema.js.map +1 -0
  15. package/dist/schemas/apple-docs.schema.d.ts +18 -0
  16. package/dist/schemas/apple-docs.schema.d.ts.map +1 -0
  17. package/dist/schemas/apple-docs.schema.js +10 -0
  18. package/dist/schemas/apple-docs.schema.js.map +1 -0
  19. package/dist/schemas/doc-content.schema.d.ts +24 -0
  20. package/dist/schemas/doc-content.schema.d.ts.map +1 -0
  21. package/dist/schemas/doc-content.schema.js +10 -0
  22. package/dist/schemas/doc-content.schema.js.map +1 -0
  23. package/dist/schemas/documentation-updates.schema.d.ts +27 -0
  24. package/dist/schemas/documentation-updates.schema.d.ts.map +1 -0
  25. package/dist/schemas/documentation-updates.schema.js +12 -0
  26. package/dist/schemas/documentation-updates.schema.js.map +1 -0
  27. package/dist/schemas/framework-symbols.schema.d.ts +24 -0
  28. package/dist/schemas/framework-symbols.schema.d.ts.map +1 -0
  29. package/dist/schemas/framework-symbols.schema.js +11 -0
  30. package/dist/schemas/framework-symbols.schema.js.map +1 -0
  31. package/dist/schemas/index.d.ts +15 -0
  32. package/dist/schemas/index.d.ts.map +1 -0
  33. package/dist/schemas/index.js +15 -0
  34. package/dist/schemas/index.js.map +1 -0
  35. package/dist/schemas/platform-compatibility.schema.d.ts +18 -0
  36. package/dist/schemas/platform-compatibility.schema.d.ts.map +1 -0
  37. package/dist/schemas/platform-compatibility.schema.js +8 -0
  38. package/dist/schemas/platform-compatibility.schema.js.map +1 -0
  39. package/dist/schemas/references.schema.d.ts +18 -0
  40. package/dist/schemas/references.schema.d.ts.map +1 -0
  41. package/dist/schemas/references.schema.js +9 -0
  42. package/dist/schemas/references.schema.js.map +1 -0
  43. package/dist/schemas/related-apis.schema.d.ts +21 -0
  44. package/dist/schemas/related-apis.schema.d.ts.map +1 -0
  45. package/dist/schemas/related-apis.schema.js +9 -0
  46. package/dist/schemas/related-apis.schema.js.map +1 -0
  47. package/dist/schemas/sample-code.schema.d.ts +21 -0
  48. package/dist/schemas/sample-code.schema.d.ts.map +1 -0
  49. package/dist/schemas/sample-code.schema.js +10 -0
  50. package/dist/schemas/sample-code.schema.js.map +1 -0
  51. package/dist/schemas/search.schema.d.ts +15 -0
  52. package/dist/schemas/search.schema.d.ts.map +1 -0
  53. package/dist/schemas/search.schema.js +8 -0
  54. package/dist/schemas/search.schema.js.map +1 -0
  55. package/dist/schemas/similar-apis.schema.d.ts +21 -0
  56. package/dist/schemas/similar-apis.schema.d.ts.map +1 -0
  57. package/dist/schemas/similar-apis.schema.js +9 -0
  58. package/dist/schemas/similar-apis.schema.js.map +1 -0
  59. package/dist/schemas/technologies.schema.d.ts +21 -0
  60. package/dist/schemas/technologies.schema.d.ts.map +1 -0
  61. package/dist/schemas/technologies.schema.js +10 -0
  62. package/dist/schemas/technologies.schema.js.map +1 -0
  63. package/dist/schemas/technology-overviews.schema.d.ts +24 -0
  64. package/dist/schemas/technology-overviews.schema.d.ts.map +1 -0
  65. package/dist/schemas/technology-overviews.schema.js +11 -0
  66. package/dist/schemas/technology-overviews.schema.js.map +1 -0
  67. package/dist/schemas/wwdc.schemas.d.ts +208 -0
  68. package/dist/schemas/wwdc.schemas.d.ts.map +1 -0
  69. package/dist/schemas/wwdc.schemas.js +97 -0
  70. package/dist/schemas/wwdc.schemas.js.map +1 -0
  71. package/dist/tools/definitions.d.ts +10 -0
  72. package/dist/tools/definitions.d.ts.map +1 -0
  73. package/dist/tools/definitions.js +690 -0
  74. package/dist/tools/definitions.js.map +1 -0
  75. package/dist/tools/doc-fetcher.d.ts +19 -0
  76. package/dist/tools/doc-fetcher.d.ts.map +1 -0
  77. package/dist/tools/doc-fetcher.js +487 -0
  78. package/dist/tools/doc-fetcher.js.map +1 -0
  79. package/dist/tools/doc-formatter.d.ts +69 -0
  80. package/dist/tools/doc-formatter.d.ts.map +1 -0
  81. package/dist/tools/doc-formatter.js +208 -0
  82. package/dist/tools/doc-formatter.js.map +1 -0
  83. package/dist/tools/find-similar-apis.d.ts +5 -0
  84. package/dist/tools/find-similar-apis.d.ts.map +1 -0
  85. package/dist/tools/find-similar-apis.js +275 -0
  86. package/dist/tools/find-similar-apis.js.map +1 -0
  87. package/dist/tools/get-documentation-updates.d.ts +5 -0
  88. package/dist/tools/get-documentation-updates.d.ts.map +1 -0
  89. package/dist/tools/get-documentation-updates.js +244 -0
  90. package/dist/tools/get-documentation-updates.js.map +1 -0
  91. package/dist/tools/get-platform-compatibility.d.ts +5 -0
  92. package/dist/tools/get-platform-compatibility.d.ts.map +1 -0
  93. package/dist/tools/get-platform-compatibility.js +205 -0
  94. package/dist/tools/get-platform-compatibility.js.map +1 -0
  95. package/dist/tools/get-related-apis.d.ts +5 -0
  96. package/dist/tools/get-related-apis.d.ts.map +1 -0
  97. package/dist/tools/get-related-apis.js +155 -0
  98. package/dist/tools/get-related-apis.js.map +1 -0
  99. package/dist/tools/get-sample-code.d.ts +5 -0
  100. package/dist/tools/get-sample-code.d.ts.map +1 -0
  101. package/dist/tools/get-sample-code.js +375 -0
  102. package/dist/tools/get-sample-code.js.map +1 -0
  103. package/dist/tools/get-technology-overviews.d.ts +5 -0
  104. package/dist/tools/get-technology-overviews.d.ts.map +1 -0
  105. package/dist/tools/get-technology-overviews.js +306 -0
  106. package/dist/tools/get-technology-overviews.js.map +1 -0
  107. package/dist/tools/handlers.d.ts +29 -0
  108. package/dist/tools/handlers.d.ts.map +1 -0
  109. package/dist/tools/handlers.js +350 -0
  110. package/dist/tools/handlers.js.map +1 -0
  111. package/dist/tools/list-technologies.d.ts +5 -0
  112. package/dist/tools/list-technologies.d.ts.map +1 -0
  113. package/dist/tools/list-technologies.js +165 -0
  114. package/dist/tools/list-technologies.js.map +1 -0
  115. package/dist/tools/resolve-references-batch.d.ts +5 -0
  116. package/dist/tools/resolve-references-batch.d.ts.map +1 -0
  117. package/dist/tools/resolve-references-batch.js +199 -0
  118. package/dist/tools/resolve-references-batch.js.map +1 -0
  119. package/dist/tools/search-framework-symbols.d.ts +7 -0
  120. package/dist/tools/search-framework-symbols.d.ts.map +1 -0
  121. package/dist/tools/search-framework-symbols.js +235 -0
  122. package/dist/tools/search-framework-symbols.js.map +1 -0
  123. package/dist/tools/search-parser.d.ts +15 -0
  124. package/dist/tools/search-parser.d.ts.map +1 -0
  125. package/dist/tools/search-parser.js +188 -0
  126. package/dist/tools/search-parser.js.map +1 -0
  127. package/dist/tools/search-result-parser.d.ts +52 -0
  128. package/dist/tools/search-result-parser.d.ts.map +1 -0
  129. package/dist/tools/search-result-parser.js +137 -0
  130. package/dist/tools/search-result-parser.js.map +1 -0
  131. package/dist/tools/tools-guide.d.ts +124 -0
  132. package/dist/tools/tools-guide.d.ts.map +1 -0
  133. package/dist/tools/tools-guide.js +389 -0
  134. package/dist/tools/tools-guide.js.map +1 -0
  135. package/dist/tools/wwdc/content-extractor.d.ts +9 -0
  136. package/dist/tools/wwdc/content-extractor.d.ts.map +1 -0
  137. package/dist/tools/wwdc/content-extractor.js +520 -0
  138. package/dist/tools/wwdc/content-extractor.js.map +1 -0
  139. package/dist/tools/wwdc/topics-extractor.d.ts +32 -0
  140. package/dist/tools/wwdc/topics-extractor.d.ts.map +1 -0
  141. package/dist/tools/wwdc/topics-extractor.js +146 -0
  142. package/dist/tools/wwdc/topics-extractor.js.map +1 -0
  143. package/dist/tools/wwdc/video-list-extractor.d.ts +13 -0
  144. package/dist/tools/wwdc/video-list-extractor.d.ts.map +1 -0
  145. package/dist/tools/wwdc/video-list-extractor.js +238 -0
  146. package/dist/tools/wwdc/video-list-extractor.js.map +1 -0
  147. package/dist/tools/wwdc/wwdc-handlers.d.ts +32 -0
  148. package/dist/tools/wwdc/wwdc-handlers.d.ts.map +1 -0
  149. package/dist/tools/wwdc/wwdc-handlers.js +791 -0
  150. package/dist/tools/wwdc/wwdc-handlers.js.map +1 -0
  151. package/dist/types/apple-docs.d.ts +112 -0
  152. package/dist/types/apple-docs.d.ts.map +1 -0
  153. package/dist/types/apple-docs.js +5 -0
  154. package/dist/types/apple-docs.js.map +1 -0
  155. package/dist/types/cache.d.ts +28 -0
  156. package/dist/types/cache.d.ts.map +1 -0
  157. package/dist/types/cache.js +5 -0
  158. package/dist/types/cache.js.map +1 -0
  159. package/dist/types/content-sections.d.ts +37 -0
  160. package/dist/types/content-sections.d.ts.map +1 -0
  161. package/dist/types/content-sections.js +5 -0
  162. package/dist/types/content-sections.js.map +1 -0
  163. package/dist/types/error.d.ts +39 -0
  164. package/dist/types/error.d.ts.map +1 -0
  165. package/dist/types/error.js +21 -0
  166. package/dist/types/error.js.map +1 -0
  167. package/dist/types/headers.d.ts +95 -0
  168. package/dist/types/headers.d.ts.map +1 -0
  169. package/dist/types/headers.js +5 -0
  170. package/dist/types/headers.js.map +1 -0
  171. package/dist/types/http.d.ts +31 -0
  172. package/dist/types/http.d.ts.map +1 -0
  173. package/dist/types/http.js +5 -0
  174. package/dist/types/http.js.map +1 -0
  175. package/dist/types/index.d.ts +11 -0
  176. package/dist/types/index.d.ts.map +1 -0
  177. package/dist/types/index.js +12 -0
  178. package/dist/types/index.js.map +1 -0
  179. package/dist/types/search.d.ts +62 -0
  180. package/dist/types/search.d.ts.map +1 -0
  181. package/dist/types/search.js +5 -0
  182. package/dist/types/search.js.map +1 -0
  183. package/dist/types/sections.d.ts +64 -0
  184. package/dist/types/sections.d.ts.map +1 -0
  185. package/dist/types/sections.js +5 -0
  186. package/dist/types/sections.js.map +1 -0
  187. package/dist/types/tools/platform.d.ts +33 -0
  188. package/dist/types/tools/platform.d.ts.map +1 -0
  189. package/dist/types/tools/platform.js +5 -0
  190. package/dist/types/tools/platform.js.map +1 -0
  191. package/dist/types/tools/sample-code.d.ts +23 -0
  192. package/dist/types/tools/sample-code.d.ts.map +1 -0
  193. package/dist/types/tools/sample-code.js +5 -0
  194. package/dist/types/tools/sample-code.js.map +1 -0
  195. package/dist/types/tools/technology.d.ts +18 -0
  196. package/dist/types/tools/technology.d.ts.map +1 -0
  197. package/dist/types/tools/technology.js +5 -0
  198. package/dist/types/tools/technology.js.map +1 -0
  199. package/dist/types/tools/updates.d.ts +23 -0
  200. package/dist/types/tools/updates.d.ts.map +1 -0
  201. package/dist/types/tools/updates.js +5 -0
  202. package/dist/types/tools/updates.js.map +1 -0
  203. package/dist/types/wwdc.d.ts +200 -0
  204. package/dist/types/wwdc.d.ts.map +1 -0
  205. package/dist/types/wwdc.js +5 -0
  206. package/dist/types/wwdc.js.map +1 -0
  207. package/dist/utils/cache-warmer.d.ts +22 -0
  208. package/dist/utils/cache-warmer.d.ts.map +1 -0
  209. package/dist/utils/cache-warmer.js +115 -0
  210. package/dist/utils/cache-warmer.js.map +1 -0
  211. package/dist/utils/cache.d.ts +109 -0
  212. package/dist/utils/cache.d.ts.map +1 -0
  213. package/dist/utils/cache.js +302 -0
  214. package/dist/utils/cache.js.map +1 -0
  215. package/dist/utils/constants.d.ts +178 -0
  216. package/dist/utils/constants.d.ts.map +1 -0
  217. package/dist/utils/constants.js +266 -0
  218. package/dist/utils/constants.js.map +1 -0
  219. package/dist/utils/error-handler.d.ts +80 -0
  220. package/dist/utils/error-handler.d.ts.map +1 -0
  221. package/dist/utils/error-handler.js +437 -0
  222. package/dist/utils/error-handler.js.map +1 -0
  223. package/dist/utils/framework-mapper.d.ts +84 -0
  224. package/dist/utils/framework-mapper.d.ts.map +1 -0
  225. package/dist/utils/framework-mapper.js +534 -0
  226. package/dist/utils/framework-mapper.js.map +1 -0
  227. package/dist/utils/http-client.d.ts +130 -0
  228. package/dist/utils/http-client.d.ts.map +1 -0
  229. package/dist/utils/http-client.js +464 -0
  230. package/dist/utils/http-client.js.map +1 -0
  231. package/dist/utils/http-headers-generator.d.ts +151 -0
  232. package/dist/utils/http-headers-generator.d.ts.map +1 -0
  233. package/dist/utils/http-headers-generator.js +407 -0
  234. package/dist/utils/http-headers-generator.js.map +1 -0
  235. package/dist/utils/logger.d.ts +44 -0
  236. package/dist/utils/logger.d.ts.map +1 -0
  237. package/dist/utils/logger.js +73 -0
  238. package/dist/utils/logger.js.map +1 -0
  239. package/dist/utils/preloader.d.ts +20 -0
  240. package/dist/utils/preloader.d.ts.map +1 -0
  241. package/dist/utils/preloader.js +82 -0
  242. package/dist/utils/preloader.js.map +1 -0
  243. package/dist/utils/rate-limiter.d.ts +28 -0
  244. package/dist/utils/rate-limiter.d.ts.map +1 -0
  245. package/dist/utils/rate-limiter.js +48 -0
  246. package/dist/utils/rate-limiter.js.map +1 -0
  247. package/dist/utils/topic-mapper.d.ts +18 -0
  248. package/dist/utils/topic-mapper.d.ts.map +1 -0
  249. package/dist/utils/topic-mapper.js +182 -0
  250. package/dist/utils/topic-mapper.js.map +1 -0
  251. package/dist/utils/url-converter.d.ts +22 -0
  252. package/dist/utils/url-converter.d.ts.map +1 -0
  253. package/dist/utils/url-converter.js +70 -0
  254. package/dist/utils/url-converter.js.map +1 -0
  255. package/dist/utils/user-agent-pool.d.ts +241 -0
  256. package/dist/utils/user-agent-pool.d.ts.map +1 -0
  257. package/dist/utils/user-agent-pool.js +557 -0
  258. package/dist/utils/user-agent-pool.js.map +1 -0
  259. package/dist/utils/wwdc-data-source-path.d.ts +9 -0
  260. package/dist/utils/wwdc-data-source-path.d.ts.map +1 -0
  261. package/dist/utils/wwdc-data-source-path.js +22 -0
  262. package/dist/utils/wwdc-data-source-path.js.map +1 -0
  263. package/dist/utils/wwdc-data-source.d.ts +36 -0
  264. package/dist/utils/wwdc-data-source.d.ts.map +1 -0
  265. package/dist/utils/wwdc-data-source.js +133 -0
  266. package/dist/utils/wwdc-data-source.js.map +1 -0
  267. package/package.json +83 -0
@@ -0,0 +1,791 @@
1
+ /**
2
+ * WWDC Video MCP Tool Handlers
3
+ */
4
+ import { logger } from '../../utils/logger.js';
5
+ import { loadGlobalMetadata, loadTopicIndex, loadYearIndex, loadVideoData, } from '../../utils/wwdc-data-source.js';
6
+ /**
7
+ * Helper function to load multiple video data files
8
+ */
9
+ async function loadVideosData(videoFiles) {
10
+ const videos = [];
11
+ for (const file of videoFiles) {
12
+ // Extract year and video ID from filename (e.g., "videos/2024-10015.json")
13
+ const match = file.match(/(\d{4})-(\d+)\.json$/);
14
+ if (match) {
15
+ const [, year, videoId] = match;
16
+ try {
17
+ const video = await loadVideoData(year, videoId);
18
+ videos.push(video);
19
+ }
20
+ catch (error) {
21
+ // Skip videos that can't be loaded
22
+ logger.debug(`Failed to load video ${file}:`, error);
23
+ }
24
+ }
25
+ }
26
+ return videos;
27
+ }
28
+ /**
29
+ * List WWDC videos
30
+ */
31
+ export async function handleListWWDCVideos(year, topic, hasCode, limit = 50) {
32
+ try {
33
+ // Load metadata
34
+ const metadata = await loadGlobalMetadata();
35
+ let allVideos = [];
36
+ if (topic?.includes('-')) {
37
+ // If topic looks like a topic ID, try to use topic index
38
+ try {
39
+ const topicIndex = await loadTopicIndex(topic);
40
+ // Filter by year
41
+ const videosToLoad = year && year !== 'all'
42
+ ? topicIndex.videos.filter(v => v.year === year)
43
+ : topicIndex.videos;
44
+ // Load video data
45
+ const videoFiles = videosToLoad.map((v) => v.dataFile);
46
+ const videos = await loadVideosData(videoFiles);
47
+ allVideos = videos.map((v) => ({ ...v, year: v.year }));
48
+ }
49
+ catch (error) {
50
+ logger.warn(`Failed to load topic index for ${topic}, will search by keyword instead`);
51
+ // Fall through to load by year and filter by keyword
52
+ }
53
+ }
54
+ if (allVideos.length === 0 && year && year !== 'all') {
55
+ // If year is specified, use year index
56
+ const yearIndex = await loadYearIndex(year);
57
+ // 加载视频数据
58
+ const videoFiles = yearIndex.videos.map((v) => v.dataFile);
59
+ const videos = await loadVideosData(videoFiles);
60
+ allVideos = videos.map((v) => ({ ...v, year: v.year }));
61
+ }
62
+ else if (allVideos.length === 0) {
63
+ // Load all videos - through year indices
64
+ const yearsToLoad = metadata.years;
65
+ for (const y of yearsToLoad) {
66
+ try {
67
+ const yearIndex = await loadYearIndex(y);
68
+ const videoFiles = yearIndex.videos.map((v) => v.dataFile);
69
+ const videos = await loadVideosData(videoFiles);
70
+ const videosWithYear = videos.map((v) => ({ ...v, year: y }));
71
+ allVideos.push(...videosWithYear);
72
+ }
73
+ catch (error) {
74
+ logger.warn(`Failed to load year ${y}:`, error);
75
+ }
76
+ }
77
+ }
78
+ // Apply filters
79
+ let filteredVideos = allVideos;
80
+ // Topic filter (if not already filtered through topic index)
81
+ if (topic && allVideos.length > 0) {
82
+ // If we loaded videos but didn't use topic index, filter by keyword
83
+ const topicLower = topic.toLowerCase();
84
+ const wasFilteredByTopicIndex = topic.includes('-') && allVideos.length > 0;
85
+ if (!wasFilteredByTopicIndex) {
86
+ filteredVideos = filteredVideos.filter(v => v.topics.some(t => t.toLowerCase().includes(topicLower)) ||
87
+ v.title.toLowerCase().includes(topicLower));
88
+ }
89
+ }
90
+ // Code filter
91
+ if (hasCode !== undefined) {
92
+ filteredVideos = filteredVideos.filter(v => v.hasCode === hasCode);
93
+ }
94
+ // Apply limit
95
+ const limitedVideos = filteredVideos.slice(0, limit);
96
+ // Format output
97
+ return formatVideoList(limitedVideos, year, topic, hasCode);
98
+ }
99
+ catch (error) {
100
+ logger.error('Failed to list WWDC videos:', error);
101
+ const errorMessage = error instanceof Error ? error.message : String(error);
102
+ return `Error: Failed to list WWDC videos: ${errorMessage}`;
103
+ }
104
+ }
105
+ /**
106
+ * Search WWDC content
107
+ */
108
+ export async function handleSearchWWDCContent(query, searchIn = 'both', year, language, limit = 20) {
109
+ try {
110
+ const metadata = await loadGlobalMetadata();
111
+ const queryLower = query.toLowerCase();
112
+ const results = [];
113
+ // Determine years to search
114
+ const yearsToSearch = year ? [year] : metadata.years;
115
+ // Search each year
116
+ for (const y of yearsToSearch) {
117
+ try {
118
+ const yearIndex = await loadYearIndex(y);
119
+ // Pre-filter: only load videos that might contain search content
120
+ const potentialVideos = yearIndex.videos.filter(v => {
121
+ // Basic title and topic matching
122
+ const titleMatch = v.title?.toLowerCase().includes(queryLower) || false;
123
+ const topicMatch = v.topics?.some(t => t.toLowerCase().includes(queryLower)) || false;
124
+ return titleMatch || topicMatch ||
125
+ (searchIn === 'code' || searchIn === 'both') && v.hasCode ||
126
+ (searchIn === 'transcript' || searchIn === 'both') && v.hasTranscript;
127
+ });
128
+ if (potentialVideos.length === 0) {
129
+ continue;
130
+ }
131
+ // 加载视频数据
132
+ const videoFiles = potentialVideos.map((v) => v.dataFile);
133
+ const videos = await loadVideosData(videoFiles);
134
+ // Search each video
135
+ for (const video of videos) {
136
+ const matches = [];
137
+ // Search transcript
138
+ if ((searchIn === 'transcript' || searchIn === 'both') && video.transcript) {
139
+ const transcriptMatches = searchInTranscript(video.transcript.fullText, queryLower);
140
+ matches.push(...transcriptMatches.map(m => ({
141
+ type: 'transcript',
142
+ context: m.context,
143
+ timestamp: m.timestamp,
144
+ })));
145
+ }
146
+ // Search code
147
+ if ((searchIn === 'code' || searchIn === 'both') && video.codeExamples) {
148
+ const codeMatches = searchInCode(video.codeExamples, queryLower, language);
149
+ matches.push(...codeMatches.map(m => ({
150
+ type: 'code',
151
+ context: m.context,
152
+ timestamp: m.timestamp,
153
+ })));
154
+ }
155
+ if (matches.length > 0) {
156
+ results.push({
157
+ video: { ...video, year: y },
158
+ matches: matches.slice(0, 3), // Max 3 matches per video
159
+ });
160
+ }
161
+ }
162
+ }
163
+ catch (error) {
164
+ logger.warn(`Failed to search year ${y}:`, error);
165
+ }
166
+ }
167
+ // Sort by match count
168
+ results.sort((a, b) => b.matches.length - a.matches.length);
169
+ // Apply limit
170
+ const limitedResults = results.slice(0, limit);
171
+ return formatSearchResults(limitedResults, query, searchIn);
172
+ }
173
+ catch (error) {
174
+ logger.error('Failed to search WWDC content:', error);
175
+ const errorMessage = error instanceof Error ? error.message : String(error);
176
+ return `Error: Failed to search WWDC content: ${errorMessage}`;
177
+ }
178
+ }
179
+ /**
180
+ * Get WWDC video details
181
+ */
182
+ export async function handleGetWWDCVideo(year, videoId, includeTranscript = true, includeCode = true) {
183
+ try {
184
+ // Load video data directly
185
+ const video = await loadVideoData(year, videoId);
186
+ return formatVideoDetail(video, includeTranscript, includeCode);
187
+ }
188
+ catch (error) {
189
+ logger.error('Failed to get WWDC video:', error);
190
+ const errorMessage = error instanceof Error ? error.message : String(error);
191
+ return `Error: Failed to get WWDC video: ${errorMessage}`;
192
+ }
193
+ }
194
+ /**
195
+ * Get WWDC code examples
196
+ */
197
+ export async function handleGetWWDCCodeExamples(framework, topic, year, language, limit = 30) {
198
+ try {
199
+ const metadata = await loadGlobalMetadata();
200
+ const codeExamples = [];
201
+ // Determine years to search
202
+ const yearsToSearch = year ? [year] : metadata.years;
203
+ for (const y of yearsToSearch) {
204
+ try {
205
+ const yearIndex = await loadYearIndex(y);
206
+ // Pre-filter: only load videos with code
207
+ const videosWithCode = yearIndex.videos.filter(v => v.hasCode);
208
+ // Topic filter
209
+ let filteredVideos = videosWithCode;
210
+ if (topic) {
211
+ if (topic.includes('-')) {
212
+ // If it's a standard topic ID, use topic index directly
213
+ try {
214
+ const topicIndex = await loadTopicIndex(topic);
215
+ const topicVideoIds = new Set(topicIndex.videos.map((v) => v.id));
216
+ filteredVideos = videosWithCode.filter(v => topicVideoIds.has(v.id));
217
+ }
218
+ catch (error) {
219
+ // If topic index doesn't exist, fallback to string matching
220
+ const topicLower = topic.toLowerCase();
221
+ filteredVideos = videosWithCode.filter(v => v.topics.some(t => t.toLowerCase().includes(topicLower)) ||
222
+ v.title.toLowerCase().includes(topicLower));
223
+ }
224
+ }
225
+ else {
226
+ // Search by name
227
+ const topicLower = topic.toLowerCase();
228
+ filteredVideos = videosWithCode.filter(v => v.topics.some(t => t.toLowerCase().includes(topicLower)) ||
229
+ v.title.toLowerCase().includes(topicLower));
230
+ }
231
+ }
232
+ if (filteredVideos.length === 0) {
233
+ continue;
234
+ }
235
+ // 加载视频数据
236
+ const videoFiles = filteredVideos.map((v) => v.dataFile);
237
+ const videos = await loadVideosData(videoFiles);
238
+ // Extract code examples
239
+ for (const video of videos) {
240
+ if (!video.codeExamples || video.codeExamples.length === 0) {
241
+ continue;
242
+ }
243
+ for (const example of video.codeExamples) {
244
+ // Language filter
245
+ if (language && example.language.toLowerCase() !== language.toLowerCase()) {
246
+ continue;
247
+ }
248
+ // Framework filter (search in code)
249
+ if (framework && !example.code.toLowerCase().includes(framework.toLowerCase())) {
250
+ continue;
251
+ }
252
+ codeExamples.push({
253
+ code: example.code,
254
+ language: example.language,
255
+ title: example.title,
256
+ timestamp: example.timestamp,
257
+ videoTitle: video.title,
258
+ videoUrl: video.url,
259
+ year: y,
260
+ });
261
+ }
262
+ }
263
+ }
264
+ catch (error) {
265
+ logger.warn(`Failed to search code examples for year ${y}:`, error);
266
+ }
267
+ }
268
+ // Limit result count
269
+ const limitedExamples = codeExamples.slice(0, limit);
270
+ return formatCodeExamples(limitedExamples, framework, topic, language);
271
+ }
272
+ catch (error) {
273
+ logger.error('Failed to get WWDC code examples:', error);
274
+ const errorMessage = error instanceof Error ? error.message : String(error);
275
+ return `Error: Failed to get WWDC code examples: ${errorMessage}`;
276
+ }
277
+ }
278
+ /**
279
+ * Search in transcript
280
+ */
281
+ function searchInTranscript(fullText, query) {
282
+ const matches = [];
283
+ const lines = fullText.split('\n');
284
+ for (let i = 0; i < lines.length; i++) {
285
+ const line = lines[i];
286
+ if (line.toLowerCase().includes(query)) {
287
+ // Get context (one line before and after)
288
+ const context = [
289
+ lines[i - 1] || '',
290
+ line,
291
+ lines[i + 1] || '',
292
+ ].filter(l => l.trim()).join(' ... ');
293
+ matches.push({ context });
294
+ }
295
+ }
296
+ return matches;
297
+ }
298
+ /**
299
+ * Search in code
300
+ */
301
+ function searchInCode(codeExamples, query, language) {
302
+ const matches = [];
303
+ for (const example of codeExamples) {
304
+ // Language filter
305
+ if (language && example.language.toLowerCase() !== language.toLowerCase()) {
306
+ continue;
307
+ }
308
+ if (example.code.toLowerCase().includes(query)) {
309
+ // Extract code snippet containing the query
310
+ const lines = example.code.split('\n');
311
+ const matchingLines = lines.filter(line => line.toLowerCase().includes(query));
312
+ matches.push({
313
+ context: `[${example.language}] ${example.title || ''}: ${matchingLines[0]}`,
314
+ timestamp: example.timestamp,
315
+ });
316
+ }
317
+ }
318
+ return matches;
319
+ }
320
+ /**
321
+ * Format video list
322
+ */
323
+ function formatVideoList(videos, year, topic, hasCode) {
324
+ if (videos.length === 0) {
325
+ return 'No WWDC videos found matching the criteria.';
326
+ }
327
+ let content = '# WWDC Video List\n\n';
328
+ // Filter conditions
329
+ const filters = [];
330
+ if (year && year !== 'all') {
331
+ filters.push(`Year: ${year}`);
332
+ }
333
+ if (topic) {
334
+ filters.push(`Topic: ${topic}`);
335
+ }
336
+ if (hasCode !== undefined) {
337
+ filters.push(`Has Code: ${hasCode ? 'Yes' : 'No'}`);
338
+ }
339
+ if (filters.length > 0) {
340
+ content += `**Filter Conditions:** ${filters.join(', ')}\n\n`;
341
+ }
342
+ content += `**Found ${videos.length} videos**\n\n`;
343
+ // Group by year
344
+ const videosByYear = videos.reduce((acc, video) => {
345
+ if (!acc[video.year]) {
346
+ acc[video.year] = [];
347
+ }
348
+ acc[video.year].push(video);
349
+ return acc;
350
+ }, {});
351
+ // Format each year
352
+ Object.keys(videosByYear)
353
+ .sort((a, b) => parseInt(b) - parseInt(a))
354
+ .forEach(y => {
355
+ content += `## WWDC${y}\n\n`;
356
+ videosByYear[y].forEach(video => {
357
+ content += `### [${video.title}](${video.url})\n`;
358
+ const metadata = [];
359
+ if (video.duration) {
360
+ metadata.push(`Duration: ${video.duration}`);
361
+ }
362
+ if (video.speakers && video.speakers.length > 0) {
363
+ metadata.push(`Speakers: ${video.speakers.join(', ')}`);
364
+ }
365
+ if (video.hasTranscript) {
366
+ metadata.push('Transcript');
367
+ }
368
+ if (video.hasCode) {
369
+ metadata.push('Code Examples');
370
+ }
371
+ if (metadata.length > 0) {
372
+ content += `*${metadata.join(' | ')}*\n`;
373
+ }
374
+ if (video.topics.length > 0) {
375
+ content += `**Topics:** ${video.topics.join(', ')}\n`;
376
+ }
377
+ content += '\n';
378
+ });
379
+ });
380
+ return content;
381
+ }
382
+ /**
383
+ * Format search results
384
+ */
385
+ function formatSearchResults(results, query, searchIn) {
386
+ if (results.length === 0) {
387
+ return `No ${searchIn === 'code' ? 'code' : searchIn === 'transcript' ? 'transcript' : 'content'} found containing "${query}".`;
388
+ }
389
+ let content = '# WWDC Content Search Results\n\n';
390
+ content += `**Search Query:** "${query}"\n`;
391
+ content += `**Search Scope:** ${searchIn === 'code' ? 'Code' : searchIn === 'transcript' ? 'Transcript' : 'All Content'}\n`;
392
+ content += `**Found ${results.length} related videos**\n\n`;
393
+ results.forEach(result => {
394
+ content += `## [${result.video.title}](${result.video.url})\n`;
395
+ content += `*WWDC${result.video.year} | ${result.matches.length} matches*\n\n`;
396
+ result.matches.forEach(match => {
397
+ content += `**${match.type === 'code' ? 'Code' : 'Transcript'}**`;
398
+ if (match.timestamp) {
399
+ content += ` (${match.timestamp})`;
400
+ }
401
+ content += '\n';
402
+ content += `> ${match.context}\n\n`;
403
+ });
404
+ });
405
+ return content;
406
+ }
407
+ /**
408
+ * Format video details
409
+ */
410
+ function formatVideoDetail(video, includeTranscript, includeCode) {
411
+ let content = `# ${video.title}\n\n`;
412
+ content += `**WWDC${video.year}** | [Watch Video](${video.url})\n\n`;
413
+ // Basic information
414
+ if (video.duration) {
415
+ content += `**Duration:** ${video.duration}\n`;
416
+ }
417
+ if (video.speakers && video.speakers.length > 0) {
418
+ content += `**Speakers:** ${video.speakers.join(', ')}\n`;
419
+ }
420
+ if (video.topics.length > 0) {
421
+ content += `**Topics:** ${video.topics.join(', ')}\n`;
422
+ }
423
+ // Resource links
424
+ if (video.resources.hdVideo || video.resources.sdVideo || video.resources.resourceLinks) {
425
+ content += '\n**Resources:**\n';
426
+ if (video.resources.hdVideo) {
427
+ content += `- [HD Video](${video.resources.hdVideo})\n`;
428
+ }
429
+ if (video.resources.sdVideo) {
430
+ content += `- [SD Video](${video.resources.sdVideo})\n`;
431
+ }
432
+ if (video.resources.resourceLinks && video.resources.resourceLinks.length > 0) {
433
+ video.resources.resourceLinks.forEach(link => {
434
+ content += `- [${link.title}](${link.url})\n`;
435
+ });
436
+ }
437
+ }
438
+ // Chapters
439
+ if (video.chapters && video.chapters.length > 0) {
440
+ content += '\n## Chapters\n\n';
441
+ video.chapters.forEach(chapter => {
442
+ content += `- **${chapter.timestamp}** ${chapter.title}\n`;
443
+ });
444
+ }
445
+ // Transcript
446
+ if (includeTranscript && video.transcript) {
447
+ content += '\n## Transcript\n\n';
448
+ // If there are timestamped segments, use them
449
+ if (video.transcript.segments.length > 0) {
450
+ video.transcript.segments.forEach(segment => {
451
+ content += `**${segment.timestamp}**\n`;
452
+ content += `${segment.text}\n\n`;
453
+ });
454
+ }
455
+ else {
456
+ // Show full transcript text
457
+ content += video.transcript.fullText;
458
+ }
459
+ }
460
+ // Code examples
461
+ if (includeCode && video.codeExamples && video.codeExamples.length > 0) {
462
+ content += '\n## Code Examples\n\n';
463
+ video.codeExamples.forEach((example, index) => {
464
+ if (example.title) {
465
+ content += `### ${example.title}`;
466
+ }
467
+ else {
468
+ content += `### Code Example ${index + 1}`;
469
+ }
470
+ if (example.timestamp) {
471
+ content += ` (${example.timestamp})`;
472
+ }
473
+ content += '\n\n';
474
+ content += `\`\`\`${example.language}\n`;
475
+ content += example.code;
476
+ content += '\n\`\`\`\n\n';
477
+ if (example.context) {
478
+ content += `*${example.context}*\n\n`;
479
+ }
480
+ });
481
+ }
482
+ // Related videos
483
+ if (video.relatedVideos && video.relatedVideos.length > 0) {
484
+ content += '\n## Related Videos\n\n';
485
+ video.relatedVideos.forEach(related => {
486
+ content += `- [${related.title}](${related.url}) (WWDC${related.year})\n`;
487
+ });
488
+ }
489
+ return content;
490
+ }
491
+ /**
492
+ * Format code examples
493
+ */
494
+ function formatCodeExamples(examples, framework, topic, language) {
495
+ if (examples.length === 0) {
496
+ return 'No code examples found matching the criteria.';
497
+ }
498
+ let content = '# WWDC Code Examples\n\n';
499
+ // Filter conditions
500
+ const filters = [];
501
+ if (framework) {
502
+ filters.push(`Framework: ${framework}`);
503
+ }
504
+ if (topic) {
505
+ filters.push(`Topic: ${topic}`);
506
+ }
507
+ if (language) {
508
+ filters.push(`Language: ${language}`);
509
+ }
510
+ if (filters.length > 0) {
511
+ content += `**Filter Conditions:** ${filters.join(', ')}\n\n`;
512
+ }
513
+ content += `**Found ${examples.length} code examples**\n\n`;
514
+ // Group by language
515
+ const examplesByLanguage = examples.reduce((acc, ex) => {
516
+ if (!acc[ex.language]) {
517
+ acc[ex.language] = [];
518
+ }
519
+ acc[ex.language].push(ex);
520
+ return acc;
521
+ }, {});
522
+ Object.keys(examplesByLanguage).forEach(lang => {
523
+ content += `## ${lang.charAt(0).toUpperCase() + lang.slice(1)}\n\n`;
524
+ examplesByLanguage[lang].forEach(example => {
525
+ content += `### ${example.title || 'Code Example'}\n`;
526
+ content += `*From: [${example.videoTitle}](${example.videoUrl}) (WWDC${example.year})*`;
527
+ if (example.timestamp) {
528
+ content += ` *@ ${example.timestamp}*`;
529
+ }
530
+ content += '\n\n';
531
+ content += `\`\`\`${example.language}\n`;
532
+ content += example.code;
533
+ content += '\n\`\`\`\n\n';
534
+ });
535
+ });
536
+ return content;
537
+ }
538
+ /**
539
+ * Browse WWDC topics
540
+ */
541
+ export async function handleBrowseWWDCTopics(topicId, includeVideos = true, year, limit = 20) {
542
+ try {
543
+ const metadata = await loadGlobalMetadata();
544
+ if (!topicId) {
545
+ // List all available topics
546
+ let content = '# WWDC Topics\n\n';
547
+ content += `Found ${metadata.topics.length} topics:\n\n`;
548
+ metadata.topics.forEach(topic => {
549
+ content += `## [${topic.name}](${topic.url})\n`;
550
+ content += `**Topic ID:** ${topic.id}\n`;
551
+ // Show video count for this topic
552
+ const topicStats = metadata.statistics.byTopic[topic.id];
553
+ if (topicStats) {
554
+ content += `**Videos:** ${topicStats}\n`;
555
+ }
556
+ content += '\n';
557
+ });
558
+ return content;
559
+ }
560
+ // Browse specific topic
561
+ const topic = metadata.topics.find(t => t.id === topicId);
562
+ if (!topic) {
563
+ return `Topic "${topicId}" not found. Available topics: ${metadata.topics.map(t => t.id).join(', ')}`;
564
+ }
565
+ let content = `# ${topic.name}\n\n`;
566
+ content += `**Topic ID:** ${topic.id}\n`;
567
+ content += `**URL:** [${topic.url}](${topic.url})\n\n`;
568
+ if (includeVideos) {
569
+ try {
570
+ const topicIndex = await loadTopicIndex(topicId);
571
+ // Filter by year if specified
572
+ let videosToShow = topicIndex.videos;
573
+ if (year && year !== 'all') {
574
+ videosToShow = videosToShow.filter(v => v.year === year);
575
+ }
576
+ // Apply limit
577
+ videosToShow = videosToShow.slice(0, limit);
578
+ content += `## Videos (${videosToShow.length}${videosToShow.length === limit ? '+' : ''})\n\n`;
579
+ if (videosToShow.length === 0) {
580
+ content += 'No videos found for this topic.\n';
581
+ }
582
+ else {
583
+ // Group by year
584
+ const videosByYear = videosToShow.reduce((acc, video) => {
585
+ if (!acc[video.year]) {
586
+ acc[video.year] = [];
587
+ }
588
+ acc[video.year].push(video);
589
+ return acc;
590
+ }, {});
591
+ Object.keys(videosByYear)
592
+ .sort((a, b) => parseInt(b) - parseInt(a))
593
+ .forEach(y => {
594
+ content += `### WWDC${y}\n\n`;
595
+ videosByYear[y].forEach(video => {
596
+ content += `- [${video.title}](${video.url})`;
597
+ const features = [];
598
+ if (video.hasTranscript) {
599
+ features.push('Transcript');
600
+ }
601
+ if (video.hasCode) {
602
+ features.push('Code');
603
+ }
604
+ if (features.length > 0) {
605
+ content += ` | ${features.join(' | ')}`;
606
+ }
607
+ content += '\n';
608
+ });
609
+ content += '\n';
610
+ });
611
+ }
612
+ }
613
+ catch (error) {
614
+ content += `Error loading videos for topic: ${error instanceof Error ? error.message : String(error)}\n`;
615
+ }
616
+ }
617
+ return content;
618
+ }
619
+ catch (error) {
620
+ logger.error('Failed to browse WWDC topics:', error);
621
+ const errorMessage = error instanceof Error ? error.message : String(error);
622
+ return `Error: Failed to browse WWDC topics: ${errorMessage}`;
623
+ }
624
+ }
625
+ /**
626
+ * Find related WWDC videos
627
+ */
628
+ export async function handleFindRelatedWWDCVideos(videoId, year, includeExplicitRelated = true, includeTopicRelated = true, includeYearRelated = false, limit = 15) {
629
+ try {
630
+ // Load the source video
631
+ const sourceVideo = await loadVideoData(year, videoId);
632
+ let content = `# Related Videos for "${sourceVideo.title}"\n\n`;
633
+ content += `**Source:** [${sourceVideo.title}](${sourceVideo.url}) (WWDC${year})\n\n`;
634
+ const relatedVideos = [];
635
+ // 1. Explicit related videos from video metadata
636
+ if (includeExplicitRelated && sourceVideo.relatedVideos) {
637
+ for (const related of sourceVideo.relatedVideos) {
638
+ try {
639
+ const relatedVideo = await loadVideoData(related.year, related.id);
640
+ relatedVideos.push({
641
+ video: { ...relatedVideo, year: related.year },
642
+ relationship: 'Explicitly related',
643
+ score: 10,
644
+ });
645
+ }
646
+ catch (error) {
647
+ logger.warn(`Failed to load related video ${related.year}-${related.id}:`, error);
648
+ }
649
+ }
650
+ }
651
+ // 2. Topic-related videos
652
+ if (includeTopicRelated && sourceVideo.topics && sourceVideo.topics.length > 0) {
653
+ for (const topic of sourceVideo.topics) {
654
+ try {
655
+ // Try to find topic by name mapping
656
+ const metadata = await loadGlobalMetadata();
657
+ const topicEntry = metadata.topics.find(t => t.name.toLowerCase() === topic.toLowerCase() ||
658
+ t.id.toLowerCase().includes(topic.toLowerCase().replace(/\s+/g, '-')));
659
+ if (topicEntry) {
660
+ const topicIndex = await loadTopicIndex(topicEntry.id);
661
+ // Get videos from same topic (excluding source video)
662
+ const topicVideos = topicIndex.videos.filter(v => v.id !== videoId);
663
+ // Load video data for scoring
664
+ const videoFiles = topicVideos.slice(0, 10).map((v) => v.dataFile); // Limit to avoid too many requests
665
+ const videos = await loadVideosData(videoFiles);
666
+ for (const video of videos) {
667
+ // Skip if already added
668
+ if (relatedVideos.find(r => r.video.id === video.id && r.video.year === video.year)) {
669
+ continue;
670
+ }
671
+ // Calculate similarity score based on shared topics
672
+ const sharedTopics = (sourceVideo.topics || []).filter(t => video.topics?.some(vt => vt.toLowerCase() === t.toLowerCase()) || false);
673
+ const score = sharedTopics.length * 2;
674
+ relatedVideos.push({
675
+ video: { ...video, year: video.year },
676
+ relationship: `Same topic: ${topicEntry.name}`,
677
+ score,
678
+ });
679
+ }
680
+ }
681
+ }
682
+ catch (error) {
683
+ logger.warn(`Failed to find topic-related videos for topic ${topic}:`, error);
684
+ }
685
+ }
686
+ }
687
+ // 3. Year-related videos (same year, similar topics)
688
+ if (includeYearRelated) {
689
+ try {
690
+ const yearIndex = await loadYearIndex(year);
691
+ // Get videos from same year with overlapping topics
692
+ const yearVideos = yearIndex.videos.filter(v => v.id !== videoId &&
693
+ v.topics.some(t => sourceVideo.topics?.some(st => st.toLowerCase() === t.toLowerCase()) || false));
694
+ // Load a sample of videos
695
+ const videoFiles = yearVideos.slice(0, 10).map((v) => v.dataFile);
696
+ const videos = await loadVideosData(videoFiles);
697
+ for (const video of videos) {
698
+ // Skip if already added
699
+ if (relatedVideos.find(r => r.video.id === video.id && r.video.year === video.year)) {
700
+ continue;
701
+ }
702
+ const sharedTopics = (sourceVideo.topics || []).filter(t => video.topics?.some(vt => vt.toLowerCase() === t.toLowerCase()) || false);
703
+ const score = sharedTopics.length;
704
+ relatedVideos.push({
705
+ video: { ...video, year: video.year },
706
+ relationship: `Same year, shared topics: ${sharedTopics.join(', ')}`,
707
+ score,
708
+ });
709
+ }
710
+ }
711
+ catch (error) {
712
+ logger.warn('Failed to find year-related videos:', error);
713
+ }
714
+ }
715
+ // Sort by score (descending) and apply limit
716
+ relatedVideos.sort((a, b) => b.score - a.score);
717
+ const limitedResults = relatedVideos.slice(0, limit);
718
+ if (limitedResults.length === 0) {
719
+ content += 'No related videos found.\n';
720
+ }
721
+ else {
722
+ content += `## Related Videos (${limitedResults.length})\n\n`;
723
+ limitedResults.forEach(result => {
724
+ content += `### [${result.video.title}](${result.video.url})\n`;
725
+ content += `*WWDC${result.video.year} | ${result.relationship}*\n\n`;
726
+ const features = [];
727
+ if (result.video.duration) {
728
+ features.push(`Duration: ${result.video.duration}`);
729
+ }
730
+ if (result.video.hasTranscript) {
731
+ features.push('Transcript');
732
+ }
733
+ if (result.video.hasCode) {
734
+ features.push('Code');
735
+ }
736
+ if (features.length > 0) {
737
+ content += `${features.join(' | ')}\n`;
738
+ }
739
+ if (result.video.topics.length > 0) {
740
+ content += `**Topics:** ${result.video.topics.join(', ')}\n`;
741
+ }
742
+ content += '\n';
743
+ });
744
+ }
745
+ return content;
746
+ }
747
+ catch (error) {
748
+ logger.error('Failed to find related WWDC videos:', error);
749
+ const errorMessage = error instanceof Error ? error.message : String(error);
750
+ return `Error: Failed to find related WWDC videos: ${errorMessage}`;
751
+ }
752
+ }
753
+ /**
754
+ * List all available WWDC years
755
+ */
756
+ export async function handleListWWDCYears() {
757
+ try {
758
+ // Load metadata to get available years
759
+ const metadata = await loadGlobalMetadata();
760
+ let content = '# Available WWDC Years\n\n';
761
+ // Check if years exist in metadata
762
+ if (!metadata.years || metadata.years.length === 0) {
763
+ return 'No WWDC years available.';
764
+ }
765
+ // Sort years in descending order (newest first)
766
+ const sortedYears = [...metadata.years].sort((a, b) => b.localeCompare(a));
767
+ content += `**Total Years:** ${sortedYears.length}\n\n`;
768
+ content += '## Years with Video Counts\n\n';
769
+ // Get video count for each year from statistics
770
+ for (const year of sortedYears) {
771
+ const videoCount = metadata.statistics?.byYear?.[year] || 0;
772
+ content += `- **${year}**: ${videoCount} videos\n`;
773
+ }
774
+ // Add total video count
775
+ content += `\n**Total Videos:** ${metadata.totalVideos || 0}\n`;
776
+ // Add statistics summary if available
777
+ if (metadata.statistics) {
778
+ content += '\n## Statistics\n\n';
779
+ content += `- **Videos with Code:** ${metadata.statistics.videosWithCode || 0}\n`;
780
+ content += `- **Videos with Transcript:** ${metadata.statistics.videosWithTranscript || 0}\n`;
781
+ content += `- **Videos with Resources:** ${metadata.statistics.videosWithResources || 0}\n`;
782
+ }
783
+ return content;
784
+ }
785
+ catch (error) {
786
+ logger.error('Failed to list WWDC years:', error);
787
+ const errorMessage = error instanceof Error ? error.message : String(error);
788
+ return `Error: Failed to list WWDC years: ${errorMessage}`;
789
+ }
790
+ }
791
+ //# sourceMappingURL=wwdc-handlers.js.map