skrypt-ai 0.7.0 → 0.8.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.
Files changed (110) hide show
  1. package/dist/auth/index.js +3 -3
  2. package/dist/cli.js +1 -1
  3. package/dist/commands/cron.js +0 -4
  4. package/dist/commands/generate/index.d.ts +3 -0
  5. package/dist/commands/generate/index.js +393 -0
  6. package/dist/commands/generate/scan.d.ts +41 -0
  7. package/dist/commands/generate/scan.js +256 -0
  8. package/dist/commands/generate/verify.d.ts +14 -0
  9. package/dist/commands/generate/verify.js +122 -0
  10. package/dist/commands/generate/write.d.ts +25 -0
  11. package/dist/commands/generate/write.js +120 -0
  12. package/dist/commands/import.js +4 -1
  13. package/dist/commands/llms-txt.js +6 -4
  14. package/dist/config/loader.d.ts +0 -1
  15. package/dist/config/loader.js +1 -1
  16. package/dist/generator/agents-md.d.ts +25 -0
  17. package/dist/generator/agents-md.js +122 -0
  18. package/dist/generator/index.d.ts +2 -0
  19. package/dist/generator/index.js +2 -0
  20. package/dist/generator/mdx-serializer.d.ts +11 -0
  21. package/dist/generator/mdx-serializer.js +135 -0
  22. package/dist/generator/organizer.d.ts +1 -16
  23. package/dist/generator/organizer.js +0 -38
  24. package/dist/generator/writer.js +5 -4
  25. package/dist/llm/proxy-client.d.ts +32 -0
  26. package/dist/llm/proxy-client.js +103 -0
  27. package/dist/scanner/csharp.d.ts +0 -4
  28. package/dist/scanner/csharp.js +9 -49
  29. package/dist/scanner/go.d.ts +0 -3
  30. package/dist/scanner/go.js +8 -35
  31. package/dist/scanner/java.d.ts +0 -4
  32. package/dist/scanner/java.js +9 -49
  33. package/dist/scanner/kotlin.d.ts +0 -3
  34. package/dist/scanner/kotlin.js +6 -33
  35. package/dist/scanner/php.d.ts +0 -10
  36. package/dist/scanner/php.js +11 -55
  37. package/dist/scanner/ruby.d.ts +0 -3
  38. package/dist/scanner/ruby.js +8 -38
  39. package/dist/scanner/rust.d.ts +0 -3
  40. package/dist/scanner/rust.js +10 -37
  41. package/dist/scanner/swift.d.ts +0 -3
  42. package/dist/scanner/swift.js +8 -35
  43. package/dist/scanner/utils.d.ts +41 -0
  44. package/dist/scanner/utils.js +97 -0
  45. package/dist/template/docs.json +5 -2
  46. package/dist/template/next.config.mjs +31 -0
  47. package/dist/template/package.json +5 -3
  48. package/dist/template/src/app/layout.tsx +13 -13
  49. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  50. package/dist/template/src/app/llms.txt/route.ts +29 -0
  51. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  52. package/dist/template/src/app/reference/route.ts +22 -18
  53. package/dist/template/src/app/sitemap.ts +1 -1
  54. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  55. package/dist/template/src/components/ai-chat.tsx +20 -193
  56. package/dist/template/src/components/mdx/index.tsx +27 -4
  57. package/dist/template/src/lib/fonts.ts +135 -0
  58. package/dist/template/src/middleware.ts +101 -0
  59. package/dist/template/src/styles/globals.css +28 -20
  60. package/dist/utils/files.d.ts +0 -8
  61. package/dist/utils/files.js +0 -33
  62. package/package.json +1 -1
  63. package/dist/autofix/autofix.test.d.ts +0 -1
  64. package/dist/autofix/autofix.test.js +0 -487
  65. package/dist/commands/generate.d.ts +0 -9
  66. package/dist/commands/generate.js +0 -739
  67. package/dist/generator/generator.test.d.ts +0 -1
  68. package/dist/generator/generator.test.js +0 -259
  69. package/dist/generator/writer.test.d.ts +0 -1
  70. package/dist/generator/writer.test.js +0 -411
  71. package/dist/llm/llm.manual-test.d.ts +0 -1
  72. package/dist/llm/llm.manual-test.js +0 -112
  73. package/dist/llm/llm.mock-test.d.ts +0 -4
  74. package/dist/llm/llm.mock-test.js +0 -79
  75. package/dist/plugins/index.d.ts +0 -47
  76. package/dist/plugins/index.js +0 -181
  77. package/dist/scanner/content-type.test.d.ts +0 -1
  78. package/dist/scanner/content-type.test.js +0 -231
  79. package/dist/scanner/integration.test.d.ts +0 -4
  80. package/dist/scanner/integration.test.js +0 -180
  81. package/dist/scanner/scanner.test.d.ts +0 -1
  82. package/dist/scanner/scanner.test.js +0 -210
  83. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  84. package/dist/scanner/typescript.manual-test.js +0 -112
  85. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  86. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  87. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  88. package/dist/template/src/app/docs/config/page.mdx +0 -428
  89. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  90. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  91. package/dist/template/src/app/docs/generator/generator.md +0 -504
  92. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  93. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  94. package/dist/template/src/app/docs/github/page.mdx +0 -502
  95. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  96. package/dist/template/src/app/docs/llm/index.md +0 -471
  97. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  98. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  99. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  100. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  101. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  102. package/dist/template/src/app/docs/scanner/index.md +0 -212
  103. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  104. package/dist/template/src/app/docs/scanner/python.md +0 -469
  105. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  106. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  107. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  108. package/dist/template/src/app/icon.tsx +0 -29
  109. package/dist/utils/validation.d.ts +0 -1
  110. package/dist/utils/validation.js +0 -12
@@ -1,15 +1,7 @@
1
1
  'use client'
2
2
 
3
- import { useState, useRef, useEffect } from 'react'
4
- import { useChat } from '@ai-sdk/react'
5
- import { Streamdown } from 'streamdown'
6
- import { MessageSquare, X, Send, ExternalLink } from 'lucide-react'
7
-
8
- interface Citation {
9
- title: string
10
- path: string
11
- snippet: string
12
- }
3
+ import dynamic from 'next/dynamic'
4
+ import { MessageSquare } from 'lucide-react'
13
5
 
14
6
  interface AIChatProps {
15
7
  projectName?: string
@@ -17,190 +9,25 @@ interface AIChatProps {
17
9
  placeholder?: string
18
10
  }
19
11
 
20
- export function AIChat({
21
- projectName = 'this documentation',
22
- apiEndpoint = '/api/chat',
23
- placeholder = 'Ask a question...',
24
- }: AIChatProps) {
25
- const [isOpen, setIsOpen] = useState(false)
26
- const [citations, setCitations] = useState<Map<string, Citation[]>>(new Map())
27
- const messagesEndRef = useRef<HTMLDivElement>(null)
28
-
29
- const { messages, input, handleInputChange, handleSubmit, status, error } = useChat({
30
- api: apiEndpoint,
31
- onResponse(response: Response) {
32
- // Extract citations from response header
33
- const citationsHeader = response.headers.get('X-Citations')
34
- if (citationsHeader) {
35
- try {
36
- const parsed = JSON.parse(atob(citationsHeader)) as Citation[]
37
- // Associate citations with the next assistant message
38
- setCitations(prev => {
39
- const next = new Map(prev)
40
- // Use a temporary key that will be replaced when the message arrives
41
- next.set('_pending', parsed)
42
- return next
43
- })
44
- } catch {
45
- // Skip invalid citations
46
- }
47
- }
48
- },
49
- onFinish(message: { id: string }) {
50
- // Move pending citations to the actual message ID
51
- setCitations(prev => {
52
- const next = new Map(prev)
53
- const pending = next.get('_pending')
54
- if (pending) {
55
- next.delete('_pending')
56
- next.set(message.id, pending)
57
- }
58
- return next
59
- })
60
- },
61
- })
62
-
63
- const isStreaming = status === 'streaming'
64
- const isLoading = status === 'submitted'
65
-
66
- useEffect(() => {
67
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
68
- }, [messages, isStreaming])
69
-
70
- if (!isOpen) {
71
- return (
72
- <button
73
- onClick={() => setIsOpen(true)}
74
- className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-[var(--color-text)] text-[var(--color-bg)] shadow-lg transition-transform hover:scale-105"
75
- aria-label="Open AI chat"
76
- >
77
- <MessageSquare className="h-6 w-6" />
78
- </button>
79
- )
80
- }
81
-
12
+ // Thin loading placeholder — just the FAB button (no heavy deps loaded yet)
13
+ function AIChatLoading() {
82
14
  return (
83
- <div className="fixed bottom-6 right-6 z-50 flex h-[500px] w-[380px] flex-col overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-bg)] shadow-2xl">
84
- {/* Header */}
85
- <div className="flex items-center justify-between border-b border-[var(--color-border)] px-4 py-3">
86
- <div className="flex items-center gap-2">
87
- <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-[var(--color-text)]">
88
- <MessageSquare className="h-4 w-4 text-[var(--color-bg)]" />
89
- </div>
90
- <div>
91
- <p className="text-sm font-medium text-[var(--color-text)]">Ask AI</p>
92
- <p className="text-xs text-[var(--color-text-tertiary)]">About {projectName}</p>
93
- </div>
94
- </div>
95
- <button
96
- onClick={() => setIsOpen(false)}
97
- className="rounded-lg p-1 text-[var(--color-text-tertiary)] transition-colors hover:bg-[var(--color-bg-secondary)] hover:text-[var(--color-text-secondary)]"
98
- aria-label="Close chat"
99
- >
100
- <X className="h-5 w-5" />
101
- </button>
102
- </div>
103
-
104
- {/* Messages */}
105
- <div className="flex-1 overflow-y-auto p-4 space-y-4">
106
- {messages.length === 0 && (
107
- <div className="text-center text-sm text-[var(--color-text-tertiary)] py-8">
108
- <p>Ask me anything about {projectName}.</p>
109
- <p className="mt-2 text-xs">I&apos;ll search the docs and give you an answer with sources.</p>
110
- </div>
111
- )}
112
-
113
- {messages.map((message) => (
114
- <div
115
- key={message.id}
116
- className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
117
- >
118
- <div
119
- className={`max-w-[85%] rounded-2xl px-4 py-2 text-sm ${
120
- message.role === 'user'
121
- ? 'bg-[var(--color-text)] text-[var(--color-bg)]'
122
- : 'bg-[var(--color-bg-tertiary)] text-[var(--color-text)]'
123
- }`}
124
- >
125
- {message.role === 'assistant' ? (
126
- <div className="chat-markdown prose prose-sm max-w-none">
127
- <Streamdown
128
- shikiTheme={['github-light', 'github-dark']}
129
- isAnimating={isStreaming && message.id === messages[messages.length - 1]?.id}
130
- >
131
- {message.content}
132
- </Streamdown>
133
- </div>
134
- ) : (
135
- <p className="whitespace-pre-wrap">{message.content}</p>
136
- )}
137
-
138
- {/* Citations */}
139
- {citations.get(message.id) && (
140
- <div className="mt-3 border-t border-[var(--color-border)] pt-2">
141
- <p className="text-xs font-medium text-[var(--color-text-tertiary)] mb-1">Sources:</p>
142
- <div className="space-y-1">
143
- {citations.get(message.id)!.map((citation, j) => (
144
- <a
145
- key={j}
146
- href={citation.path}
147
- className="flex items-center gap-1 text-xs text-[var(--color-primary)] hover:underline"
148
- >
149
- <ExternalLink className="h-3 w-3" />
150
- {citation.title}
151
- </a>
152
- ))}
153
- </div>
154
- </div>
155
- )}
156
- </div>
157
- </div>
158
- ))}
159
-
160
- {isLoading && (
161
- <div className="flex justify-start">
162
- <div className="flex items-center gap-2 rounded-2xl bg-[var(--color-bg-tertiary)] px-4 py-2 text-sm text-[var(--color-text-tertiary)]">
163
- <span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
164
- Searching docs...
165
- </div>
166
- </div>
167
- )}
168
-
169
- {error && (
170
- <div className="flex justify-start">
171
- <div className="rounded-2xl bg-red-50 px-4 py-2 text-sm text-red-600">
172
- Something went wrong. Please try again.
173
- </div>
174
- </div>
175
- )}
15
+ <button
16
+ className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-[var(--color-text)] text-[var(--color-bg)] shadow-lg transition-transform hover:scale-105"
17
+ aria-label="Open AI chat"
18
+ >
19
+ <MessageSquare className="h-6 w-6" />
20
+ </button>
21
+ )
22
+ }
176
23
 
177
- <div ref={messagesEndRef} />
178
- </div>
24
+ // Dynamic import — @ai-sdk/react, streamdown, and the full chat UI
25
+ // are only downloaded when this component is rendered on a page
26
+ const AIChatImpl = dynamic(
27
+ () => import('./ai-chat-impl').then(m => ({ default: m.AIChat })),
28
+ { ssr: false, loading: () => <AIChatLoading /> },
29
+ )
179
30
 
180
- {/* Input */}
181
- <form
182
- onSubmit={handleSubmit}
183
- className="border-t border-[var(--color-border)] p-3"
184
- >
185
- <div className="flex items-center gap-2">
186
- <input
187
- type="text"
188
- value={input}
189
- onChange={handleInputChange}
190
- placeholder={placeholder}
191
- disabled={isStreaming || isLoading}
192
- className="flex-1 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-secondary)] px-4 py-2 text-sm text-[var(--color-text)] outline-none transition-colors placeholder:text-[var(--color-text-tertiary)] focus:border-[var(--color-border-strong)]"
193
- />
194
- <button
195
- type="submit"
196
- disabled={!input.trim() || isStreaming || isLoading}
197
- className="flex h-10 w-10 items-center justify-center rounded-xl bg-[var(--color-text)] text-[var(--color-bg)] transition-colors hover:opacity-90 disabled:opacity-50"
198
- aria-label="Send message"
199
- >
200
- <Send className="h-4 w-4" />
201
- </button>
202
- </div>
203
- </form>
204
- </div>
205
- )
31
+ export function AIChat(props: AIChatProps) {
32
+ return <AIChatImpl {...props} />
206
33
  }
@@ -1,3 +1,6 @@
1
+ import dynamic from 'next/dynamic'
2
+
3
+ // Lightweight components — static imports (always in bundle)
1
4
  export { Card, CardGroup } from './card'
2
5
  export { Tabs, TabList, Tab, TabPanel } from './tabs'
3
6
  export { CodeGroup } from './code-group'
@@ -6,9 +9,6 @@ export { HighlightedCode } from './highlighted-code'
6
9
  export { Callout, Info, Warning, Success, Error, Tip, Note } from './callout'
7
10
  export { Accordion, AccordionGroup } from './accordion'
8
11
  export { Steps, Step } from './steps'
9
- export { CodePlayground } from './code-playground'
10
- export { PythonPlayground } from './python-playground'
11
- export { GoPlayground } from './go-playground'
12
12
  export { H1, H2, H3, H4 } from './heading'
13
13
  export { ParamTable, Schema } from './param-table'
14
14
  export { Changelog, ChangelogEntry, Change } from './changelog'
@@ -17,5 +17,28 @@ export { Tooltip } from './tooltip'
17
17
  export { Frame } from './frame'
18
18
  export { DarkImage } from './dark-image'
19
19
  export { LinkPreview } from './link-preview'
20
- export { Mermaid } from './mermaid'
21
20
  export { Screenshot } from './screenshot'
21
+
22
+ // Heavy components — dynamic imports (code-split, loaded only when rendered)
23
+ // Each underlying component already has 'use client' and guards browser APIs
24
+ // behind useEffect/handlers, so SSR of the shell is safe.
25
+
26
+ // @codesandbox/sandpack-react (~5 MB)
27
+ export const CodePlayground = dynamic(
28
+ () => import('./code-playground').then(m => ({ default: m.CodePlayground })),
29
+ )
30
+
31
+ // Pyodide loaded at runtime via CDN, but the component itself is still heavy
32
+ export const PythonPlayground = dynamic(
33
+ () => import('./python-playground').then(m => ({ default: m.PythonPlayground })),
34
+ )
35
+
36
+ // Go playground (uses fetch to go.dev API)
37
+ export const GoPlayground = dynamic(
38
+ () => import('./go-playground').then(m => ({ default: m.GoPlayground })),
39
+ )
40
+
41
+ // mermaid (~2.5 MB)
42
+ export const Mermaid = dynamic(
43
+ () => import('./mermaid').then(m => ({ default: m.Mermaid })),
44
+ )
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Font configuration for the documentation template.
3
+ *
4
+ * Supports customizable Google Fonts via docs.json:
5
+ *
6
+ * {
7
+ * "fonts": {
8
+ * "sans": "Inter", // default
9
+ * "mono": "JetBrains Mono" // default
10
+ * }
11
+ * }
12
+ *
13
+ * Also supports the legacy `theme.font` key for backward compatibility.
14
+ */
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Curated font lists — only supported Google Fonts are allowed
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export const SUPPORTED_SANS_FONTS: Record<string, { weights: string }> = {
21
+ 'Inter': { weights: '400;500;600;700;800' },
22
+ 'Plus Jakarta Sans': { weights: '400;500;600;700;800' },
23
+ 'DM Sans': { weights: '400;500;600;700' },
24
+ 'Geist': { weights: '400;500;600;700;800' },
25
+ 'Outfit': { weights: '400;500;600;700;800' },
26
+ 'Space Grotesk': { weights: '400;500;600;700' },
27
+ 'Manrope': { weights: '400;500;600;700;800' },
28
+ 'Sora': { weights: '400;500;600;700;800' },
29
+ 'Instrument Sans': { weights: '400;500;600;700' },
30
+ 'Albert Sans': { weights: '400;500;600;700;800' },
31
+ }
32
+
33
+ export const SUPPORTED_MONO_FONTS: Record<string, { weights: string }> = {
34
+ 'JetBrains Mono': { weights: '400;500;600;700' },
35
+ 'Fira Code': { weights: '400;500;600;700' },
36
+ 'Source Code Pro': { weights: '400;500;600;700' },
37
+ 'IBM Plex Mono': { weights: '400;500;600;700' },
38
+ 'Geist Mono': { weights: '400;500;600;700' },
39
+ 'Space Mono': { weights: '400;700' },
40
+ 'Roboto Mono': { weights: '400;500;600;700' },
41
+ 'Inconsolata': { weights: '400;500;600;700' },
42
+ }
43
+
44
+ const DEFAULT_SANS = 'Inter'
45
+ const DEFAULT_MONO = 'JetBrains Mono'
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Types
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export interface ResolvedFont {
52
+ /** The font name as it appears in CSS font-family */
53
+ name: string
54
+ /** Whether this font needs to be loaded from Google Fonts */
55
+ isCustom: boolean
56
+ /** 'sans' or 'mono' */
57
+ type: 'sans' | 'mono'
58
+ }
59
+
60
+ export interface ResolvedFonts {
61
+ sans: ResolvedFont
62
+ mono: ResolvedFont
63
+ /** True if at least one font needs Google Fonts loading */
64
+ needsGoogleFonts: boolean
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Resolution
69
+ // ---------------------------------------------------------------------------
70
+
71
+ /** Sanitize a font name — strip anything that isn't alphanumeric, space, or hyphen */
72
+ function sanitize(name: string): string {
73
+ return name.replace(/[^a-zA-Z0-9\s-]/g, '').trim()
74
+ }
75
+
76
+ /**
77
+ * Resolve font configuration from docs.json.
78
+ *
79
+ * Priority:
80
+ * 1. `fonts.sans` / `fonts.mono` (new format)
81
+ * 2. `theme.font` (legacy — applied to sans only)
82
+ * 3. Defaults: Inter + JetBrains Mono
83
+ *
84
+ * Unrecognized font names fall back to defaults.
85
+ */
86
+ export function resolveFonts(docsConfig: Record<string, unknown>): ResolvedFonts {
87
+ const fonts = docsConfig.fonts as { sans?: string; mono?: string } | undefined
88
+ const legacyFont = (docsConfig.theme as { font?: string } | undefined)?.font
89
+
90
+ // Resolve sans font
91
+ const rawSans = fonts?.sans || legacyFont || DEFAULT_SANS
92
+ const safeSans = sanitize(rawSans)
93
+ const sansIsSupported = safeSans in SUPPORTED_SANS_FONTS
94
+ const sansName = sansIsSupported ? safeSans : DEFAULT_SANS
95
+ const sansIsCustom = sansName !== DEFAULT_SANS
96
+
97
+ // Resolve mono font
98
+ const rawMono = fonts?.mono || DEFAULT_MONO
99
+ const safeMono = sanitize(rawMono)
100
+ const monoIsSupported = safeMono in SUPPORTED_MONO_FONTS
101
+ const monoName = monoIsSupported ? safeMono : DEFAULT_MONO
102
+ const monoIsCustom = monoName !== DEFAULT_MONO
103
+
104
+ return {
105
+ sans: { name: sansName, isCustom: sansIsCustom, type: 'sans' },
106
+ mono: { name: monoName, isCustom: monoIsCustom, type: 'mono' },
107
+ needsGoogleFonts: sansIsCustom || monoIsCustom,
108
+ }
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Google Fonts URL builder
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /**
116
+ * Build a single Google Fonts CSS URL that loads both sans and mono fonts.
117
+ * Only includes fonts that are non-default (custom).
118
+ */
119
+ export function buildGoogleFontsUrl(sans: ResolvedFont, mono: ResolvedFont): string | null {
120
+ const families: string[] = []
121
+
122
+ if (sans.isCustom) {
123
+ const weights = SUPPORTED_SANS_FONTS[sans.name]?.weights || '400;500;600;700'
124
+ families.push(`family=${encodeURIComponent(sans.name)}:wght@${weights}`)
125
+ }
126
+
127
+ if (mono.isCustom) {
128
+ const weights = SUPPORTED_MONO_FONTS[mono.name]?.weights || '400;500;600;700'
129
+ families.push(`family=${encodeURIComponent(mono.name)}:wght@${weights}`)
130
+ }
131
+
132
+ if (families.length === 0) return null
133
+
134
+ return `https://fonts.googleapis.com/css2?${families.join('&')}&display=swap`
135
+ }
@@ -0,0 +1,101 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+
3
+ /**
4
+ * Content negotiation middleware for AI agents.
5
+ *
6
+ * When agents send `Accept: text/markdown`, rewrite to the markdown route handler.
7
+ * Also handles `.md` URL suffix for explicit markdown requests.
8
+ * Adds Link + X-Llms-Txt discovery headers to all responses.
9
+ */
10
+ export function middleware(request: NextRequest) {
11
+ const { pathname } = request.nextUrl
12
+
13
+ // Skip non-doc paths: API routes, static assets, internal Next.js paths
14
+ if (
15
+ pathname.startsWith('/api/') ||
16
+ pathname.startsWith('/_next/') ||
17
+ pathname.startsWith('/reference') ||
18
+ pathname === '/robots.txt' ||
19
+ pathname === '/sitemap.xml' ||
20
+ pathname === '/llms.txt' ||
21
+ pathname === '/llms-full.md' ||
22
+ pathname === '/favicon.ico' ||
23
+ /\.(ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|css|js|map)$/.test(pathname)
24
+ ) {
25
+ return addDiscoveryHeaders(NextResponse.next(), request)
26
+ }
27
+
28
+ // Handle .md URL suffix: /docs/getting-started.md → serve markdown for /docs/getting-started
29
+ if (pathname.endsWith('.md') && pathname.startsWith('/docs/')) {
30
+ const cleanPath = pathname.slice(0, -3) // strip .md
31
+ const slug = cleanPath.replace(/^\/docs\/?/, '')
32
+ if (slug) {
33
+ const url = request.nextUrl.clone()
34
+ url.pathname = `/md/${slug}`
35
+ return addDiscoveryHeaders(NextResponse.rewrite(url), request)
36
+ }
37
+ }
38
+
39
+ // Content negotiation: detect Accept: text/markdown
40
+ const accept = request.headers.get('accept') || ''
41
+ if (prefersMarkdown(accept) && pathname.startsWith('/docs/')) {
42
+ const slug = pathname.replace(/^\/docs\/?/, '')
43
+ if (slug) {
44
+ const url = request.nextUrl.clone()
45
+ url.pathname = `/md/${slug}`
46
+ return addDiscoveryHeaders(NextResponse.rewrite(url), request)
47
+ }
48
+ }
49
+
50
+ return addDiscoveryHeaders(NextResponse.next(), request)
51
+ }
52
+
53
+ /**
54
+ * Check if the Accept header prefers text/markdown over text/html.
55
+ * Handles quality values: text/markdown;q=1.0, text/html;q=0.9
56
+ */
57
+ function prefersMarkdown(accept: string): boolean {
58
+ if (!accept) return false
59
+
60
+ const lower = accept.toLowerCase()
61
+
62
+ // Quick check: if text/markdown appears at all
63
+ if (!lower.includes('text/markdown')) return false
64
+
65
+ // Parse quality values
66
+ let mdQuality = 0
67
+ let htmlQuality = 0
68
+
69
+ for (const part of lower.split(',')) {
70
+ const trimmed = part.trim()
71
+ const qMatch = trimmed.match(/;q=([0-9.]+)/)
72
+ const quality = qMatch ? Math.min(parseFloat(qMatch[1]) || 0, 1.0) : 1.0
73
+
74
+ if (trimmed.startsWith('text/markdown')) {
75
+ mdQuality = quality
76
+ } else if (trimmed.startsWith('text/html')) {
77
+ htmlQuality = quality
78
+ }
79
+ }
80
+
81
+ return mdQuality > 0 && mdQuality >= htmlQuality
82
+ }
83
+
84
+ /**
85
+ * Add agent discovery headers to every response.
86
+ * - Link: points agents to llms.txt
87
+ * - X-Llms-Txt: explicit pointer for agent tooling
88
+ */
89
+ function addDiscoveryHeaders(response: NextResponse, request: NextRequest): NextResponse {
90
+ const origin = request.nextUrl.origin
91
+ response.headers.set('Link', `<${origin}/llms.txt>; rel="help", <${origin}/llms-full.md>; rel="help"`)
92
+ response.headers.set('X-Llms-Txt', `${origin}/llms.txt`)
93
+ return response
94
+ }
95
+
96
+ export const config = {
97
+ matcher: [
98
+ // Match all paths except static files
99
+ '/((?!_next/static|_next/image).*)',
100
+ ],
101
+ }
@@ -58,10 +58,10 @@
58
58
  --color-border: rgba(3, 7, 18, 0.1);
59
59
  --color-border-strong: rgba(3, 7, 18, 0.15);
60
60
 
61
- /* Code */
62
- --color-code-bg: #ffffff;
61
+ /* Code — high-contrast, matching Mintlify/Stripe/Vercel standards */
62
+ --color-code-bg: #fafafa;
63
63
  --color-code-border: rgba(3, 7, 18, 0.1);
64
- --color-code-text: #1f2937;
64
+ --color-code-text: #1a1a2e;
65
65
 
66
66
  /* Layout */
67
67
  --sidebar-width: 264px;
@@ -80,14 +80,14 @@
80
80
  --color-bg: #0f1117;
81
81
  --color-bg-secondary: #1a1d27;
82
82
  --color-bg-tertiary: #252833;
83
- --color-text: #e0e2e6;
84
- --color-text-secondary: #b0b4bc;
83
+ --color-text: #e4e5e9;
84
+ --color-text-secondary: #b4b8c1;
85
85
  --color-text-tertiary: #6b7280;
86
86
  --color-border: rgba(255, 255, 255, 0.07);
87
87
  --color-border-strong: rgba(255, 255, 255, 0.12);
88
- --color-code-bg: #0b0c0e;
89
- --color-code-border: rgba(255, 255, 255, 0.1);
90
- --color-code-text: #e0e2e6;
88
+ --color-code-bg: #0a0a0c;
89
+ --color-code-border: rgba(255, 255, 255, 0.08);
90
+ --color-code-text: #e8eaed;
91
91
  }
92
92
  }
93
93
 
@@ -95,14 +95,14 @@
95
95
  --color-bg: #0f1117;
96
96
  --color-bg-secondary: #1a1d27;
97
97
  --color-bg-tertiary: #252833;
98
- --color-text: #e0e2e6;
99
- --color-text-secondary: #b0b4bc;
98
+ --color-text: #e4e5e9;
99
+ --color-text-secondary: #b4b8c1;
100
100
  --color-text-tertiary: #6b7280;
101
101
  --color-border: rgba(255, 255, 255, 0.07);
102
102
  --color-border-strong: rgba(255, 255, 255, 0.12);
103
- --color-code-bg: #0b0c0e;
104
- --color-code-border: rgba(255, 255, 255, 0.1);
105
- --color-code-text: #e0e2e6;
103
+ --color-code-bg: #0a0a0c;
104
+ --color-code-border: rgba(255, 255, 255, 0.08);
105
+ --color-code-text: #e8eaed;
106
106
  }
107
107
 
108
108
  /* Error page icon — dark mode overrides (avoids Tailwind dark: variant mismatch) */
@@ -205,33 +205,41 @@ pre {
205
205
  background-color: var(--color-code-bg) !important;
206
206
  }
207
207
 
208
+ /* Ensure Shiki syntax tokens render at full opacity — never washed out */
209
+ .shiki code {
210
+ color: var(--color-code-text);
211
+ }
212
+ .shiki .line span {
213
+ opacity: 1 !important;
214
+ }
215
+
208
216
  code {
209
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
217
+ font-family: var(--font-mono, ui-monospace), SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
210
218
  font-size: 0.875em;
211
219
  font-variant-ligatures: none;
212
220
  }
213
221
 
214
- /* Inline code — Mintlify style */
222
+ /* Inline code — high contrast, Mintlify/Stripe style */
215
223
  :not(pre) > code {
216
224
  background-color: var(--color-gray-100);
217
225
  padding: 0.125rem 0.5rem;
218
226
  border-radius: 0.375rem;
219
227
  font-size: 0.875em;
220
228
  line-height: 1.5;
221
- color: var(--color-gray-600);
229
+ color: var(--color-gray-800);
222
230
  overflow-wrap: break-word;
223
231
  word-break: break-word;
224
232
  }
225
233
 
226
234
  :root.dark :not(pre) > code {
227
- background-color: rgba(255, 255, 255, 0.05);
228
- color: var(--color-gray-200);
235
+ background-color: rgba(255, 255, 255, 0.08);
236
+ color: #e0e2e6;
229
237
  }
230
238
 
231
239
  @media (prefers-color-scheme: dark) {
232
240
  :root:not(.light) :not(pre) > code {
233
- background-color: rgba(255, 255, 255, 0.05);
234
- color: var(--color-gray-200);
241
+ background-color: rgba(255, 255, 255, 0.08);
242
+ color: #e0e2e6;
235
243
  }
236
244
  }
237
245
 
@@ -7,11 +7,3 @@ export declare function findMdxFiles(dir: string): string[];
7
7
  * Convert string to URL-safe slug
8
8
  */
9
9
  export declare function slugify(str: string): string;
10
- /**
11
- * Simple YAML frontmatter parser — splits on --- markers.
12
- * Returns parsed key-value data and remaining content body.
13
- */
14
- export declare function parseFrontmatter(content: string): {
15
- data: Record<string, unknown>;
16
- content: string;
17
- };
@@ -47,36 +47,3 @@ export function slugify(str) {
47
47
  .replace(/[^a-z0-9]+/g, '-')
48
48
  .replace(/^-|-$/g, '');
49
49
  }
50
- /**
51
- * Simple YAML frontmatter parser — splits on --- markers.
52
- * Returns parsed key-value data and remaining content body.
53
- */
54
- export function parseFrontmatter(content) {
55
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
56
- if (!match)
57
- return { data: {}, content };
58
- const yamlStr = match[1];
59
- const body = match[2];
60
- const data = {};
61
- for (const line of yamlStr.split('\n')) {
62
- const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
63
- if (!kvMatch)
64
- continue;
65
- const key = kvMatch[1];
66
- let value = kvMatch[2].trim();
67
- if (typeof value === 'string') {
68
- if ((value.startsWith('"') && value.endsWith('"')) ||
69
- (value.startsWith("'") && value.endsWith("'"))) {
70
- value = value.slice(1, -1);
71
- }
72
- else if (value === 'true')
73
- value = true;
74
- else if (value === 'false')
75
- value = false;
76
- else if (/^\d+$/.test(value))
77
- value = parseInt(value, 10);
78
- }
79
- data[key] = value;
80
- }
81
- return { data, content: body };
82
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skrypt-ai",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "AI-powered documentation generator with code examples",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -1 +0,0 @@
1
- export {};