skrypt-ai 0.3.4 → 0.4.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 (95) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/index.d.ts +0 -1
  3. package/dist/auth/index.js +3 -5
  4. package/dist/autofix/index.js +15 -3
  5. package/dist/cli.js +19 -4
  6. package/dist/commands/check-links.js +164 -174
  7. package/dist/commands/deploy.js +5 -2
  8. package/dist/commands/generate.js +206 -199
  9. package/dist/commands/i18n.js +3 -20
  10. package/dist/commands/init.js +47 -40
  11. package/dist/commands/lint.js +3 -20
  12. package/dist/commands/mcp.js +125 -122
  13. package/dist/commands/monitor.js +125 -108
  14. package/dist/commands/review-pr.js +1 -1
  15. package/dist/commands/sdk.js +1 -1
  16. package/dist/config/loader.js +21 -2
  17. package/dist/generator/organizer.d.ts +3 -0
  18. package/dist/generator/organizer.js +4 -9
  19. package/dist/generator/writer.js +2 -10
  20. package/dist/github/pr-comments.js +21 -8
  21. package/dist/plugins/index.js +1 -0
  22. package/dist/scanner/index.js +8 -2
  23. package/dist/template/docs.json +2 -1
  24. package/dist/template/next.config.mjs +2 -1
  25. package/dist/template/package.json +17 -15
  26. package/dist/template/public/favicon.svg +4 -0
  27. package/dist/template/public/search-index.json +1 -1
  28. package/dist/template/scripts/build-search-index.mjs +120 -25
  29. package/dist/template/src/app/api/chat/route.ts +11 -3
  30. package/dist/template/src/app/docs/README.md +28 -0
  31. package/dist/template/src/app/docs/[...slug]/page.tsx +139 -16
  32. package/dist/template/src/app/docs/auth/page.mdx +589 -0
  33. package/dist/template/src/app/docs/autofix/page.mdx +624 -0
  34. package/dist/template/src/app/docs/cli/page.mdx +217 -0
  35. package/dist/template/src/app/docs/config/page.mdx +428 -0
  36. package/dist/template/src/app/docs/configuration/page.mdx +86 -0
  37. package/dist/template/src/app/docs/deployment/page.mdx +112 -0
  38. package/dist/template/src/app/docs/error.tsx +20 -0
  39. package/dist/template/src/app/docs/generator/generator.md +504 -0
  40. package/dist/template/src/app/docs/generator/organizer.md +779 -0
  41. package/dist/template/src/app/docs/generator/page.mdx +613 -0
  42. package/dist/template/src/app/docs/github/page.mdx +502 -0
  43. package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
  44. package/dist/template/src/app/docs/llm/index.md +471 -0
  45. package/dist/template/src/app/docs/llm/page.mdx +428 -0
  46. package/dist/template/src/app/docs/llms-full.md +256 -0
  47. package/dist/template/src/app/docs/llms.txt +2971 -0
  48. package/dist/template/src/app/docs/not-found.tsx +23 -0
  49. package/dist/template/src/app/docs/page.mdx +0 -3
  50. package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
  51. package/dist/template/src/app/docs/pro/page.mdx +121 -0
  52. package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
  53. package/dist/template/src/app/docs/scanner/content-type.md +599 -0
  54. package/dist/template/src/app/docs/scanner/index.md +212 -0
  55. package/dist/template/src/app/docs/scanner/page.mdx +307 -0
  56. package/dist/template/src/app/docs/scanner/python.md +469 -0
  57. package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
  58. package/dist/template/src/app/docs/scanner/rust.md +325 -0
  59. package/dist/template/src/app/docs/scanner/typescript.md +201 -0
  60. package/dist/template/src/app/error.tsx +3 -3
  61. package/dist/template/src/app/icon.tsx +29 -0
  62. package/dist/template/src/app/layout.tsx +42 -0
  63. package/dist/template/src/app/not-found.tsx +35 -0
  64. package/dist/template/src/app/page.tsx +62 -28
  65. package/dist/template/src/components/ai-chat.tsx +26 -21
  66. package/dist/template/src/components/breadcrumbs.tsx +46 -2
  67. package/dist/template/src/components/copy-button.tsx +17 -3
  68. package/dist/template/src/components/docs-layout.tsx +142 -8
  69. package/dist/template/src/components/feedback.tsx +4 -2
  70. package/dist/template/src/components/footer.tsx +42 -0
  71. package/dist/template/src/components/header.tsx +29 -5
  72. package/dist/template/src/components/mdx/accordion.tsx +7 -6
  73. package/dist/template/src/components/mdx/card.tsx +19 -7
  74. package/dist/template/src/components/mdx/code-block.tsx +17 -3
  75. package/dist/template/src/components/mdx/code-group.tsx +65 -18
  76. package/dist/template/src/components/mdx/code-playground.tsx +3 -0
  77. package/dist/template/src/components/mdx/go-playground.tsx +3 -0
  78. package/dist/template/src/components/mdx/highlighted-code.tsx +171 -76
  79. package/dist/template/src/components/mdx/python-playground.tsx +2 -0
  80. package/dist/template/src/components/mdx/tabs.tsx +74 -6
  81. package/dist/template/src/components/page-header.tsx +19 -0
  82. package/dist/template/src/components/scroll-to-top.tsx +33 -0
  83. package/dist/template/src/components/search-dialog.tsx +206 -52
  84. package/dist/template/src/components/sidebar.tsx +136 -77
  85. package/dist/template/src/components/table-of-contents.tsx +23 -7
  86. package/dist/template/src/lib/highlight.ts +90 -31
  87. package/dist/template/src/lib/search.ts +14 -4
  88. package/dist/template/src/lib/theme-utils.ts +140 -0
  89. package/dist/template/src/styles/globals.css +307 -166
  90. package/dist/template/src/types/remark-gfm.d.ts +2 -0
  91. package/dist/utils/files.d.ts +9 -0
  92. package/dist/utils/files.js +33 -0
  93. package/dist/utils/validation.d.ts +4 -0
  94. package/dist/utils/validation.js +38 -0
  95. package/package.json +1 -4
@@ -1,11 +1,21 @@
1
1
  import { create, insertMultiple, save } from '@orama/orama'
2
- import { readdir, readFile, writeFile, mkdir } from 'fs/promises'
3
- import { join, relative } from 'path'
2
+ import { readdir, readFile, writeFile, mkdir, access } from 'fs/promises'
3
+ import { join, relative, dirname, basename } from 'path'
4
4
  import matter from 'gray-matter'
5
5
 
6
6
  const CONTENT_DIR = join(process.cwd(), 'content', 'docs')
7
+ const APP_DOCS_DIR = join(process.cwd(), 'src', 'app', 'docs')
7
8
  const OUTPUT_DIR = join(process.cwd(), 'public')
8
9
 
10
+ async function dirExists(dir) {
11
+ try {
12
+ await access(dir)
13
+ return true
14
+ } catch {
15
+ return false
16
+ }
17
+ }
18
+
9
19
  async function getAllMDXFiles(dir) {
10
20
  const files = []
11
21
 
@@ -31,6 +41,10 @@ async function getAllMDXFiles(dir) {
31
41
 
32
42
  function extractPlainText(content) {
33
43
  return content
44
+ // Remove import statements
45
+ .replace(/^import\s+.*$/gm, '')
46
+ // Remove export statements
47
+ .replace(/^export\s+.*$/gm, '')
34
48
  // Remove MDX/JSX components
35
49
  .replace(/<[^>]+>/g, '')
36
50
  // Remove code blocks
@@ -46,12 +60,15 @@ function extractPlainText(content) {
46
60
  .replace(/[*_]{1,2}([^*_]+)[*_]{1,2}/g, '$1')
47
61
  // Remove horizontal rules
48
62
  .replace(/^[-*_]{3,}$/gm, '')
63
+ // Remove table formatting
64
+ .replace(/\|/g, ' ')
65
+ .replace(/^[\s-:]+$/gm, '')
49
66
  // Remove extra whitespace
50
67
  .replace(/\s+/g, ' ')
51
68
  .trim()
52
69
  }
53
70
 
54
- function getSlugFromPath(filePath) {
71
+ function getSlugFromContentPath(filePath) {
55
72
  const rel = relative(CONTENT_DIR, filePath)
56
73
  const slug = rel
57
74
  .replace(/\.(mdx?|md)$/, '')
@@ -61,6 +78,43 @@ function getSlugFromPath(filePath) {
61
78
  return slug ? `/docs/${slug}` : '/docs'
62
79
  }
63
80
 
81
+ function getSlugFromAppPath(filePath) {
82
+ // For App Router: src/app/docs/quickstart/page.mdx -> /docs/quickstart
83
+ // src/app/docs/page.mdx -> /docs
84
+ const rel = relative(APP_DOCS_DIR, filePath)
85
+ const dir = dirname(rel)
86
+ const name = basename(rel)
87
+
88
+ if (name === 'page.mdx' || name === 'page.md') {
89
+ if (dir === '.') return '/docs'
90
+ return `/docs/${dir.replace(/\\/g, '/')}`
91
+ }
92
+
93
+ const slug = rel
94
+ .replace(/\.(mdx?|md)$/, '')
95
+ .replace(/\/index$/, '')
96
+ .replace(/\\/g, '/')
97
+
98
+ return `/docs/${slug}`
99
+ }
100
+
101
+ function extractTitle(content, filePath) {
102
+ // Try to get title from first heading
103
+ const h1Match = content.match(/^#\s+(.+)$/m)
104
+ if (h1Match) return h1Match[1].trim()
105
+
106
+ // Derive from file path
107
+ const dir = dirname(filePath)
108
+ const folderName = basename(dir)
109
+ if (folderName && folderName !== 'docs' && folderName !== '.') {
110
+ return folderName
111
+ .replace(/-/g, ' ')
112
+ .replace(/\b\w/g, c => c.toUpperCase())
113
+ }
114
+
115
+ return basename(filePath).replace(/\.(mdx?|md)$/, '')
116
+ }
117
+
64
118
  async function buildSearchIndex() {
65
119
  console.log('Building search index...')
66
120
 
@@ -74,36 +128,77 @@ async function buildSearchIndex() {
74
128
  },
75
129
  })
76
130
 
77
- const files = await getAllMDXFiles(CONTENT_DIR)
78
131
  const documents = []
132
+ const seen = new Set()
133
+
134
+ // 1. Index content/docs/ (generated docs)
135
+ if (await dirExists(CONTENT_DIR)) {
136
+ const contentFiles = await getAllMDXFiles(CONTENT_DIR)
137
+ for (const file of contentFiles) {
138
+ try {
139
+ const raw = await readFile(file, 'utf-8')
140
+ const { data, content } = matter(raw)
141
+
142
+ const title = data.title || extractTitle(content, file)
143
+ const plainContent = extractPlainText(content)
144
+ const href = getSlugFromContentPath(file)
145
+
146
+ if (seen.has(href)) continue
147
+ seen.add(href)
148
+
149
+ documents.push({
150
+ id: href,
151
+ title,
152
+ content: plainContent.slice(0, 5000),
153
+ href,
154
+ section: data.section || data.category || '',
155
+ })
156
+
157
+ console.log(` Indexed: ${href} (content/)`)
158
+ } catch (err) {
159
+ console.error(` Error indexing ${file}:`, err.message)
160
+ }
161
+ }
162
+ }
79
163
 
80
- for (const file of files) {
81
- try {
82
- const raw = await readFile(file, 'utf-8')
83
- const { data, content } = matter(raw)
84
-
85
- const title = data.title || file.split('/').pop()?.replace(/\.(mdx?|md)$/, '') || 'Untitled'
86
- const plainContent = extractPlainText(content)
87
- const href = getSlugFromPath(file)
88
- const section = data.section || data.category || ''
89
-
90
- documents.push({
91
- id: href,
92
- title,
93
- content: plainContent.slice(0, 5000), // Limit content size
94
- href,
95
- section,
96
- })
97
-
98
- console.log(` Indexed: ${href}`)
99
- } catch (err) {
100
- console.error(` Error indexing ${file}:`, err.message)
164
+ // 2. Index src/app/docs/ (App Router MDX pages)
165
+ if (await dirExists(APP_DOCS_DIR)) {
166
+ const appFiles = await getAllMDXFiles(APP_DOCS_DIR)
167
+ for (const file of appFiles) {
168
+ // Skip catch-all route files and layout files
169
+ if (file.includes('[...slug]') || file.includes('layout.')) continue
170
+
171
+ try {
172
+ const raw = await readFile(file, 'utf-8')
173
+ const { data, content } = matter(raw)
174
+
175
+ const title = data.title || extractTitle(content, file)
176
+ const plainContent = extractPlainText(content)
177
+ const href = getSlugFromAppPath(file)
178
+
179
+ if (seen.has(href)) continue
180
+ seen.add(href)
181
+
182
+ documents.push({
183
+ id: href,
184
+ title,
185
+ content: plainContent.slice(0, 5000),
186
+ href,
187
+ section: data.section || data.category || '',
188
+ })
189
+
190
+ console.log(` Indexed: ${href} (app/)`)
191
+ } catch (err) {
192
+ console.error(` Error indexing ${file}:`, err.message)
193
+ }
101
194
  }
102
195
  }
103
196
 
104
197
  if (documents.length > 0) {
105
198
  await insertMultiple(db, documents)
106
199
  console.log(`\nIndexed ${documents.length} documents`)
200
+ } else {
201
+ console.log('\nNo documents found to index')
107
202
  }
108
203
 
109
204
  // Export the index
@@ -86,6 +86,10 @@ function indexDocs(): DocChunk[] {
86
86
  return chunks
87
87
  }
88
88
 
89
+ function escapeRegex(str: string): string {
90
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
91
+ }
92
+
89
93
  function searchDocs(query: string, chunks: DocChunk[]): DocChunk[] {
90
94
  const queryLower = query.toLowerCase()
91
95
  const queryWords = queryLower.split(/\s+/).filter(w => w.length > 2)
@@ -113,7 +117,7 @@ function searchDocs(query: string, chunks: DocChunk[]): DocChunk[] {
113
117
 
114
118
  // Content matches (lower weight)
115
119
  queryWords.forEach(word => {
116
- const matches = (contentLower.match(new RegExp(word, 'g')) || []).length
120
+ const matches = (contentLower.match(new RegExp(escapeRegex(word), 'g')) || []).length
117
121
  score += Math.min(matches, 5) // Cap at 5 matches per word
118
122
  })
119
123
 
@@ -231,11 +235,15 @@ export async function POST(request: Request) {
231
235
  const body = await request.json()
232
236
  const { messages } = body as { messages: Message[] }
233
237
 
234
- if (!messages || messages.length === 0) {
235
- return NextResponse.json({ error: 'Messages required' }, { status: 400 })
238
+ if (!Array.isArray(messages) || messages.length === 0 || messages.length > 50) {
239
+ return NextResponse.json({ error: 'Invalid request' }, { status: 400 })
236
240
  }
237
241
 
238
242
  const lastMessage = messages[messages.length - 1]
243
+ if (typeof lastMessage?.content !== 'string' || lastMessage.content.length > 10000) {
244
+ return NextResponse.json({ error: 'Message too long' }, { status: 400 })
245
+ }
246
+
239
247
  if (lastMessage.role !== 'user') {
240
248
  return NextResponse.json({ error: 'Last message must be from user' }, { status: 400 })
241
249
  }
@@ -0,0 +1,28 @@
1
+ # API Documentation
2
+
3
+ Generated by [skrypt](https://github.com/debgotwired/skrypt)
4
+
5
+ ## Summary
6
+
7
+ - **Total elements:** 81
8
+
9
+ ## Files
10
+
11
+ - [auth/index.ts](./auth/index.md) (6 elements)
12
+ - [autofix/index.ts](./autofix/index.md) (4 elements)
13
+ - [config/loader.ts](./config/loader.md) (4 elements)
14
+ - [generator/generator.ts](./generator/generator.md) (3 elements)
15
+ - [generator/organizer.ts](./generator/organizer.md) (6 elements)
16
+ - [generator/writer.ts](./generator/writer.md) (4 elements)
17
+ - [github/pr-comments.ts](./github/pr-comments.md) (3 elements)
18
+ - [llm/anthropic-client.ts](./llm/anthropic-client.md) (4 elements)
19
+ - [llm/index.ts](./llm/index.md) (3 elements)
20
+ - [llm/openai-client.ts](./llm/openai-client.md) (4 elements)
21
+ - [plugins/index.ts](./plugins/index.md) (14 elements)
22
+ - [scanner/content-type.ts](./scanner/content-type.md) (4 elements)
23
+ - [scanner/go.ts](./scanner/go.md) (3 elements)
24
+ - [scanner/index.ts](./scanner/index.md) (2 elements)
25
+ - [scanner/python.ts](./scanner/python.md) (3 elements)
26
+ - [scanner/python_parser.py](./scanner/python_parser.md) (8 elements)
27
+ - [scanner/rust.ts](./scanner/rust.md) (3 elements)
28
+ - [scanner/typescript.ts](./scanner/typescript.md) (3 elements)
@@ -1,14 +1,26 @@
1
1
  import { notFound } from 'next/navigation'
2
- import { readFile, readdir, stat } from 'fs/promises'
2
+ import { readFile, readdir } from 'fs/promises'
3
3
  import { join } from 'path'
4
4
  import { compileMDX } from 'next-mdx-remote/rsc'
5
5
  import remarkGfm from 'remark-gfm'
6
6
  import * as components from '@/components/mdx'
7
+ import type { Metadata } from 'next'
7
8
 
8
9
  interface PageProps {
9
10
  params: Promise<{ slug: string[] }>
10
11
  }
11
12
 
13
+ interface Frontmatter {
14
+ title?: string
15
+ description?: string
16
+ }
17
+
18
+ function getDocsConfig() {
19
+ const fs = require('fs')
20
+ const configPath = join(process.cwd(), 'docs.json')
21
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
22
+ }
23
+
12
24
  async function getContent(slug: string[]) {
13
25
  const contentDir = join(process.cwd(), 'content', 'docs')
14
26
  const filePath = join(contentDir, ...slug)
@@ -27,6 +39,74 @@ async function getContent(slug: string[]) {
27
39
  return null
28
40
  }
29
41
 
42
+ async function getFrontmatter(slug: string[]): Promise<Frontmatter> {
43
+ const result = await getContent(slug)
44
+ if (!result) return {}
45
+
46
+ // Simple frontmatter extraction without full MDX compilation
47
+ const fmMatch = result.content.match(/^---\s*\n([\s\S]*?)\n---/)
48
+ if (!fmMatch) return {}
49
+
50
+ const fm: Frontmatter = {}
51
+ const lines = fmMatch[1].split('\n')
52
+ for (const line of lines) {
53
+ const match = line.match(/^(\w+)\s*:\s*(.+)$/)
54
+ if (match) {
55
+ const key = match[1].trim()
56
+ let value = match[2].trim()
57
+ // Strip quotes
58
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
59
+ value = value.slice(1, -1)
60
+ }
61
+ if (key === 'title') fm.title = value
62
+ if (key === 'description') fm.description = value
63
+ }
64
+ }
65
+ return fm
66
+ }
67
+
68
+ export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
69
+ const { slug } = await params
70
+ const docsConfig = getDocsConfig()
71
+ const siteName = docsConfig.name || 'Documentation'
72
+
73
+ const frontmatter = await getFrontmatter(slug)
74
+
75
+ // Fall back to nav config title
76
+ if (!frontmatter.title) {
77
+ const path = '/docs/' + slug.join('/')
78
+ for (const group of docsConfig.navigation || []) {
79
+ for (const page of group.pages || []) {
80
+ if (page.path === path) {
81
+ frontmatter.title = page.title
82
+ break
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ const title = frontmatter.title || slug[slug.length - 1].replace(/-/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase())
89
+
90
+ return {
91
+ title,
92
+ description: frontmatter.description || `${title} - ${siteName} documentation`,
93
+ }
94
+ }
95
+
96
+ /** Look up page title from docs.json navigation by pathname */
97
+ function getNavTitle(slug: string[]): string | undefined {
98
+ const docsConfig = getDocsConfig()
99
+ const path = '/docs/' + slug.join('/')
100
+ for (const group of docsConfig.navigation || []) {
101
+ for (const page of group.pages || []) {
102
+ if (page.path === path) {
103
+ return page.title
104
+ }
105
+ }
106
+ }
107
+ return undefined
108
+ }
109
+
30
110
  export default async function DocPage({ params }: PageProps) {
31
111
  const { slug } = await params
32
112
  const result = await getContent(slug)
@@ -35,22 +115,65 @@ export default async function DocPage({ params }: PageProps) {
35
115
  notFound()
36
116
  }
37
117
 
38
- const { content } = await compileMDX({
39
- source: result.content,
40
- components: components as any,
41
- options: {
42
- parseFrontmatter: true,
43
- mdxOptions: {
44
- remarkPlugins: [remarkGfm],
118
+ // Resolve page title from docs.json navigation (preferred) and description from frontmatter
119
+ const navTitle = getNavTitle(slug)
120
+ const frontmatter = await getFrontmatter(slug)
121
+ const pageDescription = frontmatter.description
122
+
123
+ try {
124
+ const { content } = await compileMDX({
125
+ source: result.content,
126
+ components: components as any,
127
+ options: {
128
+ parseFrontmatter: true,
129
+ mdxOptions: {
130
+ remarkPlugins: [remarkGfm],
131
+ },
45
132
  },
46
- },
47
- })
48
-
49
- return (
50
- <div className="prose max-w-none">
51
- {content}
52
- </div>
53
- )
133
+ })
134
+
135
+ // Resolve metadata for JSON-LD
136
+ const docsConfig = getDocsConfig()
137
+ const siteUrl = docsConfig.siteUrl || ''
138
+ const siteName = docsConfig.name || 'Documentation'
139
+ const pageTitle = navTitle || frontmatter.title || slug[slug.length - 1].replace(/-/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase())
140
+
141
+ const jsonLd = {
142
+ '@context': 'https://schema.org',
143
+ '@type': 'TechArticle',
144
+ headline: pageTitle,
145
+ description: pageDescription || `${pageTitle} - ${siteName} documentation`,
146
+ url: `${siteUrl}/docs/${slug.join('/')}`,
147
+ author: { '@type': 'Organization', name: siteName },
148
+ }
149
+
150
+ return (
151
+ <>
152
+ <script
153
+ type="application/ld+json"
154
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
155
+ />
156
+ {/*
157
+ Page title is resolved by DocsLayout from docs.json navigation via usePathname().
158
+ Description from MDX frontmatter is passed via data attribute for DocsLayout to read.
159
+ */}
160
+ {pageDescription && (
161
+ <div data-page-description={pageDescription} className="hidden" />
162
+ )}
163
+ <div className="prose max-w-none">
164
+ {content}
165
+ </div>
166
+ </>
167
+ )
168
+ } catch (error) {
169
+ console.error(`MDX compilation error for ${slug.join('/')}:`, error)
170
+ return (
171
+ <article className="prose">
172
+ <h1>Error loading page</h1>
173
+ <p>This documentation page could not be rendered. The MDX content may contain syntax errors.</p>
174
+ </article>
175
+ )
176
+ }
54
177
  }
55
178
 
56
179
  export async function generateStaticParams() {