skrypt-ai 0.4.2 → 0.5.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 (51) hide show
  1. package/dist/auth/index.d.ts +13 -3
  2. package/dist/auth/index.js +94 -9
  3. package/dist/auth/keychain.d.ts +5 -0
  4. package/dist/auth/keychain.js +82 -0
  5. package/dist/auth/notices.d.ts +3 -0
  6. package/dist/auth/notices.js +42 -0
  7. package/dist/autofix/index.js +10 -3
  8. package/dist/cli.js +16 -3
  9. package/dist/commands/generate.js +37 -1
  10. package/dist/commands/import.d.ts +2 -0
  11. package/dist/commands/import.js +157 -0
  12. package/dist/commands/init.js +19 -7
  13. package/dist/commands/login.js +15 -4
  14. package/dist/commands/review-pr.js +10 -0
  15. package/dist/commands/security.d.ts +2 -0
  16. package/dist/commands/security.js +103 -0
  17. package/dist/generator/writer.js +12 -3
  18. package/dist/importers/confluence.d.ts +5 -0
  19. package/dist/importers/confluence.js +137 -0
  20. package/dist/importers/detect.d.ts +20 -0
  21. package/dist/importers/detect.js +121 -0
  22. package/dist/importers/docusaurus.d.ts +5 -0
  23. package/dist/importers/docusaurus.js +279 -0
  24. package/dist/importers/gitbook.d.ts +5 -0
  25. package/dist/importers/gitbook.js +189 -0
  26. package/dist/importers/github.d.ts +8 -0
  27. package/dist/importers/github.js +99 -0
  28. package/dist/importers/index.d.ts +15 -0
  29. package/dist/importers/index.js +30 -0
  30. package/dist/importers/markdown.d.ts +6 -0
  31. package/dist/importers/markdown.js +105 -0
  32. package/dist/importers/mintlify.d.ts +5 -0
  33. package/dist/importers/mintlify.js +172 -0
  34. package/dist/importers/notion.d.ts +5 -0
  35. package/dist/importers/notion.js +174 -0
  36. package/dist/importers/readme.d.ts +5 -0
  37. package/dist/importers/readme.js +184 -0
  38. package/dist/importers/transform.d.ts +90 -0
  39. package/dist/importers/transform.js +457 -0
  40. package/dist/importers/types.d.ts +37 -0
  41. package/dist/importers/types.js +1 -0
  42. package/dist/plugins/index.js +7 -0
  43. package/dist/scanner/index.js +37 -24
  44. package/dist/scanner/python.js +17 -0
  45. package/dist/template/public/search-index.json +1 -1
  46. package/dist/template/scripts/build-search-index.mjs +67 -9
  47. package/dist/template/src/lib/search-types.ts +4 -1
  48. package/dist/template/src/lib/search.ts +30 -7
  49. package/dist/utils/files.d.ts +9 -1
  50. package/dist/utils/files.js +59 -10
  51. package/package.json +4 -1
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Mintlify: <Note>content</Note>, <Warning>, <Tip>, <Info>, <Check>
3
+ */
4
+ export declare function transformMintlifyCallouts(content: string): string;
5
+ /**
6
+ * Docusaurus: :::note[Title]\ncontent\n:::
7
+ */
8
+ export declare function transformDocusaurusAdmonitions(content: string): string;
9
+ /**
10
+ * GitBook: {% hint style="info" %}\ncontent\n{% endhint %}
11
+ */
12
+ export declare function transformGitBookHints(content: string): string;
13
+ /**
14
+ * ReadMe RDMD: > 📘 Title\n> content lines
15
+ */
16
+ export declare function transformReadmeCallouts(content: string): string;
17
+ /**
18
+ * Notion: <aside>content</aside> and :::callout
19
+ */
20
+ export declare function transformNotionCallouts(content: string): string;
21
+ /**
22
+ * Confluence: <ac:structured-macro ac:name="info">...<ac:rich-text-body>content</ac:rich-text-body>
23
+ */
24
+ export declare function transformConfluenceCallouts(content: string): string;
25
+ /**
26
+ * Mintlify: <Tabs><Tab title="X">content</Tab></Tabs>
27
+ */
28
+ export declare function transformMintlifyTabs(content: string): string;
29
+ /**
30
+ * Docusaurus: <Tabs><TabItem value="x" label="X">content</TabItem></Tabs>
31
+ */
32
+ export declare function transformDocusaurusTabs(content: string): string;
33
+ /**
34
+ * GitBook: {% tabs %}{% tab title="X" %}content{% endtab %}{% endtabs %}
35
+ */
36
+ export declare function transformGitBookTabs(content: string): string;
37
+ /**
38
+ * ReadMe [block:code] JSON → <CodeGroup>
39
+ */
40
+ export declare function transformReadmeCodeBlocks(content: string): string;
41
+ /**
42
+ * GitBook: {% stepper %}{% step %}...{% endstep %}{% endstepper %}
43
+ */
44
+ export declare function transformGitBookSteps(content: string): string;
45
+ /**
46
+ * GitBook: {% expandable title="X" %}content{% endexpandable %}
47
+ */
48
+ export declare function transformGitBookExpandable(content: string): string;
49
+ /**
50
+ * GitBook: {% content-ref url="path" %} → markdown link
51
+ */
52
+ export declare function transformGitBookContentRef(content: string): string;
53
+ /**
54
+ * GitBook: {% embed url="..." %} → plain URL
55
+ */
56
+ export declare function transformGitBookEmbed(content: string): string;
57
+ /**
58
+ * Notion: <details><summary>Title</summary>content</details> → <Accordion>
59
+ */
60
+ export declare function transformNotionToggles(content: string): string;
61
+ /**
62
+ * Confluence: Convert common HTML and AC macros to markdown
63
+ */
64
+ export declare function transformConfluenceHtml(content: string): string;
65
+ /**
66
+ * Docusaurus: Strip import ... from '@theme/...' statements
67
+ */
68
+ export declare function stripDocusaurusImports(content: string): string;
69
+ interface FrontmatterDefaults {
70
+ title?: string;
71
+ description?: string;
72
+ }
73
+ /**
74
+ * Normalize frontmatter fields to Skrypt format
75
+ */
76
+ export declare function normalizeFrontmatter(content: string, defaults?: FrontmatterDefaults): string;
77
+ /**
78
+ * Rewrite image paths based on asset copy mapping
79
+ */
80
+ export declare function rewriteImagePaths(content: string, mapping: Map<string, string>): string;
81
+ /**
82
+ * Strip 32-char hex UUID suffixes from Notion filenames
83
+ * e.g., "Getting Started a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4.md" → "Getting Started.md"
84
+ */
85
+ export declare function stripNotionUUIDs(filename: string): string;
86
+ /**
87
+ * Get sorting weight from frontmatter
88
+ */
89
+ export declare function getSortWeight(content: string): number;
90
+ export {};
@@ -0,0 +1,457 @@
1
+ // ============================================================
2
+ // Content Transformation Engine
3
+ // Composable string→string transforms for all platform syntaxes
4
+ // ============================================================
5
+ // --- Callout Transforms ---
6
+ const MINTLIFY_CALLOUT_MAP = {
7
+ Note: 'note',
8
+ Warning: 'warning',
9
+ Tip: 'tip',
10
+ Info: 'info',
11
+ Check: 'success',
12
+ };
13
+ /**
14
+ * Mintlify: <Note>content</Note>, <Warning>, <Tip>, <Info>, <Check>
15
+ */
16
+ export function transformMintlifyCallouts(content) {
17
+ for (const [tag, type] of Object.entries(MINTLIFY_CALLOUT_MAP)) {
18
+ const regex = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, 'g');
19
+ content = content.replace(regex, `<Callout type="${type}">$1</Callout>`);
20
+ }
21
+ return content;
22
+ }
23
+ const DOCUSAURUS_ADMONITION_MAP = {
24
+ note: 'note',
25
+ tip: 'tip',
26
+ info: 'info',
27
+ caution: 'warning',
28
+ danger: 'error',
29
+ warning: 'warning',
30
+ };
31
+ /**
32
+ * Docusaurus: :::note[Title]\ncontent\n:::
33
+ */
34
+ export function transformDocusaurusAdmonitions(content) {
35
+ return content.replace(/:::(note|tip|info|caution|danger|warning)(?:\[(.+?)\])?\n([\s\S]*?):::/g, (_match, type, title, body) => {
36
+ const calloutType = DOCUSAURUS_ADMONITION_MAP[type] || 'info';
37
+ const titleAttr = title ? ` title="${title}"` : '';
38
+ return `<Callout type="${calloutType}"${titleAttr}>${body}</Callout>`;
39
+ });
40
+ }
41
+ const GITBOOK_HINT_MAP = {
42
+ info: 'info',
43
+ warning: 'warning',
44
+ danger: 'error',
45
+ success: 'success',
46
+ tip: 'tip',
47
+ };
48
+ /**
49
+ * GitBook: {% hint style="info" %}\ncontent\n{% endhint %}
50
+ */
51
+ export function transformGitBookHints(content) {
52
+ return content.replace(/\{%\s*hint\s+style="(\w+)"\s*%\}([\s\S]*?)\{%\s*endhint\s*%\}/g, (_match, style, body) => {
53
+ const calloutType = GITBOOK_HINT_MAP[style] || 'info';
54
+ return `<Callout type="${calloutType}">${body.trim()}</Callout>`;
55
+ });
56
+ }
57
+ const README_EMOJI_MAP = {
58
+ '\u{1F4D8}': 'info', // 📘
59
+ '\u{2139}\uFE0F': 'info', // ℹ️
60
+ '\u{1F44D}': 'success', // 👍
61
+ '\u{2705}': 'success', // ✅
62
+ '\u{1F6A7}': 'warning', // 🚧
63
+ '\u{26A0}\uFE0F': 'warning', // ⚠️
64
+ '\u{2757}': 'error', // ❗
65
+ '\u{1F6D1}': 'error', // 🛑
66
+ };
67
+ /**
68
+ * ReadMe RDMD: > 📘 Title\n> content lines
69
+ */
70
+ export function transformReadmeCallouts(content) {
71
+ // Modern RDMD emoji format
72
+ const emojiPattern = Object.keys(README_EMOJI_MAP).join('|');
73
+ const rdmdRegex = new RegExp(`> (${emojiPattern}) (.+)\\n((?:> .+\\n?)*)`, 'g');
74
+ content = content.replace(rdmdRegex, (_match, emoji, title, bodyLines) => {
75
+ const type = README_EMOJI_MAP[emoji] || 'info';
76
+ const body = bodyLines.replace(/^> /gm, '').trim();
77
+ return `<Callout type="${type}" title="${title}">\n${body}\n</Callout>`;
78
+ });
79
+ // Legacy [block:callout] format
80
+ content = content.replace(/\[block:callout\]\n?([\s\S]*?)\n?\[\/block\]/g, (_match, jsonStr) => {
81
+ try {
82
+ const data = JSON.parse(jsonStr);
83
+ const type = data.type === 'warning' ? 'warning' :
84
+ data.type === 'danger' ? 'error' :
85
+ data.type === 'success' ? 'success' : 'info';
86
+ const titleAttr = data.title ? ` title="${data.title}"` : '';
87
+ return `<Callout type="${type}"${titleAttr}>\n${data.body || ''}\n</Callout>`;
88
+ }
89
+ catch {
90
+ return _match; // leave as-is if JSON parse fails
91
+ }
92
+ });
93
+ return content;
94
+ }
95
+ /**
96
+ * Notion: <aside>content</aside> and :::callout
97
+ */
98
+ export function transformNotionCallouts(content) {
99
+ // <aside> blocks
100
+ content = content.replace(/<aside>([\s\S]*?)<\/aside>/g, (_match, body) => {
101
+ // Strip common Notion emoji prefixes (single codepoint emojis + variation selectors)
102
+ const cleaned = body.replace(/^[\s\n]*(?:☝️|💡|⚠️|❗|📌|🔥|✨|🎯|📝)\s*/u, '').trim();
103
+ return `<Callout type="info">${cleaned}</Callout>`;
104
+ });
105
+ // :::callout blocks (Notion API export format)
106
+ content = content.replace(/:::callout([\s\S]*?):::/g, (_match, body) => `<Callout type="info">${body.trim()}</Callout>`);
107
+ return content;
108
+ }
109
+ /**
110
+ * Confluence: <ac:structured-macro ac:name="info">...<ac:rich-text-body>content</ac:rich-text-body>
111
+ */
112
+ export function transformConfluenceCallouts(content) {
113
+ return content.replace(/<ac:structured-macro[^>]*ac:name="(info|note|warning|tip)"[^>]*>[\s\S]*?<ac:rich-text-body>([\s\S]*?)<\/ac:rich-text-body>[\s\S]*?<\/ac:structured-macro>/g, (_match, type, body) => {
114
+ const cleaned = stripHtmlTags(body).trim();
115
+ return `<Callout type="${type}">${cleaned}</Callout>`;
116
+ });
117
+ }
118
+ // --- Tabs Transforms ---
119
+ /**
120
+ * Mintlify: <Tabs><Tab title="X">content</Tab></Tabs>
121
+ */
122
+ export function transformMintlifyTabs(content) {
123
+ return content.replace(/<Tabs>([\s\S]*?)<\/Tabs>/g, (_match, inner) => {
124
+ const tabs = [];
125
+ const tabRegex = /<Tab\s+title="([^"]+)">([\s\S]*?)<\/Tab>/g;
126
+ let tabMatch;
127
+ while ((tabMatch = tabRegex.exec(inner)) !== null) {
128
+ tabs.push({ title: tabMatch[1], content: tabMatch[2] });
129
+ }
130
+ if (tabs.length === 0)
131
+ return _match;
132
+ const defaultValue = tabs[0].title;
133
+ const tabList = tabs.map(t => ` <Tab value="${t.title}">${t.title}</Tab>`).join('\n');
134
+ const panels = tabs.map(t => `<TabPanel value="${t.title}">${t.content}</TabPanel>`).join('\n');
135
+ return `<Tabs defaultValue="${defaultValue}">\n<TabList>\n${tabList}\n</TabList>\n${panels}\n</Tabs>`;
136
+ });
137
+ }
138
+ /**
139
+ * Docusaurus: <Tabs><TabItem value="x" label="X">content</TabItem></Tabs>
140
+ */
141
+ export function transformDocusaurusTabs(content) {
142
+ return content.replace(/<Tabs[^>]*>([\s\S]*?)<\/Tabs>/g, (_match, inner) => {
143
+ const tabs = [];
144
+ const tabRegex = /<TabItem\s+value="([^"]+)"(?:\s+label="([^"]*)")?>([\s\S]*?)<\/TabItem>/g;
145
+ let tabMatch;
146
+ while ((tabMatch = tabRegex.exec(inner)) !== null) {
147
+ tabs.push({ value: tabMatch[1], label: tabMatch[2] || tabMatch[1], content: tabMatch[3] });
148
+ }
149
+ if (tabs.length === 0)
150
+ return _match;
151
+ const defaultValue = tabs[0].value;
152
+ const tabList = tabs.map(t => ` <Tab value="${t.value}">${t.label}</Tab>`).join('\n');
153
+ const panels = tabs.map(t => `<TabPanel value="${t.value}">${t.content}</TabPanel>`).join('\n');
154
+ return `<Tabs defaultValue="${defaultValue}">\n<TabList>\n${tabList}\n</TabList>\n${panels}\n</Tabs>`;
155
+ });
156
+ }
157
+ /**
158
+ * GitBook: {% tabs %}{% tab title="X" %}content{% endtab %}{% endtabs %}
159
+ */
160
+ export function transformGitBookTabs(content) {
161
+ return content.replace(/\{%\s*tabs\s*%\}([\s\S]*?)\{%\s*endtabs\s*%\}/g, (_match, inner) => {
162
+ const tabs = [];
163
+ const tabRegex = /\{%\s*tab\s+title="([^"]+)"\s*%\}([\s\S]*?)\{%\s*endtab\s*%\}/g;
164
+ let tabMatch;
165
+ while ((tabMatch = tabRegex.exec(inner)) !== null) {
166
+ tabs.push({ title: tabMatch[1], content: tabMatch[2].trim() });
167
+ }
168
+ if (tabs.length === 0)
169
+ return _match;
170
+ const defaultValue = tabs[0].title;
171
+ const tabList = tabs.map(t => ` <Tab value="${t.title}">${t.title}</Tab>`).join('\n');
172
+ const panels = tabs.map(t => `<TabPanel value="${t.title}">\n${t.content}\n</TabPanel>`).join('\n');
173
+ return `<Tabs defaultValue="${defaultValue}">\n<TabList>\n${tabList}\n</TabList>\n${panels}\n</Tabs>`;
174
+ });
175
+ }
176
+ /**
177
+ * ReadMe [block:code] JSON → <CodeGroup>
178
+ */
179
+ export function transformReadmeCodeBlocks(content) {
180
+ return content.replace(/\[block:code\]\n?([\s\S]*?)\n?\[\/block\]/g, (_match, jsonStr) => {
181
+ try {
182
+ const data = JSON.parse(jsonStr);
183
+ if (!data.codes || !Array.isArray(data.codes))
184
+ return _match;
185
+ if (data.codes.length === 1) {
186
+ const c = data.codes[0];
187
+ const lang = c.language || '';
188
+ const name = c.name ? ` ${c.name}` : '';
189
+ return `\`\`\`${lang}${name}\n${c.code}\n\`\`\``;
190
+ }
191
+ const blocks = data.codes.map((c) => {
192
+ const lang = c.language || '';
193
+ const name = c.name ? ` ${c.name}` : '';
194
+ return `\`\`\`${lang}${name}\n${c.code || ''}\n\`\`\``;
195
+ }).join('\n\n');
196
+ return `<CodeGroup>\n\n${blocks}\n\n</CodeGroup>`;
197
+ }
198
+ catch {
199
+ return _match;
200
+ }
201
+ });
202
+ }
203
+ // --- Other Component Transforms ---
204
+ /**
205
+ * GitBook: {% stepper %}{% step %}...{% endstep %}{% endstepper %}
206
+ */
207
+ export function transformGitBookSteps(content) {
208
+ return content.replace(/\{%\s*stepper\s*%\}([\s\S]*?)\{%\s*endstepper\s*%\}/g, (_match, inner) => {
209
+ const steps = inner.split(/\{%\s*step\s*%\}/).filter(s => s.trim());
210
+ const mapped = steps.map(step => {
211
+ const body = step.replace(/\{%\s*endstep\s*%\}/g, '').trim();
212
+ return `<Step>\n${body}\n</Step>`;
213
+ }).join('\n');
214
+ return `<Steps>\n${mapped}\n</Steps>`;
215
+ });
216
+ }
217
+ /**
218
+ * GitBook: {% expandable title="X" %}content{% endexpandable %}
219
+ */
220
+ export function transformGitBookExpandable(content) {
221
+ return content.replace(/\{%\s*expandable\s+title="([^"]+)"\s*%\}([\s\S]*?)\{%\s*endexpandable\s*%\}/g, (_match, title, body) => `<Accordion title="${title}">\n${body.trim()}\n</Accordion>`);
222
+ }
223
+ /**
224
+ * GitBook: {% content-ref url="path" %} → markdown link
225
+ */
226
+ export function transformGitBookContentRef(content) {
227
+ return content.replace(/\{%\s*content-ref\s+url="([^"]+)"\s*%\}[\s\S]*?\{%\s*endcontent-ref\s*%\}/g, (_match, url) => {
228
+ const label = url.replace(/\.md$/, '').split('/').pop() || url;
229
+ return `[${label}](${url})`;
230
+ });
231
+ }
232
+ /**
233
+ * GitBook: {% embed url="..." %} → plain URL
234
+ */
235
+ export function transformGitBookEmbed(content) {
236
+ return content.replace(/\{%\s*embed\s+url="([^"]+)"\s*%\}/g, (_match, url) => url);
237
+ }
238
+ /**
239
+ * Notion: <details><summary>Title</summary>content</details> → <Accordion>
240
+ */
241
+ export function transformNotionToggles(content) {
242
+ return content.replace(/<details>\s*<summary>([\s\S]*?)<\/summary>([\s\S]*?)<\/details>/g, (_match, title, body) => `<Accordion title="${title.trim()}">\n${body.trim()}\n</Accordion>`);
243
+ }
244
+ /**
245
+ * Confluence: Convert common HTML and AC macros to markdown
246
+ */
247
+ export function transformConfluenceHtml(content) {
248
+ // Code macros
249
+ content = content.replace(/<ac:structured-macro[^>]*ac:name="code"[^>]*>[\s\S]*?(?:<ac:parameter ac:name="language">([^<]*)<\/ac:parameter>)?[\s\S]*?<ac:plain-text-body><!\[CDATA\[([\s\S]*?)\]\]><\/ac:plain-text-body>[\s\S]*?<\/ac:structured-macro>/g, (_match, lang, code) => `\`\`\`${lang || ''}\n${code}\n\`\`\``);
250
+ // Expand macros → Accordion
251
+ content = content.replace(/<ac:structured-macro[^>]*ac:name="expand"[^>]*>[\s\S]*?<ac:parameter ac:name="title">([^<]*)<\/ac:parameter>[\s\S]*?<ac:rich-text-body>([\s\S]*?)<\/ac:rich-text-body>[\s\S]*?<\/ac:structured-macro>/g, (_match, title, body) => `<Accordion title="${title}">\n${stripHtmlTags(body).trim()}\n</Accordion>`);
252
+ // Strip remaining AC macros
253
+ content = content.replace(/<ac:[^>]+>|<\/ac:[^>]+>/g, '');
254
+ // Convert common HTML
255
+ content = convertBasicHtml(content);
256
+ return content;
257
+ }
258
+ /**
259
+ * Docusaurus: Strip import ... from '@theme/...' statements
260
+ */
261
+ export function stripDocusaurusImports(content) {
262
+ return content.replace(/^import\s+.*from\s+['"]@theme\/.*['"];?\s*\n?/gm, '');
263
+ }
264
+ /**
265
+ * Normalize frontmatter fields to Skrypt format
266
+ */
267
+ export function normalizeFrontmatter(content, defaults) {
268
+ const { data, body } = parseFrontmatterRaw(content);
269
+ if (!data && !defaults)
270
+ return content;
271
+ const fm = data || {};
272
+ // Map known field aliases
273
+ if (!fm.title && fm.sidebarTitle)
274
+ fm.title = fm.sidebarTitle;
275
+ if (!fm.description && fm.excerpt)
276
+ fm.description = fm.excerpt;
277
+ // Apply defaults
278
+ if (defaults?.title && !fm.title)
279
+ fm.title = defaults.title;
280
+ if (defaults?.description && !fm.description)
281
+ fm.description = defaults.description;
282
+ // Map icon field
283
+ if (fm.sidebar_icon && !fm.icon)
284
+ fm.icon = fm.sidebar_icon;
285
+ // Remove non-Skrypt fields
286
+ const removeFields = ['sidebar_position', 'order', 'weight', 'slug', 'sidebarTitle',
287
+ 'excerpt', 'sidebar_icon', 'sidebar_label', 'sidebar_class_name',
288
+ 'pagination_next', 'pagination_prev', 'custom_edit_url',
289
+ 'displayed_sidebar', 'hide_table_of_contents', 'hide_title',
290
+ 'keywords', 'tags', 'image', 'last_update', 'category'];
291
+ for (const field of removeFields) {
292
+ delete fm[field];
293
+ }
294
+ // Build normalized frontmatter
295
+ const skryptFields = {};
296
+ if (fm.title)
297
+ skryptFields.title = fm.title;
298
+ if (fm.description)
299
+ skryptFields.description = fm.description;
300
+ if (fm.icon)
301
+ skryptFields.icon = fm.icon;
302
+ // Include any remaining unknown fields
303
+ for (const [key, value] of Object.entries(fm)) {
304
+ if (!(key in skryptFields) && !removeFields.includes(key)) {
305
+ skryptFields[key] = value;
306
+ }
307
+ }
308
+ if (Object.keys(skryptFields).length === 0)
309
+ return body;
310
+ const fmLines = Object.entries(skryptFields)
311
+ .map(([k, v]) => {
312
+ if (typeof v === 'string') {
313
+ // Escape double quotes and backslashes in YAML string values
314
+ const escaped = v.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
315
+ return `${k}: "${escaped}"`;
316
+ }
317
+ return `${k}: ${JSON.stringify(v)}`;
318
+ })
319
+ .join('\n');
320
+ return `---\n${fmLines}\n---\n\n${body}`;
321
+ }
322
+ /**
323
+ * Rewrite image paths based on asset copy mapping
324
+ */
325
+ export function rewriteImagePaths(content, mapping) {
326
+ for (const [oldPath, newPath] of mapping) {
327
+ content = content.replaceAll(oldPath, newPath);
328
+ }
329
+ return content;
330
+ }
331
+ /**
332
+ * Strip 32-char hex UUID suffixes from Notion filenames
333
+ * e.g., "Getting Started a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4.md" → "Getting Started.md"
334
+ */
335
+ export function stripNotionUUIDs(filename) {
336
+ return filename.replace(/\s+[0-9a-f]{32}/g, '');
337
+ }
338
+ // --- Helpers ---
339
+ function stripHtmlTags(html) {
340
+ return html
341
+ .replace(/<br\s*\/?>/gi, '\n')
342
+ .replace(/<\/p>/gi, '\n\n')
343
+ .replace(/<[^>]+>/g, '')
344
+ .replace(/&amp;/g, '&')
345
+ .replace(/&lt;/g, '<')
346
+ .replace(/&gt;/g, '>')
347
+ .replace(/&quot;/g, '"')
348
+ .replace(/&#39;/g, "'")
349
+ .replace(/\n{3,}/g, '\n\n');
350
+ }
351
+ function convertBasicHtml(html) {
352
+ let md = html;
353
+ // Headings
354
+ md = md.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, '# $1\n\n');
355
+ md = md.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, '## $1\n\n');
356
+ md = md.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, '### $1\n\n');
357
+ md = md.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, '#### $1\n\n');
358
+ // Bold / italic
359
+ md = md.replace(/<strong>([\s\S]*?)<\/strong>/gi, '**$1**');
360
+ md = md.replace(/<b>([\s\S]*?)<\/b>/gi, '**$1**');
361
+ md = md.replace(/<em>([\s\S]*?)<\/em>/gi, '*$1*');
362
+ md = md.replace(/<i>([\s\S]*?)<\/i>/gi, '*$1*');
363
+ // Links
364
+ md = md.replace(/<a[^>]+href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, '[$2]($1)');
365
+ // Images
366
+ md = md.replace(/<img[^>]+src="([^"]*)"(?:[^>]*alt="([^"]*)")?[^>]*\/?>/gi, '![$2]($1)');
367
+ // Lists
368
+ md = md.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, '- $1\n');
369
+ md = md.replace(/<\/?[uo]l[^>]*>/gi, '\n');
370
+ // Paragraphs & breaks
371
+ md = md.replace(/<\/p>/gi, '\n\n');
372
+ md = md.replace(/<p[^>]*>/gi, '');
373
+ md = md.replace(/<br\s*\/?>/gi, '\n');
374
+ // Code
375
+ md = md.replace(/<code>([\s\S]*?)<\/code>/gi, '`$1`');
376
+ md = md.replace(/<pre>([\s\S]*?)<\/pre>/gi, '```\n$1\n```');
377
+ // Tables
378
+ md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, tableContent) => {
379
+ return convertHtmlTable(tableContent);
380
+ });
381
+ // Strip remaining HTML tags (preserve Skrypt components like Accordion, Callout, Steps, etc.)
382
+ md = md.replace(/<\/?(?!Accordion|Callout|Steps|Step|Tabs|Tab|TabList|TabPanel|CodeGroup|Card|CardGroup)[a-z][^>]*>/gi, '');
383
+ // Decode entities
384
+ md = md.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'");
385
+ return md;
386
+ }
387
+ function convertHtmlTable(tableHtml) {
388
+ const rows = [];
389
+ const rowRegex = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
390
+ let rowMatch;
391
+ while ((rowMatch = rowRegex.exec(tableHtml)) !== null) {
392
+ const cells = [];
393
+ const cellRegex = /<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi;
394
+ let cellMatch;
395
+ while ((cellMatch = cellRegex.exec(rowMatch[1])) !== null) {
396
+ cells.push(cellMatch[1].replace(/<[^>]+>/g, '').trim());
397
+ }
398
+ if (cells.length > 0)
399
+ rows.push(cells);
400
+ }
401
+ if (rows.length === 0)
402
+ return '';
403
+ const colCount = Math.max(...rows.map(r => r.length));
404
+ const lines = [];
405
+ for (let i = 0; i < rows.length; i++) {
406
+ const paddedCells = Array.from({ length: colCount }, (_, j) => rows[i][j] || '');
407
+ lines.push('| ' + paddedCells.join(' | ') + ' |');
408
+ if (i === 0) {
409
+ lines.push('| ' + paddedCells.map(() => '---').join(' | ') + ' |');
410
+ }
411
+ }
412
+ return '\n' + lines.join('\n') + '\n';
413
+ }
414
+ /**
415
+ * Simple frontmatter parser: splits on --- markers
416
+ */
417
+ function parseFrontmatterRaw(content) {
418
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
419
+ if (!match)
420
+ return { data: null, body: content };
421
+ const yamlStr = match[1];
422
+ const body = match[2];
423
+ // Simple YAML key: value parser (handles strings, numbers, booleans)
424
+ const data = {};
425
+ for (const line of yamlStr.split('\n')) {
426
+ const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
427
+ if (!kvMatch)
428
+ continue;
429
+ const key = kvMatch[1];
430
+ let value = kvMatch[2].trim();
431
+ // Strip quotes
432
+ if (typeof value === 'string') {
433
+ if ((value.startsWith('"') && value.endsWith('"')) ||
434
+ (value.startsWith("'") && value.endsWith("'"))) {
435
+ value = value.slice(1, -1);
436
+ }
437
+ else if (value === 'true')
438
+ value = true;
439
+ else if (value === 'false')
440
+ value = false;
441
+ else if (/^\d+$/.test(value))
442
+ value = parseInt(value, 10);
443
+ }
444
+ data[key] = value;
445
+ }
446
+ return { data, body };
447
+ }
448
+ /**
449
+ * Get sorting weight from frontmatter
450
+ */
451
+ export function getSortWeight(content) {
452
+ const { data } = parseFrontmatterRaw(content);
453
+ if (!data)
454
+ return Infinity;
455
+ const weight = data.sidebar_position ?? data.order ?? data.weight ?? data.position;
456
+ return typeof weight === 'number' ? weight : Infinity;
457
+ }
@@ -0,0 +1,37 @@
1
+ export type ImportFormat = 'mintlify' | 'docusaurus' | 'gitbook' | 'readme' | 'notion' | 'confluence' | 'markdown';
2
+ export interface ImportedPage {
3
+ title: string;
4
+ slug: string;
5
+ sourcePath: string;
6
+ content: string;
7
+ description?: string;
8
+ frontmatter?: Record<string, unknown>;
9
+ }
10
+ export interface ImportedGroup {
11
+ group: string;
12
+ icon?: string;
13
+ pages: ImportedPage[];
14
+ }
15
+ export interface TransformStats {
16
+ callouts: number;
17
+ tabs: number;
18
+ codeGroups: number;
19
+ steps: number;
20
+ accordions: number;
21
+ images: number;
22
+ other: number;
23
+ }
24
+ export interface ImportResult {
25
+ navigation: ImportedGroup[];
26
+ name: string;
27
+ description: string;
28
+ files: Map<string, string>;
29
+ assets: Map<string, string>;
30
+ warnings: string[];
31
+ stats: {
32
+ pages: number;
33
+ groups: number;
34
+ transforms: TransformStats;
35
+ };
36
+ sourceFormat: ImportFormat;
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -23,6 +23,8 @@ export class PluginManager {
23
23
  if (!configFile)
24
24
  return;
25
25
  try {
26
+ console.log(` Loading config: ${configFile}`);
27
+ console.warn(' ⚠ Plugin configs execute code — only load trusted files');
26
28
  const configUrl = pathToFileURL(configFile).href;
27
29
  const module = await import(configUrl);
28
30
  const config = module.default || module;
@@ -57,6 +59,11 @@ export class PluginManager {
57
59
  return null;
58
60
  }
59
61
  async loadPluginByName(name) {
62
+ // Validate plugin name — must be a valid npm package name, no path traversal
63
+ if (!/^(@[a-zA-Z0-9._-]+\/)?[a-zA-Z0-9._-]+$/.test(name)) {
64
+ console.warn(`Skipping plugin with invalid name: ${name}`);
65
+ return;
66
+ }
60
67
  // Try to load from node_modules
61
68
  try {
62
69
  const module = await import(name);