docus 5.8.1 → 5.10.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.
- package/README.md +1 -1
- package/app/app.config.ts +13 -0
- package/app/app.vue +5 -1
- package/app/components/OgImage/Docs.takumi.vue +43 -0
- package/app/components/OgImage/Landing.takumi.vue +67 -0
- package/app/components/app/AppFooterRight.vue +4 -1
- package/app/components/app/AppHeader.vue +6 -7
- package/app/components/app/AppHeaderBody.vue +6 -2
- package/app/components/app/AppHeaderBottom.vue +6 -2
- package/app/components/app/AppHeaderLeft.vue +16 -0
- package/app/components/docs/DocsAsideLeftBody.vue +6 -1
- package/app/components/docs/DocsAsideMobileBar.vue +11 -1
- package/app/components/docs/DocsAsideRight.vue +6 -1
- package/app/composables/useDocusColorMode.ts +7 -0
- package/app/composables/useUIConfig.ts +30 -0
- package/app/error.vue +3 -0
- package/app/middleware/colorMode.global.ts +8 -0
- package/app/pages/[[lang]]/[...slug].vue +16 -12
- package/app/templates/landing.vue +3 -3
- package/app/utils/ogImage.ts +23 -0
- package/i18n/locales/ar.json +27 -0
- package/i18n/locales/be.json +27 -0
- package/i18n/locales/bg.json +27 -0
- package/i18n/locales/bn.json +27 -0
- package/i18n/locales/ca.json +27 -0
- package/i18n/locales/ckb.json +32 -1
- package/i18n/locales/cs.json +27 -0
- package/i18n/locales/da.json +27 -0
- package/i18n/locales/de.json +27 -0
- package/i18n/locales/el.json +27 -0
- package/i18n/locales/es.json +27 -0
- package/i18n/locales/et.json +27 -0
- package/i18n/locales/fi.json +27 -0
- package/i18n/locales/he.json +27 -0
- package/i18n/locales/hi.json +27 -0
- package/i18n/locales/hy.json +27 -0
- package/i18n/locales/id.json +27 -0
- package/i18n/locales/it.json +27 -0
- package/i18n/locales/ja.json +27 -0
- package/i18n/locales/kk.json +27 -0
- package/i18n/locales/km.json +27 -0
- package/i18n/locales/ko.json +27 -0
- package/i18n/locales/ky.json +27 -0
- package/i18n/locales/lb.json +27 -0
- package/i18n/locales/ms.json +27 -0
- package/i18n/locales/nb.json +27 -0
- package/i18n/locales/nl.json +27 -0
- package/i18n/locales/pl.json +27 -0
- package/i18n/locales/pt-BR.json +27 -0
- package/i18n/locales/ro.json +27 -0
- package/i18n/locales/ru.json +27 -0
- package/i18n/locales/si.json +27 -0
- package/i18n/locales/sl.json +27 -0
- package/i18n/locales/sv.json +27 -0
- package/i18n/locales/tr.json +27 -0
- package/i18n/locales/uk.json +27 -0
- package/i18n/locales/ur.json +27 -0
- package/i18n/locales/vi.json +27 -0
- package/i18n/locales/zh-CN.json +27 -0
- package/index.d.ts +33 -0
- package/modules/assistant/README.md +17 -8
- package/modules/assistant/index.ts +26 -16
- package/modules/assistant/runtime/server/api/search.ts +5 -0
- package/modules/config.ts +7 -2
- package/modules/css.ts +12 -0
- package/modules/skills/index.ts +158 -0
- package/modules/skills/runtime/server/routes/skills-files.ts +49 -0
- package/modules/skills/runtime/server/routes/skills-index.ts +8 -0
- package/nuxt.config.ts +8 -2
- package/nuxt.schema.ts +22 -0
- package/package.json +24 -21
- package/server/mcp/tools/get-page.ts +16 -5
- package/server/mcp/tools/list-pages.ts +13 -3
- package/app/components/OgImage/OgImageDocs.vue +0 -76
- package/app/components/OgImage/OgImageLanding.vue +0 -98
|
@@ -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,24 +23,33 @@ 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
35
|
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
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
|
+
|
|
44
|
+
const hasAiGatewayAuth = !!(
|
|
45
|
+
process.env.AI_GATEWAY_API_KEY || process.env.VERCEL_OIDC_TOKEN
|
|
46
|
+
)
|
|
37
47
|
|
|
38
48
|
const { resolve } = createResolver(import.meta.url)
|
|
39
49
|
|
|
40
50
|
nuxt.options.runtimeConfig.public.assistant = {
|
|
41
|
-
enabled:
|
|
42
|
-
apiPath: options.apiPath
|
|
51
|
+
enabled: hasAiGatewayAuth,
|
|
52
|
+
apiPath: options.apiPath,
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
addImports([
|
|
@@ -60,23 +70,23 @@ export default defineNuxtModule<AssistantModuleOptions>({
|
|
|
60
70
|
components.forEach(name =>
|
|
61
71
|
addComponent({
|
|
62
72
|
name,
|
|
63
|
-
filePath:
|
|
73
|
+
filePath: hasAiGatewayAuth
|
|
64
74
|
? resolve(`./runtime/components/${name}.vue`)
|
|
65
75
|
: resolve('./runtime/components/AssistantChatDisabled.vue'),
|
|
66
76
|
}),
|
|
67
77
|
)
|
|
68
78
|
|
|
69
|
-
if (!
|
|
70
|
-
log.warn('AI assistant disabled: AI_GATEWAY_API_KEY
|
|
79
|
+
if (!hasAiGatewayAuth) {
|
|
80
|
+
log.warn('AI assistant disabled: neither AI_GATEWAY_API_KEY nor VERCEL_OIDC_TOKEN found')
|
|
71
81
|
return
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
nuxt.options.runtimeConfig.assistant = {
|
|
75
|
-
mcpServer: options.mcpServer
|
|
76
|
-
model: options.model
|
|
85
|
+
mcpServer: options.mcpServer,
|
|
86
|
+
model: options.model,
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
const routePath = options.apiPath
|
|
89
|
+
const routePath = options.apiPath.replace(/^\//, '')
|
|
80
90
|
addServerHandler({
|
|
81
91
|
route: `/${routePath}`,
|
|
82
92
|
handler: resolve('./runtime/server/api/search'),
|
|
@@ -37,6 +37,11 @@ function getSystemPrompt(siteName: string) {
|
|
|
37
37
|
- Be concise, helpful, and direct
|
|
38
38
|
- Guide users like a friendly expert would
|
|
39
39
|
|
|
40
|
+
**Links and exploration:**
|
|
41
|
+
- Tool results include a \`url\` for each page — prefer markdown links \`[label](url)\` so users can open the doc in one click
|
|
42
|
+
- When it helps, add extra links (related pages, “read more”, side topics) — make the answer easy to dig into, not a wall of text
|
|
43
|
+
- Stick to URLs from tool results (\`url\` / \`path\`) so links stay valid
|
|
44
|
+
|
|
40
45
|
**FORMATTING RULES (CRITICAL):**
|
|
41
46
|
- NEVER use markdown headings (#, ##, ###, etc.)
|
|
42
47
|
- Use **bold text** for emphasis and section labels
|
package/modules/config.ts
CHANGED
|
@@ -58,13 +58,18 @@ export default defineNuxtModule({
|
|
|
58
58
|
branch: getGitBranch(),
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
+
const forcedColorMode = (nuxt.options.appConfig.docus as Record<string, unknown>)?.colorMode as string | undefined
|
|
62
|
+
if (forcedColorMode === 'light' || forcedColorMode === 'dark') {
|
|
63
|
+
nuxt.options.colorMode = defu({ preference: forcedColorMode, fallback: forcedColorMode }, nuxt.options.colorMode || {}) as typeof nuxt.options.colorMode
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
/*
|
|
62
67
|
** I18N
|
|
63
68
|
*/
|
|
64
|
-
const typedNuxtOptions = nuxt.options as typeof nuxt.options & { i18n?: DocusI18nOptions }
|
|
69
|
+
const typedNuxtOptions = nuxt.options as typeof nuxt.options & { i18n?: false | DocusI18nOptions }
|
|
65
70
|
const i18nOptions = typedNuxtOptions.i18n
|
|
66
71
|
|
|
67
|
-
if (i18nOptions
|
|
72
|
+
if (i18nOptions && typeof i18nOptions === 'object' && i18nOptions.locales) {
|
|
68
73
|
const { resolve } = createResolver(import.meta.url)
|
|
69
74
|
|
|
70
75
|
// Filter locales to only include existing ones
|
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
|
})
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { addPrerenderRoutes, addServerHandler, createResolver, defineNuxtModule, logger } from '@nuxt/kit'
|
|
2
|
+
import { defu } from 'defu'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { readdir, readFile } from 'node:fs/promises'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import type { NitroConfig } from 'nitropack'
|
|
7
|
+
import { parse as parseYaml } from 'yaml'
|
|
8
|
+
|
|
9
|
+
interface SkillEntry {
|
|
10
|
+
name: string
|
|
11
|
+
description: string
|
|
12
|
+
files: string[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SkillsModuleOptions {
|
|
16
|
+
dir?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SKILL_NAME_REGEX = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/
|
|
20
|
+
const MAX_NAME_LENGTH = 64
|
|
21
|
+
|
|
22
|
+
const log = logger.withTag('Docus')
|
|
23
|
+
|
|
24
|
+
const defaults: Required<SkillsModuleOptions> = {
|
|
25
|
+
dir: 'skills',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default defineNuxtModule<SkillsModuleOptions>({
|
|
29
|
+
meta: {
|
|
30
|
+
name: 'skills',
|
|
31
|
+
},
|
|
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)
|
|
36
|
+
if (!existsSync(skillsDir)) return
|
|
37
|
+
|
|
38
|
+
const catalog = await scanSkills(skillsDir)
|
|
39
|
+
if (!catalog.length) return
|
|
40
|
+
|
|
41
|
+
log.info(`Found ${catalog.length} agent skill${catalog.length > 1 ? 's' : ''}: ${catalog.map(s => s.name).join(', ')}`)
|
|
42
|
+
|
|
43
|
+
nuxt.options.runtimeConfig.skills = { catalog }
|
|
44
|
+
|
|
45
|
+
const { resolve } = createResolver(import.meta.url)
|
|
46
|
+
|
|
47
|
+
const onNitroConfig = nuxt.hook as (name: 'nitro:config', cb: (nitroConfig: NitroConfig) => void) => void
|
|
48
|
+
onNitroConfig('nitro:config', (nitroConfig) => {
|
|
49
|
+
nitroConfig.serverAssets ||= []
|
|
50
|
+
nitroConfig.serverAssets.push({ baseName: 'skills', dir: skillsDir })
|
|
51
|
+
})
|
|
52
|
+
|
|
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}`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
addPrerenderRoutes(prerenderRoutes)
|
|
60
|
+
|
|
61
|
+
addServerHandler({
|
|
62
|
+
route: '/.well-known/skills/index.json',
|
|
63
|
+
handler: resolve('./runtime/server/routes/skills-index'),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
addServerHandler({
|
|
67
|
+
route: '/.well-known/skills/**',
|
|
68
|
+
handler: resolve('./runtime/server/routes/skills-files'),
|
|
69
|
+
})
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
function parseFrontmatter(content: string): { name?: string, description?: string } | null {
|
|
74
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
75
|
+
if (!match?.[1]) return null
|
|
76
|
+
try {
|
|
77
|
+
return parseYaml(match[1])
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function validateSkillName(name: string, dirName: string): boolean {
|
|
85
|
+
if (name.length > MAX_NAME_LENGTH) {
|
|
86
|
+
log.warn(`Skill "${name}" exceeds ${MAX_NAME_LENGTH} character limit`)
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
if (!SKILL_NAME_REGEX.test(name) || name.includes('--')) {
|
|
90
|
+
log.warn(`Skill name "${name}" does not match the Agent Skills naming spec`)
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
if (name !== dirName) {
|
|
94
|
+
log.warn(`Skill name "${name}" does not match directory name "${dirName}"`)
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function listFilesRecursively(dir: string, base: string = ''): Promise<string[]> {
|
|
101
|
+
const files: string[] = []
|
|
102
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
const relPath = base ? `${base}/${entry.name}` : entry.name
|
|
105
|
+
if (entry.isDirectory()) {
|
|
106
|
+
files.push(...await listFilesRecursively(join(dir, entry.name), relPath))
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
files.push(relPath)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return files
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function scanSkills(skillsDir: string): Promise<SkillEntry[]> {
|
|
116
|
+
const catalog: SkillEntry[] = []
|
|
117
|
+
const entries = await readdir(skillsDir, { withFileTypes: true })
|
|
118
|
+
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (!entry.isDirectory()) continue
|
|
121
|
+
|
|
122
|
+
const skillDir = join(skillsDir, entry.name)
|
|
123
|
+
const skillMdPath = join(skillDir, 'SKILL.md')
|
|
124
|
+
|
|
125
|
+
if (!existsSync(skillMdPath)) continue
|
|
126
|
+
|
|
127
|
+
const content = await readFile(skillMdPath, 'utf-8')
|
|
128
|
+
const frontmatter = parseFrontmatter(content)
|
|
129
|
+
|
|
130
|
+
if (!frontmatter?.description) {
|
|
131
|
+
log.warn(`Skipping skill "${entry.name}": missing description in SKILL.md frontmatter`)
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const name = frontmatter.name || entry.name
|
|
136
|
+
if (!validateSkillName(name, entry.name)) continue
|
|
137
|
+
|
|
138
|
+
const allFiles = await listFilesRecursively(skillDir)
|
|
139
|
+
const files = allFiles.filter(f => !f.split('/').some(s => s.startsWith('.')))
|
|
140
|
+
const sortedFiles = ['SKILL.md', ...files.filter(f => f !== 'SKILL.md')]
|
|
141
|
+
|
|
142
|
+
catalog.push({
|
|
143
|
+
name,
|
|
144
|
+
description: frontmatter.description,
|
|
145
|
+
files: sortedFiles,
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return catalog
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
declare module 'nuxt/schema' {
|
|
153
|
+
interface RuntimeConfig {
|
|
154
|
+
skills: {
|
|
155
|
+
catalog: SkillEntry[]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const CONTENT_TYPES: Record<string, string> = {
|
|
2
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
3
|
+
'.json': 'application/json; charset=utf-8',
|
|
4
|
+
'.yaml': 'text/yaml; charset=utf-8',
|
|
5
|
+
'.yml': 'text/yaml; charset=utf-8',
|
|
6
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
7
|
+
'.py': 'text/plain; charset=utf-8',
|
|
8
|
+
'.sh': 'text/plain; charset=utf-8',
|
|
9
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
10
|
+
'.ts': 'text/plain; charset=utf-8',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getContentType(path: string): string {
|
|
14
|
+
const ext = path.slice(path.lastIndexOf('.'))
|
|
15
|
+
return CONTENT_TYPES[ext] || 'application/octet-stream'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default defineEventHandler(async (event) => {
|
|
19
|
+
const url = getRequestURL(event)
|
|
20
|
+
const prefix = '/.well-known/skills/'
|
|
21
|
+
const idx = url.pathname.indexOf(prefix)
|
|
22
|
+
if (idx === -1) {
|
|
23
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const filePath = decodeURIComponent(url.pathname.slice(idx + prefix.length))
|
|
27
|
+
|
|
28
|
+
if (!filePath || filePath.includes('..')) {
|
|
29
|
+
throw createError({ statusCode: 400, statusMessage: 'Bad Request' })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { skills } = useRuntimeConfig(event)
|
|
33
|
+
const skillName = filePath.split('/')[0]
|
|
34
|
+
if (!skills.catalog.some((s: { name: string }) => s.name === skillName)) {
|
|
35
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const storage = useStorage('assets:skills')
|
|
39
|
+
const content = await storage.getItemRaw<string>(filePath)
|
|
40
|
+
|
|
41
|
+
if (!content) {
|
|
42
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setResponseHeader(event, 'content-type', getContentType(filePath))
|
|
46
|
+
setResponseHeader(event, 'cache-control', 'public, max-age=3600')
|
|
47
|
+
|
|
48
|
+
return content
|
|
49
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export default defineEventHandler((event) => {
|
|
2
|
+
const { skills } = useRuntimeConfig(event)
|
|
3
|
+
|
|
4
|
+
setResponseHeader(event, 'content-type', 'application/json')
|
|
5
|
+
setResponseHeader(event, 'cache-control', 'public, max-age=3600')
|
|
6
|
+
|
|
7
|
+
return { skills: skills.catalog }
|
|
8
|
+
})
|
package/nuxt.config.ts
CHANGED
|
@@ -10,6 +10,7 @@ export default defineNuxtConfig({
|
|
|
10
10
|
resolve('./modules/config'),
|
|
11
11
|
resolve('./modules/routing'),
|
|
12
12
|
resolve('./modules/markdown-rewrite'),
|
|
13
|
+
resolve('./modules/skills'),
|
|
13
14
|
resolve('./modules/css'),
|
|
14
15
|
() => {
|
|
15
16
|
const nuxt = useNuxt()
|
|
@@ -38,9 +39,11 @@ export default defineNuxtConfig({
|
|
|
38
39
|
|
|
39
40
|
// Fix @vercel/oidc ESM export issue (transitive dep of @ai-sdk/gateway)
|
|
40
41
|
// Only needed when AI assistant is enabled.
|
|
41
|
-
if (process.env.AI_GATEWAY_API_KEY) {
|
|
42
|
+
if (process.env.AI_GATEWAY_API_KEY || process.env.VERCEL_OIDC_TOKEN) {
|
|
42
43
|
config.optimizeDeps.include.push('@vercel/oidc')
|
|
43
|
-
config.optimizeDeps.include.map(id =>
|
|
44
|
+
config.optimizeDeps.include = config.optimizeDeps.include.map(id =>
|
|
45
|
+
id.replace(/^@vercel\/oidc$/, 'docus > @vercel/oidc'),
|
|
46
|
+
)
|
|
44
47
|
}
|
|
45
48
|
})
|
|
46
49
|
},
|
|
@@ -118,6 +121,9 @@ export default defineNuxtConfig({
|
|
|
118
121
|
},
|
|
119
122
|
provider: 'iconify',
|
|
120
123
|
},
|
|
124
|
+
ogImage: {
|
|
125
|
+
zeroRuntime: true,
|
|
126
|
+
},
|
|
121
127
|
robots: {
|
|
122
128
|
groups: [
|
|
123
129
|
{
|
package/nuxt.schema.ts
CHANGED
|
@@ -277,6 +277,28 @@ export default defineNuxtSchema({
|
|
|
277
277
|
}),
|
|
278
278
|
},
|
|
279
279
|
}),
|
|
280
|
+
docus: group({
|
|
281
|
+
title: 'Docus',
|
|
282
|
+
description: 'Docus configuration.',
|
|
283
|
+
icon: 'i-lucide-settings',
|
|
284
|
+
fields: {
|
|
285
|
+
locale: field({
|
|
286
|
+
type: 'string',
|
|
287
|
+
title: 'Locale',
|
|
288
|
+
description: 'Default locale for single-language documentation.',
|
|
289
|
+
icon: 'i-lucide-languages',
|
|
290
|
+
default: 'en',
|
|
291
|
+
}),
|
|
292
|
+
colorMode: field({
|
|
293
|
+
type: 'string',
|
|
294
|
+
title: 'Color Mode',
|
|
295
|
+
description: 'Force a specific color mode. Leave empty for system preference with toggle.',
|
|
296
|
+
icon: 'i-lucide-monitor',
|
|
297
|
+
default: '',
|
|
298
|
+
required: ['', 'light', 'dark'],
|
|
299
|
+
}),
|
|
300
|
+
},
|
|
301
|
+
}),
|
|
280
302
|
assistant: group({
|
|
281
303
|
title: 'Assistant',
|
|
282
304
|
description: 'Assistant configuration.',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docus",
|
|
3
3
|
"description": "Nuxt layer for Docus documentation theme",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.10.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
7
7
|
"repository": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"app",
|
|
15
15
|
"i18n",
|
|
16
16
|
"content.config.ts",
|
|
17
|
+
"index.d.ts",
|
|
17
18
|
"modules",
|
|
18
19
|
"nuxt.config.ts",
|
|
19
20
|
"nuxt.schema.ts",
|
|
@@ -22,39 +23,41 @@
|
|
|
22
23
|
"README.md"
|
|
23
24
|
],
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@ai-sdk/gateway": "^3.0.
|
|
26
|
-
"@ai-sdk/mcp": "^1.0.
|
|
27
|
-
"@ai-sdk/vue": "3.0.
|
|
28
|
-
"@iconify-json/lucide": "^1.2.
|
|
29
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
26
|
+
"@ai-sdk/gateway": "^3.0.83",
|
|
27
|
+
"@ai-sdk/mcp": "^1.0.30",
|
|
28
|
+
"@ai-sdk/vue": "3.0.141",
|
|
29
|
+
"@iconify-json/lucide": "^1.2.100",
|
|
30
|
+
"@iconify-json/simple-icons": "^1.2.75",
|
|
30
31
|
"@iconify-json/vscode-icons": "^1.2.45",
|
|
31
32
|
"@nuxt/content": "^3.12.0",
|
|
32
33
|
"@nuxt/image": "^2.0.0",
|
|
33
|
-
"@nuxt/kit": "^4.
|
|
34
|
-
"@nuxt/ui": "^4.
|
|
35
|
-
"@nuxtjs/i18n": "^10.2.
|
|
36
|
-
"@nuxtjs/mcp-toolkit": "^0.
|
|
37
|
-
"@nuxtjs/mdc": "^0.
|
|
38
|
-
"@nuxtjs/robots": "^
|
|
34
|
+
"@nuxt/kit": "^4.4.2",
|
|
35
|
+
"@nuxt/ui": "^4.6.0",
|
|
36
|
+
"@nuxtjs/i18n": "^10.2.4",
|
|
37
|
+
"@nuxtjs/mcp-toolkit": "^0.13.2",
|
|
38
|
+
"@nuxtjs/mdc": "^0.21.0",
|
|
39
|
+
"@nuxtjs/robots": "^6.0.6",
|
|
40
|
+
"@shikijs/core": "^4.0.2",
|
|
41
|
+
"@shikijs/engine-javascript": "^4.0.2",
|
|
42
|
+
"@shikijs/langs": "^4.0.2",
|
|
43
|
+
"@shikijs/themes": "^4.0.2",
|
|
44
|
+
"@takumi-rs/core": "^0.73.1",
|
|
39
45
|
"@vueuse/core": "^14.2.1",
|
|
40
|
-
"ai": "6.0.
|
|
46
|
+
"ai": "6.0.141",
|
|
41
47
|
"defu": "^6.1.4",
|
|
42
48
|
"exsolve": "^1.0.8",
|
|
43
49
|
"git-url-parse": "^16.1.0",
|
|
44
|
-
"motion-v": "^
|
|
50
|
+
"motion-v": "^2.2.0",
|
|
45
51
|
"nuxt-llms": "^0.2.0",
|
|
46
|
-
"nuxt-og-image": "^
|
|
52
|
+
"nuxt-og-image": "^6.3.1",
|
|
47
53
|
"pkg-types": "^2.3.0",
|
|
48
54
|
"scule": "^1.3.0",
|
|
49
|
-
"@shikijs/core": "^3.22.0",
|
|
50
|
-
"@shikijs/engine-javascript": "^3.22.0",
|
|
51
|
-
"@shikijs/langs": "^3.22.0",
|
|
52
|
-
"@shikijs/themes": "^3.22.0",
|
|
53
55
|
"shiki-stream": "^0.1.4",
|
|
54
|
-
"tailwindcss": "^4.2.
|
|
56
|
+
"tailwindcss": "^4.2.2",
|
|
55
57
|
"ufo": "^1.6.3",
|
|
58
|
+
"yaml": "^2.7.1",
|
|
56
59
|
"zod": "^4.3.6",
|
|
57
|
-
"zod-to-json-schema": "^3.25.
|
|
60
|
+
"zod-to-json-schema": "^3.25.2"
|
|
58
61
|
},
|
|
59
62
|
"peerDependencies": {
|
|
60
63
|
"better-sqlite3": "12.x",
|
|
@@ -17,9 +17,19 @@ WHEN NOT TO USE: If you don't know the exact path and need to search/explore, us
|
|
|
17
17
|
|
|
18
18
|
WORKFLOW: This tool returns the complete page content including title, description, and full markdown. Use this when you need to provide detailed answers or code examples from specific documentation pages.
|
|
19
19
|
`,
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: true,
|
|
22
|
+
destructiveHint: false,
|
|
23
|
+
idempotentHint: true,
|
|
24
|
+
openWorldHint: false,
|
|
25
|
+
},
|
|
20
26
|
inputSchema: {
|
|
21
27
|
path: z.string().describe('The page path from list-pages or provided by the user (e.g., /en/getting-started/installation)'),
|
|
22
28
|
},
|
|
29
|
+
inputExamples: [
|
|
30
|
+
{ path: '/en/getting-started/installation' },
|
|
31
|
+
{ path: '/getting-started/introduction' },
|
|
32
|
+
],
|
|
23
33
|
cache: '1h',
|
|
24
34
|
handler: async ({ path }) => {
|
|
25
35
|
const event = useEvent()
|
|
@@ -38,21 +48,22 @@ WORKFLOW: This tool returns the complete page content including title, descripti
|
|
|
38
48
|
.first()
|
|
39
49
|
|
|
40
50
|
if (!page) {
|
|
41
|
-
|
|
51
|
+
throw createError({ statusCode: 404, message: 'Page not found' })
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
const content = await event.$fetch<string>(`/raw${path}.md`)
|
|
45
55
|
|
|
46
|
-
return
|
|
56
|
+
return {
|
|
47
57
|
title: page.title,
|
|
48
58
|
path: page.path,
|
|
49
59
|
description: page.description,
|
|
50
60
|
content,
|
|
51
61
|
url: `${siteUrl}${page.path}`,
|
|
52
|
-
}
|
|
62
|
+
}
|
|
53
63
|
}
|
|
54
|
-
catch {
|
|
55
|
-
|
|
64
|
+
catch (error) {
|
|
65
|
+
if ((error as { statusCode?: number }).statusCode === 404) throw error
|
|
66
|
+
throw createError({ statusCode: 500, message: 'Failed to get page' })
|
|
56
67
|
}
|
|
57
68
|
},
|
|
58
69
|
})
|
|
@@ -23,9 +23,19 @@ OUTPUT: Returns a structured list with:
|
|
|
23
23
|
- path: Exact path for use with get-page
|
|
24
24
|
- description: Brief summary of page content
|
|
25
25
|
- url: Full URL for reference`,
|
|
26
|
+
annotations: {
|
|
27
|
+
readOnlyHint: true,
|
|
28
|
+
destructiveHint: false,
|
|
29
|
+
idempotentHint: true,
|
|
30
|
+
openWorldHint: false,
|
|
31
|
+
},
|
|
26
32
|
inputSchema: {
|
|
27
|
-
locale: z.string().optional().describe('The locale to filter pages by'),
|
|
33
|
+
locale: z.string().optional().describe('The locale to filter pages by (e.g., "en", "fr")'),
|
|
28
34
|
},
|
|
35
|
+
inputExamples: [
|
|
36
|
+
{ locale: 'en' },
|
|
37
|
+
{},
|
|
38
|
+
],
|
|
29
39
|
cache: '1h',
|
|
30
40
|
handler: async ({ locale }) => {
|
|
31
41
|
const event = useEvent()
|
|
@@ -52,10 +62,10 @@ OUTPUT: Returns a structured list with:
|
|
|
52
62
|
}),
|
|
53
63
|
)
|
|
54
64
|
|
|
55
|
-
return
|
|
65
|
+
return allPages.flat()
|
|
56
66
|
}
|
|
57
67
|
catch {
|
|
58
|
-
|
|
68
|
+
throw createError({ statusCode: 500, message: 'Failed to list pages' })
|
|
59
69
|
}
|
|
60
70
|
},
|
|
61
71
|
})
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
const props = withDefaults(defineProps<{ title?: string, description?: string, headline?: string }>(), {
|
|
3
|
-
title: 'title',
|
|
4
|
-
description: 'description',
|
|
5
|
-
})
|
|
6
|
-
|
|
7
|
-
const title = (props.title || '').slice(0, 60)
|
|
8
|
-
const description = (props.description || '').slice(0, 200)
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<template>
|
|
12
|
-
<div class="w-full h-full flex flex-col justify-center bg-neutral-900">
|
|
13
|
-
<svg
|
|
14
|
-
class="absolute right-0 top-0 opacity-50"
|
|
15
|
-
width="629"
|
|
16
|
-
height="593"
|
|
17
|
-
viewBox="0 0 629 593"
|
|
18
|
-
fill="none"
|
|
19
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
-
>
|
|
21
|
-
<g filter="url(#filter0_f_199_94966)">
|
|
22
|
-
<path
|
|
23
|
-
d="M628.5 -578L639.334 -94.4223L806.598 -548.281L659.827 -87.387L965.396 -462.344L676.925 -74.0787L1087.69 -329.501L688.776 -55.9396L1160.22 -164.149L694.095 -34.9354L1175.13 15.7948L692.306 -13.3422L1130.8 190.83L683.602 6.50012L1032.04 341.989L668.927 22.4412L889.557 452.891L649.872 32.7537L718.78 511.519L628.5 36.32L538.22 511.519L607.128 32.7537L367.443 452.891L588.073 22.4412L224.955 341.989L573.398 6.50012L126.198 190.83L564.694 -13.3422L81.8734 15.7948L562.905 -34.9354L96.7839 -164.149L568.224 -55.9396L169.314 -329.501L580.075 -74.0787L291.604 -462.344L597.173 -87.387L450.402 -548.281L617.666 -94.4223L628.5 -578Z"
|
|
24
|
-
fill="white"
|
|
25
|
-
/>
|
|
26
|
-
</g>
|
|
27
|
-
<defs>
|
|
28
|
-
<filter
|
|
29
|
-
id="filter0_f_199_94966"
|
|
30
|
-
x="0.873535"
|
|
31
|
-
y="-659"
|
|
32
|
-
width="1255.25"
|
|
33
|
-
height="1251.52"
|
|
34
|
-
filterUnits="userSpaceOnUse"
|
|
35
|
-
color-interpolation-filters="sRGB"
|
|
36
|
-
>
|
|
37
|
-
<feFlood
|
|
38
|
-
flood-opacity="0"
|
|
39
|
-
result="BackgroundImageFix"
|
|
40
|
-
/>
|
|
41
|
-
<feBlend
|
|
42
|
-
mode="normal"
|
|
43
|
-
in="SourceGraphic"
|
|
44
|
-
in2="BackgroundImageFix"
|
|
45
|
-
result="shape"
|
|
46
|
-
/>
|
|
47
|
-
<feGaussianBlur
|
|
48
|
-
stdDeviation="40.5"
|
|
49
|
-
result="effect1_foregroundBlur_199_94966"
|
|
50
|
-
/>
|
|
51
|
-
</filter>
|
|
52
|
-
</defs>
|
|
53
|
-
</svg>
|
|
54
|
-
|
|
55
|
-
<div class="pl-[100px]">
|
|
56
|
-
<p
|
|
57
|
-
v-if="headline"
|
|
58
|
-
class="uppercase text-[24px] text-emerald-500 mb-4 font-semibold"
|
|
59
|
-
>
|
|
60
|
-
{{ headline }}
|
|
61
|
-
</p>
|
|
62
|
-
<h1
|
|
63
|
-
v-if="title"
|
|
64
|
-
class="m-0 text-[75px] font-semibold mb-4 text-white flex items-center"
|
|
65
|
-
>
|
|
66
|
-
<span>{{ title }}</span>
|
|
67
|
-
</h1>
|
|
68
|
-
<p
|
|
69
|
-
v-if="description"
|
|
70
|
-
class="text-[32px] text-neutral-300 leading-tight w-[700px]"
|
|
71
|
-
>
|
|
72
|
-
{{ description }}
|
|
73
|
-
</p>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
</template>
|