docmk 1.0.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/.claude/skills/pdf/SKILL.md +89 -0
- package/.claude/skills/web-scraping/SKILL.md +78 -0
- package/CLAUDE.md +90 -0
- package/bin/docmk.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +636 -0
- package/dist/index.js.map +1 -0
- package/final-site/assets/main-B4orIFxK.css +1 -0
- package/final-site/assets/main-CSoKXua6.js +25 -0
- package/final-site/favicon.svg +4 -0
- package/final-site/index.html +26 -0
- package/final-site/robots.txt +4 -0
- package/final-site/sitemap.xml +14 -0
- package/my-docs/api/README.md +152 -0
- package/my-docs/api/advanced.md +260 -0
- package/my-docs/getting-started/README.md +24 -0
- package/my-docs/tutorials/README.md +272 -0
- package/my-docs/tutorials/customization.md +492 -0
- package/package.json +59 -0
- package/postcss.config.js +6 -0
- package/site/assets/main-BZUsYUCF.css +1 -0
- package/site/assets/main-q6laQtCD.js +114 -0
- package/site/favicon.svg +4 -0
- package/site/index.html +23 -0
- package/site/robots.txt +4 -0
- package/site/sitemap.xml +34 -0
- package/site-output/assets/main-B4orIFxK.css +1 -0
- package/site-output/assets/main-CSoKXua6.js +25 -0
- package/site-output/favicon.svg +4 -0
- package/site-output/index.html +26 -0
- package/site-output/robots.txt +4 -0
- package/site-output/sitemap.xml +14 -0
- package/src/builder/index.ts +189 -0
- package/src/builder/vite-dev.ts +117 -0
- package/src/cli/commands/build.ts +48 -0
- package/src/cli/commands/dev.ts +53 -0
- package/src/cli/commands/preview.ts +57 -0
- package/src/cli/index.ts +42 -0
- package/src/client/App.vue +15 -0
- package/src/client/components/SearchBox.vue +204 -0
- package/src/client/components/Sidebar.vue +18 -0
- package/src/client/components/SidebarItem.vue +108 -0
- package/src/client/index.html +21 -0
- package/src/client/layouts/AppLayout.vue +99 -0
- package/src/client/lib/utils.ts +6 -0
- package/src/client/main.ts +42 -0
- package/src/client/pages/Home.vue +279 -0
- package/src/client/pages/SkillPage.vue +565 -0
- package/src/client/router.ts +16 -0
- package/src/client/styles/global.css +92 -0
- package/src/client/utils/routes.ts +69 -0
- package/src/parser/index.ts +253 -0
- package/src/scanner/index.ts +127 -0
- package/src/types/index.ts +45 -0
- package/tailwind.config.js +65 -0
- package/test-build/assets/main-C2ARPC0e.css +1 -0
- package/test-build/assets/main-CHIQpV3B.js +25 -0
- package/test-build/favicon.svg +4 -0
- package/test-build/index.html +47 -0
- package/test-build/robots.txt +4 -0
- package/test-build/sitemap.xml +19 -0
- package/test-dist/assets/main-B4orIFxK.css +1 -0
- package/test-dist/assets/main-CSoKXua6.js +25 -0
- package/test-dist/favicon.svg +4 -0
- package/test-dist/index.html +26 -0
- package/test-dist/robots.txt +4 -0
- package/test-dist/sitemap.xml +14 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +13 -0
- package/vite.config.ts +21 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { SkillFile } from '../../types/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate route from file path based on the configured skills directory
|
|
5
|
+
* This replaces hardcoded .claude/skills references
|
|
6
|
+
*/
|
|
7
|
+
export function getFileRoute(file: SkillFile, skillsDir?: string): string {
|
|
8
|
+
const filePath = file.path
|
|
9
|
+
|
|
10
|
+
// If skillsDir is provided, use it; otherwise try to detect from path
|
|
11
|
+
if (skillsDir) {
|
|
12
|
+
const normalizedPath = filePath.replace(/\\/g, '/')
|
|
13
|
+
const normalizedSkillsDir = skillsDir.replace(/\\/g, '/')
|
|
14
|
+
|
|
15
|
+
const index = normalizedPath.indexOf(normalizedSkillsDir)
|
|
16
|
+
if (index !== -1) {
|
|
17
|
+
const relativePath = normalizedPath.slice(index + normalizedSkillsDir.length)
|
|
18
|
+
return pathToRoute(relativePath)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Fallback: try common patterns
|
|
23
|
+
const patterns = ['.claude/skills', 'docs', 'documentation', 'my-docs']
|
|
24
|
+
for (const pattern of patterns) {
|
|
25
|
+
const index = filePath.indexOf(pattern)
|
|
26
|
+
if (index !== -1) {
|
|
27
|
+
const relativePath = filePath.slice(index + pattern.length)
|
|
28
|
+
return pathToRoute(relativePath)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Last resort: use filename
|
|
33
|
+
return pathToRoute('/' + file.name)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function pathToRoute(relativePath: string): string {
|
|
37
|
+
const segments = relativePath.split('/').filter(Boolean)
|
|
38
|
+
|
|
39
|
+
if (segments.length === 0) return '/'
|
|
40
|
+
|
|
41
|
+
const lastSegment = segments[segments.length - 1]
|
|
42
|
+
if (lastSegment.endsWith('.md')) {
|
|
43
|
+
segments[segments.length - 1] = lastSegment.slice(0, -3)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// SKILL.md and README.md should map to parent directory route
|
|
47
|
+
const finalSegment = segments[segments.length - 1]
|
|
48
|
+
if (finalSegment === 'SKILL' || finalSegment === 'README') {
|
|
49
|
+
segments.pop()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return '/' + segments.join('/')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get display path for search results
|
|
57
|
+
*/
|
|
58
|
+
export function getPathDisplay(filePath: string, skillsDir?: string): string {
|
|
59
|
+
if (skillsDir) {
|
|
60
|
+
const index = filePath.indexOf(skillsDir)
|
|
61
|
+
if (index !== -1) {
|
|
62
|
+
return filePath.slice(index)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Fallback to showing last few segments
|
|
67
|
+
const segments = filePath.split('/').filter(Boolean)
|
|
68
|
+
return segments.slice(-3).join('/')
|
|
69
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import matter from 'gray-matter'
|
|
2
|
+
import MarkdownIt from 'markdown-it'
|
|
3
|
+
import { createHighlighter, Highlighter } from 'shiki'
|
|
4
|
+
import { SkillDirectory, SkillFile, SiteConfig, DocGenConfig, Navigation } from '../types/index.js'
|
|
5
|
+
|
|
6
|
+
let highlighter: Highlighter | null = null
|
|
7
|
+
|
|
8
|
+
async function getHighlighter() {
|
|
9
|
+
if (!highlighter) {
|
|
10
|
+
highlighter = await createHighlighter({
|
|
11
|
+
themes: ['github-dark', 'github-light'],
|
|
12
|
+
langs: ['javascript', 'typescript', 'bash', 'shell', 'json', 'html', 'css', 'vue', 'jsx', 'tsx', 'python', 'markdown', 'yaml', 'sql', 'go', 'rust', 'java', 'c', 'cpp']
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
return highlighter
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const md = new MarkdownIt({
|
|
19
|
+
html: true,
|
|
20
|
+
linkify: true,
|
|
21
|
+
typographer: true,
|
|
22
|
+
highlight: function (str: string, lang: string) {
|
|
23
|
+
// Synchronous fallback - actual highlighting done in enhanceFileContent
|
|
24
|
+
return `<pre class="shiki-pending" data-lang="${lang || 'text'}"><code>${md.utils.escapeHtml(str)}</code></pre>`
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export async function parseSkillsToConfig(
|
|
29
|
+
directories: SkillDirectory[],
|
|
30
|
+
siteConfig: SiteConfig
|
|
31
|
+
): Promise<DocGenConfig> {
|
|
32
|
+
const files: SkillFile[] = []
|
|
33
|
+
const navigation = await generateNavigation(directories)
|
|
34
|
+
|
|
35
|
+
// Collect all files for search indexing
|
|
36
|
+
collectAllFiles(directories, files)
|
|
37
|
+
|
|
38
|
+
// Enhance files with parsed content
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
await enhanceFileContent(file)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
siteConfig,
|
|
45
|
+
navigation,
|
|
46
|
+
files,
|
|
47
|
+
directories
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectAllFiles(items: (SkillDirectory | SkillFile)[], files: SkillFile[]) {
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
if ('content' in item) {
|
|
54
|
+
// It's a SkillFile
|
|
55
|
+
files.push(item)
|
|
56
|
+
} else {
|
|
57
|
+
// It's a SkillDirectory
|
|
58
|
+
if (item.skillFile) {
|
|
59
|
+
files.push(item.skillFile)
|
|
60
|
+
}
|
|
61
|
+
collectAllFiles(item.children, files)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function enhanceFileContent(file: SkillFile) {
|
|
67
|
+
try {
|
|
68
|
+
let content = file.content
|
|
69
|
+
|
|
70
|
+
// Update title and description from frontmatter if available
|
|
71
|
+
file.title = file.frontmatter.title || file.title
|
|
72
|
+
file.description = file.frontmatter.description || file.description
|
|
73
|
+
|
|
74
|
+
// Remove duplicate h1 if it matches frontmatter title
|
|
75
|
+
if (file.frontmatter.title) {
|
|
76
|
+
// Match first h1 heading in content (may have leading whitespace/newlines)
|
|
77
|
+
const h1Match = content.match(/^\s*#\s+(.+)$/m)
|
|
78
|
+
if (h1Match) {
|
|
79
|
+
const h1Text = h1Match[1].trim()
|
|
80
|
+
// If h1 matches title, remove it to avoid duplication
|
|
81
|
+
if (h1Text === file.frontmatter.title) {
|
|
82
|
+
content = content.replace(/^\s*#\s+.+\n*/, '')
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Render markdown to HTML
|
|
88
|
+
let html = md.render(content)
|
|
89
|
+
|
|
90
|
+
// Apply syntax highlighting with shiki
|
|
91
|
+
html = await highlightCodeBlocks(html)
|
|
92
|
+
file.frontmatter.html = html
|
|
93
|
+
|
|
94
|
+
// Extract headings for TOC
|
|
95
|
+
const headings = extractHeadings(content)
|
|
96
|
+
file.frontmatter.headings = headings
|
|
97
|
+
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.warn(`Failed to enhance content for ${file.path}:`, error)
|
|
100
|
+
try {
|
|
101
|
+
file.frontmatter.html = md.render(file.content)
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.error(`Failed to render markdown for ${file.path}:`, e)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function highlightCodeBlocks(html: string): Promise<string> {
|
|
109
|
+
const hl = await getHighlighter()
|
|
110
|
+
|
|
111
|
+
// Find all pending shiki code blocks and highlight them
|
|
112
|
+
const codeBlockRegex = /<pre class="shiki-pending" data-lang="([^"]*)"[^>]*><code>([^]*?)<\/code><\/pre>/g
|
|
113
|
+
|
|
114
|
+
const matches = [...html.matchAll(codeBlockRegex)]
|
|
115
|
+
|
|
116
|
+
for (const match of matches) {
|
|
117
|
+
const [fullMatch, lang, escapedCode] = match
|
|
118
|
+
// Unescape HTML entities
|
|
119
|
+
const code = escapedCode
|
|
120
|
+
.replace(/</g, '<')
|
|
121
|
+
.replace(/>/g, '>')
|
|
122
|
+
.replace(/&/g, '&')
|
|
123
|
+
.replace(/"/g, '"')
|
|
124
|
+
.replace(/'/g, "'")
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const validLang = hl.getLoadedLanguages().includes(lang) ? lang : 'text'
|
|
128
|
+
const highlighted = hl.codeToHtml(code, {
|
|
129
|
+
lang: validLang,
|
|
130
|
+
theme: 'github-dark'
|
|
131
|
+
})
|
|
132
|
+
html = html.replace(fullMatch, highlighted)
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// Keep original if highlighting fails
|
|
135
|
+
console.warn(`Failed to highlight ${lang}:`, e)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return html
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function extractHeadings(content: string) {
|
|
143
|
+
const headings: Array<{ level: number; text: string; anchor: string }> = []
|
|
144
|
+
const lines = content.split('\n')
|
|
145
|
+
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
const match = line.match(/^(#{1,6})\\s+(.+)$/)
|
|
148
|
+
if (match) {
|
|
149
|
+
const level = match[1].length
|
|
150
|
+
const text = match[2].trim()
|
|
151
|
+
const anchor = text.toLowerCase()
|
|
152
|
+
.replace(/[^\\w\\s-]/g, '')
|
|
153
|
+
.replace(/\\s+/g, '-')
|
|
154
|
+
.trim()
|
|
155
|
+
|
|
156
|
+
headings.push({ level, text, anchor })
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return headings
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function generateNavigation(directories: SkillDirectory[]): Promise<Navigation[]> {
|
|
164
|
+
const navigation: Navigation[] = []
|
|
165
|
+
|
|
166
|
+
for (const dir of directories) {
|
|
167
|
+
const navItem: Navigation = {
|
|
168
|
+
text: dir.skillFile?.title || formatDirName(dir.name),
|
|
169
|
+
link: dir.skillFile ? getFileRoute(dir.skillFile) : undefined
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (dir.children.length > 0) {
|
|
173
|
+
navItem.children = []
|
|
174
|
+
|
|
175
|
+
for (const child of dir.children) {
|
|
176
|
+
if ('content' in child) {
|
|
177
|
+
// It's a file
|
|
178
|
+
navItem.children.push({
|
|
179
|
+
text: child.title || formatFileName(child.name),
|
|
180
|
+
link: getFileRoute(child)
|
|
181
|
+
})
|
|
182
|
+
} else {
|
|
183
|
+
// It's a subdirectory
|
|
184
|
+
const subNav = await generateNavigation([child])
|
|
185
|
+
navItem.children.push(...subNav)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
navigation.push(navItem)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return navigation
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getFileRoute(file: SkillFile): string {
|
|
197
|
+
// Import the utility function from client utils
|
|
198
|
+
// For now, use inline implementation
|
|
199
|
+
const filePath = file.path
|
|
200
|
+
|
|
201
|
+
// Try common patterns
|
|
202
|
+
const patterns = ['my-docs', 'docs', 'documentation', '.claude/skills']
|
|
203
|
+
let relativePath = ''
|
|
204
|
+
|
|
205
|
+
for (const pattern of patterns) {
|
|
206
|
+
const index = filePath.indexOf(pattern)
|
|
207
|
+
if (index !== -1) {
|
|
208
|
+
relativePath = filePath.slice(index + pattern.length)
|
|
209
|
+
break
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!relativePath) {
|
|
214
|
+
// Fallback: use last 2 segments
|
|
215
|
+
const segments = filePath.split('/').filter(Boolean)
|
|
216
|
+
if (segments.length >= 2) {
|
|
217
|
+
relativePath = '/' + segments.slice(-2).join('/')
|
|
218
|
+
} else {
|
|
219
|
+
return '/'
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const segments = relativePath.split('/').filter(Boolean)
|
|
224
|
+
|
|
225
|
+
if (segments.length === 0) return '/'
|
|
226
|
+
|
|
227
|
+
// Remove file extension
|
|
228
|
+
const lastSegment = segments[segments.length - 1]
|
|
229
|
+
if (lastSegment.endsWith('.md')) {
|
|
230
|
+
segments[segments.length - 1] = lastSegment.slice(0, -3)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// SKILL.md and README.md should map to parent directory route
|
|
234
|
+
const finalSegment = segments[segments.length - 1]
|
|
235
|
+
if (finalSegment === 'SKILL' || finalSegment === 'README') {
|
|
236
|
+
segments.pop()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return '/' + segments.join('/')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function formatDirName(name: string): string {
|
|
243
|
+
return name.split('-').map(word =>
|
|
244
|
+
word.charAt(0).toUpperCase() + word.slice(1)
|
|
245
|
+
).join(' ')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function formatFileName(name: string): string {
|
|
249
|
+
const baseName = name.replace('.md', '')
|
|
250
|
+
return formatDirName(baseName)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export { md as markdownRenderer }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from 'fs/promises'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { SkillDirectory, SkillFile } from '../types/index.js'
|
|
4
|
+
|
|
5
|
+
export async function scanSkillsDirectory(sourceDir: string): Promise<SkillDirectory[]> {
|
|
6
|
+
try {
|
|
7
|
+
await fs.access(sourceDir)
|
|
8
|
+
} catch {
|
|
9
|
+
throw new Error(`Source directory not found: ${sourceDir}`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const entries = await fs.readdir(sourceDir, { withFileTypes: true })
|
|
13
|
+
const directories: SkillDirectory[] = []
|
|
14
|
+
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
const dirPath = path.join(sourceDir, entry.name)
|
|
18
|
+
const skillDirectory = await scanDirectory(dirPath, entry.name)
|
|
19
|
+
directories.push(skillDirectory)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return directories
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function scanDirectory(dirPath: string, name: string): Promise<SkillDirectory> {
|
|
27
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
|
28
|
+
const children: (SkillDirectory | SkillFile)[] = []
|
|
29
|
+
let skillFile: SkillFile | undefined
|
|
30
|
+
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const entryPath = path.join(dirPath, entry.name)
|
|
33
|
+
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
const subDir = await scanDirectory(entryPath, entry.name)
|
|
36
|
+
children.push(subDir)
|
|
37
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
38
|
+
const file = await parseMarkdownFile(entryPath, entry.name)
|
|
39
|
+
|
|
40
|
+
if (entry.name === 'SKILL.md') {
|
|
41
|
+
skillFile = file
|
|
42
|
+
} else {
|
|
43
|
+
children.push(file)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
path: dirPath,
|
|
50
|
+
name,
|
|
51
|
+
children,
|
|
52
|
+
skillFile
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function parseMarkdownFile(filePath: string, fileName: string): Promise<SkillFile> {
|
|
57
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
58
|
+
const stats = await fs.stat(filePath)
|
|
59
|
+
|
|
60
|
+
// Basic frontmatter extraction
|
|
61
|
+
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/)
|
|
62
|
+
const frontmatter: Record<string, any> = {}
|
|
63
|
+
let markdownContent = content
|
|
64
|
+
|
|
65
|
+
if (frontmatterMatch) {
|
|
66
|
+
// Remove frontmatter from content
|
|
67
|
+
markdownContent = content.slice(frontmatterMatch[0].length)
|
|
68
|
+
|
|
69
|
+
// Parse frontmatter key-value pairs
|
|
70
|
+
const fmLines = frontmatterMatch[1].split(/\r?\n/)
|
|
71
|
+
for (const line of fmLines) {
|
|
72
|
+
const colonIndex = line.indexOf(':')
|
|
73
|
+
if (colonIndex > 0) {
|
|
74
|
+
const key = line.slice(0, colonIndex).trim()
|
|
75
|
+
const value = line.slice(colonIndex + 1).trim().replace(/^["']|["']$/g, '')
|
|
76
|
+
if (key && value) {
|
|
77
|
+
frontmatter[key] = value
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
path: filePath,
|
|
85
|
+
name: fileName,
|
|
86
|
+
title: frontmatter.title || fileName.replace('.md', ''),
|
|
87
|
+
description: frontmatter.description,
|
|
88
|
+
content: markdownContent,
|
|
89
|
+
frontmatter,
|
|
90
|
+
lastModified: stats.mtime.getTime()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function watchSkillsDirectory(
|
|
95
|
+
sourceDir: string,
|
|
96
|
+
callback: (directories: SkillDirectory[]) => void
|
|
97
|
+
) {
|
|
98
|
+
const chokidar = await import('chokidar')
|
|
99
|
+
|
|
100
|
+
const watcher = chokidar.watch(sourceDir, {
|
|
101
|
+
ignored: /node_modules/,
|
|
102
|
+
persistent: true,
|
|
103
|
+
ignoreInitial: true
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
let debounceTimer: NodeJS.Timeout
|
|
107
|
+
|
|
108
|
+
const handleChange = () => {
|
|
109
|
+
clearTimeout(debounceTimer)
|
|
110
|
+
debounceTimer = setTimeout(async () => {
|
|
111
|
+
try {
|
|
112
|
+
const directories = await scanSkillsDirectory(sourceDir)
|
|
113
|
+
callback(directories)
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Error rescanning source directory:', error)
|
|
116
|
+
}
|
|
117
|
+
}, 300)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
watcher.on('add', handleChange)
|
|
121
|
+
watcher.on('change', handleChange)
|
|
122
|
+
watcher.on('unlink', handleChange)
|
|
123
|
+
watcher.on('addDir', handleChange)
|
|
124
|
+
watcher.on('unlinkDir', handleChange)
|
|
125
|
+
|
|
126
|
+
return watcher
|
|
127
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface SkillFile {
|
|
2
|
+
path: string
|
|
3
|
+
name: string
|
|
4
|
+
title?: string
|
|
5
|
+
description?: string
|
|
6
|
+
category?: string
|
|
7
|
+
content: string
|
|
8
|
+
frontmatter: Record<string, any>
|
|
9
|
+
lastModified: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SkillDirectory {
|
|
13
|
+
path: string
|
|
14
|
+
name: string
|
|
15
|
+
children: (SkillDirectory | SkillFile)[]
|
|
16
|
+
skillFile?: SkillFile // SKILL.md file if exists
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SiteConfig {
|
|
20
|
+
title: string
|
|
21
|
+
description: string
|
|
22
|
+
baseUrl: string
|
|
23
|
+
skillsDir: string
|
|
24
|
+
outputDir: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Navigation {
|
|
28
|
+
text: string
|
|
29
|
+
link?: string
|
|
30
|
+
children?: Navigation[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DocGenConfig {
|
|
34
|
+
siteConfig: SiteConfig
|
|
35
|
+
navigation: Navigation[]
|
|
36
|
+
files: SkillFile[]
|
|
37
|
+
directories: SkillDirectory[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface BuildOptions {
|
|
41
|
+
input: string
|
|
42
|
+
output: string
|
|
43
|
+
mode: 'development' | 'production'
|
|
44
|
+
watch?: boolean
|
|
45
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
darkMode: ["class"],
|
|
4
|
+
content: [
|
|
5
|
+
'./src/**/*.{ts,tsx,vue}',
|
|
6
|
+
],
|
|
7
|
+
theme: {
|
|
8
|
+
container: {
|
|
9
|
+
center: true,
|
|
10
|
+
padding: "2rem",
|
|
11
|
+
screens: {
|
|
12
|
+
"2xl": "1400px",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
extend: {
|
|
16
|
+
colors: {
|
|
17
|
+
border: "hsl(var(--border))",
|
|
18
|
+
input: "hsl(var(--input))",
|
|
19
|
+
ring: "hsl(var(--ring))",
|
|
20
|
+
background: "hsl(var(--background))",
|
|
21
|
+
foreground: "hsl(var(--foreground))",
|
|
22
|
+
primary: {
|
|
23
|
+
DEFAULT: "hsl(var(--primary))",
|
|
24
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
25
|
+
},
|
|
26
|
+
secondary: {
|
|
27
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
28
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
29
|
+
},
|
|
30
|
+
destructive: {
|
|
31
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
32
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
33
|
+
},
|
|
34
|
+
muted: {
|
|
35
|
+
DEFAULT: "hsl(var(--muted))",
|
|
36
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
37
|
+
},
|
|
38
|
+
accent: {
|
|
39
|
+
DEFAULT: "hsl(var(--accent))",
|
|
40
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
41
|
+
},
|
|
42
|
+
popover: {
|
|
43
|
+
DEFAULT: "hsl(var(--popover))",
|
|
44
|
+
foreground: "hsl(var(--popover-foreground))",
|
|
45
|
+
},
|
|
46
|
+
card: {
|
|
47
|
+
DEFAULT: "hsl(var(--card))",
|
|
48
|
+
foreground: "hsl(var(--card-foreground))",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
borderRadius: {
|
|
52
|
+
lg: "var(--radius)",
|
|
53
|
+
md: "calc(var(--radius) - 2px)",
|
|
54
|
+
sm: "calc(var(--radius) - 4px)",
|
|
55
|
+
},
|
|
56
|
+
fontFamily: {
|
|
57
|
+
sans: ["Inter", "ui-sans-serif", "system-ui", "sans-serif"],
|
|
58
|
+
mono: ["ui-monospace", "SF Mono", "Menlo", "monospace"],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
plugins: [
|
|
63
|
+
require("@tailwindcss/typography"),
|
|
64
|
+
],
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.sidebar-item[data-v-b5febdd6]{margin-bottom:.25rem}.item-wrapper[data-v-b5febdd6]{display:flex;align-items:center;padding:.5rem 1.5rem;position:relative}.expand-button[data-v-b5febdd6]{background:none;border:none;color:#666;cursor:pointer;padding:.25rem;margin-right:.5rem;display:flex;align-items:center;justify-content:center;transition:transform .2s ease}.expand-button.expanded[data-v-b5febdd6]{transform:rotate(90deg)}.expand-button[data-v-b5febdd6]:hover{color:#333}.nav-link[data-v-b5febdd6],.nav-text[data-v-b5febdd6]{flex:1;text-decoration:none;color:#555;font-weight:500;font-size:.9rem;line-height:1.4;transition:color .2s ease}.nav-link[data-v-b5febdd6]:hover{color:#3182ce}.nav-link.active[data-v-b5febdd6]{color:#3182ce;font-weight:600;position:relative}.nav-link.active[data-v-b5febdd6]:before{content:"";position:absolute;left:-1.5rem;top:50%;transform:translateY(-50%);width:3px;height:20px;background-color:#3182ce;border-radius:2px}.nav-text[data-v-b5febdd6]{color:#333;font-weight:600}.children[data-v-b5febdd6]{overflow:hidden;max-height:0;transition:max-height .3s ease;padding-left:1rem}.children.expanded[data-v-b5febdd6]{max-height:1000px}.children .sidebar-item[data-v-b5febdd6]{margin-left:1rem}.children .nav-link[data-v-b5febdd6],.children .nav-text[data-v-b5febdd6]{font-size:.85rem;font-weight:400;color:#666}.children .nav-link[data-v-b5febdd6]:hover{color:#3182ce}.children .nav-link.active[data-v-b5febdd6]{color:#3182ce;font-weight:500}.sidebar-nav[data-v-014c42bf]{height:100%;overflow-y:auto}.nav-content[data-v-014c42bf]{padding:1.5rem 0}.search-box[data-v-b8e3e134]{position:relative;width:320px}.search-input-wrapper[data-v-b8e3e134]{position:relative;display:flex;align-items:center}.search-input[data-v-b8e3e134]{width:100%;padding:.75rem 2.5rem .75rem 1rem;border:2px solid #e1e5e9;border-radius:6px;font-size:.9rem;transition:border-color .2s ease;outline:none}.search-input[data-v-b8e3e134]:focus{border-color:#3182ce}.search-icon[data-v-b8e3e134]{position:absolute;right:.75rem;color:#666;pointer-events:none}.search-results[data-v-b8e3e134]{position:absolute;top:100%;left:0;right:0;background:#fff;border:1px solid #e1e5e9;border-radius:6px;box-shadow:0 10px 25px #0000001a;z-index:1000;max-height:400px;overflow-y:auto;margin-top:.5rem}.no-results[data-v-b8e3e134]{padding:1rem;color:#666;text-align:center;font-style:italic}.search-result[data-v-b8e3e134]{display:block;padding:1rem;text-decoration:none;color:inherit;border-bottom:1px solid #f0f0f0;transition:background-color .2s ease}.search-result[data-v-b8e3e134]:hover{background-color:#f8f9fa}.search-result[data-v-b8e3e134]:last-child{border-bottom:none}.result-title[data-v-b8e3e134]{font-weight:600;color:#2c3e50;margin-bottom:.25rem}.result-description[data-v-b8e3e134]{font-size:.85rem;color:#666;margin-bottom:.25rem;line-height:1.4}.result-path[data-v-b8e3e134]{font-size:.75rem;color:#999;font-family:monospace}@media (max-width: 768px){.search-box[data-v-b8e3e134]{width:100%;max-width:280px}}.app-layout[data-v-52037cca]{min-height:100vh;display:flex;flex-direction:column}.header[data-v-52037cca]{background:#fff;border-bottom:1px solid #e1e5e9;padding:1rem 0;position:sticky;top:0;z-index:100}.header-content[data-v-52037cca]{max-width:1400px;margin:0 auto;padding:0 2rem;display:flex;justify-content:space-between;align-items:center}.site-title[data-v-52037cca]{font-size:1.5rem;font-weight:600;color:#2c3e50;margin:0}.main-container[data-v-52037cca]{flex:1;display:flex;max-width:1400px;margin:0 auto;width:100%}.sidebar[data-v-52037cca]{width:280px;flex-shrink:0;background:#f8f9fa;border-right:1px solid #e1e5e9;height:calc(100vh - 80px);overflow-y:auto;position:sticky;top:80px}.content[data-v-52037cca]{flex:1;padding:2rem;overflow-y:auto;max-width:calc(100vw - 280px)}.loading[data-v-52037cca]{display:flex;justify-content:center;align-items:center;height:200px;color:#666;font-size:1.1rem}@media (max-width: 768px){.main-container[data-v-52037cca]{flex-direction:column}.sidebar[data-v-52037cca]{width:100%;height:auto;position:static;border-right:none;border-bottom:1px solid #e1e5e9}.content[data-v-52037cca]{max-width:100%;padding:1rem}.header-content[data-v-52037cca]{padding:0 1rem;flex-direction:column;gap:1rem}}#app[data-v-00943de1]{min-height:100vh}.home-page[data-v-83887790]{max-width:1000px;margin:0 auto}.hero-section[data-v-83887790]{text-align:center;padding:3rem 0;margin-bottom:3rem}.hero-title[data-v-83887790]{font-size:3rem;font-weight:700;color:#2c3e50;margin-bottom:1rem}.hero-description[data-v-83887790]{font-size:1.2rem;color:#666;max-width:600px;margin:0 auto;line-height:1.6}.content-overview[data-v-83887790]{space-y:3rem}.stats-grid[data-v-83887790]{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1.5rem;margin-bottom:3rem}.stat-card[data-v-83887790]{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:2rem;border-radius:12px;text-align:center;box-shadow:0 4px 6px #0000001a}.stat-number[data-v-83887790]{font-size:2.5rem;font-weight:700;margin-bottom:.5rem}.stat-label[data-v-83887790]{font-size:1rem;opacity:.9}.recent-section[data-v-83887790],.navigation-section[data-v-83887790]{margin-bottom:3rem}.recent-section h2[data-v-83887790],.navigation-section h2[data-v-83887790]{font-size:1.8rem;font-weight:600;color:#2c3e50;margin-bottom:1.5rem}.recent-files[data-v-83887790]{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1rem}.recent-file-card[data-v-83887790]{background:#fff;border:1px solid #e1e5e9;border-radius:8px;padding:1.5rem;text-decoration:none;color:inherit;transition:all .2s ease}.recent-file-card[data-v-83887790]:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0000001a;border-color:#3182ce}.file-title[data-v-83887790]{font-weight:600;color:#2c3e50;margin-bottom:.5rem}.file-description[data-v-83887790]{color:#666;font-size:.9rem;margin-bottom:1rem;line-height:1.4}.file-meta[data-v-83887790]{color:#999;font-size:.8rem}.skill-grid[data-v-83887790]{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem}.skill-card[data-v-83887790]{background:#fff;border:1px solid #e1e5e9;border-radius:8px;padding:1.5rem;text-decoration:none;color:inherit;transition:all .2s ease}.skill-card[data-v-83887790]:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0000001a;border-color:#3182ce}.skill-name[data-v-83887790]{font-weight:600;color:#2c3e50;font-size:1.1rem;margin-bottom:.5rem}.skill-description[data-v-83887790]{color:#666;font-size:.9rem;line-height:1.4;margin-bottom:1rem}.skill-files-count[data-v-83887790]{color:#3182ce;font-size:.8rem;font-weight:500}@media (max-width: 768px){.hero-title[data-v-83887790]{font-size:2rem}.hero-description[data-v-83887790]{font-size:1rem}.stats-grid[data-v-83887790],.recent-files[data-v-83887790],.skill-grid[data-v-83887790]{grid-template-columns:1fr}}.skill-page[data-v-ec306008]{max-width:800px;margin:0 auto}.skill-header[data-v-ec306008]{margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid #e1e5e9}.skill-title[data-v-ec306008]{font-size:2.5rem;font-weight:700;color:#2c3e50;margin-bottom:1rem;line-height:1.2}.skill-description[data-v-ec306008]{font-size:1.2rem;color:#666;margin-bottom:1rem;line-height:1.6}.skill-meta[data-v-ec306008]{color:#999;font-size:.9rem}.toc[data-v-ec306008]{background:#f8f9fa;border:1px solid #e1e5e9;border-radius:8px;padding:1.5rem;margin-bottom:2rem}.toc h3[data-v-ec306008]{margin:0 0 1rem;color:#2c3e50;font-size:1.1rem}.toc-list[data-v-ec306008]{list-style:none;padding:0;margin:0}.toc-list li[data-v-ec306008]{margin-bottom:.5rem}.toc-level-1[data-v-ec306008]{padding-left:0}.toc-level-2[data-v-ec306008]{padding-left:1rem}.toc-level-3[data-v-ec306008]{padding-left:2rem}.toc-level-4[data-v-ec306008]{padding-left:3rem}.toc-level-5[data-v-ec306008]{padding-left:4rem}.toc-level-6[data-v-ec306008]{padding-left:5rem}.toc-link[data-v-ec306008]{color:#555;text-decoration:none;font-size:.9rem;transition:color .2s ease}.toc-link[data-v-ec306008]:hover{color:#3182ce}.markdown-content[data-v-ec306008]{line-height:1.7;color:#333}.page-navigation[data-v-ec306008]{display:flex;justify-content:space-between;margin-top:3rem;padding-top:2rem;border-top:1px solid #e1e5e9;gap:1rem}.nav-link[data-v-ec306008]{flex:1;text-decoration:none;color:inherit;padding:1.5rem;border:1px solid #e1e5e9;border-radius:8px;transition:all .2s ease}.nav-link[data-v-ec306008]:hover{border-color:#3182ce;box-shadow:0 2px 8px #0000001a}.nav-prev[data-v-ec306008]{text-align:left}.nav-next[data-v-ec306008]{text-align:right}.nav-direction[data-v-ec306008]{color:#3182ce;font-size:.9rem;font-weight:500;margin-bottom:.25rem}.nav-title[data-v-ec306008]{color:#2c3e50;font-weight:600}.not-found[data-v-ec306008]{text-align:center;padding:3rem 0}.not-found h1[data-v-ec306008]{font-size:2rem;color:#2c3e50;margin-bottom:1rem}.not-found p[data-v-ec306008]{color:#666;margin-bottom:2rem}.back-home[data-v-ec306008]{color:#3182ce;text-decoration:none;font-weight:500}.back-home[data-v-ec306008]:hover{text-decoration:underline}@media (max-width: 768px){.skill-title[data-v-ec306008]{font-size:2rem}.page-navigation[data-v-ec306008]{flex-direction:column}.nav-next[data-v-ec306008]{text-align:left}}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{color:#2c3e50;font-weight:600;margin-top:2rem;margin-bottom:1rem;line-height:1.3}.markdown-content h1{font-size:2rem}.markdown-content h2{font-size:1.5rem}.markdown-content h3{font-size:1.25rem}.markdown-content h4{font-size:1.1rem}.markdown-content p{margin-bottom:1rem}.markdown-content ul,.markdown-content ol{margin-bottom:1rem;padding-left:1.5rem}.markdown-content li{margin-bottom:.5rem}.markdown-content code{background:#f1f3f4;padding:.2rem .4rem;border-radius:3px;font-family:SF Mono,Consolas,monospace;font-size:.9em}.markdown-content pre{background:#f8f9fa;border:1px solid #e1e5e9;border-radius:6px;padding:1rem;overflow-x:auto;margin:1rem 0}.markdown-content pre code{background:none;padding:0;border-radius:0}.markdown-content blockquote{border-left:4px solid #3182ce;padding-left:1rem;margin:1rem 0;color:#666;font-style:italic}.markdown-content table{width:100%;border-collapse:collapse;margin:1rem 0}.markdown-content th,.markdown-content td{border:1px solid #e1e5e9;padding:.5rem;text-align:left}.markdown-content th{background:#f8f9fa;font-weight:600}
|