botdocs 0.1.0 → 0.2.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/README.md +46 -1
- package/dist/src/builder/index.js +30 -9
- package/dist/src/builder/site-generator.js +13 -10
- package/dist/src/cli/index.js +3 -0
- package/dist/src/cli/options.d.ts +2 -0
- package/dist/src/cli/options.js +1 -0
- package/dist/src/types/config.d.ts +4 -3
- package/dist/src/types/config.js +3 -4
- package/package.json +2 -5
- package/src/styles/main.css +23 -0
- package/src/styles/themes/classic.css +579 -0
- package/src/styles/themes/material.css +652 -0
- package/src/styles/themes/minimal.css +720 -0
- package/src/styles/themes/modern.css +770 -0
- package/src/styles/themes/slate.css +714 -0
- package/src/templates/layout.html +7 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Botdocs
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/botdocs)
|
|
4
|
+
|
|
3
5
|
Convert markdown documentation into beautiful static sites with AI-powered semantic search — no backend required.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
@@ -33,8 +35,35 @@ botdocs ./docs -o ./public
|
|
|
33
35
|
|
|
34
36
|
# Verbose logging
|
|
35
37
|
botdocs ./docs -v
|
|
38
|
+
|
|
39
|
+
# Use a specific theme
|
|
40
|
+
botdocs ./docs -t material
|
|
41
|
+
|
|
42
|
+
# Custom config file
|
|
43
|
+
botdocs ./docs -c ./my-config.json
|
|
44
|
+
|
|
45
|
+
# Combine multiple options
|
|
46
|
+
botdocs ./docs -o ./public -t slate -v
|
|
36
47
|
```
|
|
37
48
|
|
|
49
|
+
### CLI Options
|
|
50
|
+
|
|
51
|
+
| Option | Alias | Description | Default |
|
|
52
|
+
|--------|-------|-------------|---------|
|
|
53
|
+
| `--output <dir>` | `-o` | Output directory for generated site | `output` |
|
|
54
|
+
| `--no-chat` | | Disable AI chatbot functionality | `false` |
|
|
55
|
+
| `--config <file>` | `-c` | Path to config file | `botdocs.config.json` |
|
|
56
|
+
| `--theme <theme>` | `-t` | Theme to use | `classic` |
|
|
57
|
+
| `--verbose` | `-v` | Enable verbose logging | `false` |
|
|
58
|
+
|
|
59
|
+
### Available Themes
|
|
60
|
+
|
|
61
|
+
- **classic** - Clean, professional theme (default)
|
|
62
|
+
- **material** - Material Design theme
|
|
63
|
+
- **minimal** - Clean, minimalist theme
|
|
64
|
+
- **slate** - Dark slate theme
|
|
65
|
+
- **modern** - Modern documentation theme
|
|
66
|
+
|
|
38
67
|
## Configuration
|
|
39
68
|
|
|
40
69
|
Create `botdocs.config.json` in your docs directory:
|
|
@@ -43,15 +72,31 @@ Create `botdocs.config.json` in your docs directory:
|
|
|
43
72
|
{
|
|
44
73
|
"title": "My Documentation",
|
|
45
74
|
"description": "Project docs",
|
|
75
|
+
"theme": "classic",
|
|
76
|
+
"attribution": true,
|
|
46
77
|
"chat": { "enabled": true },
|
|
47
78
|
"build": {
|
|
48
79
|
"chunkSize": 500,
|
|
49
80
|
"chunkOverlap": 50,
|
|
50
|
-
"topK":
|
|
81
|
+
"topK": 3
|
|
51
82
|
}
|
|
52
83
|
}
|
|
53
84
|
```
|
|
54
85
|
|
|
86
|
+
### Configuration Options
|
|
87
|
+
|
|
88
|
+
| Option | Type | Default | Description |
|
|
89
|
+
|--------|------|---------|-------------|
|
|
90
|
+
| `title` | string | `"Documentation"` | Site title |
|
|
91
|
+
| `description` | string | `"Project documentation"` | Site description |
|
|
92
|
+
| `theme` | string | `"classic"` | Theme to use (classic, material, minimal, slate, modern) |
|
|
93
|
+
| `attribution` | boolean | `true` | Show "Built with Botdocs" footer link |
|
|
94
|
+
| `chat.enabled` | boolean | `true` | Enable AI chatbot |
|
|
95
|
+
| `chat.welcomeMessage` | string | `"Ask me anything about the docs!"` | Chatbot welcome message |
|
|
96
|
+
| `build.chunkSize` | number | `500` | Text chunk size for embeddings |
|
|
97
|
+
| `build.chunkOverlap` | number | `50` | Overlap between chunks |
|
|
98
|
+
| `build.topK` | number | `3` | Number of results to return |
|
|
99
|
+
|
|
55
100
|
## Front Matter
|
|
56
101
|
|
|
57
102
|
```markdown
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { defaultConfig } from '../types/config.js';
|
|
2
2
|
import { SiteGenerator } from './site-generator.js';
|
|
3
3
|
import { VectorDBBuilder } from './vector-db-builder.js';
|
|
4
|
-
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
|
|
5
|
-
import fs from 'fs-extra';
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, cpSync } from 'fs';
|
|
6
5
|
import { join, dirname, resolve } from 'path';
|
|
7
6
|
import { fileURLToPath } from 'url';
|
|
8
7
|
import { exec } from 'child_process';
|
|
@@ -14,7 +13,7 @@ const __dirname = dirname(__filename);
|
|
|
14
13
|
* Main build orchestrator
|
|
15
14
|
*/
|
|
16
15
|
export async function build(options) {
|
|
17
|
-
const { inputDir, outputDir, chatEnabled, configPath, verbose } = options;
|
|
16
|
+
const { inputDir, outputDir, chatEnabled, configPath, verbose, theme } = options;
|
|
18
17
|
// Validate input directory
|
|
19
18
|
if (!existsSync(inputDir)) {
|
|
20
19
|
throw new Error(`Input directory does not exist: ${inputDir}`);
|
|
@@ -26,11 +25,15 @@ export async function build(options) {
|
|
|
26
25
|
config.chat = config.chat || {};
|
|
27
26
|
config.chat.enabled = chatEnabled;
|
|
28
27
|
}
|
|
28
|
+
// Override theme if specified in CLI
|
|
29
|
+
if (theme) {
|
|
30
|
+
config.theme = theme;
|
|
31
|
+
}
|
|
29
32
|
if (verbose) {
|
|
30
33
|
console.log('Configuration:', JSON.stringify(config, null, 2));
|
|
31
34
|
}
|
|
32
35
|
// Prepare output directory
|
|
33
|
-
|
|
36
|
+
mkdirSync(outputDir, { recursive: true });
|
|
34
37
|
if (verbose) {
|
|
35
38
|
console.log(`Cleaning output directory: ${outputDir}`);
|
|
36
39
|
}
|
|
@@ -40,8 +43,8 @@ export async function build(options) {
|
|
|
40
43
|
const documents = await generator.generate(inputDir, outputDir, config);
|
|
41
44
|
// Create assets directories
|
|
42
45
|
const assetsDir = join(outputDir, 'assets');
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
mkdirSync(join(assetsDir, 'css'), { recursive: true });
|
|
47
|
+
mkdirSync(join(assetsDir, 'js'), { recursive: true });
|
|
45
48
|
if (verbose) {
|
|
46
49
|
console.log(`Processed ${documents.length} documents`);
|
|
47
50
|
}
|
|
@@ -85,7 +88,7 @@ export async function build(options) {
|
|
|
85
88
|
const distAssetsDir = join(distClientDir, 'assets');
|
|
86
89
|
if (existsSync(distAssetsDir)) {
|
|
87
90
|
const outputAssetsDir = join(jsOutputDir, 'assets');
|
|
88
|
-
|
|
91
|
+
cpSync(distAssetsDir, outputAssetsDir, { recursive: true });
|
|
89
92
|
if (verbose) {
|
|
90
93
|
console.log('Copied code-split chunks');
|
|
91
94
|
}
|
|
@@ -108,9 +111,12 @@ export async function build(options) {
|
|
|
108
111
|
console.log('Copying styles...');
|
|
109
112
|
// From dist/src/builder, go to project root, then to src/styles
|
|
110
113
|
const stylesDir = resolve(__dirname, '../../../src/styles');
|
|
114
|
+
const themesDir = join(stylesDir, 'themes');
|
|
111
115
|
const outputCssDir = join(assetsDir, 'css');
|
|
112
|
-
//
|
|
113
|
-
const
|
|
116
|
+
// Determine which theme to use
|
|
117
|
+
const selectedTheme = config.theme || defaultConfig.theme || 'classic';
|
|
118
|
+
// Combine CSS files: base styles + selected theme + chat
|
|
119
|
+
const cssFiles = ['themes.css', 'chat.css'];
|
|
114
120
|
let bundledCss = '';
|
|
115
121
|
for (const cssFile of cssFiles) {
|
|
116
122
|
const cssPath = join(stylesDir, cssFile);
|
|
@@ -118,6 +124,21 @@ export async function build(options) {
|
|
|
118
124
|
bundledCss += readFileSync(cssPath, 'utf-8') + '\n\n';
|
|
119
125
|
}
|
|
120
126
|
}
|
|
127
|
+
// Add selected theme CSS
|
|
128
|
+
const themePath = join(themesDir, `${selectedTheme}.css`);
|
|
129
|
+
if (existsSync(themePath)) {
|
|
130
|
+
bundledCss += readFileSync(themePath, 'utf-8') + '\n\n';
|
|
131
|
+
if (verbose) {
|
|
132
|
+
console.log(`Using theme: ${selectedTheme}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.warn(`Theme '${selectedTheme}' not found, falling back to classic`);
|
|
137
|
+
const fallbackPath = join(themesDir, 'classic.css');
|
|
138
|
+
if (existsSync(fallbackPath)) {
|
|
139
|
+
bundledCss += readFileSync(fallbackPath, 'utf-8') + '\n\n';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
121
142
|
writeFileSync(join(outputCssDir, 'bundle.css'), bundledCss, 'utf-8');
|
|
122
143
|
if (verbose) {
|
|
123
144
|
console.log('Styles copied');
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
3
|
-
import fs from 'fs-extra';
|
|
1
|
+
import { readFileSync, writeFileSync, readdirSync, mkdirSync, cpSync } from 'fs';
|
|
4
2
|
import { join, dirname, resolve } from 'path';
|
|
5
3
|
import { MarkdownProcessor } from './markdown-processor.js';
|
|
6
4
|
import { TemplateEngine } from './template-engine.js';
|
|
@@ -20,12 +18,16 @@ export class SiteGenerator {
|
|
|
20
18
|
*/
|
|
21
19
|
async generate(inputDir, outputDir, config) {
|
|
22
20
|
console.log('Processing markdown files...');
|
|
23
|
-
// Find all markdown files
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
ignore: ['node_modules/**', '**/node_modules/**'],
|
|
21
|
+
// Find all markdown files using native Node.js readdir
|
|
22
|
+
const allFiles = readdirSync(inputDir, {
|
|
23
|
+
recursive: true,
|
|
24
|
+
withFileTypes: true
|
|
28
25
|
});
|
|
26
|
+
const markdownFiles = allFiles
|
|
27
|
+
.filter(dirent => dirent.isFile() &&
|
|
28
|
+
dirent.name.endsWith('.md') &&
|
|
29
|
+
!dirent.parentPath.includes('node_modules'))
|
|
30
|
+
.map(dirent => join(dirent.parentPath || dirent.path, dirent.name));
|
|
29
31
|
if (markdownFiles.length === 0) {
|
|
30
32
|
throw new Error(`No markdown files found in ${inputDir}`);
|
|
31
33
|
}
|
|
@@ -90,10 +92,11 @@ export class SiteGenerator {
|
|
|
90
92
|
content,
|
|
91
93
|
navigation: this.renderNavigation(navigation, doc.url),
|
|
92
94
|
chatEnabled: config.chat?.enabled,
|
|
95
|
+
attribution: config.attribution !== false, // defaults to true
|
|
93
96
|
});
|
|
94
97
|
// Write HTML file
|
|
95
98
|
const outputPath = join(outputDir, doc.relativePath.replace(/\.md$/, '.html'));
|
|
96
|
-
|
|
99
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
97
100
|
writeFileSync(outputPath, html, 'utf-8');
|
|
98
101
|
}
|
|
99
102
|
// Copy index.html if README.md exists
|
|
@@ -102,7 +105,7 @@ export class SiteGenerator {
|
|
|
102
105
|
const readmePath = join(outputDir, readmeDoc.relativePath.replace(/\.md$/, '.html'));
|
|
103
106
|
const indexPath = join(outputDir, 'index.html');
|
|
104
107
|
if (readmePath !== indexPath) {
|
|
105
|
-
|
|
108
|
+
cpSync(readmePath, indexPath);
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
console.log(`Generated ${this.documents.length} HTML pages`);
|
package/dist/src/cli/index.js
CHANGED
|
@@ -18,6 +18,7 @@ program
|
|
|
18
18
|
.option('-o, --output <dir>', 'Output directory for generated site', defaultOptions.output)
|
|
19
19
|
.option('--no-chat', 'Disable AI chatbot functionality')
|
|
20
20
|
.option('-c, --config <file>', 'Path to config file (botdocs.config.json)')
|
|
21
|
+
.option('-t, --theme <theme>', 'Theme to use (classic, material, minimal, slate, modern)', defaultOptions.theme)
|
|
21
22
|
.option('-v, --verbose', 'Enable verbose logging')
|
|
22
23
|
.action(async (input, options) => {
|
|
23
24
|
try {
|
|
@@ -28,6 +29,7 @@ program
|
|
|
28
29
|
console.log(`Input: ${inputDir}`);
|
|
29
30
|
console.log(`Output: ${outputDir}`);
|
|
30
31
|
console.log(`Chat enabled: ${!options.noChat}`);
|
|
32
|
+
console.log(`Theme: ${options.theme || defaultOptions.theme}`);
|
|
31
33
|
}
|
|
32
34
|
await build({
|
|
33
35
|
inputDir,
|
|
@@ -35,6 +37,7 @@ program
|
|
|
35
37
|
chatEnabled: !options.noChat,
|
|
36
38
|
configPath: options.config,
|
|
37
39
|
verbose: options.verbose || false,
|
|
40
|
+
theme: options.theme,
|
|
38
41
|
});
|
|
39
42
|
console.log('Build complete!');
|
|
40
43
|
console.log(`Site generated at: ${outputDir}`);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export type Theme = 'classic' | 'material' | 'minimal' | 'slate' | 'modern';
|
|
1
2
|
export interface CliOptions {
|
|
2
3
|
output?: string;
|
|
3
4
|
noChat?: boolean;
|
|
4
5
|
config?: string;
|
|
5
6
|
verbose?: boolean;
|
|
7
|
+
theme?: Theme;
|
|
6
8
|
}
|
|
7
9
|
export declare const defaultOptions: Partial<CliOptions>;
|
package/dist/src/cli/options.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
export type Theme = 'classic' | 'material' | 'minimal' | 'slate' | 'modern';
|
|
1
2
|
export interface BotdocsConfig {
|
|
2
3
|
title?: string;
|
|
3
4
|
description?: string;
|
|
4
|
-
theme?:
|
|
5
|
-
|
|
6
|
-
};
|
|
5
|
+
theme?: Theme;
|
|
6
|
+
attribution?: boolean;
|
|
7
7
|
chat?: {
|
|
8
8
|
enabled?: boolean;
|
|
9
9
|
welcomeMessage?: string;
|
|
@@ -20,5 +20,6 @@ export interface BuildOptions {
|
|
|
20
20
|
chatEnabled: boolean;
|
|
21
21
|
configPath?: string;
|
|
22
22
|
verbose: boolean;
|
|
23
|
+
theme?: Theme;
|
|
23
24
|
}
|
|
24
25
|
export declare const defaultConfig: BotdocsConfig;
|
package/dist/src/types/config.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
export const defaultConfig = {
|
|
2
2
|
title: 'Documentation',
|
|
3
3
|
description: 'Project documentation',
|
|
4
|
-
theme:
|
|
5
|
-
|
|
6
|
-
},
|
|
4
|
+
theme: 'classic',
|
|
5
|
+
attribution: true,
|
|
7
6
|
chat: {
|
|
8
7
|
enabled: true,
|
|
9
8
|
welcomeMessage: 'Ask me anything about the docs!',
|
|
@@ -11,6 +10,6 @@ export const defaultConfig = {
|
|
|
11
10
|
build: {
|
|
12
11
|
chunkSize: 500,
|
|
13
12
|
chunkOverlap: 50,
|
|
14
|
-
topK:
|
|
13
|
+
topK: 3,
|
|
15
14
|
},
|
|
16
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botdocs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "An npm CLI tool that converts markdown documentation into a static website with an optional client-side AI chatbot",
|
|
5
5
|
"author": "usr-wwelsh <https://wwel.sh>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,6 @@
|
|
|
38
38
|
"@shikijs/markdown-it": "^1.0.0",
|
|
39
39
|
"@xenova/transformers": "^2.17.0",
|
|
40
40
|
"commander": "^12.0.0",
|
|
41
|
-
"fs-extra": "^11.2.0",
|
|
42
|
-
"glob": "^11.0.0",
|
|
43
41
|
"gray-matter": "^4.0.3",
|
|
44
42
|
"markdown-it": "^14.0.0",
|
|
45
43
|
"markdown-it-anchor": "^9.0.0",
|
|
@@ -53,8 +51,7 @@
|
|
|
53
51
|
"shiki": "^1.0.0"
|
|
54
52
|
},
|
|
55
53
|
"devDependencies": {
|
|
56
|
-
"@types/
|
|
57
|
-
"@types/markdown-it": "^13.0.0",
|
|
54
|
+
"@types/markdown-it": "^14.0.0",
|
|
58
55
|
"@types/node": "^20.11.0",
|
|
59
56
|
"terser": "^5.36.0",
|
|
60
57
|
"typescript": "^5.3.0",
|
package/src/styles/main.css
CHANGED
|
@@ -503,6 +503,29 @@ img {
|
|
|
503
503
|
font-size: 0.875rem;
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
/* Footer Attribution */
|
|
507
|
+
.botdocs-attribution {
|
|
508
|
+
margin-top: 4rem;
|
|
509
|
+
padding-top: 2rem;
|
|
510
|
+
border-top: 1px solid var(--border-color);
|
|
511
|
+
text-align: center;
|
|
512
|
+
font-size: 0.875rem;
|
|
513
|
+
color: var(--text-secondary);
|
|
514
|
+
opacity: 0.8;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.botdocs-attribution a {
|
|
518
|
+
color: var(--text-secondary);
|
|
519
|
+
text-decoration: none;
|
|
520
|
+
font-weight: 500;
|
|
521
|
+
transition: color 0.2s;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.botdocs-attribution a:hover {
|
|
525
|
+
color: var(--link-color);
|
|
526
|
+
text-decoration: none;
|
|
527
|
+
}
|
|
528
|
+
|
|
506
529
|
/* Responsive */
|
|
507
530
|
@media (max-width: 768px) {
|
|
508
531
|
.sidebar {
|