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.
- mkdocs_ask_ai-0.3.0/LICENSE +22 -0
- mkdocs_ask_ai-0.3.0/PKG-INFO +113 -0
- mkdocs_ask_ai-0.3.0/README.md +86 -0
- mkdocs_ask_ai-0.3.0/pyproject.toml +72 -0
- mkdocs_ask_ai-0.3.0/setup.cfg +4 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai/__init__.py +7 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai/config.py +48 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai/plugin.py +344 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai.egg-info/PKG-INFO +113 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai.egg-info/SOURCES.txt +12 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai.egg-info/dependency_links.txt +1 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai.egg-info/entry_points.txt +2 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai.egg-info/requires.txt +1 -0
- mkdocs_ask_ai-0.3.0/src/mkdocs_ask_ai.egg-info/top_level.txt +1 -0
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mkdocs>=1.4.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mkdocs_ask_ai
|