botdocs 0.5.0 → 0.6.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 CHANGED
@@ -15,7 +15,8 @@ Convert markdown documentation into beautiful static sites with AI-powered seman
15
15
  - **No Backend** - Everything runs in the browser
16
16
  - **Fast** - Syntax highlighting with Shiki
17
17
  - **Live Preview** - `--watch` rebuilds on save and serves the site locally
18
- - **SEO** - Optional Open Graph/Twitter tags and `sitemap.xml` via `baseUrl`
18
+ - **SEO** - Optional Open Graph/Twitter tags, `sitemap.xml` and `robots.txt` via `baseUrl`
19
+ - **Agent Friendly** - Raw `.md` source published next to every page, plus an `llms.txt` index
19
20
 
20
21
  ## Installation
21
22
 
@@ -105,7 +106,7 @@ Create `botdocs.config.json` in your docs directory:
105
106
  | `theme` | string | `"classic"` | Theme to use (classic, material, minimal, slate, modern) |
106
107
  | `customCss` | string | none | Path to a CSS file, resolved relative to the config file's directory. Appended after theme CSS in `bundle.css`, so same-specificity selectors override the theme without `!important` |
107
108
  | `attribution` | boolean | `true` | Show "Built with Botdocs" footer link |
108
- | `baseUrl` | string | none | Canonical URL where the site is hosted. When set, pages get `rel=canonical` and Open Graph/Twitter card tags, and a `sitemap.xml` is generated |
109
+ | `baseUrl` | string | none | Canonical URL where the site is hosted. When set, pages get `rel=canonical` and Open Graph/Twitter card tags, a `sitemap.xml` and `robots.txt` are generated, and `llms.txt` links become absolute |
109
110
  | `chat.enabled` | boolean | `true` | Enable AI chatbot |
110
111
  | `chat.welcomeMessage` | string | `"Ask me anything about the docs!"` | Chatbot welcome message |
111
112
  | `build.chunkSize` | number | `500` | Text chunk size for embeddings |
@@ -10,6 +10,8 @@ export declare class SiteGenerator {
10
10
  * Generate the complete site
11
11
  */
12
12
  generate(inputDir: string, outputDir: string, config: BotdocsConfig): Promise<ProcessedDocument[]>;
13
+ private writeLlmsTxt;
14
+ private writeRobotsTxt;
13
15
  private writeSitemap;
14
16
  /**
15
17
  * Build navigation structure from documents, grouping by top-level
@@ -109,6 +109,7 @@ export class SiteGenerator {
109
109
  const outputPath = join(outputDir, doc.relativePath.replace(/\.md$/, '.html'));
110
110
  mkdirSync(dirname(outputPath), { recursive: true });
111
111
  writeFileSync(outputPath, html, 'utf-8');
112
+ cpSync(doc.filePath, join(outputDir, doc.relativePath));
112
113
  }
113
114
  // Copy index.html if README.md exists
114
115
  const readmeDoc = this.documents.find((d) => d.relativePath === 'README.md' || d.relativePath === 'index.md');
@@ -120,11 +121,34 @@ export class SiteGenerator {
120
121
  }
121
122
  }
122
123
  console.log(`Generated ${this.documents.length} HTML pages`);
124
+ this.writeLlmsTxt(outputDir, config);
123
125
  if (config.baseUrl) {
124
126
  this.writeSitemap(outputDir, config.baseUrl);
127
+ this.writeRobotsTxt(outputDir, config.baseUrl);
125
128
  }
126
129
  return this.documents;
127
130
  }
131
+ writeLlmsTxt(outputDir, config) {
132
+ const oneLine = (value) => stripHtml(value).replace(/\s+/g, ' ').trim();
133
+ const isRootIndex = (doc) => doc.relativePath === 'README.md' || doc.relativePath === 'index.md';
134
+ const pages = [...this.documents.filter(isRootIndex), ...this.documents.filter((doc) => !isRootIndex(doc))]
135
+ .map((doc) => {
136
+ const title = oneLine(doc.metadata.title || basename(doc.relativePath, '.md'));
137
+ const url = config.baseUrl ? absoluteUrl(config.baseUrl, doc.relativePath) : doc.relativePath;
138
+ const description = oneLine(doc.metadata.description || '');
139
+ return `- [${title}](${url})${description ? `: ${description}` : ''}`;
140
+ })
141
+ .join('\n');
142
+ const summary = oneLine(config.description || '');
143
+ const llms = `# ${oneLine(config.title || 'Documentation')}\n\n${summary ? `> ${summary}\n\n` : ''}## Docs\n\n${pages}\n`;
144
+ writeFileSync(join(outputDir, 'llms.txt'), llms, 'utf-8');
145
+ console.log('Generated llms.txt');
146
+ }
147
+ writeRobotsTxt(outputDir, baseUrl) {
148
+ const robots = `User-agent: *\nAllow: /\n\nSitemap: ${absoluteUrl(baseUrl, 'sitemap.xml')}\n`;
149
+ writeFileSync(join(outputDir, 'robots.txt'), robots, 'utf-8');
150
+ console.log('Generated robots.txt');
151
+ }
128
152
  writeSitemap(outputDir, baseUrl) {
129
153
  const urls = this.documents
130
154
  .map((doc) => {
package/man/botdocs.1 CHANGED
@@ -106,8 +106,18 @@ When set, each page emits
106
106
  .B rel=canonical
107
107
  and Open Graph/Twitter card metadata, and a
108
108
  .B sitemap.xml
109
- listing every generated page is written to the output directory.
110
- When unset, these tags are omitted and no sitemap is generated.
109
+ listing every generated page and a
110
+ .B robots.txt
111
+ pointing at it are written to the output directory, and
112
+ .B llms.txt
113
+ links become absolute.
114
+ When unset, these tags are omitted and no sitemap or robots.txt is generated.
115
+ .PP
116
+ Every build copies each page's markdown source next to its HTML and writes an
117
+ .B llms.txt
118
+ index linking to those
119
+ .B .md
120
+ files, so agents can read the docs without parsing HTML.
111
121
  .PP
112
122
  .B build.minChunkSize
113
123
  folds chunks smaller than this many estimated tokens into a neighboring
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botdocs",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "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",