@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.
- package/app/app.config.ts +239 -0
- package/app/app.vue +19 -0
- package/app/assets/css/tailwind.css +335 -0
- package/app/assets/css/theme.css +1171 -0
- package/app/components/OgImage/Docs.takumi.vue +69 -0
- package/app/components/OgImage/Landing.takumi.vue +64 -0
- package/app/components/OgImage/Site.takumi.vue +32 -0
- package/app/components/Ui/Avatar/Avatar.vue +75 -0
- package/app/components/Ui/Avatar/Badge.vue +34 -0
- package/app/components/Ui/Avatar/Fallback.vue +30 -0
- package/app/components/Ui/Avatar/Group.vue +29 -0
- package/app/components/Ui/Avatar/GroupCount.vue +29 -0
- package/app/components/Ui/Avatar/Image.vue +29 -0
- package/app/components/Ui/Badge.vue +86 -0
- package/app/components/Ui/BadgeGroup.vue +314 -0
- package/app/components/Ui/BgPattern/Grid.vue +137 -0
- package/app/components/Ui/BorderBeam.vue +97 -0
- package/app/components/Ui/Breadcrumbs.vue +103 -0
- package/app/components/Ui/Button.vue +169 -0
- package/app/components/Ui/ButtonGroup/ButtonGroup.vue +54 -0
- package/app/components/Ui/ButtonGroup/Separator.vue +38 -0
- package/app/components/Ui/ButtonGroup/Text.vue +33 -0
- package/app/components/Ui/Card/Action.vue +31 -0
- package/app/components/Ui/Card/Card.vue +58 -0
- package/app/components/Ui/Card/Content.vue +34 -0
- package/app/components/Ui/Card/Description.vue +36 -0
- package/app/components/Ui/Card/Footer.vue +31 -0
- package/app/components/Ui/Card/Header.vue +31 -0
- package/app/components/Ui/Card/Title.vue +37 -0
- package/app/components/Ui/Collapsible/Collapsible.vue +16 -0
- package/app/components/Ui/Collapsible/Content.vue +27 -0
- package/app/components/Ui/Collapsible/Trigger.vue +13 -0
- package/app/components/Ui/Command/Command.vue +115 -0
- package/app/components/Ui/Command/Dialog.vue +65 -0
- package/app/components/Ui/Command/Empty.vue +32 -0
- package/app/components/Ui/Command/Footer.vue +40 -0
- package/app/components/Ui/Command/Group.vue +58 -0
- package/app/components/Ui/Command/Input.vue +57 -0
- package/app/components/Ui/Command/Item.vue +102 -0
- package/app/components/Ui/Command/List.vue +33 -0
- package/app/components/Ui/Command/Separator.vue +30 -0
- package/app/components/Ui/Command/Shortcut.vue +28 -0
- package/app/components/Ui/Container.vue +46 -0
- package/app/components/Ui/ContextMenu/Arrow.vue +43 -0
- package/app/components/Ui/ContextMenu/CheckboxItem.vue +42 -0
- package/app/components/Ui/ContextMenu/Content.vue +53 -0
- package/app/components/Ui/ContextMenu/ContextMenu.vue +14 -0
- package/app/components/Ui/ContextMenu/Group.vue +12 -0
- package/app/components/Ui/ContextMenu/Item.vue +65 -0
- package/app/components/Ui/ContextMenu/ItemIndicator.vue +30 -0
- package/app/components/Ui/ContextMenu/Label.vue +35 -0
- package/app/components/Ui/ContextMenu/Portal.vue +12 -0
- package/app/components/Ui/ContextMenu/RadioGroup.vue +16 -0
- package/app/components/Ui/ContextMenu/RadioItem.vue +40 -0
- package/app/components/Ui/ContextMenu/Separator.vue +25 -0
- package/app/components/Ui/ContextMenu/Shortcut.vue +30 -0
- package/app/components/Ui/ContextMenu/Sub.vue +15 -0
- package/app/components/Ui/ContextMenu/SubContent.vue +55 -0
- package/app/components/Ui/ContextMenu/SubTrigger.vue +42 -0
- package/app/components/Ui/ContextMenu/Trigger.vue +12 -0
- package/app/components/Ui/Dialog/Close.vue +12 -0
- package/app/components/Ui/Dialog/Content.vue +79 -0
- package/app/components/Ui/Dialog/Description.vue +29 -0
- package/app/components/Ui/Dialog/Dialog.vue +14 -0
- package/app/components/Ui/Dialog/Footer.vue +32 -0
- package/app/components/Ui/Dialog/Header.vue +32 -0
- package/app/components/Ui/Dialog/Overlay.vue +25 -0
- package/app/components/Ui/Dialog/Portal.vue +12 -0
- package/app/components/Ui/Dialog/Title.vue +29 -0
- package/app/components/Ui/Dialog/Trigger.vue +12 -0
- package/app/components/Ui/Divider.vue +102 -0
- package/app/components/Ui/Drawer/Close.vue +12 -0
- package/app/components/Ui/Drawer/Content.vue +67 -0
- package/app/components/Ui/Drawer/Description.vue +33 -0
- package/app/components/Ui/Drawer/Drawer.vue +17 -0
- package/app/components/Ui/Drawer/Footer.vue +35 -0
- package/app/components/Ui/Drawer/Header.vue +34 -0
- package/app/components/Ui/Drawer/Overlay.vue +20 -0
- package/app/components/Ui/Drawer/Portal.vue +12 -0
- package/app/components/Ui/Drawer/Title.vue +35 -0
- package/app/components/Ui/Drawer/Trigger.vue +12 -0
- package/app/components/Ui/DropdownMenu/Arrow.vue +43 -0
- package/app/components/Ui/DropdownMenu/CheckboxItem.vue +41 -0
- package/app/components/Ui/DropdownMenu/Content.vue +56 -0
- package/app/components/Ui/DropdownMenu/DropdownMenu.vue +15 -0
- package/app/components/Ui/DropdownMenu/Group.vue +12 -0
- package/app/components/Ui/DropdownMenu/Item.vue +64 -0
- package/app/components/Ui/DropdownMenu/ItemIndicator.vue +32 -0
- package/app/components/Ui/DropdownMenu/Label.vue +34 -0
- package/app/components/Ui/DropdownMenu/Portal.vue +12 -0
- package/app/components/Ui/DropdownMenu/RadioGroup.vue +16 -0
- package/app/components/Ui/DropdownMenu/RadioItem.vue +40 -0
- package/app/components/Ui/DropdownMenu/Separator.vue +25 -0
- package/app/components/Ui/DropdownMenu/Shortcut.vue +44 -0
- package/app/components/Ui/DropdownMenu/Sub.vue +14 -0
- package/app/components/Ui/DropdownMenu/SubContent.vue +56 -0
- package/app/components/Ui/DropdownMenu/SubTrigger.vue +47 -0
- package/app/components/Ui/DropdownMenu/Trigger.vue +12 -0
- package/app/components/Ui/FancyIcon.vue +243 -0
- package/app/components/Ui/GradientDivider.vue +26 -0
- package/app/components/Ui/IframeLazy.vue +97 -0
- package/app/components/Ui/Input.vue +97 -0
- package/app/components/Ui/InputGroup/Addon.vue +66 -0
- package/app/components/Ui/InputGroup/Button.vue +50 -0
- package/app/components/Ui/InputGroup/Input.vue +25 -0
- package/app/components/Ui/InputGroup/InputGroup.vue +44 -0
- package/app/components/Ui/InputGroup/Text.vue +33 -0
- package/app/components/Ui/InputGroup/Textarea.vue +25 -0
- package/app/components/Ui/Kbd/Group.vue +32 -0
- package/app/components/Ui/Kbd/Kbd.vue +62 -0
- package/app/components/Ui/Label.vue +40 -0
- package/app/components/Ui/Popover/Anchor.vue +13 -0
- package/app/components/Ui/Popover/Arrow.vue +42 -0
- package/app/components/Ui/Popover/Close.vue +13 -0
- package/app/components/Ui/Popover/Content.vue +55 -0
- package/app/components/Ui/Popover/Popover.vue +15 -0
- package/app/components/Ui/Popover/Portal.vue +13 -0
- package/app/components/Ui/Popover/Trigger.vue +12 -0
- package/app/components/Ui/Popover/X.vue +38 -0
- package/app/components/Ui/ScrollArea/Corner.vue +12 -0
- package/app/components/Ui/ScrollArea/ScrollArea.vue +38 -0
- package/app/components/Ui/ScrollArea/Scrollbar.vue +39 -0
- package/app/components/Ui/ScrollArea/Thumb.vue +39 -0
- package/app/components/Ui/ScrollArea/Viewport.vue +27 -0
- package/app/components/Ui/Scrollspy.vue +61 -0
- package/app/components/Ui/Separator.vue +40 -0
- package/app/components/Ui/Skeleton.vue +39 -0
- package/app/components/Ui/Sonner.vue +25 -0
- package/app/components/Ui/Tabs/Content.vue +27 -0
- package/app/components/Ui/Tabs/Indicator.vue +37 -0
- package/app/components/Ui/Tabs/List.vue +37 -0
- package/app/components/Ui/Tabs/Tabs.vue +25 -0
- package/app/components/Ui/Tabs/Trigger.vue +41 -0
- package/app/components/Ui/Textarea.vue +81 -0
- package/app/components/Ui/Tooltip/Arrow.vue +30 -0
- package/app/components/Ui/Tooltip/Content.vue +50 -0
- package/app/components/Ui/Tooltip/Portal.vue +12 -0
- package/app/components/Ui/Tooltip/Provider.vue +14 -0
- package/app/components/Ui/Tooltip/Tooltip.vue +25 -0
- package/app/components/Ui/Tooltip/Trigger.vue +12 -0
- package/app/components/Ui/Tree/Item.vue +15 -0
- package/app/components/Ui/Tree/Tree.vue +15 -0
- package/app/components/Ui/Tree/Virtualizer.vue +13 -0
- package/app/components/component-api/ComponentApiSection.vue +135 -0
- package/app/components/content/docs/Callout/DocsCallout.vue +99 -0
- package/app/components/content/prose/ProseA.global.vue +31 -0
- package/app/components/content/prose/ProseBlockquote.global.vue +28 -0
- package/app/components/content/prose/ProseCallout.global.vue +306 -0
- package/app/components/content/prose/ProseCard.global.vue +100 -0
- package/app/components/content/prose/ProseCodeCollapse.global.vue +125 -0
- package/app/components/content/prose/ProseCodeCopy.global.vue +67 -0
- package/app/components/content/prose/ProseCodeGroup.global.vue +104 -0
- package/app/components/content/prose/ProseCodeTree.global.vue +277 -0
- package/app/components/content/prose/ProseCollapsible.global.vue +64 -0
- package/app/components/content/prose/ProseColorModeImage.global.vue +58 -0
- package/app/components/content/prose/ProseComponentApi.global.vue +104 -0
- package/app/components/content/prose/ProseComponentEvents.global.vue +27 -0
- package/app/components/content/prose/ProseComponentExposed.global.vue +27 -0
- package/app/components/content/prose/ProseComponentProps.global.vue +27 -0
- package/app/components/content/prose/ProseComponentSlots.global.vue +27 -0
- package/app/components/content/prose/ProseEm.global.vue +5 -0
- package/app/components/content/prose/ProseField.global.vue +118 -0
- package/app/components/content/prose/ProseFieldGroup.global.vue +62 -0
- package/app/components/content/prose/ProseH1.global.vue +58 -0
- package/app/components/content/prose/ProseH2.global.vue +58 -0
- package/app/components/content/prose/ProseH3.global.vue +58 -0
- package/app/components/content/prose/ProseH4.global.vue +58 -0
- package/app/components/content/prose/ProseH5.global.vue +47 -0
- package/app/components/content/prose/ProseH6.global.vue +47 -0
- package/app/components/content/prose/ProseHr.global.vue +33 -0
- package/app/components/content/prose/ProseIconList.global.vue +113 -0
- package/app/components/content/prose/ProseImg.global.vue +119 -0
- package/app/components/content/prose/ProseLead.global.vue +28 -0
- package/app/components/content/prose/ProseLi.global.vue +25 -0
- package/app/components/content/prose/ProseMermaid.global.vue +127 -0
- package/app/components/content/prose/ProseOl.global.vue +25 -0
- package/app/components/content/prose/ProseP.global.vue +25 -0
- package/app/components/content/prose/ProsePmInstall.global.vue +60 -0
- package/app/components/content/prose/ProsePmRun.global.vue +51 -0
- package/app/components/content/prose/ProsePmX.global.vue +56 -0
- package/app/components/content/prose/ProsePre.global.vue +181 -0
- package/app/components/content/prose/ProseShowCase.global.vue +56 -0
- package/app/components/content/prose/ProseSmartIcon.global.vue +54 -0
- package/app/components/content/prose/ProseStep.global.vue +22 -0
- package/app/components/content/prose/ProseSteps.global.vue +22 -0
- package/app/components/content/prose/ProseStrong.global.vue +34 -0
- package/app/components/content/prose/ProseTable.global.vue +34 -0
- package/app/components/content/prose/ProseTabs.global.vue +104 -0
- package/app/components/content/prose/ProseTabsInner.global.vue +274 -0
- package/app/components/content/prose/ProseTbody.global.vue +5 -0
- package/app/components/content/prose/ProseTd.global.vue +25 -0
- package/app/components/content/prose/ProseTh.global.vue +25 -0
- package/app/components/content/prose/ProseThead.global.vue +9 -0
- package/app/components/content/prose/ProseTr.global.vue +35 -0
- package/app/components/content/prose/ProseUl.global.vue +25 -0
- package/app/components/docs/DocsExtraStuff.vue +39 -0
- package/app/components/docs/DocsGithubLink.vue +11 -0
- package/app/components/docs/DocsHeader.vue +3 -0
- package/app/components/docs/DocsLogo.vue +74 -0
- package/app/components/docs/DocsMobileBar.vue +26 -0
- package/app/components/docs/DocsMobileNav.vue +49 -0
- package/app/components/docs/DocsNav.vue +161 -0
- package/app/components/docs/DocsPageTransition.vue +32 -0
- package/app/components/docs/DocsSearch.vue +220 -0
- package/app/components/docs/DocsSearchButton.vue +20 -0
- package/app/components/docs/DocsThemeCustomizer.vue +229 -0
- package/app/components/docs/DocsThemeToggler.vue +98 -0
- package/app/components/docs/DocsTocRail.vue +162 -0
- package/app/components/docs/DocsToclink.vue +52 -0
- package/app/components/docs/DocsZigZagRail.vue +70 -0
- package/app/components/landing/Hero.vue +222 -0
- package/app/components/page/PageFooter.vue +49 -0
- package/app/components/page/PageHeader.vue +68 -0
- package/app/components/page/PageHeaderLinks.vue +138 -0
- package/app/components/page/PageMobileToc.vue +162 -0
- package/app/components/page/PageToc.vue +33 -0
- package/app/composables/useDefaultSlotItems.ts +90 -0
- package/app/composables/useDocPage.ts +66 -0
- package/app/composables/useDocSearch.ts +23 -0
- package/app/composables/useDocd.ts +107 -0
- package/app/composables/useLogoAssets.ts +319 -0
- package/app/composables/usePm.ts +73 -0
- package/app/composables/useProseComponentMeta.ts +30 -0
- package/app/composables/useScrollspy.ts +327 -0
- package/app/composables/useSeo.ts +240 -0
- package/app/composables/useUIConfig.ts +36 -0
- package/app/layouts/default.vue +5 -0
- package/app/layouts/docs.vue +64 -0
- package/app/middleware/colorMode.global.ts +8 -0
- package/app/middleware/doc-page-layout.ts +11 -0
- package/app/middleware/landing-page-layout.ts +11 -0
- package/app/pages/[...slug].vue +63 -0
- package/app/plugins/mermaid.client.ts +35 -0
- package/app/plugins/navigation-direction.client.ts +15 -0
- package/app/plugins/theme-restore.client.ts +7 -0
- package/app/templates/landing.vue +40 -0
- package/app/transitions/presets.ts +71 -0
- package/app/types/prose-component-meta.d.ts +4 -0
- package/app/utils/og.ts +26 -0
- package/app/utils/prerender.ts +9 -0
- package/app/utils/translucent.ts +56 -0
- package/app/utils/tw-helper.ts +4 -0
- package/content.config.ts +81 -0
- package/index.d.ts +25 -0
- package/modules/config.ts +89 -0
- package/modules/custom-icons.ts +17 -0
- package/modules/markdown-rewrite.ts +96 -0
- package/modules/prose-component-meta.ts +150 -0
- package/modules/routing.ts +22 -0
- package/modules/skills/index.ts +167 -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 +96 -0
- package/nuxt.schema.ts +288 -0
- package/package.json +95 -0
- package/server/mcp/tools/get-page.ts +66 -0
- package/server/mcp/tools/list-pages.ts +61 -0
- package/server/routes/sitemap.xml.ts +73 -0
- package/server/utils/content.ts +3 -0
- package/tailwind.config.cjs +55 -0
- package/utils/component-api-content.ts +467 -0
- package/utils/component-api-discovery.ts +165 -0
- package/utils/component-api-schema.ts +28 -0
- package/utils/component-api.ts +243 -0
- package/utils/generate-prose-component-meta.ts +303 -0
- package/utils/git.ts +123 -0
- package/utils/meta.ts +40 -0
- 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
|
+
});
|