skrypt-ai 0.5.0 → 0.6.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.
- package/dist/auth/index.js +8 -1
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +0 -21
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +88 -6
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +61 -43
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +114 -103
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.js +86 -80
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +24 -4
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +16 -2
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +84 -6
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +3 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { usePathname } from 'next/navigation'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handles scrolling to hash targets after page navigation.
|
|
8
|
+
* Next.js App Router can intercept hash links before content is rendered.
|
|
9
|
+
* This component uses a MutationObserver to wait for the target element.
|
|
10
|
+
*/
|
|
11
|
+
export function ScrollToHash() {
|
|
12
|
+
const pathname = usePathname()
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const hash = window.location.hash
|
|
16
|
+
if (!hash) return
|
|
17
|
+
|
|
18
|
+
const id = hash.slice(1)
|
|
19
|
+
|
|
20
|
+
// Try immediate scroll first
|
|
21
|
+
const target = document.getElementById(id)
|
|
22
|
+
if (target) {
|
|
23
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If element not found yet (SSR/hydration timing), observe DOM
|
|
28
|
+
const observer = new MutationObserver(() => {
|
|
29
|
+
const el = document.getElementById(id)
|
|
30
|
+
if (el) {
|
|
31
|
+
observer.disconnect()
|
|
32
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
37
|
+
|
|
38
|
+
// Cleanup after 5 seconds
|
|
39
|
+
const timeout = setTimeout(() => observer.disconnect(), 5000)
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
observer.disconnect()
|
|
43
|
+
clearTimeout(timeout)
|
|
44
|
+
}
|
|
45
|
+
}, [pathname])
|
|
46
|
+
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
@@ -5,15 +5,13 @@ import { usePathname } from 'next/navigation'
|
|
|
5
5
|
import { ChevronRight, BookOpen, Code, FileText, Settings, Key, Zap, Shield, Globe, Terminal, Database, Cloud, Lock, Rocket, Search, Star, Heart, Package, Puzzle, GitBranch, Cpu } from 'lucide-react'
|
|
6
6
|
import { useState, useMemo } from 'react'
|
|
7
7
|
import { cn } from '@/lib/utils'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
interface NavGroup { group: string; icon?: string; pages: (NavPage | NavGroup)[] }
|
|
11
|
-
interface DocsConfig { navigation: NavGroup[] }
|
|
8
|
+
import type { NavPage, NavGroup } from '@/lib/navigation'
|
|
9
|
+
import { isNavGroup } from '@/lib/navigation'
|
|
12
10
|
|
|
13
11
|
interface SidebarProps {
|
|
14
12
|
open?: boolean
|
|
15
13
|
onClose?: () => void
|
|
16
|
-
|
|
14
|
+
groups: NavGroup[]
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
const iconMap: Record<string, React.ComponentType<{ size?: number; className?: string }>> = {
|
|
@@ -22,10 +20,6 @@ const iconMap: Record<string, React.ComponentType<{ size?: number; className?: s
|
|
|
22
20
|
Package, Puzzle, GitBranch, Cpu,
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
function isNavGroup(item: NavPage | NavGroup): item is NavGroup {
|
|
26
|
-
return 'group' in item && !('path' in item)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
/** Check if any descendant page matches the current pathname */
|
|
30
24
|
function hasActivePage(pages: (NavPage | NavGroup)[], pathname: string): boolean {
|
|
31
25
|
for (const item of pages) {
|
|
@@ -39,7 +33,7 @@ function hasActivePage(pages: (NavPage | NavGroup)[], pathname: string): boolean
|
|
|
39
33
|
return false
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
export function Sidebar({ open, onClose,
|
|
36
|
+
export function Sidebar({ open, onClose, groups }: SidebarProps) {
|
|
43
37
|
const pathname = usePathname()
|
|
44
38
|
|
|
45
39
|
return (
|
|
@@ -61,8 +55,8 @@ export function Sidebar({ open, onClose, docsConfig }: SidebarProps) {
|
|
|
61
55
|
open ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'
|
|
62
56
|
)}
|
|
63
57
|
>
|
|
64
|
-
<nav className="py-4 pr-2 space-y-
|
|
65
|
-
{
|
|
58
|
+
<nav className="py-4 pr-2 space-y-5">
|
|
59
|
+
{groups.map((group) => (
|
|
66
60
|
<SidebarGroup key={group.group} group={group} pathname={pathname} onNavigate={onClose} level={0} />
|
|
67
61
|
))}
|
|
68
62
|
</nav>
|
|
@@ -80,7 +74,7 @@ function SidebarGroup({ group, pathname, onNavigate, level }: { group: NavGroup;
|
|
|
80
74
|
// Limit nesting to 3 levels
|
|
81
75
|
if (level > 2) return null
|
|
82
76
|
|
|
83
|
-
const paddingLeft = level === 0 ? 'pl-4' : level === 1 ? 'pl-
|
|
77
|
+
const paddingLeft = level === 0 ? 'pl-4' : level === 1 ? 'pl-8' : 'pl-12'
|
|
84
78
|
|
|
85
79
|
return (
|
|
86
80
|
<div>
|
|
@@ -88,15 +82,15 @@ function SidebarGroup({ group, pathname, onNavigate, level }: { group: NavGroup;
|
|
|
88
82
|
onClick={() => setExpanded(!expanded)}
|
|
89
83
|
aria-expanded={expanded}
|
|
90
84
|
className={cn(
|
|
91
|
-
'flex items-center gap-2
|
|
85
|
+
'flex items-center gap-2 w-full mb-1 py-1 text-[0.6875rem] font-semibold uppercase tracking-wider text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors',
|
|
92
86
|
paddingLeft
|
|
93
87
|
)}
|
|
94
88
|
>
|
|
95
89
|
<ChevronRight
|
|
96
|
-
size={
|
|
97
|
-
className={cn('
|
|
90
|
+
size={10}
|
|
91
|
+
className={cn('shrink-0 transition-transform duration-100', expanded && 'rotate-90')}
|
|
98
92
|
/>
|
|
99
|
-
{Icon && <Icon size={
|
|
93
|
+
{Icon && <Icon size={12} className="shrink-0" />}
|
|
100
94
|
{group.group}
|
|
101
95
|
</button>
|
|
102
96
|
{expanded && (
|
|
@@ -141,7 +135,7 @@ function SidebarPageItem({ page, pathname, onNavigate, level }: { page: NavPage;
|
|
|
141
135
|
}, [page, pathname])
|
|
142
136
|
const [expanded, setExpanded] = useState(isActive)
|
|
143
137
|
|
|
144
|
-
const paddingClass = level === 0 ? 'pl-
|
|
138
|
+
const paddingClass = level === 0 ? 'pl-7' : level === 1 ? 'pl-11' : 'pl-14'
|
|
145
139
|
|
|
146
140
|
return (
|
|
147
141
|
<div>
|
|
@@ -21,6 +21,7 @@ export function TableOfContents() {
|
|
|
21
21
|
|
|
22
22
|
const elements = article.querySelectorAll('h2, h3')
|
|
23
23
|
const items: Heading[] = []
|
|
24
|
+
const seenIds = new Set<string>()
|
|
24
25
|
|
|
25
26
|
// Generic headings that repeat in API reference pages — skip them in TOC
|
|
26
27
|
const genericHeadings = new Set([
|
|
@@ -28,6 +29,7 @@ export function TableOfContents() {
|
|
|
28
29
|
'returned validator function', 'requirements',
|
|
29
30
|
'when results are returned', 'when each value is returned',
|
|
30
31
|
'validationresult shape',
|
|
32
|
+
'example', 'related', 'notes', 'properties',
|
|
31
33
|
])
|
|
32
34
|
|
|
33
35
|
elements.forEach((el) => {
|
|
@@ -37,9 +39,16 @@ export function TableOfContents() {
|
|
|
37
39
|
// Skip generic/repeated headings
|
|
38
40
|
if (genericHeadings.has(normalized)) return
|
|
39
41
|
|
|
42
|
+
// Skip headings inside Card components (Cards use h3 for titles, not doc headings)
|
|
43
|
+
if (el.closest('.card-link') || el.closest('[data-card]')) return
|
|
44
|
+
|
|
40
45
|
const id = el.id || normalized.replace(/\s+/g, '-')
|
|
41
46
|
if (!el.id) el.id = id
|
|
42
47
|
|
|
48
|
+
// Deduplicate — skip if we've already seen this ID
|
|
49
|
+
if (seenIds.has(id)) return
|
|
50
|
+
seenIds.add(id)
|
|
51
|
+
|
|
43
52
|
items.push({
|
|
44
53
|
id,
|
|
45
54
|
text,
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
// Cache highlighter instances per loaded theme set
|
|
4
|
-
const highlighterCache = new Map<string, Highlighter>()
|
|
1
|
+
import { codeToHtml, type BundledTheme } from 'shiki'
|
|
5
2
|
|
|
6
3
|
export type ThemeName =
|
|
7
4
|
| 'catppuccin-mocha'
|
|
@@ -28,98 +25,16 @@ export const AVAILABLE_THEMES: { name: ThemeName; label: string; isDark: boolean
|
|
|
28
25
|
|
|
29
26
|
export const DEFAULT_THEME: ThemeName = 'catppuccin-mocha'
|
|
30
27
|
|
|
31
|
-
const SUPPORTED_LANGS = [
|
|
32
|
-
'typescript',
|
|
33
|
-
'javascript',
|
|
34
|
-
'python',
|
|
35
|
-
'go',
|
|
36
|
-
'rust',
|
|
37
|
-
'json',
|
|
38
|
-
'yaml',
|
|
39
|
-
'bash',
|
|
40
|
-
'shell',
|
|
41
|
-
'markdown',
|
|
42
|
-
'html',
|
|
43
|
-
'css',
|
|
44
|
-
'jsx',
|
|
45
|
-
'tsx',
|
|
46
|
-
'sql',
|
|
47
|
-
'graphql',
|
|
48
|
-
'diff',
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
// Start with just the default theme; load others on demand
|
|
52
|
-
let defaultHighlighter: Highlighter | null = null
|
|
53
|
-
let defaultHighlighterPromise: Promise<Highlighter> | null = null
|
|
54
|
-
|
|
55
|
-
async function getDefaultHighlighter(): Promise<Highlighter> {
|
|
56
|
-
if (defaultHighlighter) return defaultHighlighter
|
|
57
|
-
if (defaultHighlighterPromise) return defaultHighlighterPromise
|
|
58
|
-
|
|
59
|
-
defaultHighlighterPromise = createHighlighter({
|
|
60
|
-
themes: [DEFAULT_THEME] as BundledTheme[],
|
|
61
|
-
langs: SUPPORTED_LANGS,
|
|
62
|
-
}).then(hl => {
|
|
63
|
-
defaultHighlighter = hl
|
|
64
|
-
highlighterCache.set(DEFAULT_THEME, hl)
|
|
65
|
-
return hl
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
return defaultHighlighterPromise
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function getHighlighterForTheme(theme: ThemeName): Promise<Highlighter> {
|
|
72
|
-
if (theme === DEFAULT_THEME || highlighterCache.has(DEFAULT_THEME)) {
|
|
73
|
-
// Try to load the theme into the existing default highlighter
|
|
74
|
-
const hl = await getDefaultHighlighter()
|
|
75
|
-
try {
|
|
76
|
-
// Check if the theme is already loaded by attempting to use it
|
|
77
|
-
const loadedThemes = hl.getLoadedThemes()
|
|
78
|
-
if (!loadedThemes.includes(theme as BundledTheme)) {
|
|
79
|
-
await hl.loadTheme(theme as BundledTheme)
|
|
80
|
-
}
|
|
81
|
-
return hl
|
|
82
|
-
} catch {
|
|
83
|
-
// If loading into existing highlighter fails, create a new one
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Check cache
|
|
88
|
-
const cached = highlighterCache.get(theme)
|
|
89
|
-
if (cached) return cached
|
|
90
|
-
|
|
91
|
-
// Create a new highlighter with this theme
|
|
92
|
-
const hl = await createHighlighter({
|
|
93
|
-
themes: [theme] as BundledTheme[],
|
|
94
|
-
langs: SUPPORTED_LANGS,
|
|
95
|
-
})
|
|
96
|
-
highlighterCache.set(theme, hl)
|
|
97
|
-
return hl
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function getHighlighter(): Promise<Highlighter> {
|
|
101
|
-
return getDefaultHighlighter()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
28
|
export async function highlight(
|
|
105
29
|
code: string,
|
|
106
30
|
language: string,
|
|
107
31
|
theme: ThemeName = DEFAULT_THEME
|
|
108
32
|
): Promise<string> {
|
|
109
|
-
const hl = await getHighlighterForTheme(theme)
|
|
110
|
-
|
|
111
33
|
const lang = normalizeLanguage(language)
|
|
112
|
-
|
|
113
34
|
try {
|
|
114
|
-
return
|
|
115
|
-
lang,
|
|
116
|
-
theme: theme as BundledTheme,
|
|
117
|
-
})
|
|
35
|
+
return await codeToHtml(code, { lang, theme: theme as BundledTheme })
|
|
118
36
|
} catch {
|
|
119
|
-
return
|
|
120
|
-
lang: 'text',
|
|
121
|
-
theme: theme as BundledTheme,
|
|
122
|
-
})
|
|
37
|
+
return await codeToHtml(code, { lang: 'text', theme: theme as BundledTheme })
|
|
123
38
|
}
|
|
124
39
|
}
|
|
125
40
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export interface NavPage {
|
|
2
|
+
title: string
|
|
3
|
+
path: string
|
|
4
|
+
description?: string
|
|
5
|
+
pages?: NavPage[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface NavGroup {
|
|
9
|
+
group: string
|
|
10
|
+
icon?: string
|
|
11
|
+
pages: (NavPage | NavGroup)[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface NavTab {
|
|
15
|
+
tab: string
|
|
16
|
+
icon?: string
|
|
17
|
+
groups: NavGroup[]
|
|
18
|
+
href?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Navigation = NavGroup[] | NavTab[]
|
|
22
|
+
|
|
23
|
+
export function isNavGroup(item: NavPage | NavGroup): item is NavGroup {
|
|
24
|
+
return 'group' in item && !('path' in item)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Check if navigation uses tabs format */
|
|
28
|
+
export function hasTabs(navigation: Navigation): navigation is NavTab[] {
|
|
29
|
+
return navigation.length > 0 && 'tab' in navigation[0]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Recursively check if a page path exists within a list of groups */
|
|
33
|
+
function groupsContainPath(groups: NavGroup[], pathname: string): boolean {
|
|
34
|
+
for (const group of groups) {
|
|
35
|
+
for (const item of group.pages) {
|
|
36
|
+
if (isNavGroup(item)) {
|
|
37
|
+
if (groupsContainPath([item], pathname)) return true
|
|
38
|
+
} else {
|
|
39
|
+
if (item.path === pathname) return true
|
|
40
|
+
if (item.pages && pagesContainPath(item.pages, pathname)) return true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function pagesContainPath(pages: NavPage[], pathname: string): boolean {
|
|
48
|
+
for (const page of pages) {
|
|
49
|
+
if (page.path === pathname) return true
|
|
50
|
+
if (page.pages && pagesContainPath(page.pages, pathname)) return true
|
|
51
|
+
}
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Find the active tab based on current pathname */
|
|
56
|
+
export function getActiveTab(pathname: string, tabs: NavTab[]): NavTab | null {
|
|
57
|
+
for (const tab of tabs) {
|
|
58
|
+
if (tab.href && tab.href === pathname) return tab
|
|
59
|
+
if (tab.groups && groupsContainPath(tab.groups, pathname)) return tab
|
|
60
|
+
}
|
|
61
|
+
// Default to first tab if no match
|
|
62
|
+
return tabs.length > 0 ? tabs[0] : null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Get sidebar groups for current pathname (works for both flat and tabbed nav) */
|
|
66
|
+
export function getTabGroups(navigation: Navigation, pathname: string): NavGroup[] {
|
|
67
|
+
if (!hasTabs(navigation)) return navigation
|
|
68
|
+
const active = getActiveTab(pathname, navigation)
|
|
69
|
+
return active?.groups ?? []
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Get first page path for a tab (for linking) */
|
|
73
|
+
export function getTabHref(tab: NavTab): string {
|
|
74
|
+
if (tab.href) return tab.href
|
|
75
|
+
for (const group of tab.groups) {
|
|
76
|
+
for (const item of group.pages) {
|
|
77
|
+
if (isNavGroup(item)) {
|
|
78
|
+
for (const subItem of item.pages) {
|
|
79
|
+
if (!isNavGroup(subItem) && subItem.path) return subItem.path
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
return item.path
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return '/'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Flatten all pages across all tabs/groups for prev/next navigation */
|
|
90
|
+
export function getAllPagesFlat(navigation: Navigation): Array<{ title: string; path: string }> {
|
|
91
|
+
const pages: Array<{ title: string; path: string }> = []
|
|
92
|
+
|
|
93
|
+
function collectFromPages(items: (NavPage | NavGroup)[]) {
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
if (isNavGroup(item)) {
|
|
96
|
+
collectFromPages(item.pages)
|
|
97
|
+
} else {
|
|
98
|
+
pages.push({ title: item.title, path: item.path })
|
|
99
|
+
if (item.pages) collectFromPages(item.pages)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (hasTabs(navigation)) {
|
|
105
|
+
for (const tab of navigation) {
|
|
106
|
+
if (tab.groups) {
|
|
107
|
+
for (const group of tab.groups) {
|
|
108
|
+
collectFromPages(group.pages)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
for (const group of navigation) {
|
|
114
|
+
collectFromPages(group.pages)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return pages
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Look up page info from navigation (works with both formats) */
|
|
122
|
+
export function findPageInNavigation(
|
|
123
|
+
navigation: Navigation,
|
|
124
|
+
pathname: string
|
|
125
|
+
): { title?: string; description?: string } {
|
|
126
|
+
function searchPages(items: (NavPage | NavGroup)[]): { title?: string; description?: string } | null {
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
if (isNavGroup(item)) {
|
|
129
|
+
const found = searchPages(item.pages)
|
|
130
|
+
if (found) return found
|
|
131
|
+
} else {
|
|
132
|
+
if (item.path === pathname) return { title: item.title, description: item.description }
|
|
133
|
+
if (item.pages) {
|
|
134
|
+
const found = searchPages(item.pages)
|
|
135
|
+
if (found) return found
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (hasTabs(navigation)) {
|
|
143
|
+
for (const tab of navigation) {
|
|
144
|
+
if (tab.groups) {
|
|
145
|
+
for (const group of tab.groups) {
|
|
146
|
+
const found = searchPages(group.pages)
|
|
147
|
+
if (found) return found
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
for (const group of navigation) {
|
|
153
|
+
const found = searchPages(group.pages)
|
|
154
|
+
if (found) return found
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {}
|
|
159
|
+
}
|
|
@@ -80,14 +80,14 @@
|
|
|
80
80
|
--color-bg: #0f1117;
|
|
81
81
|
--color-bg-secondary: #1a1d27;
|
|
82
82
|
--color-bg-tertiary: #252833;
|
|
83
|
-
--color-text: #
|
|
84
|
-
--color-text-secondary: #
|
|
83
|
+
--color-text: #e0e2e6;
|
|
84
|
+
--color-text-secondary: #b0b4bc;
|
|
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
88
|
--color-code-bg: #0b0c0e;
|
|
89
89
|
--color-code-border: rgba(255, 255, 255, 0.1);
|
|
90
|
-
--color-code-text: #
|
|
90
|
+
--color-code-text: #e0e2e6;
|
|
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: #
|
|
99
|
-
--color-text-secondary: #
|
|
98
|
+
--color-text: #e0e2e6;
|
|
99
|
+
--color-text-secondary: #b0b4bc;
|
|
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
103
|
--color-code-bg: #0b0c0e;
|
|
104
104
|
--color-code-border: rgba(255, 255, 255, 0.1);
|
|
105
|
-
--color-code-text: #
|
|
105
|
+
--color-code-text: #e0e2e6;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/* Error page icon — dark mode overrides (avoids Tailwind dark: variant mismatch) */
|
|
@@ -257,6 +257,17 @@ code {
|
|
|
257
257
|
border-bottom-width: 2px;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/* Cards and card groups should not inherit prose link styling */
|
|
261
|
+
.prose .card-link,
|
|
262
|
+
.prose a:has(> .group) {
|
|
263
|
+
border-bottom: none !important;
|
|
264
|
+
font-weight: inherit;
|
|
265
|
+
}
|
|
266
|
+
.prose .card-link:hover,
|
|
267
|
+
.prose a:has(> .group):hover {
|
|
268
|
+
border-bottom: none !important;
|
|
269
|
+
}
|
|
270
|
+
|
|
260
271
|
.prose p {
|
|
261
272
|
margin-top: 1.25em;
|
|
262
273
|
margin-bottom: 1.25em;
|
package/dist/utils/validation.js
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
|
-
export function validatePath(input, mustExist = true) {
|
|
4
|
-
const resolved = resolve(input);
|
|
5
|
-
// Prevent path traversal outside cwd
|
|
6
|
-
if (!resolved.startsWith(process.cwd()) && !resolved.startsWith('/tmp')) {
|
|
7
|
-
// Allow absolute paths but warn
|
|
8
|
-
}
|
|
9
|
-
if (mustExist && !existsSync(resolved)) {
|
|
10
|
-
throw new Error(`Path does not exist: ${resolved}`);
|
|
11
|
-
}
|
|
12
|
-
return resolved;
|
|
13
|
-
}
|
|
14
1
|
export function validateUrl(input) {
|
|
15
2
|
try {
|
|
16
3
|
const url = new URL(input);
|
|
@@ -23,16 +10,3 @@ export function validateUrl(input) {
|
|
|
23
10
|
throw new Error(`Invalid URL: ${input}`);
|
|
24
11
|
}
|
|
25
12
|
}
|
|
26
|
-
export function validateSlug(input) {
|
|
27
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
|
28
|
-
throw new Error(`Invalid slug: ${input}. Only alphanumeric, hyphens, and underscores allowed.`);
|
|
29
|
-
}
|
|
30
|
-
return input;
|
|
31
|
-
}
|
|
32
|
-
export function sanitizeForShell(input) {
|
|
33
|
-
// Only allow safe characters for git refs, filenames, etc.
|
|
34
|
-
if (!/^[a-zA-Z0-9/_~.^@{}-]+$/.test(input)) {
|
|
35
|
-
throw new Error(`Unsafe characters in input: ${input}`);
|
|
36
|
-
}
|
|
37
|
-
return input;
|
|
38
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skrypt-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "AI-powered documentation generator with code examples",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"typescript": "^5.9.3"
|
|
56
56
|
},
|
|
57
57
|
"optionalDependencies": {
|
|
58
|
-
"@napi-rs/keyring": "^1.1.6"
|
|
58
|
+
"@napi-rs/keyring": "^1.1.6",
|
|
59
|
+
"playwright": "^1.52.0"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@eslint/js": "^10.0.1",
|