skrypt-ai 0.8.0 → 0.8.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 (101) hide show
  1. package/dist/auth/index.js +6 -0
  2. package/dist/binding/binder.d.ts +5 -0
  3. package/dist/binding/binder.js +63 -0
  4. package/dist/binding/detector.d.ts +5 -0
  5. package/dist/binding/detector.js +51 -0
  6. package/dist/binding/doc-parser.d.ts +9 -0
  7. package/dist/binding/doc-parser.js +138 -0
  8. package/dist/binding/extractor.d.ts +14 -0
  9. package/dist/binding/extractor.js +39 -0
  10. package/dist/binding/index.d.ts +5 -0
  11. package/dist/binding/index.js +5 -0
  12. package/dist/binding/types.d.ts +74 -0
  13. package/dist/binding/types.js +1 -0
  14. package/dist/claims/extractor.d.ts +13 -0
  15. package/dist/claims/extractor.js +138 -0
  16. package/dist/claims/index.d.ts +4 -0
  17. package/dist/claims/index.js +4 -0
  18. package/dist/claims/reporter.d.ts +9 -0
  19. package/dist/claims/reporter.js +65 -0
  20. package/dist/claims/store.d.ts +13 -0
  21. package/dist/claims/store.js +51 -0
  22. package/dist/claims/types.d.ts +34 -0
  23. package/dist/claims/types.js +1 -0
  24. package/dist/cli.js +516 -56
  25. package/dist/commands/bind.d.ts +2 -0
  26. package/dist/commands/bind.js +139 -0
  27. package/dist/commands/claims.d.ts +2 -0
  28. package/dist/commands/claims.js +84 -0
  29. package/dist/commands/coverage.d.ts +2 -0
  30. package/dist/commands/coverage.js +61 -0
  31. package/dist/commands/generate/index.js +5 -0
  32. package/dist/commands/generate/scan.js +33 -14
  33. package/dist/commands/generate/write.d.ts +7 -0
  34. package/dist/commands/generate/write.js +65 -1
  35. package/dist/commands/import.js +12 -3
  36. package/dist/commands/init.js +68 -5
  37. package/dist/commands/monitor.d.ts +15 -0
  38. package/dist/commands/monitor.js +2 -2
  39. package/dist/commands/mutate.d.ts +2 -0
  40. package/dist/commands/mutate.js +177 -0
  41. package/dist/config/types.js +2 -2
  42. package/dist/coverage/calculator.d.ts +7 -0
  43. package/dist/coverage/calculator.js +86 -0
  44. package/dist/coverage/index.d.ts +3 -0
  45. package/dist/coverage/index.js +3 -0
  46. package/dist/coverage/reporter.d.ts +9 -0
  47. package/dist/coverage/reporter.js +65 -0
  48. package/dist/coverage/types.d.ts +36 -0
  49. package/dist/coverage/types.js +1 -0
  50. package/dist/generator/generator.d.ts +3 -1
  51. package/dist/generator/generator.js +137 -23
  52. package/dist/generator/mdx-serializer.js +3 -2
  53. package/dist/generator/organizer.d.ts +5 -1
  54. package/dist/generator/organizer.js +29 -14
  55. package/dist/generator/types.d.ts +6 -0
  56. package/dist/generator/writer.js +7 -2
  57. package/dist/github/org-discovery.js +5 -0
  58. package/dist/importers/mintlify.js +4 -3
  59. package/dist/llm/anthropic-client.js +1 -0
  60. package/dist/llm/index.d.ts +15 -0
  61. package/dist/llm/index.js +148 -29
  62. package/dist/llm/openai-client.js +2 -0
  63. package/dist/mutation/index.d.ts +4 -0
  64. package/dist/mutation/index.js +4 -0
  65. package/dist/mutation/mutator.d.ts +5 -0
  66. package/dist/mutation/mutator.js +101 -0
  67. package/dist/mutation/reporter.d.ts +14 -0
  68. package/dist/mutation/reporter.js +66 -0
  69. package/dist/mutation/runner.d.ts +9 -0
  70. package/dist/mutation/runner.js +70 -0
  71. package/dist/mutation/types.d.ts +51 -0
  72. package/dist/mutation/types.js +1 -0
  73. package/dist/qa/checks.d.ts +1 -0
  74. package/dist/qa/checks.js +47 -0
  75. package/dist/qa/index.js +2 -1
  76. package/dist/scanner/index.js +78 -11
  77. package/dist/scanner/typescript.js +42 -31
  78. package/dist/sentry.d.ts +3 -0
  79. package/dist/sentry.js +28 -0
  80. package/dist/template/docs.json +6 -3
  81. package/dist/template/next.config.mjs +15 -1
  82. package/dist/template/package.json +4 -3
  83. package/dist/template/public/docs-schema.json +257 -0
  84. package/dist/template/sentry.client.config.ts +12 -0
  85. package/dist/template/sentry.edge.config.ts +7 -0
  86. package/dist/template/sentry.server.config.ts +7 -0
  87. package/dist/template/src/app/docs/[...slug]/page.tsx +11 -5
  88. package/dist/template/src/app/docs/layout.tsx +2 -4
  89. package/dist/template/src/app/global-error.tsx +60 -0
  90. package/dist/template/src/app/layout.tsx +7 -16
  91. package/dist/template/src/app/page.tsx +2 -5
  92. package/dist/template/src/components/ai-chat-impl.tsx +1 -1
  93. package/dist/template/src/components/docs-layout.tsx +1 -15
  94. package/dist/template/src/components/footer.tsx +95 -19
  95. package/dist/template/src/components/header.tsx +1 -1
  96. package/dist/template/src/components/search-dialog.tsx +5 -0
  97. package/dist/template/src/instrumentation.ts +11 -0
  98. package/dist/template/src/lib/docs-config.ts +235 -0
  99. package/dist/template/src/lib/fonts.ts +3 -3
  100. package/dist/testing/runner.js +8 -1
  101. package/package.json +2 -1
@@ -0,0 +1,257 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Skrypt docs.json",
4
+ "description": "Configuration file for Skrypt documentation sites. Controls site name, navigation, theme, fonts, and footer.",
5
+ "type": "object",
6
+ "required": ["name", "navigation"],
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "Path to the JSON Schema file for IDE autocomplete."
11
+ },
12
+ "name": {
13
+ "type": "string",
14
+ "description": "The site name displayed in the header, browser tab, and meta tags.",
15
+ "minLength": 1
16
+ },
17
+ "description": {
18
+ "type": "string",
19
+ "description": "A short description of the documentation site, used in meta tags and the landing page hero."
20
+ },
21
+ "favicon": {
22
+ "type": "string",
23
+ "description": "Path to the favicon file relative to the public directory (e.g. \"/favicon.svg\")."
24
+ },
25
+ "siteUrl": {
26
+ "type": "string",
27
+ "description": "The canonical base URL of the site (e.g. \"https://docs.example.com\"). Used for meta tags and JSON-LD."
28
+ },
29
+ "logo": {
30
+ "type": "string",
31
+ "description": "Path to a logo image displayed in the header instead of the site name."
32
+ },
33
+ "headerLinks": {
34
+ "type": "array",
35
+ "description": "Extra navigation links shown in the top header bar.",
36
+ "items": {
37
+ "type": "object",
38
+ "required": ["title", "path"],
39
+ "properties": {
40
+ "title": {
41
+ "type": "string",
42
+ "description": "Display text for the link."
43
+ },
44
+ "path": {
45
+ "type": "string",
46
+ "description": "URL or path the link navigates to."
47
+ }
48
+ },
49
+ "additionalProperties": false
50
+ }
51
+ },
52
+ "fonts": {
53
+ "type": "object",
54
+ "description": "Google Fonts to use for the site. Unrecognized font names fall back to defaults.",
55
+ "properties": {
56
+ "sans": {
57
+ "type": "string",
58
+ "description": "Sans-serif body font. Default: \"Inter\".",
59
+ "enum": [
60
+ "Inter",
61
+ "Plus Jakarta Sans",
62
+ "DM Sans",
63
+ "Geist",
64
+ "Outfit",
65
+ "Space Grotesk",
66
+ "Manrope",
67
+ "Sora",
68
+ "Instrument Sans",
69
+ "Albert Sans"
70
+ ]
71
+ },
72
+ "mono": {
73
+ "type": "string",
74
+ "description": "Monospace font for code blocks. Default: \"JetBrains Mono\".",
75
+ "enum": [
76
+ "JetBrains Mono",
77
+ "Fira Code",
78
+ "Source Code Pro",
79
+ "IBM Plex Mono",
80
+ "Geist Mono",
81
+ "Space Mono",
82
+ "Roboto Mono",
83
+ "Inconsolata"
84
+ ]
85
+ }
86
+ },
87
+ "additionalProperties": false
88
+ },
89
+ "theme": {
90
+ "type": "object",
91
+ "description": "Visual theme settings.",
92
+ "properties": {
93
+ "primaryColor": {
94
+ "type": "string",
95
+ "description": "Primary brand color in hex format (e.g. \"#171717\"). Used for buttons, links, and accents.",
96
+ "pattern": "^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$"
97
+ },
98
+ "font": {
99
+ "type": "string",
100
+ "description": "Legacy: sans-serif font name. Prefer using \"fonts.sans\" instead."
101
+ }
102
+ },
103
+ "additionalProperties": false
104
+ },
105
+ "navigation": {
106
+ "description": "Sidebar navigation structure. Can be a flat list of groups or a tabbed layout.",
107
+ "oneOf": [
108
+ {
109
+ "type": "array",
110
+ "description": "Flat navigation: an array of groups.",
111
+ "items": { "$ref": "#/$defs/navGroup" },
112
+ "minItems": 1
113
+ },
114
+ {
115
+ "type": "array",
116
+ "description": "Tabbed navigation: an array of tabs, each containing groups.",
117
+ "items": { "$ref": "#/$defs/navTab" },
118
+ "minItems": 1
119
+ }
120
+ ]
121
+ },
122
+ "footer": {
123
+ "type": "object",
124
+ "description": "Footer configuration with external links and social accounts.",
125
+ "properties": {
126
+ "links": {
127
+ "type": "array",
128
+ "description": "Links displayed in the footer.",
129
+ "items": {
130
+ "type": "object",
131
+ "required": ["title", "url"],
132
+ "properties": {
133
+ "title": {
134
+ "type": "string",
135
+ "description": "Display text for the footer link."
136
+ },
137
+ "url": {
138
+ "type": "string",
139
+ "description": "URL the footer link navigates to."
140
+ }
141
+ },
142
+ "additionalProperties": false
143
+ }
144
+ },
145
+ "social": {
146
+ "type": "object",
147
+ "description": "Social media URLs.",
148
+ "properties": {
149
+ "github": { "type": "string", "description": "GitHub profile or repository URL." },
150
+ "twitter": { "type": "string", "description": "Twitter/X profile URL." },
151
+ "discord": { "type": "string", "description": "Discord invite URL." }
152
+ },
153
+ "additionalProperties": true
154
+ }
155
+ },
156
+ "additionalProperties": false
157
+ },
158
+ "editLink": {
159
+ "type": "object",
160
+ "description": "Configuration for \"Edit this page\" links that point to the source repository.",
161
+ "properties": {
162
+ "repoUrl": {
163
+ "type": "string",
164
+ "description": "Base URL of the repository (e.g. \"https://github.com/org/repo\")."
165
+ },
166
+ "branch": {
167
+ "type": "string",
168
+ "description": "Git branch name. Default: \"main\"."
169
+ },
170
+ "docsPath": {
171
+ "type": "string",
172
+ "description": "Path within the repo to the docs content directory (e.g. \"content/docs\")."
173
+ }
174
+ },
175
+ "additionalProperties": false
176
+ }
177
+ },
178
+ "additionalProperties": false,
179
+ "$defs": {
180
+ "navPage": {
181
+ "type": "object",
182
+ "description": "A single documentation page in the sidebar.",
183
+ "required": ["title", "path"],
184
+ "properties": {
185
+ "title": {
186
+ "type": "string",
187
+ "description": "Display title in the sidebar."
188
+ },
189
+ "path": {
190
+ "type": "string",
191
+ "description": "URL path to the page (e.g. \"/docs/getting-started\")."
192
+ },
193
+ "description": {
194
+ "type": "string",
195
+ "description": "Short description shown in tooltips or page headers."
196
+ },
197
+ "pages": {
198
+ "type": "array",
199
+ "description": "Nested sub-pages for hierarchical navigation.",
200
+ "items": { "$ref": "#/$defs/navPage" }
201
+ }
202
+ },
203
+ "additionalProperties": false
204
+ },
205
+ "navGroup": {
206
+ "type": "object",
207
+ "description": "A group of pages in the sidebar with a section heading.",
208
+ "required": ["group", "pages"],
209
+ "properties": {
210
+ "group": {
211
+ "type": "string",
212
+ "description": "Section heading displayed in the sidebar."
213
+ },
214
+ "icon": {
215
+ "type": "string",
216
+ "description": "Lucide icon name for the group heading (e.g. \"BookOpen\", \"Code\")."
217
+ },
218
+ "pages": {
219
+ "type": "array",
220
+ "description": "Pages and nested groups within this section.",
221
+ "items": {
222
+ "oneOf": [
223
+ { "$ref": "#/$defs/navPage" },
224
+ { "$ref": "#/$defs/navGroup" }
225
+ ]
226
+ }
227
+ }
228
+ },
229
+ "additionalProperties": false
230
+ },
231
+ "navTab": {
232
+ "type": "object",
233
+ "description": "A top-level navigation tab containing groups.",
234
+ "required": ["tab", "groups"],
235
+ "properties": {
236
+ "tab": {
237
+ "type": "string",
238
+ "description": "Tab label displayed in the header."
239
+ },
240
+ "icon": {
241
+ "type": "string",
242
+ "description": "Lucide icon name for the tab."
243
+ },
244
+ "href": {
245
+ "type": "string",
246
+ "description": "Direct link URL for the tab (overrides group-based routing)."
247
+ },
248
+ "groups": {
249
+ "type": "array",
250
+ "description": "Navigation groups shown when this tab is active.",
251
+ "items": { "$ref": "#/$defs/navGroup" }
252
+ }
253
+ },
254
+ "additionalProperties": false
255
+ }
256
+ }
257
+ }
@@ -0,0 +1,12 @@
1
+ import * as Sentry from '@sentry/nextjs'
2
+
3
+ Sentry.init({
4
+ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || 'https://5192fcdbb58de60661cfa103b00d5a91@o4511084239781888.ingest.us.sentry.io/4511084256362496',
5
+ environment: process.env.NEXT_PUBLIC_VERCEL_ENV || 'development',
6
+
7
+ tracesSampleRate: 0.1,
8
+
9
+ replaysSessionSampleRate: 0,
10
+ replaysOnErrorSampleRate: 1.0,
11
+ integrations: [Sentry.replayIntegration()],
12
+ })
@@ -0,0 +1,7 @@
1
+ import * as Sentry from '@sentry/nextjs'
2
+
3
+ Sentry.init({
4
+ dsn: process.env.SENTRY_DSN || 'https://5192fcdbb58de60661cfa103b00d5a91@o4511084239781888.ingest.us.sentry.io/4511084256362496',
5
+
6
+ tracesSampleRate: 0.2,
7
+ })
@@ -0,0 +1,7 @@
1
+ import * as Sentry from '@sentry/nextjs'
2
+
3
+ Sentry.init({
4
+ dsn: process.env.SENTRY_DSN || 'https://5192fcdbb58de60661cfa103b00d5a91@o4511084239781888.ingest.us.sentry.io/4511084256362496',
5
+
6
+ tracesSampleRate: 0.2,
7
+ })
@@ -5,6 +5,7 @@ import { compileMDX } from 'next-mdx-remote/rsc'
5
5
  import remarkGfm from 'remark-gfm'
6
6
  import * as mdxComponents from '@/components/mdx'
7
7
  import { findPageInNavigation, type Navigation } from '@/lib/navigation'
8
+ import { loadDocsConfig } from '@/lib/docs-config'
8
9
  import { Children, isValidElement, type ReactNode } from 'react'
9
10
 
10
11
  /** Wrapper for <pre> that routes mermaid code blocks to the Mermaid component */
@@ -52,9 +53,9 @@ const components = {
52
53
  h3: mdxComponents.H3,
53
54
  h4: mdxComponents.H4,
54
55
  a: AnchorOrLink,
56
+ // Map TabItem (Docusaurus-style) to TabPanel (our Tabs component API)
57
+ TabItem: mdxComponents.TabPanel,
55
58
  // Passthrough for JSX tags that appear in generated code examples
56
- Tabs: Passthrough,
57
- TabItem: Passthrough,
58
59
  Details: Passthrough,
59
60
  }
60
61
  import type { Metadata } from 'next'
@@ -69,9 +70,7 @@ interface Frontmatter {
69
70
  }
70
71
 
71
72
  function getDocsConfig() {
72
- const fs = require('fs')
73
- const configPath = join(process.cwd(), 'docs.json')
74
- return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
73
+ return loadDocsConfig()
75
74
  }
76
75
 
77
76
  async function getContent(slug: string[]) {
@@ -172,12 +171,19 @@ export default async function DocPage({ params }: PageProps) {
172
171
  // Strip empty <a id="..."></a> anchors from generated content — headings already have IDs
173
172
  const cleanedSource = result.content.replace(/<a\s+id="[^"]*"\s*><\/a>\s*/g, '')
174
173
 
174
+ // Use format: 'md' for .md files so curly braces in code blocks are treated as
175
+ // literal text instead of JSX expressions (avoids "Could not parse expression
176
+ // with acorn" errors from unescaped { } in TypeScript/Python code examples).
177
+ // .mdx files keep format: 'mdx' for full MDX expression support.
178
+ const isMdx = result.path.endsWith('.mdx')
179
+
175
180
  const { content } = await compileMDX({
176
181
  source: cleanedSource,
177
182
  components: components as any,
178
183
  options: {
179
184
  parseFrontmatter: true,
180
185
  mdxOptions: {
186
+ format: isMdx ? 'mdx' : 'md',
181
187
  remarkPlugins: [remarkGfm],
182
188
  },
183
189
  },
@@ -1,10 +1,8 @@
1
1
  import { DocsLayout } from '@/components/docs-layout'
2
- import { readFileSync } from 'fs'
3
- import { join } from 'path'
2
+ import { loadDocsConfig } from '@/lib/docs-config'
4
3
 
5
4
  function getDocsConfig() {
6
- const configPath = join(process.cwd(), 'docs.json')
7
- return JSON.parse(readFileSync(configPath, 'utf-8'))
5
+ return loadDocsConfig()
8
6
  }
9
7
 
10
8
  export default function Layout({
@@ -0,0 +1,60 @@
1
+ 'use client'
2
+
3
+ import * as Sentry from '@sentry/nextjs'
4
+ import { useEffect } from 'react'
5
+
6
+ export default function GlobalError({
7
+ error,
8
+ reset,
9
+ }: {
10
+ error: Error & { digest?: string }
11
+ reset: () => void
12
+ }) {
13
+ useEffect(() => {
14
+ Sentry.captureException(error)
15
+ }, [error])
16
+
17
+ return (
18
+ <html lang="en">
19
+ <body>
20
+ <div style={{
21
+ minHeight: '100vh',
22
+ display: 'flex',
23
+ alignItems: 'center',
24
+ justifyContent: 'center',
25
+ padding: '1rem',
26
+ fontFamily: 'system-ui, -apple-system, sans-serif',
27
+ }}>
28
+ <div style={{ maxWidth: '28rem', width: '100%', textAlign: 'center' }}>
29
+ <h1 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: '0.5rem' }}>
30
+ Something went wrong
31
+ </h1>
32
+ <p style={{ color: '#6b7280', marginBottom: '1.5rem' }}>
33
+ A critical error occurred. Please try again.
34
+ </p>
35
+ {error.digest && (
36
+ <p style={{ fontSize: '0.75rem', color: '#9ca3af', marginBottom: '1rem' }}>
37
+ Error ID: {error.digest}
38
+ </p>
39
+ )}
40
+ <button
41
+ onClick={reset}
42
+ style={{
43
+ padding: '0.5rem 1rem',
44
+ backgroundColor: '#3b82f6',
45
+ color: '#fff',
46
+ border: 'none',
47
+ borderRadius: '0.5rem',
48
+ cursor: 'pointer',
49
+ fontSize: '0.875rem',
50
+ fontWeight: 500,
51
+ }}
52
+ >
53
+ Try again
54
+ </button>
55
+ </div>
56
+ </div>
57
+ </body>
58
+ </html>
59
+ )
60
+ }
@@ -1,24 +1,15 @@
1
1
  import type { Metadata } from 'next'
2
- import { Inter } from 'next/font/google'
2
+ import { Inter, JetBrains_Mono } from 'next/font/google'
3
3
  import '@/styles/globals.css'
4
4
  import { SyntaxThemeProvider } from '@/contexts/syntax-theme'
5
- import { readFileSync } from 'fs'
6
- import { join } from 'path'
7
5
  import { derivePrimaryColors } from '@/lib/theme-utils'
8
6
  import { resolveFonts, buildGoogleFontsUrl } from '@/lib/fonts'
7
+ import { loadDocsConfig } from '@/lib/docs-config'
9
8
 
10
9
  const inter = Inter({ subsets: ['latin'] })
10
+ const jetbrainsMono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-jetbrains-mono' })
11
11
 
12
- function getDocsConfig() {
13
- const configPath = join(process.cwd(), 'docs.json')
14
- try {
15
- return JSON.parse(readFileSync(configPath, 'utf-8'))
16
- } catch {
17
- return {}
18
- }
19
- }
20
-
21
- const docsConfig = getDocsConfig()
12
+ const docsConfig = loadDocsConfig()
22
13
  const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://docs.example.com'
23
14
  const siteName = docsConfig.name || 'Documentation'
24
15
  const siteDescription = docsConfig.description || 'Documentation'
@@ -40,7 +31,7 @@ const themeStyles = `
40
31
  --color-primary-light: ${primaryLight};
41
32
  --color-primary-foreground: ${primaryForeground};
42
33
  --font-body: "${sans.name}", "Inter", ui-sans-serif, system-ui, sans-serif;
43
- --font-mono: "${mono.name}", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
34
+ --font-mono: ${mono.name === 'JetBrains Mono' ? 'var(--font-jetbrains-mono)' : `"${mono.name}"`}, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
44
35
  }
45
36
  `
46
37
 
@@ -67,7 +58,7 @@ export const metadata: Metadata = {
67
58
  images: ['/og-image.png'],
68
59
  },
69
60
  icons: {
70
- icon: '/favicon.svg',
61
+ icon: docsConfig.favicon || '/favicon.svg',
71
62
  },
72
63
  robots: {
73
64
  index: true,
@@ -112,7 +103,7 @@ export default function RootLayout({
112
103
  </>
113
104
  )}
114
105
  </head>
115
- <body className={inter.className}>
106
+ <body className={`${inter.className} ${jetbrainsMono.variable}`}>
116
107
  <a href="#main-content" className="skip-to-content">Skip to content</a>
117
108
  <SyntaxThemeProvider>
118
109
  {children}
@@ -1,13 +1,10 @@
1
1
  import Link from 'next/link'
2
- import { readFileSync } from 'fs'
3
- import { join } from 'path'
4
2
  import { ArrowRight, BookOpen, Zap, Code } from 'lucide-react'
5
3
  import { hasTabs, getAllPagesFlat, type Navigation } from '@/lib/navigation'
4
+ import { loadDocsConfig } from '@/lib/docs-config'
6
5
 
7
6
  function getDocsConfig() {
8
- const configPath = join(process.cwd(), 'docs.json')
9
- const content = readFileSync(configPath, 'utf-8')
10
- return JSON.parse(content)
7
+ return loadDocsConfig()
11
8
  }
12
9
 
13
10
  /** Get flat groups for display on the home page (works with both formats) */
@@ -168,7 +168,7 @@ export function AIChat({
168
168
 
169
169
  {error && (
170
170
  <div className="flex justify-start">
171
- <div className="rounded-2xl bg-red-50 px-4 py-2 text-sm text-red-600">
171
+ <div className="rounded-2xl bg-red-500/10 px-4 py-2 text-sm text-red-600 dark:text-red-400">
172
172
  Something went wrong. Please try again.
173
173
  </div>
174
174
  </div>
@@ -24,21 +24,7 @@ import {
24
24
  type NavTab,
25
25
  type NavGroup,
26
26
  } from '@/lib/navigation'
27
-
28
- interface DocsConfig {
29
- name?: string
30
- headerLinks?: Array<{ title: string; path: string }>
31
- logo?: string
32
- navigation: Navigation
33
- footer?: {
34
- links?: Array<{ title: string; url: string }>
35
- }
36
- editLink?: {
37
- repoUrl?: string
38
- branch?: string
39
- docsPath?: string
40
- }
41
- }
27
+ import type { DocsConfig } from '@/lib/docs-config'
42
28
 
43
29
  function PrevNextNav({ docsConfig }: { docsConfig: DocsConfig }) {
44
30
  const pathname = usePathname()
@@ -1,41 +1,117 @@
1
+ import { Github } from 'lucide-react'
2
+
3
+ interface FooterSocial {
4
+ github?: string
5
+ twitter?: string
6
+ discord?: string
7
+ linkedin?: string
8
+ }
9
+
1
10
  interface FooterProps {
2
11
  docsConfig: {
3
12
  footer?: {
4
13
  links?: Array<{ title: string; url: string }>
14
+ social?: FooterSocial
15
+ copyright?: string
5
16
  }
6
17
  }
7
18
  }
8
19
 
20
+ function TwitterIcon({ size = 16 }: { size?: number }) {
21
+ return (
22
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
23
+ <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
24
+ </svg>
25
+ )
26
+ }
27
+
28
+ function DiscordIcon({ size = 16 }: { size?: number }) {
29
+ return (
30
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
31
+ <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
32
+ </svg>
33
+ )
34
+ }
35
+
36
+ function LinkedInIcon({ size = 16 }: { size?: number }) {
37
+ return (
38
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
39
+ <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
40
+ </svg>
41
+ )
42
+ }
43
+
44
+ const socialEntries: Array<{
45
+ key: keyof FooterSocial
46
+ icon: React.ComponentType<{ size?: number }>
47
+ label: string
48
+ }> = [
49
+ { key: 'github', icon: Github, label: 'GitHub' },
50
+ { key: 'twitter', icon: TwitterIcon, label: 'Twitter' },
51
+ { key: 'discord', icon: DiscordIcon, label: 'Discord' },
52
+ { key: 'linkedin', icon: LinkedInIcon, label: 'LinkedIn' },
53
+ ]
54
+
9
55
  export function Footer({ docsConfig }: FooterProps) {
10
56
  const links = docsConfig.footer?.links || []
57
+ const social = docsConfig.footer?.social
58
+ const copyright = docsConfig.footer?.copyright
11
59
 
12
60
  return (
13
61
  <footer className="border-t border-[var(--color-border)] lg:ml-[var(--sidebar-width)]">
14
- <div className="max-w-[var(--content-max-width)] mx-auto px-6 md:px-10 py-6 flex items-center justify-between flex-wrap gap-4">
15
- <div className="flex items-center gap-6">
16
- {links.map((link) => (
62
+ <div className="max-w-[var(--content-max-width)] mx-auto px-6 md:px-10 py-6 flex flex-col gap-4">
63
+ <div className="flex items-center justify-between flex-wrap gap-4">
64
+ <div className="flex items-center gap-6">
65
+ {links.map((link) => (
66
+ <a
67
+ key={link.url}
68
+ href={link.url}
69
+ target="_blank"
70
+ rel="noopener noreferrer"
71
+ className="text-[0.8125rem] text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] transition-colors"
72
+ >
73
+ {link.title}
74
+ </a>
75
+ ))}
76
+ </div>
77
+ <div className="flex items-center gap-4">
78
+ {social &&
79
+ socialEntries.map(({ key, icon: Icon, label }) => {
80
+ const url = social[key]
81
+ if (!url) return null
82
+ return (
83
+ <a
84
+ key={key}
85
+ href={url}
86
+ target="_blank"
87
+ rel="noopener noreferrer"
88
+ aria-label={label}
89
+ className="text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] transition-colors"
90
+ >
91
+ <Icon size={16} />
92
+ </a>
93
+ )
94
+ })}
95
+ </div>
96
+ </div>
97
+ <div className="flex items-center justify-between flex-wrap gap-2">
98
+ {copyright && (
99
+ <span className="text-[0.75rem] text-[var(--color-text-tertiary)]">
100
+ {copyright}
101
+ </span>
102
+ )}
103
+ <span className={`text-[0.6875rem] text-[var(--color-text-tertiary)] opacity-60${copyright ? '' : ' ml-auto'}`}>
104
+ Built with{' '}
17
105
  <a
18
- key={link.url}
19
- href={link.url}
106
+ href="https://skrypt.sh"
20
107
  target="_blank"
21
108
  rel="noopener noreferrer"
22
- className="text-[0.8125rem] text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] transition-colors"
109
+ className="hover:text-[var(--color-text-secondary)] transition-colors"
23
110
  >
24
- {link.title}
111
+ Skrypt
25
112
  </a>
26
- ))}
113
+ </span>
27
114
  </div>
28
- <span className="text-[0.75rem] text-[var(--color-text-tertiary)]">
29
- Built with{' '}
30
- <a
31
- href="https://skrypt.sh"
32
- target="_blank"
33
- rel="noopener noreferrer"
34
- className="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors"
35
- >
36
- Skrypt
37
- </a>
38
- </span>
39
115
  </div>
40
116
  </footer>
41
117
  )
@@ -58,7 +58,7 @@ export function Header({ onMenuToggle, menuOpen, siteName = 'Docs', navLinks, lo
58
58
  {/* Mobile menu button */}
59
59
  <button
60
60
  onClick={onMenuToggle}
61
- className="md:hidden p-1.5 -ml-1.5 rounded-lg text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]"
61
+ className="lg:hidden p-1.5 -ml-1.5 rounded-lg text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]"
62
62
  aria-label={menuOpen ? 'Close menu' : 'Open menu'}
63
63
  aria-expanded={menuOpen}
64
64
  >