skrypt-ai 0.1.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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/autofix/index.d.ts +46 -0
  4. package/dist/autofix/index.js +240 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +40 -0
  7. package/dist/commands/autofix.d.ts +2 -0
  8. package/dist/commands/autofix.js +143 -0
  9. package/dist/commands/generate.d.ts +2 -0
  10. package/dist/commands/generate.js +320 -0
  11. package/dist/commands/init.d.ts +2 -0
  12. package/dist/commands/init.js +56 -0
  13. package/dist/commands/review-pr.d.ts +2 -0
  14. package/dist/commands/review-pr.js +117 -0
  15. package/dist/commands/watch.d.ts +2 -0
  16. package/dist/commands/watch.js +142 -0
  17. package/dist/config/index.d.ts +2 -0
  18. package/dist/config/index.js +2 -0
  19. package/dist/config/loader.d.ts +9 -0
  20. package/dist/config/loader.js +82 -0
  21. package/dist/config/types.d.ts +24 -0
  22. package/dist/config/types.js +34 -0
  23. package/dist/generator/generator.d.ts +15 -0
  24. package/dist/generator/generator.js +144 -0
  25. package/dist/generator/index.d.ts +4 -0
  26. package/dist/generator/index.js +4 -0
  27. package/dist/generator/organizer.d.ts +29 -0
  28. package/dist/generator/organizer.js +222 -0
  29. package/dist/generator/types.d.ts +83 -0
  30. package/dist/generator/types.js +1 -0
  31. package/dist/generator/writer.d.ts +28 -0
  32. package/dist/generator/writer.js +320 -0
  33. package/dist/github/pr-comments.d.ts +40 -0
  34. package/dist/github/pr-comments.js +308 -0
  35. package/dist/llm/anthropic-client.d.ts +16 -0
  36. package/dist/llm/anthropic-client.js +92 -0
  37. package/dist/llm/index.d.ts +53 -0
  38. package/dist/llm/index.js +400 -0
  39. package/dist/llm/llm.manual-test.d.ts +1 -0
  40. package/dist/llm/llm.manual-test.js +112 -0
  41. package/dist/llm/llm.mock-test.d.ts +4 -0
  42. package/dist/llm/llm.mock-test.js +79 -0
  43. package/dist/llm/openai-client.d.ts +17 -0
  44. package/dist/llm/openai-client.js +90 -0
  45. package/dist/llm/types.d.ts +60 -0
  46. package/dist/llm/types.js +20 -0
  47. package/dist/scanner/content-type.d.ts +39 -0
  48. package/dist/scanner/content-type.js +194 -0
  49. package/dist/scanner/content-type.test.d.ts +1 -0
  50. package/dist/scanner/content-type.test.js +231 -0
  51. package/dist/scanner/go.d.ts +20 -0
  52. package/dist/scanner/go.js +269 -0
  53. package/dist/scanner/index.d.ts +21 -0
  54. package/dist/scanner/index.js +137 -0
  55. package/dist/scanner/python.d.ts +6 -0
  56. package/dist/scanner/python.js +57 -0
  57. package/dist/scanner/python_parser.py +230 -0
  58. package/dist/scanner/rust.d.ts +23 -0
  59. package/dist/scanner/rust.js +304 -0
  60. package/dist/scanner/scanner.test.d.ts +1 -0
  61. package/dist/scanner/scanner.test.js +210 -0
  62. package/dist/scanner/types.d.ts +50 -0
  63. package/dist/scanner/types.js +1 -0
  64. package/dist/scanner/typescript.d.ts +34 -0
  65. package/dist/scanner/typescript.js +327 -0
  66. package/dist/scanner/typescript.manual-test.d.ts +1 -0
  67. package/dist/scanner/typescript.manual-test.js +112 -0
  68. package/dist/template/docs.json +32 -0
  69. package/dist/template/mdx-components.tsx +62 -0
  70. package/dist/template/next-env.d.ts +6 -0
  71. package/dist/template/next.config.mjs +17 -0
  72. package/dist/template/package.json +39 -0
  73. package/dist/template/postcss.config.mjs +5 -0
  74. package/dist/template/public/search-index.json +1 -0
  75. package/dist/template/scripts/build-search-index.mjs +120 -0
  76. package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
  77. package/dist/template/src/app/api/openapi/route.ts +48 -0
  78. package/dist/template/src/app/api/rate-limit/route.ts +84 -0
  79. package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
  80. package/dist/template/src/app/docs/layout.tsx +9 -0
  81. package/dist/template/src/app/docs/page.mdx +67 -0
  82. package/dist/template/src/app/error.tsx +63 -0
  83. package/dist/template/src/app/layout.tsx +71 -0
  84. package/dist/template/src/app/page.tsx +18 -0
  85. package/dist/template/src/app/reference/route.ts +36 -0
  86. package/dist/template/src/app/robots.ts +14 -0
  87. package/dist/template/src/app/sitemap.ts +64 -0
  88. package/dist/template/src/components/breadcrumbs.tsx +41 -0
  89. package/dist/template/src/components/copy-button.tsx +29 -0
  90. package/dist/template/src/components/docs-layout.tsx +35 -0
  91. package/dist/template/src/components/edit-link.tsx +39 -0
  92. package/dist/template/src/components/feedback.tsx +52 -0
  93. package/dist/template/src/components/header.tsx +66 -0
  94. package/dist/template/src/components/mdx/accordion.tsx +48 -0
  95. package/dist/template/src/components/mdx/api-badge.tsx +57 -0
  96. package/dist/template/src/components/mdx/callout.tsx +111 -0
  97. package/dist/template/src/components/mdx/card.tsx +62 -0
  98. package/dist/template/src/components/mdx/changelog.tsx +57 -0
  99. package/dist/template/src/components/mdx/code-block.tsx +42 -0
  100. package/dist/template/src/components/mdx/code-group.tsx +125 -0
  101. package/dist/template/src/components/mdx/code-playground.tsx +322 -0
  102. package/dist/template/src/components/mdx/go-playground.tsx +235 -0
  103. package/dist/template/src/components/mdx/heading.tsx +37 -0
  104. package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
  105. package/dist/template/src/components/mdx/index.tsx +15 -0
  106. package/dist/template/src/components/mdx/param-table.tsx +71 -0
  107. package/dist/template/src/components/mdx/python-playground.tsx +293 -0
  108. package/dist/template/src/components/mdx/steps.tsx +43 -0
  109. package/dist/template/src/components/mdx/tabs.tsx +81 -0
  110. package/dist/template/src/components/rate-limit-display.tsx +183 -0
  111. package/dist/template/src/components/search-dialog.tsx +178 -0
  112. package/dist/template/src/components/sidebar.tsx +129 -0
  113. package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
  114. package/dist/template/src/components/table-of-contents.tsx +84 -0
  115. package/dist/template/src/components/theme-toggle.tsx +46 -0
  116. package/dist/template/src/components/version-selector.tsx +61 -0
  117. package/dist/template/src/contexts/syntax-theme.tsx +52 -0
  118. package/dist/template/src/lib/highlight.ts +83 -0
  119. package/dist/template/src/lib/search-types.ts +37 -0
  120. package/dist/template/src/lib/search.ts +125 -0
  121. package/dist/template/src/lib/utils.ts +6 -0
  122. package/dist/template/src/styles/globals.css +152 -0
  123. package/dist/template/tsconfig.json +25 -0
  124. package/dist/template/tsconfig.tsbuildinfo +1 -0
  125. package/package.json +72 -0
@@ -0,0 +1,322 @@
1
+ 'use client'
2
+
3
+ import {
4
+ SandpackProvider,
5
+ SandpackLayout,
6
+ SandpackCodeEditor,
7
+ SandpackPreview,
8
+ SandpackConsole,
9
+ useSandpack,
10
+ type SandpackTheme,
11
+ } from '@codesandbox/sandpack-react'
12
+ import { useState, useEffect } from 'react'
13
+ import { RotateCcw, Download, Play, Pause } from 'lucide-react'
14
+
15
+ // Custom themes that match our CSS variables
16
+ const lightTheme: SandpackTheme = {
17
+ colors: {
18
+ surface1: '#f8fafc',
19
+ surface2: '#f1f5f9',
20
+ surface3: '#e2e8f0',
21
+ clickable: '#64748b',
22
+ base: '#0f172a',
23
+ disabled: '#94a3b8',
24
+ hover: '#475569',
25
+ accent: '#3b82f6',
26
+ error: '#ef4444',
27
+ errorSurface: '#fef2f2',
28
+ },
29
+ syntax: {
30
+ plain: '#0f172a',
31
+ comment: { color: '#94a3b8', fontStyle: 'italic' },
32
+ keyword: '#8b5cf6',
33
+ tag: '#3b82f6',
34
+ punctuation: '#64748b',
35
+ definition: '#0f172a',
36
+ property: '#0891b2',
37
+ static: '#059669',
38
+ string: '#16a34a',
39
+ },
40
+ font: {
41
+ body: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
42
+ mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
43
+ size: '14px',
44
+ lineHeight: '1.6',
45
+ },
46
+ }
47
+
48
+ const darkTheme: SandpackTheme = {
49
+ colors: {
50
+ surface1: '#1e293b',
51
+ surface2: '#334155',
52
+ surface3: '#475569',
53
+ clickable: '#94a3b8',
54
+ base: '#f8fafc',
55
+ disabled: '#64748b',
56
+ hover: '#cbd5e1',
57
+ accent: '#60a5fa',
58
+ error: '#f87171',
59
+ errorSurface: '#450a0a',
60
+ },
61
+ syntax: {
62
+ plain: '#e2e8f0',
63
+ comment: { color: '#64748b', fontStyle: 'italic' },
64
+ keyword: '#c4b5fd',
65
+ tag: '#93c5fd',
66
+ punctuation: '#94a3b8',
67
+ definition: '#f8fafc',
68
+ property: '#67e8f9',
69
+ static: '#6ee7b7',
70
+ string: '#86efac',
71
+ },
72
+ font: {
73
+ body: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
74
+ mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
75
+ size: '14px',
76
+ lineHeight: '1.6',
77
+ },
78
+ }
79
+
80
+ // Hook to detect dark mode
81
+ function useDarkMode(): boolean {
82
+ const [isDark, setIsDark] = useState(false)
83
+
84
+ useEffect(() => {
85
+ // Check initial state
86
+ const checkDark = () => {
87
+ const root = document.documentElement
88
+ const hasDarkClass = root.classList.contains('dark')
89
+ const prefersLight = root.classList.contains('light')
90
+ const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches
91
+
92
+ return hasDarkClass || (!prefersLight && systemDark)
93
+ }
94
+
95
+ setIsDark(checkDark())
96
+
97
+ // Listen for class changes on document
98
+ const observer = new MutationObserver(() => {
99
+ setIsDark(checkDark())
100
+ })
101
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
102
+
103
+ // Listen for system preference changes
104
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
105
+ const handleChange = () => setIsDark(checkDark())
106
+ mediaQuery.addEventListener('change', handleChange)
107
+
108
+ return () => {
109
+ observer.disconnect()
110
+ mediaQuery.removeEventListener('change', handleChange)
111
+ }
112
+ }, [])
113
+
114
+ return isDark
115
+ }
116
+
117
+ type Template = 'react' | 'react-ts' | 'vanilla' | 'vanilla-ts' | 'node'
118
+
119
+ interface CodePlaygroundProps {
120
+ code: string
121
+ template?: Template
122
+ showPreview?: boolean
123
+ showConsole?: boolean
124
+ editable?: boolean
125
+ dependencies?: Record<string, string>
126
+ filename?: string
127
+ autoRun?: boolean
128
+ }
129
+
130
+ // Toolbar component with reset/download
131
+ function PlaygroundToolbar({ originalCode, filename, autoRun, onAutoRunChange }: {
132
+ originalCode: string
133
+ filename: string
134
+ autoRun: boolean
135
+ onAutoRunChange: (v: boolean) => void
136
+ }) {
137
+ const { sandpack } = useSandpack()
138
+
139
+ const handleReset = () => {
140
+ sandpack.resetAllFiles()
141
+ }
142
+
143
+ const handleDownload = () => {
144
+ const code = sandpack.files[filename]?.code || originalCode
145
+ const blob = new Blob([code], { type: 'text/plain' })
146
+ const url = URL.createObjectURL(blob)
147
+ const a = document.createElement('a')
148
+ a.href = url
149
+ a.download = filename.replace('/', '')
150
+ a.click()
151
+ URL.revokeObjectURL(url)
152
+ }
153
+
154
+ return (
155
+ <div className="flex items-center gap-1 px-2 py-1 bg-[var(--color-bg-secondary)] border-b border-[var(--color-border)]">
156
+ <button
157
+ onClick={handleReset}
158
+ className="p-1.5 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] rounded"
159
+ title="Reset code"
160
+ >
161
+ <RotateCcw size={14} />
162
+ </button>
163
+ <button
164
+ onClick={handleDownload}
165
+ className="p-1.5 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] rounded"
166
+ title="Download code"
167
+ >
168
+ <Download size={14} />
169
+ </button>
170
+ <button
171
+ onClick={() => onAutoRunChange(!autoRun)}
172
+ className={`p-1.5 rounded ${autoRun ? 'text-emerald-500' : 'text-[var(--color-text-tertiary)]'} hover:text-[var(--color-text)]`}
173
+ title={autoRun ? 'Auto-run enabled' : 'Auto-run disabled'}
174
+ >
175
+ {autoRun ? <Play size={14} /> : <Pause size={14} />}
176
+ </button>
177
+ </div>
178
+ )
179
+ }
180
+
181
+ const templateConfig: Record<Template, { files: (code: string, filename?: string) => Record<string, string>, entry: string }> = {
182
+ 'react': {
183
+ files: (code, filename) => ({
184
+ [filename || '/App.js']: code,
185
+ }),
186
+ entry: '/App.js',
187
+ },
188
+ 'react-ts': {
189
+ files: (code, filename) => ({
190
+ [filename || '/App.tsx']: code,
191
+ }),
192
+ entry: '/App.tsx',
193
+ },
194
+ 'vanilla': {
195
+ files: (code, filename) => ({
196
+ [filename || '/index.js']: code,
197
+ '/index.html': `<!DOCTYPE html>
198
+ <html>
199
+ <head><title>Preview</title></head>
200
+ <body>
201
+ <div id="app"></div>
202
+ <script src="${filename || '/index.js'}"></script>
203
+ </body>
204
+ </html>`,
205
+ }),
206
+ entry: '/index.js',
207
+ },
208
+ 'vanilla-ts': {
209
+ files: (code, filename) => ({
210
+ [filename || '/index.ts']: code,
211
+ '/index.html': `<!DOCTYPE html>
212
+ <html>
213
+ <head><title>Preview</title></head>
214
+ <body>
215
+ <div id="app"></div>
216
+ <script src="${filename || '/index.ts'}"></script>
217
+ </body>
218
+ </html>`,
219
+ }),
220
+ entry: '/index.ts',
221
+ },
222
+ 'node': {
223
+ files: (code, filename) => ({
224
+ [filename || '/index.js']: code,
225
+ }),
226
+ entry: '/index.js',
227
+ },
228
+ }
229
+
230
+ export function CodePlayground({
231
+ code,
232
+ template = 'react-ts',
233
+ showPreview = true,
234
+ showConsole = false,
235
+ editable = true,
236
+ dependencies = {},
237
+ filename,
238
+ autoRun: initialAutoRun = true,
239
+ }: CodePlaygroundProps) {
240
+ const [activeTab, setActiveTab] = useState<'preview' | 'console'>('preview')
241
+ const [autoRun, setAutoRun] = useState(initialAutoRun)
242
+ const isDark = useDarkMode()
243
+ const config = templateConfig[template]
244
+ const files = config.files(code, filename)
245
+ const entryFile = filename || config.entry
246
+
247
+ return (
248
+ <div className="my-6 rounded-lg border border-[var(--color-border)] overflow-hidden">
249
+ <SandpackProvider
250
+ template={template === 'node' ? 'node' : template}
251
+ theme={isDark ? darkTheme : lightTheme}
252
+ files={files}
253
+ customSetup={{
254
+ dependencies,
255
+ }}
256
+ options={{
257
+ externalResources: [],
258
+ recompileMode: autoRun ? 'delayed' : 'immediate',
259
+ recompileDelay: 500,
260
+ autorun: autoRun,
261
+ }}
262
+ >
263
+ <PlaygroundToolbar
264
+ originalCode={code}
265
+ filename={entryFile}
266
+ autoRun={autoRun}
267
+ onAutoRunChange={setAutoRun}
268
+ />
269
+ <SandpackLayout>
270
+ <SandpackCodeEditor
271
+ showTabs
272
+ showLineNumbers
273
+ showInlineErrors
274
+ wrapContent
275
+ readOnly={!editable}
276
+ style={{ minHeight: 300 }}
277
+ />
278
+ {(showPreview || showConsole) && (
279
+ <div className="flex flex-col w-full">
280
+ {showPreview && showConsole && (
281
+ <div className="flex border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
282
+ <button
283
+ onClick={() => setActiveTab('preview')}
284
+ className={`px-4 py-2 text-sm font-medium ${
285
+ activeTab === 'preview'
286
+ ? 'text-blue-600 border-b-2 border-blue-600'
287
+ : 'text-gray-600 dark:text-gray-400'
288
+ }`}
289
+ >
290
+ Preview
291
+ </button>
292
+ <button
293
+ onClick={() => setActiveTab('console')}
294
+ className={`px-4 py-2 text-sm font-medium ${
295
+ activeTab === 'console'
296
+ ? 'text-blue-600 border-b-2 border-blue-600'
297
+ : 'text-gray-600 dark:text-gray-400'
298
+ }`}
299
+ >
300
+ Console
301
+ </button>
302
+ </div>
303
+ )}
304
+ {showPreview && (!showConsole || activeTab === 'preview') && (
305
+ <SandpackPreview
306
+ showNavigator={false}
307
+ showRefreshButton
308
+ style={{ minHeight: 300 }}
309
+ />
310
+ )}
311
+ {showConsole && (!showPreview || activeTab === 'console') && (
312
+ <SandpackConsole style={{ minHeight: 300 }} />
313
+ )}
314
+ </div>
315
+ )}
316
+ </SandpackLayout>
317
+ </SandpackProvider>
318
+ </div>
319
+ )
320
+ }
321
+
322
+ export default CodePlayground
@@ -0,0 +1,235 @@
1
+ 'use client'
2
+
3
+ import { useState, useRef, useCallback } from 'react'
4
+ import { Play, RotateCcw, Download, Loader2, ExternalLink } from 'lucide-react'
5
+
6
+ interface GoPlaygroundProps {
7
+ code: string
8
+ filename?: string
9
+ }
10
+
11
+ type OutputLine = {
12
+ type: 'stdout' | 'stderr' | 'error'
13
+ content: string
14
+ }
15
+
16
+ const GO_PLAYGROUND_URL = 'https://go.dev/_/compile'
17
+
18
+ export function GoPlayground({
19
+ code: initialCode,
20
+ filename = 'main.go',
21
+ }: GoPlaygroundProps) {
22
+ const [code, setCode] = useState(initialCode)
23
+ const [output, setOutput] = useState<OutputLine[]>([])
24
+ const [isRunning, setIsRunning] = useState(false)
25
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
26
+
27
+ const runCode = useCallback(async () => {
28
+ if (isRunning) return
29
+
30
+ setIsRunning(true)
31
+ setOutput([])
32
+
33
+ // Create abort controller with 30s timeout
34
+ const controller = new AbortController()
35
+ const timeoutId = setTimeout(() => controller.abort(), 30000)
36
+
37
+ try {
38
+ // Use Go Playground API
39
+ const response = await fetch(GO_PLAYGROUND_URL, {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/x-www-form-urlencoded',
43
+ },
44
+ body: new URLSearchParams({
45
+ version: '2',
46
+ body: code,
47
+ withVet: 'true',
48
+ }),
49
+ signal: controller.signal,
50
+ })
51
+
52
+ if (!response.ok) {
53
+ throw new Error(`HTTP ${response.status}`)
54
+ }
55
+
56
+ const result = await response.json()
57
+
58
+ const lines: OutputLine[] = []
59
+
60
+ if (result.Errors) {
61
+ lines.push({ type: 'error', content: result.Errors })
62
+ }
63
+
64
+ if (result.Events) {
65
+ for (const event of result.Events) {
66
+ if (event.Kind === 'stdout') {
67
+ lines.push({ type: 'stdout', content: event.Message })
68
+ } else if (event.Kind === 'stderr') {
69
+ lines.push({ type: 'stderr', content: event.Message })
70
+ }
71
+ }
72
+ }
73
+
74
+ if (result.VetErrors) {
75
+ lines.push({ type: 'stderr', content: `Vet: ${result.VetErrors}` })
76
+ }
77
+
78
+ setOutput(lines.length > 0 ? lines : [{ type: 'stdout', content: '(no output)' }])
79
+ } catch (err: unknown) {
80
+ let message: string
81
+ if (err instanceof Error) {
82
+ if (err.name === 'AbortError') {
83
+ message = 'Request timed out after 30 seconds. Try the Go Playground directly.'
84
+ } else {
85
+ message = `Failed to run: ${err.message}. Try the Go Playground directly.`
86
+ }
87
+ } else {
88
+ message = `Failed to run: ${String(err)}. Try the Go Playground directly.`
89
+ }
90
+ setOutput([{ type: 'error', content: message }])
91
+ } finally {
92
+ clearTimeout(timeoutId)
93
+ setIsRunning(false)
94
+ }
95
+ }, [code, isRunning])
96
+
97
+ const handleReset = () => {
98
+ setCode(initialCode)
99
+ setOutput([])
100
+ }
101
+
102
+ const handleDownload = () => {
103
+ const blob = new Blob([code], { type: 'text/plain' })
104
+ const url = URL.createObjectURL(blob)
105
+ const a = document.createElement('a')
106
+ a.href = url
107
+ a.download = filename
108
+ a.click()
109
+ URL.revokeObjectURL(url)
110
+ }
111
+
112
+ const openInPlayground = () => {
113
+ // Encode code for Go Playground URL
114
+ const encoded = btoa(unescape(encodeURIComponent(code)))
115
+ window.open(`https://go.dev/play/p/?source=${encoded}`, '_blank')
116
+ }
117
+
118
+ const handleKeyDown = (e: React.KeyboardEvent) => {
119
+ // Run on Ctrl/Cmd + Enter
120
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
121
+ e.preventDefault()
122
+ runCode()
123
+ }
124
+
125
+ // Tab handling
126
+ if (e.key === 'Tab') {
127
+ e.preventDefault()
128
+ const start = textareaRef.current?.selectionStart || 0
129
+ const end = textareaRef.current?.selectionEnd || 0
130
+ const newCode = code.slice(0, start) + '\t' + code.slice(end)
131
+ setCode(newCode)
132
+ setTimeout(() => {
133
+ if (textareaRef.current) {
134
+ textareaRef.current.selectionStart = textareaRef.current.selectionEnd = start + 1
135
+ }
136
+ }, 0)
137
+ }
138
+ }
139
+
140
+ return (
141
+ <div className="my-6 rounded-lg border border-[var(--color-border)] overflow-hidden">
142
+ {/* Toolbar */}
143
+ <div className="flex items-center justify-between px-3 py-2 bg-[var(--color-bg-secondary)] border-b border-[var(--color-border)]">
144
+ <div className="flex items-center gap-2">
145
+ <span className="text-xs font-medium text-[var(--color-text-secondary)]">
146
+ 🐹 Go
147
+ </span>
148
+ <span className="text-xs text-[var(--color-text-tertiary)]">
149
+ {filename}
150
+ </span>
151
+ </div>
152
+ <div className="flex items-center gap-1">
153
+ <button
154
+ onClick={handleReset}
155
+ className="p-1.5 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] rounded"
156
+ title="Reset code"
157
+ >
158
+ <RotateCcw size={14} />
159
+ </button>
160
+ <button
161
+ onClick={handleDownload}
162
+ className="p-1.5 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] rounded"
163
+ title="Download code"
164
+ >
165
+ <Download size={14} />
166
+ </button>
167
+ <button
168
+ onClick={openInPlayground}
169
+ className="p-1.5 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] rounded"
170
+ title="Open in Go Playground"
171
+ >
172
+ <ExternalLink size={14} />
173
+ </button>
174
+ <button
175
+ onClick={runCode}
176
+ disabled={isRunning}
177
+ className="flex items-center gap-1 px-2 py-1 text-xs font-medium bg-cyan-600 text-white rounded hover:bg-cyan-700 disabled:opacity-50 disabled:cursor-not-allowed"
178
+ title="Run (Ctrl+Enter)"
179
+ >
180
+ {isRunning ? (
181
+ <>
182
+ <Loader2 size={12} className="animate-spin" />
183
+ Running
184
+ </>
185
+ ) : (
186
+ <>
187
+ <Play size={12} />
188
+ Run
189
+ </>
190
+ )}
191
+ </button>
192
+ </div>
193
+ </div>
194
+
195
+ {/* Code editor */}
196
+ <div className="relative">
197
+ <textarea
198
+ ref={textareaRef}
199
+ value={code}
200
+ onChange={(e) => setCode(e.target.value)}
201
+ onKeyDown={handleKeyDown}
202
+ spellCheck={false}
203
+ className="w-full min-h-[200px] p-4 font-mono text-sm bg-[var(--color-bg)] text-[var(--color-text)] resize-none focus:outline-none"
204
+ style={{
205
+ tabSize: 4,
206
+ }}
207
+ />
208
+ <div className="absolute top-2 right-2 text-xs text-[var(--color-text-tertiary)]">
209
+ Ctrl+Enter to run
210
+ </div>
211
+ </div>
212
+
213
+ {/* Output */}
214
+ {output.length > 0 && (
215
+ <div className="border-t border-[var(--color-border)] bg-[var(--color-code-bg)] text-[var(--color-code-text)] p-4 font-mono text-sm max-h-[300px] overflow-auto">
216
+ <div className="text-xs text-[var(--color-text-tertiary)] mb-2">Output:</div>
217
+ {output.map((line, i) => (
218
+ <pre
219
+ key={i}
220
+ className={`whitespace-pre-wrap ${
221
+ line.type === 'stderr' || line.type === 'error'
222
+ ? 'text-red-400'
223
+ : ''
224
+ }`}
225
+ >
226
+ {line.content}
227
+ </pre>
228
+ ))}
229
+ </div>
230
+ )}
231
+ </div>
232
+ )
233
+ }
234
+
235
+ export default GoPlayground
@@ -0,0 +1,37 @@
1
+ 'use client'
2
+
3
+ import { Link2 } from 'lucide-react'
4
+ import { ReactNode, createElement } from 'react'
5
+
6
+ interface HeadingProps {
7
+ level: 1 | 2 | 3 | 4 | 5 | 6
8
+ children: ReactNode
9
+ id?: string
10
+ }
11
+
12
+ export function Heading({ level, children, id }: HeadingProps) {
13
+ const Tag = `h${level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
14
+ const headingId = id || (typeof children === 'string'
15
+ ? children.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '')
16
+ : undefined)
17
+
18
+ return (
19
+ <Tag id={headingId} className="group relative scroll-mt-20">
20
+ {children}
21
+ {headingId && (
22
+ <a
23
+ href={`#${headingId}`}
24
+ className="absolute -left-6 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity p-1 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)]"
25
+ aria-label="Link to this section"
26
+ >
27
+ <Link2 size={16} />
28
+ </a>
29
+ )}
30
+ </Tag>
31
+ )
32
+ }
33
+
34
+ export const H1 = (props: Omit<HeadingProps, 'level'>) => <Heading level={1} {...props} />
35
+ export const H2 = (props: Omit<HeadingProps, 'level'>) => <Heading level={2} {...props} />
36
+ export const H3 = (props: Omit<HeadingProps, 'level'>) => <Heading level={3} {...props} />
37
+ export const H4 = (props: Omit<HeadingProps, 'level'>) => <Heading level={4} {...props} />
@@ -0,0 +1,89 @@
1
+ 'use client'
2
+
3
+ import { useState, useEffect, useContext, ReactNode, isValidElement, Children } from 'react'
4
+ import { Copy, Check } from 'lucide-react'
5
+ import { highlight, DEFAULT_THEME, type ThemeName } from '@/lib/highlight'
6
+
7
+ // Import the context directly to use useContext safely
8
+ import { SyntaxThemeContext } from '@/contexts/syntax-theme'
9
+
10
+ interface HighlightedCodeProps {
11
+ children: ReactNode
12
+ className?: string
13
+ }
14
+
15
+ export function HighlightedCode({ children, className }: HighlightedCodeProps) {
16
+ const [copied, setCopied] = useState(false)
17
+ const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null)
18
+
19
+ // Use context safely - fallback to default if not available
20
+ const syntaxContext = useContext(SyntaxThemeContext)
21
+ const theme: ThemeName = syntaxContext?.theme ?? DEFAULT_THEME
22
+
23
+ // Extract code text and language from children
24
+ let codeText = ''
25
+ let language = 'text'
26
+
27
+ Children.forEach(children, (child) => {
28
+ if (isValidElement(child) && child.type === 'code') {
29
+ const props = child.props as { children?: string; className?: string }
30
+ codeText = props.children || ''
31
+ // Extract language from className like "language-typescript"
32
+ const classMatch = props.className?.match(/language-(\w+)/)
33
+ if (classMatch) {
34
+ language = classMatch[1]
35
+ }
36
+ }
37
+ })
38
+
39
+ useEffect(() => {
40
+ let cancelled = false
41
+
42
+ async function doHighlight() {
43
+ if (!codeText) return
44
+
45
+ try {
46
+ const html = await highlight(codeText, language, theme)
47
+ if (!cancelled) {
48
+ setHighlightedHtml(html)
49
+ }
50
+ } catch (err) {
51
+ console.error('Highlight failed:', err)
52
+ }
53
+ }
54
+
55
+ doHighlight()
56
+
57
+ return () => {
58
+ cancelled = true
59
+ }
60
+ }, [codeText, language, theme])
61
+
62
+ async function handleCopy() {
63
+ await navigator.clipboard.writeText(codeText)
64
+ setCopied(true)
65
+ setTimeout(() => setCopied(false), 2000)
66
+ }
67
+
68
+ return (
69
+ <div className="relative group my-4">
70
+ {highlightedHtml ? (
71
+ <div
72
+ className="[&>pre]:rounded-lg [&>pre]:p-4 [&>pre]:overflow-x-auto"
73
+ dangerouslySetInnerHTML={{ __html: highlightedHtml }}
74
+ />
75
+ ) : (
76
+ <pre className={className}>
77
+ {children}
78
+ </pre>
79
+ )}
80
+ <button
81
+ onClick={handleCopy}
82
+ className="absolute top-2 right-2 p-1.5 rounded bg-[var(--color-bg-tertiary)]/80 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] opacity-0 group-hover:opacity-100 transition-opacity"
83
+ title={copied ? 'Copied!' : 'Copy code'}
84
+ >
85
+ {copied ? <Check size={14} /> : <Copy size={14} />}
86
+ </button>
87
+ </div>
88
+ )
89
+ }
@@ -0,0 +1,15 @@
1
+ export { Card, CardGroup } from './card'
2
+ export { Tabs, TabList, Tab, TabPanel } from './tabs'
3
+ export { CodeGroup } from './code-group'
4
+ export { CodeBlock } from './code-block'
5
+ export { HighlightedCode } from './highlighted-code'
6
+ export { Callout, Info, Warning, Success, Error, Tip, Note } from './callout'
7
+ export { Accordion, AccordionGroup } from './accordion'
8
+ export { Steps, Step } from './steps'
9
+ export { CodePlayground } from './code-playground'
10
+ export { PythonPlayground } from './python-playground'
11
+ export { GoPlayground } from './go-playground'
12
+ export { H1, H2, H3, H4 } from './heading'
13
+ export { ParamTable, Schema } from './param-table'
14
+ export { Changelog, ChangelogEntry, Change } from './changelog'
15
+ export { MethodBadge, StatusBadge, Endpoint } from './api-badge'