mkdocs-ask-ai 0.3.0__tar.gz

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.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ruben Khachaturov
4
+ Copyright (c) 2024 Nok Lam Chan (original author)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: mkdocs-ask-ai
3
+ Version: 0.3.0
4
+ Summary: MkDocs plugin for LLM-friendly documentation with direct markdown serving and i18n support
5
+ Author-email: Ruben Khachaturov <mr.kha4a2rov@protonmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mrkhachaturov/mkdocs-ask-ai
8
+ Project-URL: Bug Reports, https://github.com/mrkhachaturov/mkdocs-ask-ai/issues
9
+ Project-URL: Source, https://github.com/mrkhachaturov/mkdocs-ask-ai
10
+ Keywords: mkdocs,plugin,llm,documentation,markdown,ai,llms-txt,i18n
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Documentation
21
+ Classifier: Topic :: Software Development :: Documentation
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: mkdocs>=1.4.0
26
+ Dynamic: license-file
27
+
28
+ # mkdocs-ask-ai
29
+
30
+ MkDocs plugin for LLM-friendly documentation that provides:
31
+
32
+ 1. **Direct markdown serving** - Access original markdown at `page.md` URLs
33
+ 2. **llms.txt generation** - Concise index file for LLM context
34
+ 3. **llms-full.txt generation** - Complete documentation in single file
35
+ 4. **Copy-to-markdown button** - Easy copying of source markdown
36
+ 5. **i18n support** - Works with `mkdocs-static-i18n` (suffix structure)
37
+
38
+ ## Features
39
+
40
+ - Source-first approach — works with original markdown, no HTML parsing
41
+ - LLM optimized — token-efficient formats for AI consumption
42
+ - Copy button — one-click markdown copying for developers
43
+ - Dual URLs — both human-readable HTML and LLM-friendly markdown
44
+ - Multilingual — generates separate `llms.txt` / `llms-full.txt` per locale
45
+
46
+ > **Origin:** Forked from [mkdocs-llmstxt-md](https://github.com/noklam/mkdocs-llmstxt-md)
47
+ > by Nok Lam Chan (MIT). This fork adds i18n support and will diverge
48
+ > significantly from the upstream project.
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install mkdocs-ask-ai
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ Add to your `mkdocs.yml`:
59
+
60
+ ```yaml
61
+ plugins:
62
+ - ask-ai:
63
+ sections:
64
+ "Getting Started":
65
+ - index.md: "Introduction to the project"
66
+ - quickstart.md
67
+ "API Reference":
68
+ - api/*.md
69
+ ```
70
+
71
+ ### With mkdocs-static-i18n
72
+
73
+ Place the `ask-ai` plugin **before** `i18n` in the plugins list. The plugin
74
+ automatically detects locale builds and writes `llms.txt` / `llms-full.txt`
75
+ to each locale's output directory:
76
+
77
+ ```yaml
78
+ plugins:
79
+ - ask-ai:
80
+ sections:
81
+ "Docs":
82
+ - "*.md"
83
+ - i18n:
84
+ docs_structure: suffix
85
+ languages:
86
+ - locale: en
87
+ default: true
88
+ name: English
89
+ - locale: ru
90
+ name: Русский
91
+ ```
92
+
93
+ This generates:
94
+ - `site/llms.txt` — English index
95
+ - `site/ru/llms.txt` — Russian index
96
+ - `site/llms-full.txt` — English full docs
97
+ - `site/ru/llms-full.txt` — Russian full docs
98
+
99
+ ## Configuration
100
+
101
+ | Option | Default | Description |
102
+ |--------|---------|-------------|
103
+ | `sections` | `{}` | Dict of section names to file patterns |
104
+ | `enable_markdown_urls` | `true` | Enable `.md` URL serving |
105
+ | `enable_llms_txt` | `true` | Generate `llms.txt` |
106
+ | `enable_llms_full` | `true` | Generate `llms-full.txt` |
107
+ | `enable_copy_button` | `true` | Add copy button to pages |
108
+ | `copy_button_text` | `"Copy Markdown"` | Text for the copy button |
109
+ | `markdown_description` | — | Optional description in `llms.txt` |
110
+
111
+ ## License
112
+
113
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,86 @@
1
+ # mkdocs-ask-ai
2
+
3
+ MkDocs plugin for LLM-friendly documentation that provides:
4
+
5
+ 1. **Direct markdown serving** - Access original markdown at `page.md` URLs
6
+ 2. **llms.txt generation** - Concise index file for LLM context
7
+ 3. **llms-full.txt generation** - Complete documentation in single file
8
+ 4. **Copy-to-markdown button** - Easy copying of source markdown
9
+ 5. **i18n support** - Works with `mkdocs-static-i18n` (suffix structure)
10
+
11
+ ## Features
12
+
13
+ - Source-first approach — works with original markdown, no HTML parsing
14
+ - LLM optimized — token-efficient formats for AI consumption
15
+ - Copy button — one-click markdown copying for developers
16
+ - Dual URLs — both human-readable HTML and LLM-friendly markdown
17
+ - Multilingual — generates separate `llms.txt` / `llms-full.txt` per locale
18
+
19
+ > **Origin:** Forked from [mkdocs-llmstxt-md](https://github.com/noklam/mkdocs-llmstxt-md)
20
+ > by Nok Lam Chan (MIT). This fork adds i18n support and will diverge
21
+ > significantly from the upstream project.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install mkdocs-ask-ai
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Add to your `mkdocs.yml`:
32
+
33
+ ```yaml
34
+ plugins:
35
+ - ask-ai:
36
+ sections:
37
+ "Getting Started":
38
+ - index.md: "Introduction to the project"
39
+ - quickstart.md
40
+ "API Reference":
41
+ - api/*.md
42
+ ```
43
+
44
+ ### With mkdocs-static-i18n
45
+
46
+ Place the `ask-ai` plugin **before** `i18n` in the plugins list. The plugin
47
+ automatically detects locale builds and writes `llms.txt` / `llms-full.txt`
48
+ to each locale's output directory:
49
+
50
+ ```yaml
51
+ plugins:
52
+ - ask-ai:
53
+ sections:
54
+ "Docs":
55
+ - "*.md"
56
+ - i18n:
57
+ docs_structure: suffix
58
+ languages:
59
+ - locale: en
60
+ default: true
61
+ name: English
62
+ - locale: ru
63
+ name: Русский
64
+ ```
65
+
66
+ This generates:
67
+ - `site/llms.txt` — English index
68
+ - `site/ru/llms.txt` — Russian index
69
+ - `site/llms-full.txt` — English full docs
70
+ - `site/ru/llms-full.txt` — Russian full docs
71
+
72
+ ## Configuration
73
+
74
+ | Option | Default | Description |
75
+ |--------|---------|-------------|
76
+ | `sections` | `{}` | Dict of section names to file patterns |
77
+ | `enable_markdown_urls` | `true` | Enable `.md` URL serving |
78
+ | `enable_llms_txt` | `true` | Generate `llms.txt` |
79
+ | `enable_llms_full` | `true` | Generate `llms-full.txt` |
80
+ | `enable_copy_button` | `true` | Add copy button to pages |
81
+ | `copy_button_text` | `"Copy Markdown"` | Text for the copy button |
82
+ | `markdown_description` | — | Optional description in `llms.txt` |
83
+
84
+ ## License
85
+
86
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mkdocs-ask-ai"
7
+ version = "0.3.0"
8
+ description = "MkDocs plugin for LLM-friendly documentation with direct markdown serving and i18n support"
9
+ authors = [{ name = "Ruben Khachaturov", email = "mr.kha4a2rov@protonmail.com" }]
10
+ license = "MIT"
11
+ readme = "README.md"
12
+ requires-python = ">=3.9"
13
+ keywords = ["mkdocs", "plugin", "llm", "documentation", "markdown", "ai", "llms-txt", "i18n"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Documentation",
25
+ "Topic :: Software Development :: Documentation",
26
+ ]
27
+ dependencies = ["mkdocs>=1.4.0"]
28
+
29
+ [project.urls]
30
+ "Homepage" = "https://github.com/mrkhachaturov/mkdocs-ask-ai"
31
+ "Bug Reports" = "https://github.com/mrkhachaturov/mkdocs-ask-ai/issues"
32
+ "Source" = "https://github.com/mrkhachaturov/mkdocs-ask-ai"
33
+
34
+ [project.entry-points."mkdocs.plugins"]
35
+ ask-ai = "mkdocs_ask_ai.plugin:LlmsTxtPlugin"
36
+
37
+ [tool.setuptools]
38
+ package-dir = { "" = "src" }
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [tool.setuptools.package-data]
44
+ mkdocs_ask_ai = ["templates/*", "static/*"]
45
+
46
+ [tool.ruff]
47
+ target-version = "py39"
48
+ line-length = 88
49
+ extend-exclude = ["test-site/", "build/", "dist/"]
50
+
51
+ [tool.ruff.lint]
52
+ select = [
53
+ "E", # pycodestyle errors
54
+ "W", # pycodestyle warnings
55
+ "F", # pyflakes
56
+ "I", # isort
57
+ "B", # flake8-bugbear
58
+ "C4", # flake8-comprehensions
59
+ "UP", # pyupgrade
60
+ ]
61
+ ignore = [
62
+ "E501", # line too long, handled by formatter
63
+ ]
64
+
65
+ [tool.ruff.lint.isort]
66
+ known-first-party = ["mkdocs_ask_ai"]
67
+
68
+ [tool.ruff.format]
69
+ quote-style = "double"
70
+ indent-style = "space"
71
+ skip-magic-trailing-comma = false
72
+ line-ending = "auto"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """MkDocs plugin for LLM-friendly documentation."""
2
+
3
+ __version__ = "0.3.0"
4
+
5
+ from .plugin import LlmsTxtPlugin
6
+
7
+ __all__ = ["LlmsTxtPlugin"]
@@ -0,0 +1,48 @@
1
+ """Configuration for the LLMsTxt plugin."""
2
+
3
+ from mkdocs.config import config_options
4
+ from mkdocs.config.base import Config
5
+
6
+
7
+ class LLMsTxtConfig(Config):
8
+ """Configuration options for the LLMsTxt plugin."""
9
+
10
+ sections = config_options.Type(dict, default={})
11
+ """Dictionary mapping section names to lists of file patterns."""
12
+
13
+ enable_markdown_urls = config_options.Type(bool, default=True)
14
+ """Whether to serve original markdown at .md URLs."""
15
+
16
+ enable_llms_txt = config_options.Type(bool, default=True)
17
+ """Whether to generate llms.txt file."""
18
+
19
+ enable_llms_full = config_options.Type(bool, default=True)
20
+ """Whether to generate llms-full.txt file."""
21
+
22
+ enable_copy_button = config_options.Type(bool, default=True)
23
+ """Whether to add copy-to-markdown button on pages."""
24
+
25
+ copy_button_text = config_options.Type(str, default="Copy Markdown")
26
+ """Text for the copy button."""
27
+
28
+ copy_button_position = config_options.Type(
29
+ dict, default={"top": "80px", "right": "20px", "z_index": "1100"}
30
+ )
31
+ """Position and z-index settings for the copy button."""
32
+
33
+ copy_button_style = config_options.Type(
34
+ dict,
35
+ default={
36
+ "background": "#007acc",
37
+ "color": "white",
38
+ "border": "none",
39
+ "padding": "8px 16px",
40
+ "border_radius": "4px",
41
+ "font_size": "14px",
42
+ "box_shadow": "0 2px 4px rgba(0,0,0,0.2)",
43
+ },
44
+ )
45
+ """CSS styling options for the copy button."""
46
+
47
+ markdown_description = config_options.Optional(config_options.Type(str))
48
+ """Optional description to include in llms.txt."""
@@ -0,0 +1,344 @@
1
+ """Main plugin class for LLMsTxt MkDocs plugin."""
2
+
3
+ import fnmatch
4
+ import mimetypes
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List
8
+ from urllib.parse import urljoin
9
+
10
+ from mkdocs.config.defaults import MkDocsConfig
11
+ from mkdocs.plugins import BasePlugin
12
+ from mkdocs.structure.files import Files
13
+ from mkdocs.structure.pages import Page
14
+
15
+ from .config import LLMsTxtConfig
16
+
17
+ # Pattern to match i18n locale suffixed files (e.g. page.ru.md, page.de.md)
18
+ _I18N_SUFFIX_RE = re.compile(r"\.[a-z]{2,3}\.md$")
19
+
20
+
21
+ class LlmsTxtPlugin(BasePlugin[LLMsTxtConfig]):
22
+ """MkDocs plugin for LLM-friendly documentation."""
23
+
24
+ def __init__(self):
25
+ super().__init__()
26
+ self.mkdocs_config: MkDocsConfig = None
27
+ self.pages_data: Dict[str, List[Dict[str, Any]]] = {}
28
+ self.source_files: Dict[str, str] = {}
29
+
30
+ def on_config(self, config: MkDocsConfig) -> MkDocsConfig:
31
+ """Store MkDocs configuration and validate settings."""
32
+ if not config.site_url:
33
+ raise ValueError(
34
+ "site_url must be set in MkDocs config for llms-txt plugin"
35
+ )
36
+
37
+ # Configure MIME type for .md files so they display in browser instead of downloading
38
+ if self.config.enable_markdown_urls:
39
+ mimetypes.add_type("text/plain", ".md")
40
+
41
+ self.mkdocs_config = config
42
+ self.pages_data = {section: [] for section in self.config.sections.keys()}
43
+ return config
44
+
45
+ def _is_i18n_alternate(self, src_path: str, files: Files) -> bool:
46
+ """Check if a file is an i18n alternate (e.g. page.ru.md).
47
+
48
+ These files are handled by the i18n plugin and should not be included
49
+ directly in glob results — they result in broken URLs.
50
+ """
51
+ return bool(_I18N_SUFFIX_RE.search(src_path))
52
+
53
+ def on_files(self, files: Files, *, config: MkDocsConfig) -> Files:
54
+ """Process files and expand glob patterns in sections."""
55
+ all_src_paths = [f.src_uri for f in files]
56
+
57
+ # Expand glob patterns in sections configuration
58
+ for section_name, file_patterns in self.config.sections.items():
59
+ if isinstance(file_patterns, list):
60
+ for pattern in file_patterns:
61
+ if isinstance(pattern, dict):
62
+ file_path = list(pattern.keys())[0]
63
+ description = list(pattern.values())[0]
64
+ else:
65
+ file_path = pattern
66
+ description = ""
67
+
68
+ # Handle glob patterns
69
+ if "*" in file_path:
70
+ matches = fnmatch.filter(all_src_paths, file_path)
71
+ for match in matches:
72
+ if self._is_i18n_alternate(match, files):
73
+ continue
74
+ if match not in [
75
+ p["src_uri"] for p in self.pages_data[section_name]
76
+ ]:
77
+ self.pages_data[section_name].append(
78
+ {"src_uri": match, "description": description}
79
+ )
80
+ else:
81
+ if file_path in all_src_paths:
82
+ if file_path not in [
83
+ p["src_uri"] for p in self.pages_data[section_name]
84
+ ]:
85
+ self.pages_data[section_name].append(
86
+ {"src_uri": file_path, "description": description}
87
+ )
88
+
89
+ return files
90
+
91
+ def on_page_markdown(
92
+ self, markdown: str, *, page: Page, config: MkDocsConfig, files: Files
93
+ ) -> str:
94
+ """Store original markdown content for later use."""
95
+ src_uri = page.file.src_uri
96
+
97
+ # For i18n: also try matching without locale suffix (e.g. page.ru.md -> page.md)
98
+ src_uri_base = _I18N_SUFFIX_RE.sub(".md", src_uri)
99
+
100
+ # Check if this page is in any of our sections
101
+ for _section_name, page_list in self.pages_data.items():
102
+ for page_data in page_list:
103
+ if page_data["src_uri"] in (src_uri, src_uri_base):
104
+ dest_uri = page.file.dest_uri
105
+ # Skip pages with no dest_uri
106
+ if not dest_uri:
107
+ continue
108
+ self.source_files[src_uri] = markdown
109
+ page_data.update(
110
+ {
111
+ "title": page.title or src_uri,
112
+ "markdown": markdown,
113
+ "dest_path": page.file.dest_path,
114
+ "dest_uri": dest_uri,
115
+ }
116
+ )
117
+ break
118
+
119
+ return markdown
120
+
121
+ def on_page_content(
122
+ self, html: str, *, page: Page, config: MkDocsConfig, files: Files
123
+ ) -> str:
124
+ """Inject copy button if enabled."""
125
+ if not self.config.enable_copy_button:
126
+ return html
127
+
128
+ src_uri = page.file.src_uri
129
+ if src_uri in self.source_files:
130
+ # Inject copy button HTML and JavaScript
131
+ copy_button_html = self._get_copy_button_html(config)
132
+ # Insert before closing body tag if present, otherwise at end
133
+ if "</body>" in html:
134
+ html = html.replace("</body>", f"{copy_button_html}</body>")
135
+ else:
136
+ html += copy_button_html
137
+
138
+ return html
139
+
140
+ def _detect_locale_prefix(self) -> str:
141
+ """Detect locale prefix from collected page dest_paths.
142
+
143
+ If pages have dest_paths like 'ru/infrastructure/index.html',
144
+ returns 'ru'. Returns '' for default locale (no prefix).
145
+ """
146
+ for section_pages in self.pages_data.values():
147
+ for page_data in section_pages:
148
+ dest_path = page_data.get("dest_path", "")
149
+ if dest_path:
150
+ # Check if dest_path starts with a locale-like prefix (2-3 letter dir)
151
+ parts = Path(dest_path).parts
152
+ if len(parts) >= 2 and len(parts[0]) <= 3 and parts[0].isalpha():
153
+ return parts[0]
154
+ return ""
155
+ return ""
156
+
157
+ def _get_output_dir(self, config: MkDocsConfig) -> Path:
158
+ """Get the output directory, accounting for i18n locale subdirectories."""
159
+ site_dir = Path(config.site_dir)
160
+ locale_prefix = self._detect_locale_prefix()
161
+ if locale_prefix:
162
+ locale_dir = site_dir / locale_prefix
163
+ locale_dir.mkdir(parents=True, exist_ok=True)
164
+ return locale_dir
165
+ return site_dir
166
+
167
+ def on_post_build(self, *, config: MkDocsConfig) -> None:
168
+ """Generate llms.txt, llms-full.txt, and markdown files."""
169
+ output_dir = self._get_output_dir(config)
170
+ site_dir = Path(config.site_dir)
171
+
172
+ # Generate individual markdown files if markdown URLs are enabled
173
+ if self.config.enable_markdown_urls:
174
+ self._generate_markdown_files(site_dir)
175
+
176
+ # Generate llms.txt
177
+ if self.config.enable_llms_txt:
178
+ self._generate_llms_txt(output_dir, config)
179
+
180
+ # Generate llms-full.txt
181
+ if self.config.enable_llms_full:
182
+ self._generate_llms_full_txt(output_dir, config)
183
+
184
+ def _generate_markdown_files(self, site_dir: Path) -> None:
185
+ """Generate individual .md files for each page."""
186
+ for section_pages in self.pages_data.values():
187
+ for page_data in section_pages:
188
+ if "markdown" in page_data and "dest_path" in page_data:
189
+ # Create .md version alongside HTML
190
+ html_path = Path(page_data["dest_path"])
191
+ md_path = site_dir / html_path.with_suffix(".md")
192
+
193
+ # Ensure directory exists
194
+ md_path.parent.mkdir(parents=True, exist_ok=True)
195
+
196
+ # Write markdown content
197
+ md_path.write_text(page_data["markdown"], encoding="utf-8")
198
+
199
+ def _generate_llms_txt(self, site_dir: Path, config: MkDocsConfig) -> None:
200
+ """Generate the llms.txt index file."""
201
+ llms_txt_path = site_dir / "llms.txt"
202
+
203
+ content = f"# {config.site_name}\n\n"
204
+
205
+ if config.site_description:
206
+ content += f"> {config.site_description}\n\n"
207
+
208
+ if self.config.markdown_description:
209
+ content += f"{self.config.markdown_description}\n\n"
210
+
211
+ base_url = config.site_url.rstrip("/")
212
+
213
+ for section_name, pages in self.pages_data.items():
214
+ if pages: # Only add section if it has pages
215
+ content += f"## {section_name}\n\n"
216
+
217
+ for page_data in pages:
218
+ dest_uri = page_data.get("dest_uri", "")
219
+ if not dest_uri:
220
+ continue
221
+ title = page_data.get("title", page_data["src_uri"])
222
+ description = page_data.get("description", "")
223
+
224
+ # Create markdown URL
225
+ md_url = urljoin(
226
+ base_url + "/",
227
+ dest_uri.replace(".html", ".md")
228
+ if dest_uri.endswith(".html")
229
+ else dest_uri + ".md",
230
+ )
231
+
232
+ desc_text = f": {description}" if description else ""
233
+ content += f"- [{title}]({md_url}){desc_text}\n"
234
+
235
+ content += "\n"
236
+
237
+ llms_txt_path.write_text(content.strip(), encoding="utf-8")
238
+
239
+ def _generate_llms_full_txt(self, site_dir: Path, config: MkDocsConfig) -> None:
240
+ """Generate the llms-full.txt complete documentation file."""
241
+ llms_full_path = site_dir / "llms-full.txt"
242
+
243
+ content = f"# {config.site_name}\n\n"
244
+
245
+ if config.site_description:
246
+ content += f"> {config.site_description}\n\n"
247
+
248
+ if self.config.markdown_description:
249
+ content += f"{self.config.markdown_description}\n\n"
250
+
251
+ for section_name, pages in self.pages_data.items():
252
+ if pages: # Only add section if it has pages
253
+ content += f"# {section_name}\n\n"
254
+
255
+ for page_data in pages:
256
+ if "markdown" in page_data:
257
+ title = page_data.get("title", page_data["src_uri"])
258
+ content += f"## {title}\n\n"
259
+ content += f"{page_data['markdown']}\n\n"
260
+
261
+ llms_full_path.write_text(content.strip(), encoding="utf-8")
262
+
263
+ def _get_theme_adjustments(self, config: MkDocsConfig) -> Dict[str, str]:
264
+ """Get theme-specific adjustments for copy button positioning."""
265
+ theme_name = (
266
+ config.theme.name if hasattr(config.theme, "name") else str(config.theme)
267
+ )
268
+
269
+ # Theme-specific defaults
270
+ theme_adjustments = {
271
+ "material": {"top": "64px", "z_index": "2000"}, # Material has tall header
272
+ "mkdocs": {"top": "80px", "z_index": "1100"}, # Default MkDocs theme
273
+ "readthedocs": {"top": "60px", "z_index": "1100"},
274
+ "bootstrap": {"top": "70px", "z_index": "1100"},
275
+ "windmill": {"top": "50px", "z_index": "1000"},
276
+ }
277
+
278
+ return theme_adjustments.get(theme_name, {"top": "80px", "z_index": "1100"})
279
+
280
+ def _get_copy_button_html(self, config: MkDocsConfig) -> str:
281
+ """Generate HTML for the copy button with inline CSS and JavaScript."""
282
+ # Get theme adjustments
283
+ theme_defaults = self._get_theme_adjustments(config)
284
+
285
+ # Get positioning configuration, with theme-aware defaults
286
+ position = self.config.copy_button_position
287
+ top = position.get("top", theme_defaults.get("top", "80px"))
288
+ right = position.get("right", "20px")
289
+ left = position.get("left", "")
290
+ bottom = position.get("bottom", "")
291
+ z_index = position.get("z_index", theme_defaults.get("z_index", "1100"))
292
+
293
+ # Build position style
294
+ position_style = f"position: fixed; z-index: {z_index};"
295
+ if top:
296
+ position_style += f" top: {top};"
297
+ if right:
298
+ position_style += f" right: {right};"
299
+ if left:
300
+ position_style += f" left: {left};"
301
+ if bottom:
302
+ position_style += f" bottom: {bottom};"
303
+
304
+ # Get styling configuration
305
+ style = self.config.copy_button_style
306
+ button_style = "cursor: pointer;"
307
+ for css_prop, value in style.items():
308
+ # Convert snake_case to kebab-case for CSS
309
+ css_property = css_prop.replace("_", "-")
310
+ button_style += f" {css_property}: {value};"
311
+
312
+ return rf"""
313
+ <div id="llms-copy-button" style="{position_style}">
314
+ <button onclick="copyMarkdownToClipboard()" style="{button_style}">{self.config.copy_button_text}</button>
315
+ </div>
316
+ <script>
317
+ async function copyMarkdownToClipboard() {{
318
+ try {{
319
+ const currentPath = window.location.pathname;
320
+ const mdPath = currentPath.endsWith('/') ? currentPath + 'index.md' : currentPath.replace(/\.html$/, '.md');
321
+
322
+ const response = await fetch(mdPath);
323
+ if (response.ok) {{
324
+ const markdown = await response.text();
325
+ await navigator.clipboard.writeText(markdown);
326
+ // Show feedback
327
+ const button = document.querySelector('#llms-copy-button button');
328
+ const originalText = button.textContent;
329
+ button.textContent = 'Copied!';
330
+ button.style.background = '#28a745';
331
+ setTimeout(() => {{
332
+ button.textContent = originalText;
333
+ button.style.background = '#007acc';
334
+ }}, 2000);
335
+ }} else {{
336
+ throw new Error('Markdown file not found');
337
+ }}
338
+ }} catch (err) {{
339
+ console.error('Failed to copy markdown:', err);
340
+ alert('Failed to copy markdown content');
341
+ }}
342
+ }}
343
+ </script>
344
+ """
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: mkdocs-ask-ai
3
+ Version: 0.3.0
4
+ Summary: MkDocs plugin for LLM-friendly documentation with direct markdown serving and i18n support
5
+ Author-email: Ruben Khachaturov <mr.kha4a2rov@protonmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mrkhachaturov/mkdocs-ask-ai
8
+ Project-URL: Bug Reports, https://github.com/mrkhachaturov/mkdocs-ask-ai/issues
9
+ Project-URL: Source, https://github.com/mrkhachaturov/mkdocs-ask-ai
10
+ Keywords: mkdocs,plugin,llm,documentation,markdown,ai,llms-txt,i18n
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Documentation
21
+ Classifier: Topic :: Software Development :: Documentation
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: mkdocs>=1.4.0
26
+ Dynamic: license-file
27
+
28
+ # mkdocs-ask-ai
29
+
30
+ MkDocs plugin for LLM-friendly documentation that provides:
31
+
32
+ 1. **Direct markdown serving** - Access original markdown at `page.md` URLs
33
+ 2. **llms.txt generation** - Concise index file for LLM context
34
+ 3. **llms-full.txt generation** - Complete documentation in single file
35
+ 4. **Copy-to-markdown button** - Easy copying of source markdown
36
+ 5. **i18n support** - Works with `mkdocs-static-i18n` (suffix structure)
37
+
38
+ ## Features
39
+
40
+ - Source-first approach — works with original markdown, no HTML parsing
41
+ - LLM optimized — token-efficient formats for AI consumption
42
+ - Copy button — one-click markdown copying for developers
43
+ - Dual URLs — both human-readable HTML and LLM-friendly markdown
44
+ - Multilingual — generates separate `llms.txt` / `llms-full.txt` per locale
45
+
46
+ > **Origin:** Forked from [mkdocs-llmstxt-md](https://github.com/noklam/mkdocs-llmstxt-md)
47
+ > by Nok Lam Chan (MIT). This fork adds i18n support and will diverge
48
+ > significantly from the upstream project.
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install mkdocs-ask-ai
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ Add to your `mkdocs.yml`:
59
+
60
+ ```yaml
61
+ plugins:
62
+ - ask-ai:
63
+ sections:
64
+ "Getting Started":
65
+ - index.md: "Introduction to the project"
66
+ - quickstart.md
67
+ "API Reference":
68
+ - api/*.md
69
+ ```
70
+
71
+ ### With mkdocs-static-i18n
72
+
73
+ Place the `ask-ai` plugin **before** `i18n` in the plugins list. The plugin
74
+ automatically detects locale builds and writes `llms.txt` / `llms-full.txt`
75
+ to each locale's output directory:
76
+
77
+ ```yaml
78
+ plugins:
79
+ - ask-ai:
80
+ sections:
81
+ "Docs":
82
+ - "*.md"
83
+ - i18n:
84
+ docs_structure: suffix
85
+ languages:
86
+ - locale: en
87
+ default: true
88
+ name: English
89
+ - locale: ru
90
+ name: Русский
91
+ ```
92
+
93
+ This generates:
94
+ - `site/llms.txt` — English index
95
+ - `site/ru/llms.txt` — Russian index
96
+ - `site/llms-full.txt` — English full docs
97
+ - `site/ru/llms-full.txt` — Russian full docs
98
+
99
+ ## Configuration
100
+
101
+ | Option | Default | Description |
102
+ |--------|---------|-------------|
103
+ | `sections` | `{}` | Dict of section names to file patterns |
104
+ | `enable_markdown_urls` | `true` | Enable `.md` URL serving |
105
+ | `enable_llms_txt` | `true` | Generate `llms.txt` |
106
+ | `enable_llms_full` | `true` | Generate `llms-full.txt` |
107
+ | `enable_copy_button` | `true` | Add copy button to pages |
108
+ | `copy_button_text` | `"Copy Markdown"` | Text for the copy button |
109
+ | `markdown_description` | — | Optional description in `llms.txt` |
110
+
111
+ ## License
112
+
113
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/mkdocs_ask_ai/__init__.py
5
+ src/mkdocs_ask_ai/config.py
6
+ src/mkdocs_ask_ai/plugin.py
7
+ src/mkdocs_ask_ai.egg-info/PKG-INFO
8
+ src/mkdocs_ask_ai.egg-info/SOURCES.txt
9
+ src/mkdocs_ask_ai.egg-info/dependency_links.txt
10
+ src/mkdocs_ask_ai.egg-info/entry_points.txt
11
+ src/mkdocs_ask_ai.egg-info/requires.txt
12
+ src/mkdocs_ask_ai.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [mkdocs.plugins]
2
+ ask-ai = mkdocs_ask_ai.plugin:LlmsTxtPlugin
@@ -0,0 +1 @@
1
+ mkdocs>=1.4.0
@@ -0,0 +1 @@
1
+ mkdocs_ask_ai