skrypt-ai 0.6.1 → 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.
- package/dist/audit/doc-parser.d.ts +5 -0
- package/dist/audit/doc-parser.js +106 -0
- package/dist/audit/index.d.ts +4 -0
- package/dist/audit/index.js +4 -0
- package/dist/audit/matcher.d.ts +6 -0
- package/dist/audit/matcher.js +94 -0
- package/dist/audit/reporter.d.ts +9 -0
- package/dist/audit/reporter.js +106 -0
- package/dist/audit/types.d.ts +37 -0
- package/dist/auth/index.js +6 -4
- package/dist/cli.js +12 -2
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +59 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +73 -0
- package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -0
- package/dist/commands/generate/index.js +393 -0
- package/dist/commands/generate/scan.d.ts +41 -0
- package/dist/commands/generate/scan.js +256 -0
- package/dist/commands/generate/verify.d.ts +14 -0
- package/dist/commands/generate/verify.js +122 -0
- package/dist/commands/generate/write.d.ts +25 -0
- package/dist/commands/generate/write.js +120 -0
- package/dist/commands/import.js +4 -1
- package/dist/commands/llms-txt.js +6 -4
- package/dist/commands/refresh.d.ts +2 -0
- package/dist/commands/refresh.js +158 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.js +110 -0
- package/dist/commands/test.js +177 -236
- package/dist/commands/watch.js +29 -20
- package/dist/config/loader.d.ts +6 -2
- package/dist/config/loader.js +39 -3
- package/dist/config/types.d.ts +7 -0
- package/dist/generator/agents-md.d.ts +25 -0
- package/dist/generator/agents-md.js +122 -0
- package/dist/generator/generator.js +2 -1
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/mdx-serializer.d.ts +11 -0
- package/dist/generator/mdx-serializer.js +135 -0
- package/dist/generator/organizer.d.ts +1 -16
- package/dist/generator/organizer.js +0 -38
- package/dist/generator/types.d.ts +3 -0
- package/dist/generator/writer.js +65 -32
- package/dist/github/org-discovery.d.ts +17 -0
- package/dist/github/org-discovery.js +93 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +8 -2
- package/dist/llm/proxy-client.d.ts +32 -0
- package/dist/llm/proxy-client.js +103 -0
- package/dist/next-actions/actions.d.ts +2 -0
- package/dist/next-actions/actions.js +190 -0
- package/dist/next-actions/index.d.ts +6 -0
- package/dist/next-actions/index.js +39 -0
- package/dist/next-actions/setup.d.ts +2 -0
- package/dist/next-actions/setup.js +72 -0
- package/dist/next-actions/state.d.ts +7 -0
- package/dist/next-actions/state.js +68 -0
- package/dist/next-actions/suggest.d.ts +3 -0
- package/dist/next-actions/suggest.js +47 -0
- package/dist/next-actions/types.d.ts +26 -0
- package/dist/refresh/differ.d.ts +9 -0
- package/dist/refresh/differ.js +67 -0
- package/dist/refresh/index.d.ts +4 -0
- package/dist/refresh/index.js +4 -0
- package/dist/refresh/manifest.d.ts +18 -0
- package/dist/refresh/manifest.js +71 -0
- package/dist/refresh/splicer.d.ts +9 -0
- package/dist/refresh/splicer.js +50 -0
- package/dist/refresh/types.d.ts +37 -0
- package/dist/review/index.d.ts +8 -0
- package/dist/review/index.js +94 -0
- package/dist/review/parser.d.ts +16 -0
- package/dist/review/parser.js +95 -0
- package/dist/review/types.d.ts +18 -0
- package/dist/scanner/csharp.d.ts +0 -4
- package/dist/scanner/csharp.js +9 -49
- package/dist/scanner/go.d.ts +0 -3
- package/dist/scanner/go.js +8 -35
- package/dist/scanner/java.d.ts +0 -4
- package/dist/scanner/java.js +9 -49
- package/dist/scanner/kotlin.d.ts +0 -3
- package/dist/scanner/kotlin.js +6 -33
- package/dist/scanner/php.d.ts +0 -10
- package/dist/scanner/php.js +11 -55
- package/dist/scanner/ruby.d.ts +0 -3
- package/dist/scanner/ruby.js +8 -38
- package/dist/scanner/rust.d.ts +0 -3
- package/dist/scanner/rust.js +10 -37
- package/dist/scanner/swift.d.ts +0 -3
- package/dist/scanner/swift.js +8 -35
- package/dist/scanner/types.d.ts +2 -0
- package/dist/scanner/utils.d.ts +41 -0
- package/dist/scanner/utils.js +97 -0
- package/dist/structure/index.d.ts +19 -0
- package/dist/structure/index.js +92 -0
- package/dist/structure/planner.d.ts +8 -0
- package/dist/structure/planner.js +180 -0
- package/dist/structure/topology.d.ts +16 -0
- package/dist/structure/topology.js +49 -0
- package/dist/structure/types.d.ts +26 -0
- package/dist/template/docs.json +5 -2
- package/dist/template/next.config.mjs +31 -0
- package/dist/template/package.json +5 -3
- package/dist/template/src/app/layout.tsx +13 -13
- package/dist/template/src/app/llms-full.md/route.ts +29 -0
- package/dist/template/src/app/llms.txt/route.ts +29 -0
- package/dist/template/src/app/md/[...slug]/route.ts +174 -0
- package/dist/template/src/app/reference/route.ts +22 -18
- package/dist/template/src/app/sitemap.ts +1 -1
- package/dist/template/src/components/ai-chat-impl.tsx +206 -0
- package/dist/template/src/components/ai-chat.tsx +20 -193
- package/dist/template/src/components/mdx/index.tsx +27 -4
- package/dist/template/src/lib/fonts.ts +135 -0
- package/dist/template/src/middleware.ts +101 -0
- package/dist/template/src/styles/globals.css +28 -20
- package/dist/testing/comparator.d.ts +7 -0
- package/dist/testing/comparator.js +77 -0
- package/dist/testing/docker.d.ts +21 -0
- package/dist/testing/docker.js +234 -0
- package/dist/testing/env.d.ts +16 -0
- package/dist/testing/env.js +58 -0
- package/dist/testing/extractor.d.ts +9 -0
- package/dist/testing/extractor.js +195 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/runner.d.ts +5 -0
- package/dist/testing/runner.js +225 -0
- package/dist/testing/types.d.ts +58 -0
- package/dist/utils/files.d.ts +0 -8
- package/dist/utils/files.js +0 -33
- package/package.json +1 -1
- package/dist/autofix/autofix.test.js +0 -487
- package/dist/commands/generate.js +0 -445
- package/dist/generator/generator.test.js +0 -259
- package/dist/generator/writer.test.js +0 -411
- package/dist/llm/llm.manual-test.js +0 -112
- package/dist/llm/llm.mock-test.d.ts +0 -4
- package/dist/llm/llm.mock-test.js +0 -79
- package/dist/plugins/index.d.ts +0 -47
- package/dist/plugins/index.js +0 -181
- package/dist/scanner/content-type.test.js +0 -231
- package/dist/scanner/integration.test.d.ts +0 -4
- package/dist/scanner/integration.test.js +0 -180
- package/dist/scanner/scanner.test.js +0 -210
- package/dist/scanner/typescript.manual-test.d.ts +0 -1
- package/dist/scanner/typescript.manual-test.js +0 -112
- package/dist/template/src/app/docs/auth/page.mdx +0 -589
- package/dist/template/src/app/docs/autofix/page.mdx +0 -624
- package/dist/template/src/app/docs/cli/page.mdx +0 -217
- package/dist/template/src/app/docs/config/page.mdx +0 -428
- package/dist/template/src/app/docs/configuration/page.mdx +0 -86
- package/dist/template/src/app/docs/deployment/page.mdx +0 -112
- package/dist/template/src/app/docs/generator/generator.md +0 -504
- package/dist/template/src/app/docs/generator/organizer.md +0 -779
- package/dist/template/src/app/docs/generator/page.mdx +0 -613
- package/dist/template/src/app/docs/github/page.mdx +0 -502
- package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
- package/dist/template/src/app/docs/llm/index.md +0 -471
- package/dist/template/src/app/docs/llm/page.mdx +0 -428
- package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
- package/dist/template/src/app/docs/pro/page.mdx +0 -121
- package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
- package/dist/template/src/app/docs/scanner/content-type.md +0 -599
- package/dist/template/src/app/docs/scanner/index.md +0 -212
- package/dist/template/src/app/docs/scanner/page.mdx +0 -307
- package/dist/template/src/app/docs/scanner/python.md +0 -469
- package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
- package/dist/template/src/app/docs/scanner/rust.md +0 -325
- package/dist/template/src/app/docs/scanner/typescript.md +0 -201
- package/dist/template/src/app/icon.tsx +0 -29
- package/dist/utils/validation.d.ts +0 -1
- package/dist/utils/validation.js +0 -12
- /package/dist/{autofix/autofix.test.d.ts → audit/types.js} +0 -0
- /package/dist/{generator/generator.test.d.ts → next-actions/types.js} +0 -0
- /package/dist/{generator/writer.test.d.ts → refresh/types.js} +0 -0
- /package/dist/{llm/llm.manual-test.d.ts → review/types.js} +0 -0
- /package/dist/{scanner/content-type.test.d.ts → structure/types.js} +0 -0
- /package/dist/{scanner/scanner.test.d.ts → testing/types.js} +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use LLM to plan a smart documentation structure
|
|
3
|
+
*/
|
|
4
|
+
export async function planStructure(elements, topology, client) {
|
|
5
|
+
// Build a summary of the API surface for the LLM
|
|
6
|
+
const summary = buildAPISummary(elements, topology);
|
|
7
|
+
const response = await client.complete({
|
|
8
|
+
messages: [
|
|
9
|
+
{
|
|
10
|
+
role: 'system',
|
|
11
|
+
content: STRUCTURE_PLANNER_PROMPT,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
role: 'user',
|
|
15
|
+
content: summary,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
temperature: 0,
|
|
19
|
+
maxTokens: 4096,
|
|
20
|
+
});
|
|
21
|
+
return parseStructurePlan(response.content, elements);
|
|
22
|
+
}
|
|
23
|
+
const STRUCTURE_PLANNER_PROMPT = `You are an expert documentation architect. Given a list of API elements, plan a documentation structure organized by user journey — NOT by file.
|
|
24
|
+
|
|
25
|
+
## Output Structure
|
|
26
|
+
|
|
27
|
+
Return a JSON object with this shape:
|
|
28
|
+
\`\`\`json
|
|
29
|
+
{
|
|
30
|
+
"pages": [
|
|
31
|
+
{
|
|
32
|
+
"slug": "quickstart",
|
|
33
|
+
"title": "Quickstart",
|
|
34
|
+
"category": "quickstart",
|
|
35
|
+
"description": "Get from zero to first API call in 5 minutes",
|
|
36
|
+
"elementNames": ["createClient", "configure"]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"slug": "authentication",
|
|
40
|
+
"title": "Authentication",
|
|
41
|
+
"category": "concepts",
|
|
42
|
+
"description": "How auth works — API keys, OAuth, sessions",
|
|
43
|
+
"elementNames": ["authenticate", "refreshToken", "SessionManager"]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
\`\`\`
|
|
48
|
+
|
|
49
|
+
## Rules
|
|
50
|
+
1. ALWAYS start with a "quickstart" page covering entry points
|
|
51
|
+
2. Group by CONCEPT (auth, data model, errors), not by file
|
|
52
|
+
3. Put classes and their methods together on one page
|
|
53
|
+
4. "api" category pages are per-class or per-module API reference
|
|
54
|
+
5. "guides" are task-oriented: "Setting up webhooks", "Testing your integration"
|
|
55
|
+
6. Keep slugs lowercase-kebab-case
|
|
56
|
+
7. Every element must appear in exactly one page
|
|
57
|
+
8. Return ONLY the JSON, no markdown fences, no explanation`;
|
|
58
|
+
function buildAPISummary(elements, topology) {
|
|
59
|
+
let summary = `## API Surface (${elements.length} elements)\n\n`;
|
|
60
|
+
if (topology.entryPoints.length > 0) {
|
|
61
|
+
summary += `### Entry Points\n`;
|
|
62
|
+
for (const ep of topology.entryPoints) {
|
|
63
|
+
summary += `- ${ep.kind} \`${ep.name}\` (${ep.filePath})\n`;
|
|
64
|
+
}
|
|
65
|
+
summary += '\n';
|
|
66
|
+
}
|
|
67
|
+
summary += `### Public API (${topology.publicAPI.length} elements)\n`;
|
|
68
|
+
for (const el of topology.publicAPI.slice(0, 100)) {
|
|
69
|
+
const parent = el.parentClass ? ` (${el.parentClass})` : '';
|
|
70
|
+
summary += `- ${el.kind} \`${el.name}\`${parent}: ${el.signature.slice(0, 80)}\n`;
|
|
71
|
+
}
|
|
72
|
+
if (topology.publicAPI.length > 100) {
|
|
73
|
+
summary += `... and ${topology.publicAPI.length - 100} more\n`;
|
|
74
|
+
}
|
|
75
|
+
if (topology.classClusters.size > 0) {
|
|
76
|
+
summary += `\n### Classes (${topology.classClusters.size})\n`;
|
|
77
|
+
for (const [className, methods] of topology.classClusters) {
|
|
78
|
+
summary += `- \`${className}\` with methods: ${methods.map(m => m.name).join(', ')}\n`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return summary;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parse LLM response into DocStructure
|
|
85
|
+
*/
|
|
86
|
+
function parseStructurePlan(content, elements) {
|
|
87
|
+
// Extract JSON from response
|
|
88
|
+
let jsonStr = content.trim();
|
|
89
|
+
// Remove markdown fences if present
|
|
90
|
+
jsonStr = jsonStr.replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```\s*$/, '');
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = JSON.parse(jsonStr);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Fallback: create a simple structure
|
|
97
|
+
return createFallbackStructure(elements);
|
|
98
|
+
}
|
|
99
|
+
// Map element names to actual elements (case-insensitive for LLM tolerance)
|
|
100
|
+
const elementByName = new Map();
|
|
101
|
+
const elementByNameLower = new Map();
|
|
102
|
+
for (const el of elements) {
|
|
103
|
+
elementByName.set(el.name, el);
|
|
104
|
+
elementByNameLower.set(el.name.toLowerCase(), el);
|
|
105
|
+
}
|
|
106
|
+
const pages = [];
|
|
107
|
+
const assignedElements = new Set();
|
|
108
|
+
for (const page of parsed.pages) {
|
|
109
|
+
const pageElements = [];
|
|
110
|
+
for (const name of page.elementNames || []) {
|
|
111
|
+
// Try exact match first, then case-insensitive
|
|
112
|
+
let el = elementByName.get(name);
|
|
113
|
+
if (!el) {
|
|
114
|
+
el = elementByNameLower.get(name.toLowerCase());
|
|
115
|
+
if (el) {
|
|
116
|
+
console.log(` Note: LLM returned "${name}", matched to "${el.name}" (case-insensitive)`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (el) {
|
|
120
|
+
pageElements.push(el);
|
|
121
|
+
assignedElements.add(el.name);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.log(` Warning: LLM referenced unknown element "${name}" — skipping`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const category = (['quickstart', 'concepts', 'guides', 'api'].includes(page.category)
|
|
128
|
+
? page.category
|
|
129
|
+
: 'api');
|
|
130
|
+
pages.push({
|
|
131
|
+
slug: page.slug,
|
|
132
|
+
title: page.title,
|
|
133
|
+
category,
|
|
134
|
+
description: page.description,
|
|
135
|
+
elements: pageElements,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Add unassigned elements to an "API Reference" catch-all
|
|
139
|
+
const unassigned = elements.filter(el => !assignedElements.has(el.name));
|
|
140
|
+
if (unassigned.length > 0) {
|
|
141
|
+
pages.push({
|
|
142
|
+
slug: 'api-reference',
|
|
143
|
+
title: 'API Reference',
|
|
144
|
+
category: 'api',
|
|
145
|
+
description: 'Complete API reference',
|
|
146
|
+
elements: unassigned,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// Build navigation
|
|
150
|
+
const navigation = buildNavigation(pages);
|
|
151
|
+
return { pages, navigation };
|
|
152
|
+
}
|
|
153
|
+
function createFallbackStructure(elements) {
|
|
154
|
+
const pages = [{
|
|
155
|
+
slug: 'api-reference',
|
|
156
|
+
title: 'API Reference',
|
|
157
|
+
category: 'api',
|
|
158
|
+
description: 'Complete API reference',
|
|
159
|
+
elements,
|
|
160
|
+
}];
|
|
161
|
+
return {
|
|
162
|
+
pages,
|
|
163
|
+
navigation: [{ title: 'API Reference', slug: 'api-reference' }],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function buildNavigation(pages) {
|
|
167
|
+
const categories = {
|
|
168
|
+
quickstart: { title: 'Getting Started', slug: '', children: [] },
|
|
169
|
+
concepts: { title: 'Concepts', slug: '', children: [] },
|
|
170
|
+
guides: { title: 'Guides', slug: '', children: [] },
|
|
171
|
+
api: { title: 'API Reference', slug: '', children: [] },
|
|
172
|
+
};
|
|
173
|
+
for (const page of pages) {
|
|
174
|
+
const cat = categories[page.category];
|
|
175
|
+
if (cat) {
|
|
176
|
+
cat.children.push({ title: page.title, slug: page.slug });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return Object.values(categories).filter(c => c.children.length > 0);
|
|
180
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { APIElement } from '../scanner/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Analysis of a codebase's API surface
|
|
4
|
+
*/
|
|
5
|
+
export interface TopologyAnalysis {
|
|
6
|
+
entryPoints: APIElement[];
|
|
7
|
+
publicAPI: APIElement[];
|
|
8
|
+
internalElements: APIElement[];
|
|
9
|
+
classClusters: Map<string, APIElement[]>;
|
|
10
|
+
fileClusters: Map<string, APIElement[]>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Analyze the topology of a set of API elements.
|
|
14
|
+
* Identifies entry points, public surface, class groupings, and file clusters.
|
|
15
|
+
*/
|
|
16
|
+
export declare function analyzeTopology(elements: APIElement[]): TopologyAnalysis;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze the topology of a set of API elements.
|
|
3
|
+
* Identifies entry points, public surface, class groupings, and file clusters.
|
|
4
|
+
*/
|
|
5
|
+
export function analyzeTopology(elements) {
|
|
6
|
+
const entryPoints = [];
|
|
7
|
+
const publicAPI = [];
|
|
8
|
+
const internalElements = [];
|
|
9
|
+
const classClusters = new Map();
|
|
10
|
+
const fileClusters = new Map();
|
|
11
|
+
for (const el of elements) {
|
|
12
|
+
// Classify by visibility
|
|
13
|
+
if (el.isExported || el.isPublic) {
|
|
14
|
+
publicAPI.push(el);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
internalElements.push(el);
|
|
18
|
+
}
|
|
19
|
+
// Identify likely entry points (exported top-level functions/classes)
|
|
20
|
+
if ((el.isExported || el.isPublic) && !el.parentClass && (el.kind === 'function' || el.kind === 'class')) {
|
|
21
|
+
// Heuristic: entry points are often in index.ts, main.ts, or have names like init, create, setup
|
|
22
|
+
const isEntryFile = /(?:index|main|app|server|client)\.\w+$/.test(el.filePath);
|
|
23
|
+
const isEntryName = /^(?:create|init|setup|configure|connect|start|build)/i.test(el.name);
|
|
24
|
+
if (isEntryFile || isEntryName) {
|
|
25
|
+
entryPoints.push(el);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Group by parent class
|
|
29
|
+
if (el.parentClass) {
|
|
30
|
+
if (!classClusters.has(el.parentClass)) {
|
|
31
|
+
classClusters.set(el.parentClass, []);
|
|
32
|
+
}
|
|
33
|
+
classClusters.get(el.parentClass).push(el);
|
|
34
|
+
}
|
|
35
|
+
// Group by source file
|
|
36
|
+
const fileKey = el.filePath.replace(/\.(ts|js|py|go|rs|java|cs|php|kt|swift|rb)x?$/, '');
|
|
37
|
+
if (!fileClusters.has(fileKey)) {
|
|
38
|
+
fileClusters.set(fileKey, []);
|
|
39
|
+
}
|
|
40
|
+
fileClusters.get(fileKey).push(el);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
entryPoints,
|
|
44
|
+
publicAPI,
|
|
45
|
+
internalElements,
|
|
46
|
+
classClusters,
|
|
47
|
+
fileClusters,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { APIElement } from '../scanner/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A planned page in the smart-structured docs
|
|
4
|
+
*/
|
|
5
|
+
export interface PagePlan {
|
|
6
|
+
slug: string;
|
|
7
|
+
title: string;
|
|
8
|
+
category: 'quickstart' | 'concepts' | 'guides' | 'api';
|
|
9
|
+
description: string;
|
|
10
|
+
elements: APIElement[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Full documentation structure plan
|
|
14
|
+
*/
|
|
15
|
+
export interface DocStructure {
|
|
16
|
+
pages: PagePlan[];
|
|
17
|
+
navigation: NavigationNode[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Navigation tree node
|
|
21
|
+
*/
|
|
22
|
+
export interface NavigationNode {
|
|
23
|
+
title: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
children?: NavigationNode[];
|
|
26
|
+
}
|
package/dist/template/docs.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Skrypt",
|
|
3
3
|
"description": "AI-powered documentation generation",
|
|
4
|
+
"fonts": {
|
|
5
|
+
"sans": "Inter",
|
|
6
|
+
"mono": "JetBrains Mono"
|
|
7
|
+
},
|
|
4
8
|
"theme": {
|
|
5
9
|
"primaryColor": "#3b82f6",
|
|
6
|
-
"accentColor": "#8b5cf6"
|
|
7
|
-
"font": "Inter"
|
|
10
|
+
"accentColor": "#8b5cf6"
|
|
8
11
|
},
|
|
9
12
|
"navigation": [
|
|
10
13
|
{
|
|
@@ -14,6 +14,37 @@ const withMDX = createMDX({
|
|
|
14
14
|
const nextConfig = {
|
|
15
15
|
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
|
|
16
16
|
transpilePackages: ['@scalar/api-reference-react', '@scalar/api-reference'],
|
|
17
|
+
async headers() {
|
|
18
|
+
return [{
|
|
19
|
+
source: '/(.*)',
|
|
20
|
+
headers: [
|
|
21
|
+
{
|
|
22
|
+
key: 'Content-Security-Policy',
|
|
23
|
+
value: "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https: https://fonts.gstatic.com; connect-src 'self' https:; frame-src 'self' https://codesandbox.io"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: 'X-Frame-Options',
|
|
27
|
+
value: 'DENY'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'X-Content-Type-Options',
|
|
31
|
+
value: 'nosniff'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: 'Referrer-Policy',
|
|
35
|
+
value: 'strict-origin-when-cross-origin'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'Strict-Transport-Security',
|
|
39
|
+
value: 'max-age=31536000; includeSubDomains'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: 'Permissions-Policy',
|
|
43
|
+
value: 'camera=(), microphone=(), geolocation=()'
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}]
|
|
47
|
+
},
|
|
17
48
|
}
|
|
18
49
|
|
|
19
50
|
export default withMDX(nextConfig)
|
|
@@ -13,16 +13,13 @@
|
|
|
13
13
|
"@ai-sdk/anthropic": "^1.0.0",
|
|
14
14
|
"@ai-sdk/openai": "^1.0.0",
|
|
15
15
|
"@ai-sdk/react": "^1.0.0",
|
|
16
|
-
"@codesandbox/sandpack-react": "^2.20.0",
|
|
17
16
|
"@mdx-js/loader": "^3.1.0",
|
|
18
17
|
"@mdx-js/react": "^3.1.0",
|
|
19
18
|
"@next/mdx": "^15.3.0",
|
|
20
19
|
"@orama/orama": "^3.1.0",
|
|
21
|
-
"@scalar/nextjs-api-reference": "^0.4.0",
|
|
22
20
|
"clsx": "^2.1.0",
|
|
23
21
|
"gray-matter": "^4.0.3",
|
|
24
22
|
"lucide-react": "^0.500.0",
|
|
25
|
-
"mermaid": "^11.13.0",
|
|
26
23
|
"next": "^15.3.0",
|
|
27
24
|
"next-mdx-remote": "^6.0.0",
|
|
28
25
|
"react": "^19.0.0",
|
|
@@ -35,6 +32,11 @@
|
|
|
35
32
|
"tailwind-merge": "^3.0.0",
|
|
36
33
|
"yaml": "^2.8.2"
|
|
37
34
|
},
|
|
35
|
+
"optionalDependencies": {
|
|
36
|
+
"@codesandbox/sandpack-react": "^2.20.0",
|
|
37
|
+
"mermaid": "^11.13.0",
|
|
38
|
+
"@scalar/nextjs-api-reference": "^0.4.0"
|
|
39
|
+
},
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"@tailwindcss/postcss": "^4.1.0",
|
|
40
42
|
"@types/mdx": "^2.0.0",
|
|
@@ -5,12 +5,17 @@ import { SyntaxThemeProvider } from '@/contexts/syntax-theme'
|
|
|
5
5
|
import { readFileSync } from 'fs'
|
|
6
6
|
import { join } from 'path'
|
|
7
7
|
import { derivePrimaryColors } from '@/lib/theme-utils'
|
|
8
|
+
import { resolveFonts, buildGoogleFontsUrl } from '@/lib/fonts'
|
|
8
9
|
|
|
9
10
|
const inter = Inter({ subsets: ['latin'] })
|
|
10
11
|
|
|
11
12
|
function getDocsConfig() {
|
|
12
13
|
const configPath = join(process.cwd(), 'docs.json')
|
|
13
|
-
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(readFileSync(configPath, 'utf-8'))
|
|
16
|
+
} catch {
|
|
17
|
+
return {}
|
|
18
|
+
}
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
const docsConfig = getDocsConfig()
|
|
@@ -19,19 +24,13 @@ const siteName = docsConfig.name || 'Documentation'
|
|
|
19
24
|
const siteDescription = docsConfig.description || 'Documentation'
|
|
20
25
|
|
|
21
26
|
// Theme: derive primary color variants from docs.json
|
|
22
|
-
const
|
|
27
|
+
const rawColor = docsConfig.theme?.primaryColor
|
|
28
|
+
const primaryHex = typeof rawColor === 'string' && /^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$/.test(rawColor) ? rawColor : '#3b82f6'
|
|
23
29
|
const { primary, primaryDark, primaryLight, primaryForeground } = derivePrimaryColors(primaryHex)
|
|
24
30
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const isCustomFont = safeFont !== 'Inter'
|
|
29
|
-
const googleFontsUrl = isCustomFont
|
|
30
|
-
? `https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont!)}:wght@400;500;600;700;800&display=swap`
|
|
31
|
-
: null
|
|
32
|
-
const googleFontsPreloadUrl = isCustomFont
|
|
33
|
-
? `https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont!)}:wght@400;600;700&display=swap`
|
|
34
|
-
: null
|
|
31
|
+
// Fonts: read from docs.json (supports both new `fonts` key and legacy `theme.font`)
|
|
32
|
+
const { sans, mono, needsGoogleFonts } = resolveFonts(docsConfig)
|
|
33
|
+
const googleFontsUrl = needsGoogleFonts ? buildGoogleFontsUrl(sans, mono) : null
|
|
35
34
|
|
|
36
35
|
// Build the CSS custom property overrides
|
|
37
36
|
const themeStyles = `
|
|
@@ -40,7 +39,8 @@ const themeStyles = `
|
|
|
40
39
|
--color-primary-dark: ${primaryDark};
|
|
41
40
|
--color-primary-light: ${primaryLight};
|
|
42
41
|
--color-primary-foreground: ${primaryForeground};
|
|
43
|
-
--font-body: "${
|
|
42
|
+
--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;
|
|
44
44
|
}
|
|
45
45
|
`
|
|
46
46
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { readFile } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const paths = [
|
|
7
|
+
join(process.cwd(), 'content', 'docs', 'llms-full.md'),
|
|
8
|
+
join(process.cwd(), 'public', 'llms-full.md'),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
for (const path of paths) {
|
|
12
|
+
try {
|
|
13
|
+
const content = await readFile(path, 'utf-8')
|
|
14
|
+
return new NextResponse(content, {
|
|
15
|
+
headers: {
|
|
16
|
+
'Content-Type': 'text/markdown; charset=utf-8',
|
|
17
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=86400',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
} catch {
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new NextResponse('# No llms-full.md found\n\nRun `skrypt generate` to create one.\n', {
|
|
26
|
+
status: 404,
|
|
27
|
+
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
|
|
28
|
+
})
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { readFile } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const paths = [
|
|
7
|
+
join(process.cwd(), 'content', 'docs', 'llms.txt'),
|
|
8
|
+
join(process.cwd(), 'public', 'llms.txt'),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
for (const path of paths) {
|
|
12
|
+
try {
|
|
13
|
+
const content = await readFile(path, 'utf-8')
|
|
14
|
+
return new NextResponse(content, {
|
|
15
|
+
headers: {
|
|
16
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
17
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=86400',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
} catch {
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new NextResponse('# No llms.txt found\n\nRun `skrypt generate` to create one.\n', {
|
|
26
|
+
status: 404,
|
|
27
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
28
|
+
})
|
|
29
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { readFile } from 'fs/promises'
|
|
3
|
+
import { join, resolve } from 'path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Markdown route handler for content negotiation.
|
|
7
|
+
*
|
|
8
|
+
* Serves raw MDX/Markdown content with JSX components stripped,
|
|
9
|
+
* returning clean markdown that AI agents can consume directly.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface RouteParams {
|
|
13
|
+
params: Promise<{ slug: string[] }>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function GET(request: NextRequest, { params }: RouteParams) {
|
|
17
|
+
const { slug } = await params
|
|
18
|
+
const contentDir = resolve(process.cwd(), 'content', 'docs')
|
|
19
|
+
|
|
20
|
+
// Reject path traversal: slug segments must not contain .. or absolute paths
|
|
21
|
+
if (slug.some(s => s === '..' || s === '.' || s.includes('/') || s.includes('\\'))) {
|
|
22
|
+
return new NextResponse('# 400 Bad Request\n', {
|
|
23
|
+
status: 400,
|
|
24
|
+
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Try to find the MDX/MD source file
|
|
29
|
+
const filePath = join(contentDir, ...slug)
|
|
30
|
+
|
|
31
|
+
// Double-check resolved path stays within content directory
|
|
32
|
+
const resolved = resolve(filePath)
|
|
33
|
+
if (!resolved.startsWith(contentDir)) {
|
|
34
|
+
return new NextResponse('# 400 Bad Request\n', {
|
|
35
|
+
status: 400,
|
|
36
|
+
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
let content: string | null = null
|
|
40
|
+
let resolvedPath = ''
|
|
41
|
+
|
|
42
|
+
for (const ext of ['.mdx', '.md', '/index.mdx', '/index.md']) {
|
|
43
|
+
try {
|
|
44
|
+
resolvedPath = filePath + ext
|
|
45
|
+
content = await readFile(resolvedPath, 'utf-8')
|
|
46
|
+
break
|
|
47
|
+
} catch {
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!content) {
|
|
53
|
+
return new NextResponse('# 404 Not Found\n\nThis documentation page does not exist.\n', {
|
|
54
|
+
status: 404,
|
|
55
|
+
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Strip JSX components for clean markdown output
|
|
60
|
+
const markdown = stripMdxToMarkdown(content)
|
|
61
|
+
|
|
62
|
+
return new NextResponse(markdown, {
|
|
63
|
+
status: 200,
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'text/markdown; charset=utf-8',
|
|
66
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=86400',
|
|
67
|
+
'X-Content-Source': 'mdx',
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Lightweight MDX-to-markdown stripping for the route handler.
|
|
74
|
+
* Runs at request time so must be fast — uses regex, not a full parser.
|
|
75
|
+
*/
|
|
76
|
+
function stripMdxToMarkdown(mdx: string): string {
|
|
77
|
+
let out = mdx
|
|
78
|
+
|
|
79
|
+
// Remove import/export statements
|
|
80
|
+
out = out.replace(/^import\s+.*$/gm, '')
|
|
81
|
+
out = out.replace(/^export\s+(?!default\s).*$/gm, '')
|
|
82
|
+
|
|
83
|
+
// CodeGroup → flatten (keep code blocks)
|
|
84
|
+
out = out.replace(/<CodeGroup>\s*([\s\S]*?)\s*<\/CodeGroup>/g, (_m, inner: string) => inner.trim())
|
|
85
|
+
|
|
86
|
+
// Callout variants → blockquotes
|
|
87
|
+
for (const tag of ['Callout', 'Info', 'Warning', 'Success', 'Error', 'Tip', 'Note']) {
|
|
88
|
+
out = out.replace(
|
|
89
|
+
new RegExp(`<${tag}\\s+type=["']([^"']+)["'][^>]*>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`, 'g'),
|
|
90
|
+
(_m, type: string, content: string) => {
|
|
91
|
+
const label = type.charAt(0).toUpperCase() + type.slice(1)
|
|
92
|
+
return `> **${label}:** ${content.trim()}\n`
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
out = out.replace(
|
|
96
|
+
new RegExp(`<${tag}(?:\\s[^>]*)?>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`, 'g'),
|
|
97
|
+
(_m, content: string) => {
|
|
98
|
+
const label = tag === 'Callout' ? 'Note' : tag
|
|
99
|
+
return `> **${label}:** ${content.trim()}\n`
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// CardGroup → unwrap
|
|
105
|
+
out = out.replace(/<CardGroup[^>]*>\s*/g, '')
|
|
106
|
+
out = out.replace(/\s*<\/CardGroup>/g, '')
|
|
107
|
+
|
|
108
|
+
// Card with href
|
|
109
|
+
out = out.replace(
|
|
110
|
+
/<Card\s+title=["']([^"']+)["']\s+(?:[^>]*?href=["']([^"']+)["'][^>]*?)>\s*([\s\S]*?)\s*<\/Card>/g,
|
|
111
|
+
(_m, title: string, href: string, desc: string) => {
|
|
112
|
+
const d = desc.trim().replace(/<[^>]+>/g, '')
|
|
113
|
+
return d ? `- **[${title}](${href})** — ${d}` : `- **[${title}](${href})**`
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// Card without href
|
|
118
|
+
out = out.replace(
|
|
119
|
+
/<Card\s+title=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Card>/g,
|
|
120
|
+
(_m, title: string, desc: string) => {
|
|
121
|
+
const d = desc.trim().replace(/<[^>]+>/g, '')
|
|
122
|
+
return d ? `- **${title}** — ${d}` : `- **${title}**`
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
// Tabs
|
|
127
|
+
out = out.replace(/<TabList>\s*([\s\S]*?)\s*<\/TabList>/g, '')
|
|
128
|
+
out = out.replace(/<Tab\s+label=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Tab>/g,
|
|
129
|
+
(_m, label: string, content: string) => `#### ${label}\n\n${content.trim()}\n`
|
|
130
|
+
)
|
|
131
|
+
out = out.replace(/<TabPanel[^>]*>\s*([\s\S]*?)\s*<\/TabPanel>/g, (_m, c: string) => c.trim())
|
|
132
|
+
out = out.replace(/<Tabs[^>]*>\s*/g, '')
|
|
133
|
+
out = out.replace(/\s*<\/Tabs>/g, '')
|
|
134
|
+
|
|
135
|
+
// Steps
|
|
136
|
+
out = out.replace(/<Steps>\s*([\s\S]*?)\s*<\/Steps>/g, (_match, inner: string) => {
|
|
137
|
+
let n = 0
|
|
138
|
+
return inner.replace(
|
|
139
|
+
/<Step\s*(?:title=["']([^"']+)["'][^>]*)?>?\s*([\s\S]*?)\s*<\/Step>/g,
|
|
140
|
+
(_m, title: string | undefined, content: string) => {
|
|
141
|
+
n++
|
|
142
|
+
return `${n}. ${title ? `**${title}** ` : ''}${content.trim()}\n`
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Accordion
|
|
148
|
+
out = out.replace(/<AccordionGroup[^>]*>\s*/g, '')
|
|
149
|
+
out = out.replace(/\s*<\/AccordionGroup>/g, '')
|
|
150
|
+
out = out.replace(/<Accordion\s+title=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Accordion>/g,
|
|
151
|
+
(_m, title: string, content: string) => `**${title}**\n\n${content.trim()}\n`
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// Screenshot → image
|
|
155
|
+
out = out.replace(/<Screenshot\s+(?:[^>]*?src=["']([^"']+)["'][^>]*?)(?:\s+alt=["']([^"']+)["'])?[^>]*\s*\/?>/g,
|
|
156
|
+
(_m, src: string, alt?: string) => ``
|
|
157
|
+
)
|
|
158
|
+
out = out.replace(/<Screenshot\s+(?:[^>]*?src=["']([^"']+)["'][^>]*?)>[\s\S]*?<\/Screenshot>/g,
|
|
159
|
+
(_m, src: string) => ``
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
// Frame → unwrap
|
|
163
|
+
out = out.replace(/<Frame[^>]*>\s*/g, '')
|
|
164
|
+
out = out.replace(/\s*<\/Frame>/g, '')
|
|
165
|
+
|
|
166
|
+
// Catch-all: remove remaining JSX
|
|
167
|
+
out = out.replace(/<[A-Z][A-Za-z]*\s[^>]*\/>/g, '')
|
|
168
|
+
out = out.replace(/<[A-Z][A-Za-z]*(?:\s[^>]*)?>/g, '')
|
|
169
|
+
out = out.replace(/<\/[A-Z][A-Za-z]*>/g, '')
|
|
170
|
+
|
|
171
|
+
// Clean up
|
|
172
|
+
out = out.replace(/\n{4,}/g, '\n\n\n')
|
|
173
|
+
return out.trim() + '\n'
|
|
174
|
+
}
|