docus 5.9.0 → 5.10.1

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 (57) hide show
  1. package/app/app.config.ts +12 -0
  2. package/app/components/app/AppHeader.vue +3 -6
  3. package/app/components/app/AppHeaderBody.vue +6 -2
  4. package/app/components/app/AppHeaderBottom.vue +6 -2
  5. package/app/components/app/AppHeaderLeft.vue +16 -0
  6. package/app/components/docs/DocsAsideLeftBody.vue +6 -1
  7. package/app/components/docs/DocsAsideMobileBar.vue +11 -1
  8. package/app/components/docs/DocsAsideRight.vue +6 -1
  9. package/app/composables/useUIConfig.ts +30 -0
  10. package/i18n/locales/ar.json +27 -0
  11. package/i18n/locales/be.json +27 -0
  12. package/i18n/locales/bg.json +27 -0
  13. package/i18n/locales/bn.json +27 -0
  14. package/i18n/locales/ca.json +27 -0
  15. package/i18n/locales/ckb.json +32 -1
  16. package/i18n/locales/cs.json +27 -0
  17. package/i18n/locales/da.json +27 -0
  18. package/i18n/locales/de.json +27 -0
  19. package/i18n/locales/el.json +27 -0
  20. package/i18n/locales/es.json +27 -0
  21. package/i18n/locales/et.json +27 -0
  22. package/i18n/locales/fi.json +27 -0
  23. package/i18n/locales/he.json +27 -0
  24. package/i18n/locales/hi.json +27 -0
  25. package/i18n/locales/hy.json +27 -0
  26. package/i18n/locales/id.json +27 -0
  27. package/i18n/locales/it.json +27 -0
  28. package/i18n/locales/ja.json +27 -0
  29. package/i18n/locales/kk.json +27 -0
  30. package/i18n/locales/km.json +27 -0
  31. package/i18n/locales/ko.json +27 -0
  32. package/i18n/locales/ky.json +27 -0
  33. package/i18n/locales/lb.json +27 -0
  34. package/i18n/locales/ms.json +27 -0
  35. package/i18n/locales/nb.json +27 -0
  36. package/i18n/locales/nl.json +27 -0
  37. package/i18n/locales/pl.json +27 -0
  38. package/i18n/locales/pt-BR.json +27 -0
  39. package/i18n/locales/ro.json +27 -0
  40. package/i18n/locales/ru.json +27 -0
  41. package/i18n/locales/si.json +27 -0
  42. package/i18n/locales/sl.json +27 -0
  43. package/i18n/locales/sv.json +27 -0
  44. package/i18n/locales/tr.json +27 -0
  45. package/i18n/locales/uk.json +27 -0
  46. package/i18n/locales/ur.json +27 -0
  47. package/i18n/locales/vi.json +27 -0
  48. package/i18n/locales/zh-CN.json +27 -0
  49. package/index.d.ts +33 -0
  50. package/modules/assistant/README.md +7 -5
  51. package/modules/assistant/index.ts +19 -11
  52. package/modules/assistant/runtime/server/api/search.ts +41 -9
  53. package/modules/config.ts +1 -1
  54. package/modules/css.ts +12 -0
  55. package/modules/markdown-rewrite.ts +11 -1
  56. package/modules/skills/index.ts +25 -13
  57. package/package.json +19 -18
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "වචන ලකුණ බාගත කරන ලදී",
33
33
  "copyLogoFailed": "ලාංඡනය පිටපත් කිරීමට අසමත් විය",
34
34
  "copyWordmarkFailed": "වචන ලකුණ පිටපත් කිරීමට අසමත් විය"
35
+ },
36
+ "assistant": {
37
+ "title": "AI අහන්න",
38
+ "placeholder": "ප්‍රශ්නයක් අහන්න...",
39
+ "tooltip": "AI ගෙන් ප්‍රශ්නයක් අසන්න",
40
+ "tryAsking": "ප්රශ්නයක් ඇසීමට උත්සාහ කරන්න",
41
+ "askAnything": "ඕන දෙයක් අහන්න...",
42
+ "clearChat": "කතාබස් පැහැදිලි කරන්න",
43
+ "close": "වසන්න",
44
+ "expand": "පුළුල් කරන්න",
45
+ "collapse": "හකුළන්න",
46
+ "thinking": "සිතමින්...",
47
+ "askMeAnything": "ඕන දෙයක් අහන්න",
48
+ "askMeAnythingDescription": "ලේඛන සැරිසැරීමට, සංකල්ප තේරුම් ගැනීමට සහ පිළිතුරු සෙවීමට උදවු ලබා ගන්න.",
49
+ "faq": "නිතර අසන පැන",
50
+ "chatCleared": "නැවුම් කිරීමේදී කතාබස් හිස් වේ",
51
+ "lineBreak": "රේඛා බිඳීම",
52
+ "explainWithAi": "AI සමඟ පැහැදිලි කරන්න",
53
+ "toolListPages": "ලැයිස්තුගත ලේඛන පිටු",
54
+ "toolReadPage": "කියවන්න",
55
+ "loading": {
56
+ "searching": "ලේඛන සෙවීම",
57
+ "reading": "ලේඛන හරහා කියවීම",
58
+ "analyzing": "අන්තර්ගතය විශ්ලේෂණය කිරීම",
59
+ "finding": "හොඳම පිළිතුර සොයා ගැනීම",
60
+ "finished": "භාවිතා කරන ලද මූලාශ්ර"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "Besedna znamka prenesena",
33
33
  "copyLogoFailed": "Kopiranje logotipa ni uspelo",
34
34
  "copyWordmarkFailed": "Kopiranje besedne znamke ni uspelo"
35
+ },
36
+ "assistant": {
37
+ "title": "Vprašajte AI",
38
+ "placeholder": "Postavite vprašanje...",
39
+ "tooltip": "Zastavite vprašanje AI",
40
+ "tryAsking": "Poskusite postaviti vprašanje",
41
+ "askAnything": "Vprašajte karkoli...",
42
+ "clearChat": "Počisti klepet",
43
+ "close": "Zapri",
44
+ "expand": "Razširi",
45
+ "collapse": "Strni",
46
+ "thinking": "Razmišljanje...",
47
+ "askMeAnything": "Vprašaj karkoli",
48
+ "askMeAnythingDescription": "Poiščite pomoč pri krmarjenju po dokumentaciji, razumevanju konceptov in iskanju odgovorov.",
49
+ "faq": "Pogosta vprašanja",
50
+ "chatCleared": "Klepet se ob osvežitvi izbriše",
51
+ "lineBreak": "Prelom vrstice",
52
+ "explainWithAi": "Razloži z AI",
53
+ "toolListPages": "Navedene strani dokumentacije",
54
+ "toolReadPage": "Preberi",
55
+ "loading": {
56
+ "searching": "Iskanje po dokumentaciji",
57
+ "reading": "Prebiranje dokumentov",
58
+ "analyzing": "Analiza vsebine",
59
+ "finding": "Iskanje najboljšega odgovora",
60
+ "finished": "Uporabljeni viri"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "Ordmärke nedladdat",
33
33
  "copyLogoFailed": "Kunde inte kopiera logotyp",
34
34
  "copyWordmarkFailed": "Kunde inte kopiera ordmärke"
35
+ },
36
+ "assistant": {
37
+ "title": "Fråga AI",
38
+ "placeholder": "Ställ en fråga...",
39
+ "tooltip": "Ställ en fråga till AI",
40
+ "tryAsking": "Försök att ställa en fråga",
41
+ "askAnything": "Fråga vad som helst...",
42
+ "clearChat": "Rensa chatten",
43
+ "close": "Stäng",
44
+ "expand": "Expandera",
45
+ "collapse": "Kollapsa",
46
+ "thinking": "Funderar...",
47
+ "askMeAnything": "Fråga vad som helst",
48
+ "askMeAnythingDescription": "Få hjälp med att navigera i dokumentationen, förstå begrepp och hitta svar.",
49
+ "faq": "FAQ",
50
+ "chatCleared": "Chatten rensas vid uppdatering",
51
+ "lineBreak": "Radbrytning",
52
+ "explainWithAi": "Förklara med AI",
53
+ "toolListPages": "Listade dokumentationssidor",
54
+ "toolReadPage": "Läs",
55
+ "loading": {
56
+ "searching": "Söker i dokumentationen",
57
+ "reading": "Läser igenom dokumenten",
58
+ "analyzing": "Analysera innehållet",
59
+ "finding": "Att hitta det bästa svaret",
60
+ "finished": "Använda källor"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "Wordmark indirildi",
33
33
  "copyLogoFailed": "Logo kopyalanamadı",
34
34
  "copyWordmarkFailed": "Wordmark kopyalanamadı"
35
+ },
36
+ "assistant": {
37
+ "title": "Yapay zekaya sor",
38
+ "placeholder": "Bir soru sorun...",
39
+ "tooltip": "Yapay zekaya bir soru sorun",
40
+ "tryAsking": "Bir soru sormayı deneyin",
41
+ "askAnything": "Her şeyi sor...",
42
+ "clearChat": "Sohbeti temizle",
43
+ "close": "Kapat",
44
+ "expand": "Genişlet",
45
+ "collapse": "Daralt",
46
+ "thinking": "Düşünüyorum...",
47
+ "askMeAnything": "Her şeyi sor",
48
+ "askMeAnythingDescription": "Belgelerde gezinme, kavramları anlama ve yanıt bulma konusunda yardım alın.",
49
+ "faq": "SSS",
50
+ "chatCleared": "Sohbet yenilendiğinde temizlenir",
51
+ "lineBreak": "Satır sonu",
52
+ "explainWithAi": "Yapay zeka ile açıklayın",
53
+ "toolListPages": "Listelenen dokümantasyon sayfaları",
54
+ "toolReadPage": "Oku",
55
+ "loading": {
56
+ "searching": "Dokümantasyonda arama",
57
+ "reading": "Dokümanlar okunuyor",
58
+ "analyzing": "İçerik analiz ediliyor",
59
+ "finding": "En iyi yanıt bulunuyor",
60
+ "finished": "Kullanılan kaynaklar"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "Словесний знак завантажено",
33
33
  "copyLogoFailed": "Не вдалося скопіювати логотип",
34
34
  "copyWordmarkFailed": "Не вдалося скопіювати словесний знак"
35
+ },
36
+ "assistant": {
37
+ "title": "Запитайте ШІ",
38
+ "placeholder": "Задайте питання...",
39
+ "tooltip": "Задайте питання ШІ",
40
+ "tryAsking": "Спробуйте задати питання",
41
+ "askAnything": "Запитайте будь-що...",
42
+ "clearChat": "Очистити чат",
43
+ "close": "Закрити",
44
+ "expand": "Розгорнути",
45
+ "collapse": "Згорнути",
46
+ "thinking": "Думаючи...",
47
+ "askMeAnything": "Запитайте будь-що",
48
+ "askMeAnythingDescription": "Отримайте допомогу в навігації документацією, розумінні понять і пошуку відповідей.",
49
+ "faq": "Поширені запитання",
50
+ "chatCleared": "Чат очищається під час оновлення",
51
+ "lineBreak": "Розрив рядка",
52
+ "explainWithAi": "Поясніть за допомогою ШІ",
53
+ "toolListPages": "Перелічені сторінки документації",
54
+ "toolReadPage": "Читати",
55
+ "loading": {
56
+ "searching": "Пошук документації",
57
+ "reading": "Читання документів",
58
+ "analyzing": "Аналіз змісту",
59
+ "finding": "Пошук найкращої відповіді",
60
+ "finished": "Використані джерела"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "ورڈ مارک ڈاؤن لوڈ ہو گیا",
33
33
  "copyLogoFailed": "لوگو کاپی نہیں ہو سکا",
34
34
  "copyWordmarkFailed": "ورڈ مارک کاپی نہیں ہو سکا"
35
+ },
36
+ "assistant": {
37
+ "title": "AI سے پوچھیں۔",
38
+ "placeholder": "ایک سوال پوچھیں...",
39
+ "tooltip": "AI سے ایک سوال پوچھیں۔",
40
+ "tryAsking": "ایک سوال پوچھنے کی کوشش کریں۔",
41
+ "askAnything": "کچھ بھی پوچھو...",
42
+ "clearChat": "چیٹ صاف کریں۔",
43
+ "close": "بند",
44
+ "expand": "پھیلائیں۔",
45
+ "collapse": "سمٹنا",
46
+ "thinking": "سوچ رہا ہے...",
47
+ "askMeAnything": "کچھ بھی پوچھو",
48
+ "askMeAnythingDescription": "دستاویزات کو نیویگیٹ کرنے، تصورات کو سمجھنے اور جوابات تلاش کرنے میں مدد حاصل کریں۔",
49
+ "faq": "اکثر پوچھے گئے سوالات",
50
+ "chatCleared": "ریفریش پر چیٹ صاف ہو جاتی ہے۔",
51
+ "lineBreak": "لائن بریک",
52
+ "explainWithAi": "AI کے ساتھ وضاحت کریں۔",
53
+ "toolListPages": "درج دستاویزات کے صفحات",
54
+ "toolReadPage": "پڑھیں",
55
+ "loading": {
56
+ "searching": "دستاویزات کی تلاش",
57
+ "reading": "دستاویزات کے ذریعے پڑھنا",
58
+ "analyzing": "مواد کا تجزیہ",
59
+ "finding": "بہترین جواب تلاش کرنا",
60
+ "finished": "ذرائع استعمال کیے گئے۔"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "Đã tải wordmark",
33
33
  "copyLogoFailed": "Không thể sao chép logo",
34
34
  "copyWordmarkFailed": "Không thể sao chép wordmark"
35
+ },
36
+ "assistant": {
37
+ "title": "Hỏi AI",
38
+ "placeholder": "Đặt một câu hỏi...",
39
+ "tooltip": "Đặt câu hỏi cho AI",
40
+ "tryAsking": "Hãy thử đặt một câu hỏi",
41
+ "askAnything": "Hỏi bất cứ điều gì...",
42
+ "clearChat": "Xóa cuộc trò chuyện",
43
+ "close": "Đóng",
44
+ "expand": "Mở rộng",
45
+ "collapse": "Thu gọn",
46
+ "thinking": "Đang suy nghĩ...",
47
+ "askMeAnything": "Hỏi bất cứ điều gì",
48
+ "askMeAnythingDescription": "Nhận trợ giúp điều hướng tài liệu, hiểu các khái niệm và tìm câu trả lời.",
49
+ "faq": "Câu hỏi thường gặp",
50
+ "chatCleared": "Trò chuyện sẽ bị xóa khi làm mới",
51
+ "lineBreak": "Ngắt dòng",
52
+ "explainWithAi": "Giải thích bằng AI",
53
+ "toolListPages": "Các trang tài liệu được liệt kê",
54
+ "toolReadPage": "Đọc",
55
+ "loading": {
56
+ "searching": "Tìm kiếm tài liệu",
57
+ "reading": "Đọc qua tài liệu",
58
+ "analyzing": "Phân tích nội dung",
59
+ "finding": "Tìm câu trả lời tốt nhất",
60
+ "finished": "Nguồn được sử dụng"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "文字标识已下载",
33
33
  "copyLogoFailed": "复制图标失败",
34
34
  "copyWordmarkFailed": "复制文字标识失败"
35
+ },
36
+ "assistant": {
37
+ "title": "询问人工智能",
38
+ "placeholder": "问一个问题...",
39
+ "tooltip": "问人工智能一个问题",
40
+ "tryAsking": "尝试提问",
41
+ "askAnything": "想问什么都可以...",
42
+ "clearChat": "清除聊天内容",
43
+ "close": "关闭",
44
+ "expand": "展开",
45
+ "collapse": "崩溃",
46
+ "thinking": "想着……",
47
+ "askMeAnything": "询问任何事情",
48
+ "askMeAnythingDescription": "获取浏览文档、理解概念和寻找答案的帮助。",
49
+ "faq": "常见问题解答",
50
+ "chatCleared": "刷新时聊天会被清除",
51
+ "lineBreak": "换行符",
52
+ "explainWithAi": "用AI解释",
53
+ "toolListPages": "列出的文档页面",
54
+ "toolReadPage": "阅读",
55
+ "loading": {
56
+ "searching": "搜索文档",
57
+ "reading": "通读文档",
58
+ "analyzing": "分析内容",
59
+ "finding": "寻找最佳答案",
60
+ "finished": "使用的来源"
61
+ }
35
62
  }
36
63
  }
package/index.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { AssistantModuleOptions } from './modules/assistant'
2
+ import type { SkillsModuleOptions } from './modules/skills'
3
+
4
+ export interface DocusNuxtConfig {
5
+ assistant?: AssistantModuleOptions
6
+ skills?: SkillsModuleOptions
7
+ }
8
+
9
+ declare module '@nuxt/schema' {
10
+ interface NuxtConfig {
11
+ docus?: DocusNuxtConfig
12
+ /** @deprecated Use `docus.assistant` instead */
13
+ assistant?: AssistantModuleOptions
14
+ }
15
+ interface NuxtOptions {
16
+ docus?: DocusNuxtConfig
17
+ /** @deprecated Use `docus.assistant` instead */
18
+ assistant?: AssistantModuleOptions
19
+ }
20
+ }
21
+
22
+ declare module 'nuxt/schema' {
23
+ interface NuxtConfig {
24
+ docus?: DocusNuxtConfig
25
+ /** @deprecated Use `docus.assistant` instead */
26
+ assistant?: AssistantModuleOptions
27
+ }
28
+ interface NuxtOptions {
29
+ docus?: DocusNuxtConfig
30
+ /** @deprecated Use `docus.assistant` instead */
31
+ assistant?: AssistantModuleOptions
32
+ }
33
+ }
@@ -27,11 +27,13 @@ pnpm add @ai-sdk/mcp @ai-sdk/vue @ai-sdk/gateway ai motion-v shiki shiki-stream
27
27
  export default defineNuxtConfig({
28
28
  modules: ['./modules/assistant'],
29
29
 
30
- assistant: {
31
- apiPath: '/__docus__/assistant',
32
- mcpServer: '/mcp',
33
- model: 'google/gemini-3-flash',
34
- }
30
+ docus: {
31
+ assistant: {
32
+ apiPath: '/__docus__/assistant',
33
+ mcpServer: '/mcp',
34
+ model: 'google/gemini-3-flash',
35
+ },
36
+ },
35
37
  })
36
38
  ```
37
39
 
@@ -1,4 +1,5 @@
1
1
  import { addComponent, addImports, addServerHandler, createResolver, defineNuxtModule, logger } from '@nuxt/kit'
2
+ import { defu } from 'defu'
2
3
 
3
4
  export interface AssistantModuleOptions {
4
5
  /**
@@ -22,17 +23,24 @@ export interface AssistantModuleOptions {
22
23
 
23
24
  const log = logger.withTag('Docus')
24
25
 
26
+ const defaults: Required<AssistantModuleOptions> = {
27
+ apiPath: '/__docus__/assistant',
28
+ mcpServer: '/mcp',
29
+ model: 'google/gemini-3-flash',
30
+ }
31
+
25
32
  export default defineNuxtModule<AssistantModuleOptions>({
26
33
  meta: {
27
34
  name: 'assistant',
28
- configKey: 'assistant',
29
- },
30
- defaults: {
31
- apiPath: '/__docus__/assistant',
32
- mcpServer: '/mcp',
33
- model: 'google/gemini-3-flash',
34
35
  },
35
- setup(options, nuxt) {
36
+ setup(_options, nuxt) {
37
+ const legacyOptions = nuxt.options.assistant
38
+ if (legacyOptions && Object.keys(legacyOptions).length > 0) {
39
+ log.warn('`assistant` top-level config is deprecated. Move it under `docus.assistant` in nuxt.config.ts')
40
+ }
41
+
42
+ const options = defu(nuxt.options.docus?.assistant, legacyOptions, defaults) as Required<AssistantModuleOptions>
43
+
36
44
  const hasAiGatewayAuth = !!(
37
45
  process.env.AI_GATEWAY_API_KEY || process.env.VERCEL_OIDC_TOKEN
38
46
  )
@@ -41,7 +49,7 @@ export default defineNuxtModule<AssistantModuleOptions>({
41
49
 
42
50
  nuxt.options.runtimeConfig.public.assistant = {
43
51
  enabled: hasAiGatewayAuth,
44
- apiPath: options.apiPath!,
52
+ apiPath: options.apiPath,
45
53
  }
46
54
 
47
55
  addImports([
@@ -74,11 +82,11 @@ export default defineNuxtModule<AssistantModuleOptions>({
74
82
  }
75
83
 
76
84
  nuxt.options.runtimeConfig.assistant = {
77
- mcpServer: options.mcpServer!,
78
- model: options.model!,
85
+ mcpServer: options.mcpServer,
86
+ model: options.model,
79
87
  }
80
88
 
81
- const routePath = options.apiPath!.replace(/^\//, '')
89
+ const routePath = options.apiPath.replace(/^\//, '')
82
90
  addServerHandler({
83
91
  route: `/${routePath}`,
84
92
  handler: resolve('./runtime/server/api/search'),
@@ -1,9 +1,27 @@
1
1
  import { streamText, convertToModelMessages, createUIMessageStream, createUIMessageStreamResponse } from 'ai'
2
2
  import type { UIMessageStreamWriter, ToolCallPart, ToolSet } from 'ai'
3
3
  import { createMCPClient } from '@ai-sdk/mcp'
4
+ import type { H3Event } from 'h3'
4
5
 
5
6
  const MAX_STEPS = 10
6
7
 
8
+ function createLocalFetch(event: H3Event): typeof fetch {
9
+ const origin = getRequestURL(event).origin
10
+
11
+ return (input, init) => {
12
+ const requestUrl = input instanceof URL
13
+ ? input
14
+ : typeof input === 'string'
15
+ ? new URL(input, origin)
16
+ : new URL(input.url)
17
+ const localPath = requestUrl.origin === origin
18
+ ? `${requestUrl.pathname}${requestUrl.search}`
19
+ : requestUrl.toString()
20
+
21
+ return event.fetch(localPath, init)
22
+ }
23
+ }
24
+
7
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
26
  function stopWhenResponseComplete({ steps }: { steps: any[] }): boolean {
9
27
  const lastStep = steps.at(-1)
@@ -66,15 +84,29 @@ export default defineEventHandler(async (event) => {
66
84
  const mcpServer = config.assistant.mcpServer
67
85
  const isExternalUrl = mcpServer.startsWith('http://') || mcpServer.startsWith('https://')
68
86
  const baseURL = config.app?.baseURL?.replace(/\/$/, '') || ''
69
- const mcpUrl = isExternalUrl
70
- ? mcpServer
71
- : import.meta.dev
72
- ? `http://localhost:3000${baseURL}${mcpServer}`
73
- : `${getRequestURL(event).origin}${baseURL}${mcpServer}`
74
-
75
- const httpClient = await createMCPClient({
76
- transport: { type: 'http', url: mcpUrl },
77
- })
87
+
88
+ let transport: Parameters<typeof createMCPClient>[0]['transport']
89
+ if (isExternalUrl) {
90
+ transport = {
91
+ type: 'http',
92
+ url: mcpServer,
93
+ }
94
+ }
95
+ else if (import.meta.dev) {
96
+ transport = {
97
+ type: 'http',
98
+ url: `http://localhost:3000${baseURL}${mcpServer}`,
99
+ }
100
+ }
101
+ else {
102
+ transport = {
103
+ type: 'http',
104
+ url: `${getRequestURL(event).origin}${baseURL}${mcpServer}`,
105
+ fetch: createLocalFetch(event),
106
+ }
107
+ }
108
+
109
+ const httpClient = await createMCPClient({ transport })
78
110
  const mcpTools = await httpClient.tools()
79
111
 
80
112
  const stream = createUIMessageStream({
package/modules/config.ts CHANGED
@@ -39,7 +39,7 @@ export default defineNuxtModule({
39
39
  url,
40
40
  name: siteName,
41
41
  debug: false,
42
- })
42
+ }) as typeof nuxt.options.site
43
43
 
44
44
  nuxt.options.appConfig.header = defu(nuxt.options.appConfig.header, {
45
45
  title: siteName,
package/modules/css.ts CHANGED
@@ -32,5 +32,17 @@ export default defineNuxtModule({
32
32
  if (Array.isArray(nuxt.options.css)) {
33
33
  nuxt.options.css.unshift(cssTemplate.dst)
34
34
  }
35
+
36
+ // Noisy Vite warnings
37
+ const sourcemapWarnIgnore = ['@tailwindcss/vite:generate:build', 'nuxt:module-preload-polyfill']
38
+ nuxt.hook('vite:extendConfig', (config) => {
39
+ const logger = config.customLogger
40
+ if (!logger) return
41
+ const originalWarn = logger.warn.bind(logger)
42
+ logger.warn = (msg, options) => {
43
+ if (sourcemapWarnIgnore.some(p => msg.includes(p))) return
44
+ originalWarn(msg, options)
45
+ }
46
+ })
35
47
  },
36
48
  })
@@ -32,16 +32,22 @@ export default defineNuxtModule({
32
32
  return
33
33
  }
34
34
 
35
- // Always redirect / to /llms.txt
35
+ // Always redirect / to /llms.txt and ensure plain text content type
36
+ const markdownHeaders = {
37
+ 'content-type': 'text/markdown; charset=utf-8',
38
+ }
39
+
36
40
  const routes = [
37
41
  {
38
42
  src: '^/$',
39
43
  dest: '/llms.txt',
44
+ headers: markdownHeaders,
40
45
  has: [{ type: 'header', key: 'accept', value: '(.*)text/markdown(.*)' }],
41
46
  },
42
47
  {
43
48
  src: '^/$',
44
49
  dest: '/llms.txt',
50
+ headers: markdownHeaders,
45
51
  has: [{ type: 'header', key: 'user-agent', value: 'curl/.*' }],
46
52
  },
47
53
  ]
@@ -66,11 +72,13 @@ export default defineNuxtModule({
66
72
  {
67
73
  src: `^/(${localePattern})$`,
68
74
  dest: '/llms.txt',
75
+ headers: markdownHeaders,
69
76
  has: [{ type: 'header', key: 'accept', value: '(.*)text/markdown(.*)' }],
70
77
  },
71
78
  {
72
79
  src: `^/(${localePattern})$`,
73
80
  dest: '/llms.txt',
81
+ headers: markdownHeaders,
74
82
  has: [{ type: 'header', key: 'user-agent', value: 'curl/.*' }],
75
83
  },
76
84
  )
@@ -109,11 +117,13 @@ export default defineNuxtModule({
109
117
  {
110
118
  src: `^${pagePath}$`,
111
119
  dest: rawPath,
120
+ headers: markdownHeaders,
112
121
  has: [{ type: 'header', key: 'accept', value: '(.*)text/markdown(.*)' }],
113
122
  },
114
123
  {
115
124
  src: `^${pagePath}$`,
116
125
  dest: rawPath,
126
+ headers: markdownHeaders,
117
127
  has: [{ type: 'header', key: 'user-agent', value: 'curl/.*' }],
118
128
  },
119
129
  ]
@@ -1,7 +1,9 @@
1
- import { addServerHandler, createResolver, defineNuxtModule, logger } from '@nuxt/kit'
1
+ import { addPrerenderRoutes, addServerHandler, createResolver, defineNuxtModule, logger } from '@nuxt/kit'
2
+ import { defu } from 'defu'
2
3
  import { existsSync } from 'node:fs'
3
4
  import { readdir, readFile } from 'node:fs/promises'
4
5
  import { join } from 'node:path'
6
+ import type { NitroConfig } from 'nitropack'
5
7
  import { parse as parseYaml } from 'yaml'
6
8
 
7
9
  interface SkillEntry {
@@ -10,17 +12,27 @@ interface SkillEntry {
10
12
  files: string[]
11
13
  }
12
14
 
15
+ export interface SkillsModuleOptions {
16
+ dir?: string
17
+ }
18
+
13
19
  const SKILL_NAME_REGEX = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/
14
20
  const MAX_NAME_LENGTH = 64
15
21
 
16
22
  const log = logger.withTag('Docus')
17
23
 
18
- export default defineNuxtModule({
24
+ const defaults: Required<SkillsModuleOptions> = {
25
+ dir: 'skills',
26
+ }
27
+
28
+ export default defineNuxtModule<SkillsModuleOptions>({
19
29
  meta: {
20
30
  name: 'skills',
21
31
  },
22
- async setup(_options, nuxt) {
23
- const skillsDir = join(nuxt.options.rootDir, 'skills')
32
+ async setup(_inlineOptions, nuxt) {
33
+ const options = defu(nuxt.options.docus?.skills, defaults) as Required<SkillsModuleOptions>
34
+
35
+ const skillsDir = join(nuxt.options.rootDir, options.dir)
24
36
  if (!existsSync(skillsDir)) return
25
37
 
26
38
  const catalog = await scanSkills(skillsDir)
@@ -32,19 +44,19 @@ export default defineNuxtModule({
32
44
 
33
45
  const { resolve } = createResolver(import.meta.url)
34
46
 
35
- nuxt.hook('nitro:config', (nitroConfig) => {
47
+ const onNitroConfig = nuxt.hook as (name: 'nitro:config', cb: (nitroConfig: NitroConfig) => void) => void
48
+ onNitroConfig('nitro:config', (nitroConfig) => {
36
49
  nitroConfig.serverAssets ||= []
37
50
  nitroConfig.serverAssets.push({ baseName: 'skills', dir: skillsDir })
51
+ })
38
52
 
39
- nitroConfig.prerender ||= {}
40
- nitroConfig.prerender.routes ||= []
41
- nitroConfig.prerender.routes.push('/.well-known/skills/index.json')
42
- for (const skill of catalog) {
43
- for (const file of skill.files) {
44
- nitroConfig.prerender.routes.push(`/.well-known/skills/${skill.name}/${file}`)
45
- }
53
+ const prerenderRoutes = ['/.well-known/skills/index.json']
54
+ for (const skill of catalog) {
55
+ for (const file of skill.files) {
56
+ prerenderRoutes.push(`/.well-known/skills/${skill.name}/${file}`)
46
57
  }
47
- })
58
+ }
59
+ addPrerenderRoutes(prerenderRoutes)
48
60
 
49
61
  addServerHandler({
50
62
  route: '/.well-known/skills/index.json',