@movk/nuxt-docs 1.17.4 → 1.17.5

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.
@@ -10,6 +10,7 @@ const { ui } = useAppConfig()
10
10
  const appBaseURL = useRuntimeConfig().app?.baseURL || '/'
11
11
 
12
12
  const mdPath = computed(() => `${site.url}/raw${route.path}.md`)
13
+ const aiPrompt = computed(() => `I'm looking at this documentation: ${mdPath.value}\nHelp me understand how to use it. Be ready to explain concepts, give examples, or help debug based on it.`)
13
14
 
14
15
  const items = [[
15
16
  {
@@ -33,13 +34,13 @@ const items = [[
33
34
  label: 'Open in ChatGPT',
34
35
  icon: 'i-simple-icons-openai',
35
36
  target: '_blank',
36
- to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${mdPath.value} so I can ask questions about it.`)}`
37
+ to: `https://chatgpt.com/?prompt=${encodeURIComponent(aiPrompt.value)}`
37
38
  },
38
39
  {
39
40
  label: 'Open in Claude',
40
41
  icon: 'i-simple-icons-anthropic',
41
42
  target: '_blank',
42
- to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${mdPath.value} so I can ask questions about it.`)}`
43
+ to: `https://claude.ai/new?q=${encodeURIComponent(aiPrompt.value)}`
43
44
  }
44
45
  ], [
45
46
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@movk/nuxt-docs",
3
3
  "type": "module",
4
- "version": "1.17.4",
4
+ "version": "1.17.5",
5
5
  "private": false,
6
6
  "description": "Modern Nuxt 4 documentation theme with auto-generated component docs, AI chat assistant, MCP server, and complete developer experience optimization.",
7
7
  "author": "YiXuan <mhaibaraai@gmail.com>",
@@ -39,9 +39,9 @@
39
39
  "README.md"
40
40
  ],
41
41
  "dependencies": {
42
- "@ai-sdk/gateway": "^3.0.91",
42
+ "@ai-sdk/gateway": "^3.0.93",
43
43
  "@ai-sdk/mcp": "^1.0.35",
44
- "@ai-sdk/vue": "^3.0.149",
44
+ "@ai-sdk/vue": "^3.0.152",
45
45
  "@iconify-json/lucide": "^1.2.101",
46
46
  "@iconify-json/simple-icons": "^1.2.77",
47
47
  "@iconify-json/vscode-icons": "^1.2.45",
@@ -63,14 +63,14 @@
63
63
  "@takumi-rs/wasm": "^0.73.1",
64
64
  "@vueuse/core": "^14.2.1",
65
65
  "@vueuse/nuxt": "^14.2.1",
66
- "ai": "^6.0.149",
66
+ "ai": "^6.0.152",
67
67
  "defu": "^6.1.7",
68
68
  "exsolve": "^1.0.8",
69
69
  "git-url-parse": "^16.1.0",
70
70
  "motion-v": "^2.2.0",
71
71
  "nuxt-component-meta": "^0.17.2",
72
72
  "nuxt-llms": "^0.2.0",
73
- "nuxt-og-image": "^6.3.2",
73
+ "nuxt-og-image": "^6.3.3",
74
74
  "nuxt-site-config": "^4.0.7",
75
75
  "ohash": "^2.0.11",
76
76
  "pathe": "^2.0.3",
@@ -2,18 +2,31 @@ import { z } from 'zod'
2
2
 
3
3
  export default defineMcpTool({
4
4
  description: '检索特定的示例实现代码和详细信息',
5
+ annotations: {
6
+ readOnlyHint: true,
7
+ destructiveHint: false,
8
+ idempotentHint: true,
9
+ openWorldHint: false
10
+ },
5
11
  inputSchema: {
6
12
  exampleName: z.string().describe('示例名称(PascalCase)')
7
13
  },
14
+ inputExamples: [
15
+ { exampleName: 'ButtonBasic' },
16
+ { exampleName: 'ModalOverlay' }
17
+ ],
8
18
  cache: '30m',
9
19
  async handler({ exampleName }) {
10
20
  try {
11
21
  const result = await $fetch<{ code: string }>(`/api/component-example/${exampleName}.json`)
12
- return {
13
- content: [{ type: 'text' as const, text: result.code }]
22
+ return result.code
23
+ } catch (error: unknown) {
24
+ const err = error as { statusCode?: number, response?: { status?: number } }
25
+ const status = err?.statusCode ?? err?.response?.status
26
+ if (status === 404) {
27
+ throw createError({ statusCode: 404, message: `示例 '${exampleName}' 未找到。使用 list_examples 工具查看所有可用示例。` })
14
28
  }
15
- } catch {
16
- throw createError({ statusCode: 404, message: `示例 '${exampleName}' 未找到。使用 list_examples 工具查看所有可用示例。` })
29
+ throw error
17
30
  }
18
31
  }
19
32
  })
@@ -2,88 +2,33 @@ import { z } from 'zod'
2
2
 
3
3
  export default defineMcpTool({
4
4
  description: `检索特定文档页面的完整内容和详细信息,使用 \`sections\` 参数仅获取特定的 h2 部分以减少响应大小。`,
5
+ annotations: {
6
+ readOnlyHint: true,
7
+ destructiveHint: false,
8
+ idempotentHint: true,
9
+ openWorldHint: false
10
+ },
5
11
  inputSchema: {
6
12
  path: z.string().describe('从 list-pages 获取或用户提供的页面路径(例如 /docs/getting-started/installation)'),
7
13
  sections: z.array(z.string()).optional().describe('要返回的特定 h2 部分标题(例如 ["Usage","API"])。如果省略,则返回完整文档。')
8
14
  },
15
+ inputExamples: [
16
+ { path: '/docs/components/button', sections: ['Usage', 'API'] },
17
+ { path: '/docs/getting-started/installation' }
18
+ ],
9
19
  cache: '30m',
10
20
  handler: async ({ path, sections }) => {
21
+ let content
11
22
  try {
12
- const fullContent = await $fetch<string>(`/raw${path}.md`)
13
-
14
- let content = fullContent
15
-
16
- // If sections are specified, extract only those sections
17
- if (sections && sections.length > 0) {
18
- content = extractSections(fullContent, sections)
19
- }
20
-
21
- return {
22
- content: [{ type: 'text', text: JSON.stringify(content, null, 2) }]
23
- }
24
- } catch (error) {
25
- throw createError({ statusCode: 500, message: `获取页面失败: ${error}` })
26
- }
27
- }
28
- })
29
-
30
- /**
31
- * Extract specific sections from markdown content based on h2 headings
32
- */
33
- function extractSections(markdown: string, sectionTitles: string[]): string {
34
- const lines = markdown.split('\n')
35
- const result: string[] = []
36
-
37
- // Normalize section titles for matching
38
- const normalizedTitles = sectionTitles.map(t => t.toLowerCase().trim())
39
-
40
- // Always include title (h1) and description (first blockquote)
41
- let inHeader = true
42
- for (const line of lines) {
43
- if (inHeader) {
44
- result.push(line)
45
- // Stop after the description blockquote
46
- if (line.startsWith('>') && result.length > 1) {
47
- result.push('')
48
- inHeader = false
49
- }
50
- continue
23
+ content = await $fetch<string>(`/raw${path}.md`)
24
+ } catch {
25
+ throw createError({ statusCode: 404, message: `获取页面失败: ${path}` })
51
26
  }
52
- break
53
- }
54
-
55
- // Find and extract requested sections
56
- let currentSection: string | null = null
57
- let sectionContent: string[] = []
58
-
59
- for (let i = 0; i < lines.length; i++) {
60
- const line = lines[i]
61
- if (!line) continue
62
-
63
- // Check for h2 heading
64
- if (line.startsWith('## ')) {
65
- // Save previous section if it was requested
66
- if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
67
- result.push(...sectionContent)
68
- result.push('')
69
- }
70
-
71
- // Start new section
72
- currentSection = line.replace('## ', '').trim()
73
- sectionContent = [line]
74
- continue
27
+ // If sections are specified, extract only those sections
28
+ if (sections && sections.length > 0) {
29
+ content = extractSections(content, sections)
75
30
  }
76
31
 
77
- // Add line to current section
78
- if (currentSection) {
79
- sectionContent.push(line)
80
- }
81
- }
82
-
83
- // Don't forget the last section
84
- if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
85
- result.push(...sectionContent)
32
+ return content
86
33
  }
87
-
88
- return result.join('\n').trim()
89
- }
34
+ })
@@ -3,8 +3,14 @@ import { listComponentExamples } from '#component-example/nitro'
3
3
 
4
4
  export default defineMcpTool({
5
5
  description: '列出所有可用的示例和代码演示',
6
+ annotations: {
7
+ readOnlyHint: true,
8
+ destructiveHint: false,
9
+ idempotentHint: true,
10
+ openWorldHint: false
11
+ },
6
12
  cache: '1h',
7
13
  async handler() {
8
- return await listComponentExamples()
14
+ return listComponentExamples()
9
15
  }
10
16
  })
@@ -3,6 +3,12 @@ import { inferSiteURL } from '../../../utils/meta'
3
3
 
4
4
  export default defineMcpTool({
5
5
  description: '列出所有入门指南和安装说明',
6
+ annotations: {
7
+ readOnlyHint: true,
8
+ destructiveHint: false,
9
+ idempotentHint: true,
10
+ openWorldHint: false
11
+ },
6
12
  cache: '30m',
7
13
  async handler() {
8
14
  const event = useEvent()
@@ -14,14 +20,12 @@ export default defineMcpTool({
14
20
  .select('id', 'title', 'description', 'path', 'navigation')
15
21
  .all()
16
22
 
17
- const result = pages.map(page => ({
23
+ return pages.map(page => ({
18
24
  title: page.title,
19
25
  description: page.description,
20
26
  path: page.path,
21
27
  url: `${siteUrl}${page.path}`,
22
28
  navigation: page.navigation
23
29
  })).sort((a, b) => a.path.localeCompare(b.path))
24
-
25
- return result
26
30
  }
27
31
  })
@@ -2,6 +2,12 @@ import { queryCollection } from '@nuxt/content/server'
2
2
 
3
3
  export default defineMcpTool({
4
4
  description: `列出所有可用的文档页面及其分类和基本信息。`,
5
+ annotations: {
6
+ readOnlyHint: true,
7
+ destructiveHint: false,
8
+ idempotentHint: true,
9
+ openWorldHint: false
10
+ },
5
11
  cache: '1h',
6
12
  async handler() {
7
13
  const event = useEvent()
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Extract specific h2 sections from markdown content.
3
+ * Always includes the title (h1) and description blockquote.
4
+ */
5
+ export function extractSections(markdown: string, sectionTitles: string[]): string {
6
+ const lines = markdown.split('\n')
7
+ const result: string[] = []
8
+
9
+ // Normalize section titles for matching
10
+ const normalizedTitles = sectionTitles.map(t => t.toLowerCase().trim())
11
+
12
+ // Always include title (h1) and description (first blockquote)
13
+ let inHeader = true
14
+ for (const line of lines) {
15
+ if (inHeader) {
16
+ if (line.startsWith('## ')) {
17
+ inHeader = false
18
+ break
19
+ }
20
+ result.push(line)
21
+ // Stop after the description blockquote
22
+ if (line.startsWith('>') && result.length > 1) {
23
+ result.push('')
24
+ inHeader = false
25
+ }
26
+ continue
27
+ }
28
+ break
29
+ }
30
+
31
+ // Find and extract requested sections
32
+ let currentSection: string | null = null
33
+ let sectionContent: string[] = []
34
+
35
+ for (const line of lines) {
36
+ // Check for h2 heading
37
+ if (line.startsWith('## ')) {
38
+ // Save previous section if it was requested
39
+ if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
40
+ result.push(...sectionContent)
41
+ result.push('')
42
+ }
43
+
44
+ // Start new section
45
+ currentSection = line.replace('## ', '').trim()
46
+ sectionContent = [line]
47
+ continue
48
+ }
49
+
50
+ // Add line to current section
51
+ if (currentSection) {
52
+ sectionContent.push(line)
53
+ }
54
+ }
55
+
56
+ // Don't forget the last section
57
+ if (currentSection && normalizedTitles.includes(currentSection.toLowerCase())) {
58
+ result.push(...sectionContent)
59
+ }
60
+
61
+ return result.join('\n').trim()
62
+ }