@baybreezy/docd 0.0.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 (268) hide show
  1. package/app/app.config.ts +239 -0
  2. package/app/app.vue +19 -0
  3. package/app/assets/css/tailwind.css +335 -0
  4. package/app/assets/css/theme.css +1171 -0
  5. package/app/components/OgImage/Docs.takumi.vue +69 -0
  6. package/app/components/OgImage/Landing.takumi.vue +64 -0
  7. package/app/components/OgImage/Site.takumi.vue +32 -0
  8. package/app/components/Ui/Avatar/Avatar.vue +75 -0
  9. package/app/components/Ui/Avatar/Badge.vue +34 -0
  10. package/app/components/Ui/Avatar/Fallback.vue +30 -0
  11. package/app/components/Ui/Avatar/Group.vue +29 -0
  12. package/app/components/Ui/Avatar/GroupCount.vue +29 -0
  13. package/app/components/Ui/Avatar/Image.vue +29 -0
  14. package/app/components/Ui/Badge.vue +86 -0
  15. package/app/components/Ui/BadgeGroup.vue +314 -0
  16. package/app/components/Ui/BgPattern/Grid.vue +137 -0
  17. package/app/components/Ui/BorderBeam.vue +97 -0
  18. package/app/components/Ui/Breadcrumbs.vue +103 -0
  19. package/app/components/Ui/Button.vue +169 -0
  20. package/app/components/Ui/ButtonGroup/ButtonGroup.vue +54 -0
  21. package/app/components/Ui/ButtonGroup/Separator.vue +38 -0
  22. package/app/components/Ui/ButtonGroup/Text.vue +33 -0
  23. package/app/components/Ui/Card/Action.vue +31 -0
  24. package/app/components/Ui/Card/Card.vue +58 -0
  25. package/app/components/Ui/Card/Content.vue +34 -0
  26. package/app/components/Ui/Card/Description.vue +36 -0
  27. package/app/components/Ui/Card/Footer.vue +31 -0
  28. package/app/components/Ui/Card/Header.vue +31 -0
  29. package/app/components/Ui/Card/Title.vue +37 -0
  30. package/app/components/Ui/Collapsible/Collapsible.vue +16 -0
  31. package/app/components/Ui/Collapsible/Content.vue +27 -0
  32. package/app/components/Ui/Collapsible/Trigger.vue +13 -0
  33. package/app/components/Ui/Command/Command.vue +115 -0
  34. package/app/components/Ui/Command/Dialog.vue +65 -0
  35. package/app/components/Ui/Command/Empty.vue +32 -0
  36. package/app/components/Ui/Command/Footer.vue +40 -0
  37. package/app/components/Ui/Command/Group.vue +58 -0
  38. package/app/components/Ui/Command/Input.vue +57 -0
  39. package/app/components/Ui/Command/Item.vue +102 -0
  40. package/app/components/Ui/Command/List.vue +33 -0
  41. package/app/components/Ui/Command/Separator.vue +30 -0
  42. package/app/components/Ui/Command/Shortcut.vue +28 -0
  43. package/app/components/Ui/Container.vue +46 -0
  44. package/app/components/Ui/ContextMenu/Arrow.vue +43 -0
  45. package/app/components/Ui/ContextMenu/CheckboxItem.vue +42 -0
  46. package/app/components/Ui/ContextMenu/Content.vue +53 -0
  47. package/app/components/Ui/ContextMenu/ContextMenu.vue +14 -0
  48. package/app/components/Ui/ContextMenu/Group.vue +12 -0
  49. package/app/components/Ui/ContextMenu/Item.vue +65 -0
  50. package/app/components/Ui/ContextMenu/ItemIndicator.vue +30 -0
  51. package/app/components/Ui/ContextMenu/Label.vue +35 -0
  52. package/app/components/Ui/ContextMenu/Portal.vue +12 -0
  53. package/app/components/Ui/ContextMenu/RadioGroup.vue +16 -0
  54. package/app/components/Ui/ContextMenu/RadioItem.vue +40 -0
  55. package/app/components/Ui/ContextMenu/Separator.vue +25 -0
  56. package/app/components/Ui/ContextMenu/Shortcut.vue +30 -0
  57. package/app/components/Ui/ContextMenu/Sub.vue +15 -0
  58. package/app/components/Ui/ContextMenu/SubContent.vue +55 -0
  59. package/app/components/Ui/ContextMenu/SubTrigger.vue +42 -0
  60. package/app/components/Ui/ContextMenu/Trigger.vue +12 -0
  61. package/app/components/Ui/Dialog/Close.vue +12 -0
  62. package/app/components/Ui/Dialog/Content.vue +79 -0
  63. package/app/components/Ui/Dialog/Description.vue +29 -0
  64. package/app/components/Ui/Dialog/Dialog.vue +14 -0
  65. package/app/components/Ui/Dialog/Footer.vue +32 -0
  66. package/app/components/Ui/Dialog/Header.vue +32 -0
  67. package/app/components/Ui/Dialog/Overlay.vue +25 -0
  68. package/app/components/Ui/Dialog/Portal.vue +12 -0
  69. package/app/components/Ui/Dialog/Title.vue +29 -0
  70. package/app/components/Ui/Dialog/Trigger.vue +12 -0
  71. package/app/components/Ui/Divider.vue +102 -0
  72. package/app/components/Ui/Drawer/Close.vue +12 -0
  73. package/app/components/Ui/Drawer/Content.vue +67 -0
  74. package/app/components/Ui/Drawer/Description.vue +33 -0
  75. package/app/components/Ui/Drawer/Drawer.vue +17 -0
  76. package/app/components/Ui/Drawer/Footer.vue +35 -0
  77. package/app/components/Ui/Drawer/Header.vue +34 -0
  78. package/app/components/Ui/Drawer/Overlay.vue +20 -0
  79. package/app/components/Ui/Drawer/Portal.vue +12 -0
  80. package/app/components/Ui/Drawer/Title.vue +35 -0
  81. package/app/components/Ui/Drawer/Trigger.vue +12 -0
  82. package/app/components/Ui/DropdownMenu/Arrow.vue +43 -0
  83. package/app/components/Ui/DropdownMenu/CheckboxItem.vue +41 -0
  84. package/app/components/Ui/DropdownMenu/Content.vue +56 -0
  85. package/app/components/Ui/DropdownMenu/DropdownMenu.vue +15 -0
  86. package/app/components/Ui/DropdownMenu/Group.vue +12 -0
  87. package/app/components/Ui/DropdownMenu/Item.vue +64 -0
  88. package/app/components/Ui/DropdownMenu/ItemIndicator.vue +32 -0
  89. package/app/components/Ui/DropdownMenu/Label.vue +34 -0
  90. package/app/components/Ui/DropdownMenu/Portal.vue +12 -0
  91. package/app/components/Ui/DropdownMenu/RadioGroup.vue +16 -0
  92. package/app/components/Ui/DropdownMenu/RadioItem.vue +40 -0
  93. package/app/components/Ui/DropdownMenu/Separator.vue +25 -0
  94. package/app/components/Ui/DropdownMenu/Shortcut.vue +44 -0
  95. package/app/components/Ui/DropdownMenu/Sub.vue +14 -0
  96. package/app/components/Ui/DropdownMenu/SubContent.vue +56 -0
  97. package/app/components/Ui/DropdownMenu/SubTrigger.vue +47 -0
  98. package/app/components/Ui/DropdownMenu/Trigger.vue +12 -0
  99. package/app/components/Ui/FancyIcon.vue +243 -0
  100. package/app/components/Ui/GradientDivider.vue +26 -0
  101. package/app/components/Ui/IframeLazy.vue +97 -0
  102. package/app/components/Ui/Input.vue +97 -0
  103. package/app/components/Ui/InputGroup/Addon.vue +66 -0
  104. package/app/components/Ui/InputGroup/Button.vue +50 -0
  105. package/app/components/Ui/InputGroup/Input.vue +25 -0
  106. package/app/components/Ui/InputGroup/InputGroup.vue +44 -0
  107. package/app/components/Ui/InputGroup/Text.vue +33 -0
  108. package/app/components/Ui/InputGroup/Textarea.vue +25 -0
  109. package/app/components/Ui/Kbd/Group.vue +32 -0
  110. package/app/components/Ui/Kbd/Kbd.vue +62 -0
  111. package/app/components/Ui/Label.vue +40 -0
  112. package/app/components/Ui/Popover/Anchor.vue +13 -0
  113. package/app/components/Ui/Popover/Arrow.vue +42 -0
  114. package/app/components/Ui/Popover/Close.vue +13 -0
  115. package/app/components/Ui/Popover/Content.vue +55 -0
  116. package/app/components/Ui/Popover/Popover.vue +15 -0
  117. package/app/components/Ui/Popover/Portal.vue +13 -0
  118. package/app/components/Ui/Popover/Trigger.vue +12 -0
  119. package/app/components/Ui/Popover/X.vue +38 -0
  120. package/app/components/Ui/ScrollArea/Corner.vue +12 -0
  121. package/app/components/Ui/ScrollArea/ScrollArea.vue +38 -0
  122. package/app/components/Ui/ScrollArea/Scrollbar.vue +39 -0
  123. package/app/components/Ui/ScrollArea/Thumb.vue +39 -0
  124. package/app/components/Ui/ScrollArea/Viewport.vue +27 -0
  125. package/app/components/Ui/Scrollspy.vue +61 -0
  126. package/app/components/Ui/Separator.vue +40 -0
  127. package/app/components/Ui/Skeleton.vue +39 -0
  128. package/app/components/Ui/Sonner.vue +25 -0
  129. package/app/components/Ui/Tabs/Content.vue +27 -0
  130. package/app/components/Ui/Tabs/Indicator.vue +37 -0
  131. package/app/components/Ui/Tabs/List.vue +37 -0
  132. package/app/components/Ui/Tabs/Tabs.vue +25 -0
  133. package/app/components/Ui/Tabs/Trigger.vue +41 -0
  134. package/app/components/Ui/Textarea.vue +81 -0
  135. package/app/components/Ui/Tooltip/Arrow.vue +30 -0
  136. package/app/components/Ui/Tooltip/Content.vue +50 -0
  137. package/app/components/Ui/Tooltip/Portal.vue +12 -0
  138. package/app/components/Ui/Tooltip/Provider.vue +14 -0
  139. package/app/components/Ui/Tooltip/Tooltip.vue +25 -0
  140. package/app/components/Ui/Tooltip/Trigger.vue +12 -0
  141. package/app/components/Ui/Tree/Item.vue +15 -0
  142. package/app/components/Ui/Tree/Tree.vue +15 -0
  143. package/app/components/Ui/Tree/Virtualizer.vue +13 -0
  144. package/app/components/component-api/ComponentApiSection.vue +135 -0
  145. package/app/components/content/docs/Callout/DocsCallout.vue +99 -0
  146. package/app/components/content/prose/ProseA.global.vue +31 -0
  147. package/app/components/content/prose/ProseBlockquote.global.vue +28 -0
  148. package/app/components/content/prose/ProseCallout.global.vue +306 -0
  149. package/app/components/content/prose/ProseCard.global.vue +100 -0
  150. package/app/components/content/prose/ProseCodeCollapse.global.vue +125 -0
  151. package/app/components/content/prose/ProseCodeCopy.global.vue +67 -0
  152. package/app/components/content/prose/ProseCodeGroup.global.vue +104 -0
  153. package/app/components/content/prose/ProseCodeTree.global.vue +277 -0
  154. package/app/components/content/prose/ProseCollapsible.global.vue +64 -0
  155. package/app/components/content/prose/ProseColorModeImage.global.vue +58 -0
  156. package/app/components/content/prose/ProseComponentApi.global.vue +104 -0
  157. package/app/components/content/prose/ProseComponentEvents.global.vue +27 -0
  158. package/app/components/content/prose/ProseComponentExposed.global.vue +27 -0
  159. package/app/components/content/prose/ProseComponentProps.global.vue +27 -0
  160. package/app/components/content/prose/ProseComponentSlots.global.vue +27 -0
  161. package/app/components/content/prose/ProseEm.global.vue +5 -0
  162. package/app/components/content/prose/ProseField.global.vue +118 -0
  163. package/app/components/content/prose/ProseFieldGroup.global.vue +62 -0
  164. package/app/components/content/prose/ProseH1.global.vue +58 -0
  165. package/app/components/content/prose/ProseH2.global.vue +58 -0
  166. package/app/components/content/prose/ProseH3.global.vue +58 -0
  167. package/app/components/content/prose/ProseH4.global.vue +58 -0
  168. package/app/components/content/prose/ProseH5.global.vue +47 -0
  169. package/app/components/content/prose/ProseH6.global.vue +47 -0
  170. package/app/components/content/prose/ProseHr.global.vue +33 -0
  171. package/app/components/content/prose/ProseIconList.global.vue +113 -0
  172. package/app/components/content/prose/ProseImg.global.vue +119 -0
  173. package/app/components/content/prose/ProseLead.global.vue +28 -0
  174. package/app/components/content/prose/ProseLi.global.vue +25 -0
  175. package/app/components/content/prose/ProseMermaid.global.vue +127 -0
  176. package/app/components/content/prose/ProseOl.global.vue +25 -0
  177. package/app/components/content/prose/ProseP.global.vue +25 -0
  178. package/app/components/content/prose/ProsePmInstall.global.vue +60 -0
  179. package/app/components/content/prose/ProsePmRun.global.vue +51 -0
  180. package/app/components/content/prose/ProsePmX.global.vue +56 -0
  181. package/app/components/content/prose/ProsePre.global.vue +181 -0
  182. package/app/components/content/prose/ProseShowCase.global.vue +56 -0
  183. package/app/components/content/prose/ProseSmartIcon.global.vue +54 -0
  184. package/app/components/content/prose/ProseStep.global.vue +22 -0
  185. package/app/components/content/prose/ProseSteps.global.vue +22 -0
  186. package/app/components/content/prose/ProseStrong.global.vue +34 -0
  187. package/app/components/content/prose/ProseTable.global.vue +34 -0
  188. package/app/components/content/prose/ProseTabs.global.vue +104 -0
  189. package/app/components/content/prose/ProseTabsInner.global.vue +274 -0
  190. package/app/components/content/prose/ProseTbody.global.vue +5 -0
  191. package/app/components/content/prose/ProseTd.global.vue +25 -0
  192. package/app/components/content/prose/ProseTh.global.vue +25 -0
  193. package/app/components/content/prose/ProseThead.global.vue +9 -0
  194. package/app/components/content/prose/ProseTr.global.vue +35 -0
  195. package/app/components/content/prose/ProseUl.global.vue +25 -0
  196. package/app/components/docs/DocsExtraStuff.vue +39 -0
  197. package/app/components/docs/DocsGithubLink.vue +11 -0
  198. package/app/components/docs/DocsHeader.vue +3 -0
  199. package/app/components/docs/DocsLogo.vue +74 -0
  200. package/app/components/docs/DocsMobileBar.vue +26 -0
  201. package/app/components/docs/DocsMobileNav.vue +49 -0
  202. package/app/components/docs/DocsNav.vue +161 -0
  203. package/app/components/docs/DocsPageTransition.vue +32 -0
  204. package/app/components/docs/DocsSearch.vue +220 -0
  205. package/app/components/docs/DocsSearchButton.vue +20 -0
  206. package/app/components/docs/DocsThemeCustomizer.vue +229 -0
  207. package/app/components/docs/DocsThemeToggler.vue +98 -0
  208. package/app/components/docs/DocsTocRail.vue +162 -0
  209. package/app/components/docs/DocsToclink.vue +52 -0
  210. package/app/components/docs/DocsZigZagRail.vue +70 -0
  211. package/app/components/landing/Hero.vue +222 -0
  212. package/app/components/page/PageFooter.vue +49 -0
  213. package/app/components/page/PageHeader.vue +68 -0
  214. package/app/components/page/PageHeaderLinks.vue +138 -0
  215. package/app/components/page/PageMobileToc.vue +162 -0
  216. package/app/components/page/PageToc.vue +33 -0
  217. package/app/composables/useDefaultSlotItems.ts +90 -0
  218. package/app/composables/useDocPage.ts +66 -0
  219. package/app/composables/useDocSearch.ts +23 -0
  220. package/app/composables/useDocd.ts +107 -0
  221. package/app/composables/useLogoAssets.ts +319 -0
  222. package/app/composables/usePm.ts +73 -0
  223. package/app/composables/useProseComponentMeta.ts +30 -0
  224. package/app/composables/useScrollspy.ts +327 -0
  225. package/app/composables/useSeo.ts +240 -0
  226. package/app/composables/useUIConfig.ts +36 -0
  227. package/app/layouts/default.vue +5 -0
  228. package/app/layouts/docs.vue +64 -0
  229. package/app/middleware/colorMode.global.ts +8 -0
  230. package/app/middleware/doc-page-layout.ts +11 -0
  231. package/app/middleware/landing-page-layout.ts +11 -0
  232. package/app/pages/[...slug].vue +63 -0
  233. package/app/plugins/mermaid.client.ts +35 -0
  234. package/app/plugins/navigation-direction.client.ts +15 -0
  235. package/app/plugins/theme-restore.client.ts +7 -0
  236. package/app/templates/landing.vue +40 -0
  237. package/app/transitions/presets.ts +71 -0
  238. package/app/types/prose-component-meta.d.ts +4 -0
  239. package/app/utils/og.ts +26 -0
  240. package/app/utils/prerender.ts +9 -0
  241. package/app/utils/translucent.ts +56 -0
  242. package/app/utils/tw-helper.ts +4 -0
  243. package/content.config.ts +81 -0
  244. package/index.d.ts +25 -0
  245. package/modules/config.ts +89 -0
  246. package/modules/custom-icons.ts +17 -0
  247. package/modules/markdown-rewrite.ts +96 -0
  248. package/modules/prose-component-meta.ts +150 -0
  249. package/modules/routing.ts +22 -0
  250. package/modules/skills/index.ts +167 -0
  251. package/modules/skills/runtime/server/routes/skills-files.ts +49 -0
  252. package/modules/skills/runtime/server/routes/skills-index.ts +8 -0
  253. package/nuxt.config.ts +96 -0
  254. package/nuxt.schema.ts +288 -0
  255. package/package.json +95 -0
  256. package/server/mcp/tools/get-page.ts +66 -0
  257. package/server/mcp/tools/list-pages.ts +61 -0
  258. package/server/routes/sitemap.xml.ts +73 -0
  259. package/server/utils/content.ts +3 -0
  260. package/tailwind.config.cjs +55 -0
  261. package/utils/component-api-content.ts +467 -0
  262. package/utils/component-api-discovery.ts +165 -0
  263. package/utils/component-api-schema.ts +28 -0
  264. package/utils/component-api.ts +243 -0
  265. package/utils/generate-prose-component-meta.ts +303 -0
  266. package/utils/git.ts +123 -0
  267. package/utils/meta.ts +40 -0
  268. package/utils/pages.ts +21 -0
@@ -0,0 +1,150 @@
1
+ import { resolve as resolvePath } from "node:path";
2
+
3
+ import { addTemplate, createResolver, defineNuxtModule, logger, updateTemplates } from "@nuxt/kit";
4
+
5
+ import {
6
+ appendComponentApiBlocks,
7
+ builtInComponentApiItems,
8
+ isBuiltInProseDocFile,
9
+ projectComponentApiConfig,
10
+ warnForMissingComponentPaths,
11
+ withAutoTitles,
12
+ } from "../utils/component-api-content";
13
+ import { discoverProjectComponentApiPaths } from "../utils/component-api-discovery";
14
+ import {
15
+ EMPTY_PROSE_COMPONENT_META_MANIFEST,
16
+ generateProseComponentMeta,
17
+ readGeneratedProseComponentMeta,
18
+ resolveProseComponentPaths,
19
+ } from "../utils/generate-prose-component-meta";
20
+
21
+ const log = logger.withTag("Docd");
22
+
23
+ function normalizeFsPath(path: string) {
24
+ return path.replace(/\\/g, "/");
25
+ }
26
+
27
+ export default defineNuxtModule({
28
+ meta: {
29
+ name: "docd:prose-component-meta",
30
+ },
31
+ async setup(_, nuxt) {
32
+ const resolver = createResolver(import.meta.url);
33
+ const layerRoot = resolver.resolve("..");
34
+ const { componentsDir, outputFile, cacheDir } = resolveProseComponentPaths({
35
+ rootDir: nuxt.options.rootDir,
36
+ layerRoot,
37
+ });
38
+ const contentDir = resolvePath(nuxt.options.rootDir, "content");
39
+ let watchedProjectComponentFiles = new Set<string>();
40
+
41
+ let manifest = readGeneratedProseComponentMeta(outputFile);
42
+ if (!Object.keys(manifest.components).length) {
43
+ manifest = EMPTY_PROSE_COMPONENT_META_MANIFEST;
44
+ }
45
+
46
+ const manifestTemplate = addTemplate({
47
+ filename: "docd/prose-component-meta.mjs",
48
+ write: true,
49
+ getContents: () => `export default ${JSON.stringify(manifest, null, 2)};\n`,
50
+ });
51
+
52
+ async function regenerate() {
53
+ const discovery = await discoverProjectComponentApiPaths({
54
+ rootDir: nuxt.options.rootDir,
55
+ layerRoot,
56
+ contentDir,
57
+ });
58
+
59
+ watchedProjectComponentFiles = new Set(
60
+ discovery.components.map((component) => normalizeFsPath(component.absolutePath))
61
+ );
62
+
63
+ manifest = await generateProseComponentMeta({
64
+ rootDir: nuxt.options.rootDir,
65
+ layerRoot,
66
+ componentsDir,
67
+ outputFile,
68
+ cache: true,
69
+ cacheDir,
70
+ additionalComponents: discovery.components,
71
+ });
72
+
73
+ await updateTemplates({
74
+ filter: (template) => template.filename === manifestTemplate.filename,
75
+ });
76
+ }
77
+
78
+ await regenerate();
79
+
80
+ nuxt.hook("content:file:afterParse", async (event) => {
81
+ const context = {
82
+ rootDir: nuxt.options.rootDir,
83
+ layerRoot,
84
+ warn: (message: string) => log.warn(message),
85
+ };
86
+
87
+ if (isBuiltInProseDocFile(event.content)) {
88
+ const { heading, items, explicit } = builtInComponentApiItems(event.content, context);
89
+
90
+ if (!items.length) {
91
+ return;
92
+ }
93
+
94
+ if (explicit) {
95
+ warnForMissingComponentPaths(event.content, items, context);
96
+ }
97
+
98
+ event.content = await appendComponentApiBlocks(event.content, items, heading);
99
+ return;
100
+ }
101
+
102
+ const componentApi = projectComponentApiConfig(event.content);
103
+
104
+ if (!componentApi || componentApi === false) {
105
+ return;
106
+ }
107
+
108
+ if (!componentApi.hasExplicitComponents) {
109
+ log.warn(
110
+ `The componentApi config in ${typeof event.content.id === "string" ? event.content.id : "this page"} must declare a path or components array.`
111
+ );
112
+ return;
113
+ }
114
+
115
+ const items = withAutoTitles(componentApi.components);
116
+ warnForMissingComponentPaths(event.content, items, context);
117
+ event.content = await appendComponentApiBlocks(event.content, items, componentApi.heading);
118
+ });
119
+
120
+ nuxt.hook("builder:watch", async (_event, changedPath) => {
121
+ const normalizedComponentsDir = normalizeFsPath(componentsDir);
122
+ const normalizedContentDir = normalizeFsPath(contentDir);
123
+ const candidates = [
124
+ normalizeFsPath(changedPath),
125
+ normalizeFsPath(resolvePath(nuxt.options.rootDir, changedPath)),
126
+ normalizeFsPath(resolvePath(layerRoot, changedPath)),
127
+ ];
128
+
129
+ const matchesProseComponent = candidates.some(
130
+ (candidate) => candidate.endsWith(".vue") && candidate.startsWith(normalizedComponentsDir)
131
+ );
132
+ const matchesWatchedProjectComponent = candidates.some((candidate) =>
133
+ watchedProjectComponentFiles.has(candidate)
134
+ );
135
+ const matchesContentFrontmatter = candidates.some(
136
+ (candidate) => candidate.endsWith(".md") && candidate.startsWith(normalizedContentDir)
137
+ );
138
+
139
+ if (!matchesProseComponent && !matchesWatchedProjectComponent && !matchesContentFrontmatter) {
140
+ return;
141
+ }
142
+
143
+ try {
144
+ await regenerate();
145
+ } catch (error) {
146
+ log.error(`Failed to refresh prose component metadata: ${String(error)}`);
147
+ }
148
+ });
149
+ },
150
+ });
@@ -0,0 +1,22 @@
1
+ import { createResolver, defineNuxtModule, extendPages } from "@nuxt/kit";
2
+
3
+ import { landingPageExists } from "../utils/pages";
4
+
5
+ export default defineNuxtModule({
6
+ meta: {
7
+ name: "docd:routing",
8
+ },
9
+ setup(_options, nuxt) {
10
+ const { resolve } = createResolver(import.meta.url);
11
+
12
+ if (!landingPageExists(nuxt.options.rootDir)) {
13
+ extendPages((pages) => {
14
+ pages.push({
15
+ name: "index",
16
+ path: "/",
17
+ file: resolve("../app/templates/landing.vue"),
18
+ });
19
+ });
20
+ }
21
+ },
22
+ });
@@ -0,0 +1,167 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+
5
+ import {
6
+ addPrerenderRoutes,
7
+ addServerHandler,
8
+ createResolver,
9
+ defineNuxtModule,
10
+ logger,
11
+ } from "@nuxt/kit";
12
+ import { defu } from "defu";
13
+ import type { NitroConfig } from "nitropack";
14
+ import { parse as parseYaml } from "yaml";
15
+
16
+ interface SkillEntry {
17
+ name: string;
18
+ description: string;
19
+ files: string[];
20
+ }
21
+
22
+ export interface SkillsModuleOptions {
23
+ dir?: string;
24
+ }
25
+
26
+ const SKILL_NAME_REGEX = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
27
+ const MAX_NAME_LENGTH = 64;
28
+
29
+ const log = logger.withTag("Docd");
30
+
31
+ const defaults: Required<SkillsModuleOptions> = {
32
+ dir: "skills",
33
+ };
34
+
35
+ export default defineNuxtModule<SkillsModuleOptions>({
36
+ meta: {
37
+ name: "docd:skills",
38
+ },
39
+ async setup(_inlineOptions, nuxt) {
40
+ const options = defu(
41
+ (nuxt.options as unknown as { docd?: { skills?: SkillsModuleOptions } }).docd?.skills,
42
+ defaults
43
+ ) as Required<SkillsModuleOptions>;
44
+
45
+ const skillsDir = join(nuxt.options.rootDir, options.dir);
46
+ if (!existsSync(skillsDir)) return;
47
+
48
+ const catalog = await scanSkills(skillsDir);
49
+ if (!catalog.length) return;
50
+
51
+ log.info(
52
+ `Found ${catalog.length} agent skill${catalog.length > 1 ? "s" : ""}: ${catalog.map((s) => s.name).join(", ")}`
53
+ );
54
+
55
+ nuxt.options.runtimeConfig.skills = { catalog };
56
+
57
+ const { resolve } = createResolver(import.meta.url);
58
+
59
+ const onNitroConfig = nuxt.hook as (
60
+ name: "nitro:config",
61
+ cb: (nitroConfig: NitroConfig) => void
62
+ ) => void;
63
+ onNitroConfig("nitro:config", (nitroConfig) => {
64
+ nitroConfig.serverAssets ||= [];
65
+ nitroConfig.serverAssets.push({ baseName: "skills", dir: skillsDir });
66
+ });
67
+
68
+ const prerenderRoutes = ["/.well-known/skills/index.json"];
69
+ for (const skill of catalog) {
70
+ for (const file of skill.files) {
71
+ prerenderRoutes.push(`/.well-known/skills/${skill.name}/${file}`);
72
+ }
73
+ }
74
+ addPrerenderRoutes(prerenderRoutes);
75
+
76
+ addServerHandler({
77
+ route: "/.well-known/skills/index.json",
78
+ handler: resolve("./runtime/server/routes/skills-index"),
79
+ });
80
+
81
+ addServerHandler({
82
+ route: "/.well-known/skills/**",
83
+ handler: resolve("./runtime/server/routes/skills-files"),
84
+ });
85
+ },
86
+ });
87
+
88
+ function parseFrontmatter(content: string): { name?: string; description?: string } | null {
89
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
90
+ if (!match?.[1]) return null;
91
+ try {
92
+ return parseYaml(match[1]);
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ function validateSkillName(name: string, dirName: string): boolean {
99
+ if (name.length > MAX_NAME_LENGTH) {
100
+ log.warn(`Skill "${name}" exceeds ${MAX_NAME_LENGTH} character limit`);
101
+ return false;
102
+ }
103
+ if (!SKILL_NAME_REGEX.test(name) || name.includes("--")) {
104
+ log.warn(`Skill name "${name}" does not match the Agent Skills naming spec`);
105
+ return false;
106
+ }
107
+ if (name !== dirName) {
108
+ log.warn(`Skill name "${name}" does not match directory name "${dirName}"`);
109
+ return false;
110
+ }
111
+ return true;
112
+ }
113
+
114
+ async function listFilesRecursively(dir: string, base: string = ""): Promise<string[]> {
115
+ const files: string[] = [];
116
+ const entries = await readdir(dir, { withFileTypes: true });
117
+ for (const entry of entries) {
118
+ const relPath = base ? `${base}/${entry.name}` : entry.name;
119
+ if (entry.isDirectory()) {
120
+ files.push(...(await listFilesRecursively(join(dir, entry.name), relPath)));
121
+ } else {
122
+ files.push(relPath);
123
+ }
124
+ }
125
+ return files;
126
+ }
127
+
128
+ async function scanSkills(skillsDir: string): Promise<SkillEntry[]> {
129
+ const catalog: SkillEntry[] = [];
130
+ const entries = await readdir(skillsDir, { withFileTypes: true });
131
+
132
+ for (const entry of entries) {
133
+ if (!entry.isDirectory()) continue;
134
+
135
+ const skillDir = join(skillsDir, entry.name);
136
+ const skillMdPath = join(skillDir, "SKILL.md");
137
+
138
+ if (!existsSync(skillMdPath)) continue;
139
+
140
+ const content = await readFile(skillMdPath, "utf-8");
141
+ const frontmatter = parseFrontmatter(content);
142
+
143
+ if (!frontmatter?.description) {
144
+ log.warn(`Skipping skill "${entry.name}": missing description in SKILL.md frontmatter`);
145
+ continue;
146
+ }
147
+
148
+ const name = frontmatter.name || entry.name;
149
+ if (!validateSkillName(name, entry.name)) continue;
150
+
151
+ const allFiles = await listFilesRecursively(skillDir);
152
+ const files = allFiles.filter((f) => !f.split("/").some((s) => s.startsWith(".")));
153
+ const sortedFiles = ["SKILL.md", ...files.filter((f) => f !== "SKILL.md")];
154
+
155
+ catalog.push({ name, description: frontmatter.description, files: sortedFiles });
156
+ }
157
+
158
+ return catalog;
159
+ }
160
+
161
+ declare module "nuxt/schema" {
162
+ interface RuntimeConfig {
163
+ skills: {
164
+ catalog: SkillEntry[];
165
+ };
166
+ }
167
+ }
@@ -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 ADDED
@@ -0,0 +1,96 @@
1
+ import { createResolver } from "@nuxt/kit";
2
+ import tailwindcss from "@tailwindcss/vite";
3
+ import type { BundledLanguage } from "shiki";
4
+
5
+ const resolver = createResolver(import.meta.url);
6
+
7
+ const langs: BundledLanguage[] = [
8
+ "json",
9
+ "js",
10
+ "ts",
11
+ "css",
12
+ "html",
13
+ "md",
14
+ "yaml",
15
+ "vue",
16
+ "vue-html",
17
+ "bash",
18
+ "sh",
19
+ "typescript",
20
+ "javascript",
21
+ "svelte",
22
+ "tsx",
23
+ "jsx",
24
+ "prisma",
25
+ "sql",
26
+ "docker",
27
+ "dockerfile",
28
+ "python",
29
+ ];
30
+
31
+ export default defineNuxtConfig({
32
+ devtools: { enabled: true },
33
+ compatibilityDate: "latest",
34
+ modules: [
35
+ "@nuxtjs/robots",
36
+ "@nuxt/content",
37
+ "@nuxtjs/mdc",
38
+ "@nuxt/fonts",
39
+ "@nuxt/icon",
40
+ "@nuxt/image",
41
+ "reka-ui/nuxt",
42
+ "@nuxtjs/color-mode",
43
+ "motion-v/nuxt",
44
+ "@vueuse/nuxt",
45
+ "nuxt-gtag",
46
+ "nuxt-llms",
47
+ "@nuxtjs/robots",
48
+ "@nuxtjs/mcp-toolkit",
49
+ "nuxt-og-image",
50
+ "@morev/vue-transitions/nuxt",
51
+ "vue-sonner/nuxt",
52
+ resolver.resolve("./modules/routing"),
53
+ resolver.resolve("./modules/config"),
54
+ resolver.resolve("./modules/custom-icons"),
55
+ resolver.resolve("./modules/prose-component-meta"),
56
+ resolver.resolve("./modules/skills"),
57
+ resolver.resolve("./modules/markdown-rewrite"),
58
+ ],
59
+
60
+ mdc: {
61
+ highlight: { langs, noApiRoute: false },
62
+ },
63
+
64
+ content: {
65
+ build: {
66
+ markdown: {
67
+ toc: { depth: 4, searchDepth: 4 },
68
+ highlight: { langs },
69
+ },
70
+ },
71
+ experimental: { sqliteConnector: "native" },
72
+ },
73
+
74
+ imports: {
75
+ imports: [
76
+ { from: "tailwind-variants", name: "tv" },
77
+ { from: "tailwind-variants", name: "VariantProps", type: true },
78
+ { from: "vue-sonner", name: "toast", as: "useSonner" },
79
+ ],
80
+ },
81
+
82
+ icon: {
83
+ clientBundle: { scan: true, sizeLimitKb: 0 },
84
+ mode: "svg",
85
+ class: "shrink-0",
86
+ fetchTimeout: 2000,
87
+ serverBundle: "local",
88
+ },
89
+ css: [
90
+ resolver.resolve("./app/assets/css/tailwind.css"),
91
+ resolver.resolve("./app/assets/css/theme.css"),
92
+ ],
93
+ vite: {
94
+ plugins: [tailwindcss()],
95
+ },
96
+ });