mkdocs-ultralytics-plugin 0.2.4__tar.gz → 0.2.6__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.
Files changed (16) hide show
  1. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/PKG-INFO +13 -10
  2. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/README.md +11 -9
  3. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/PKG-INFO +13 -10
  4. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/__init__.py +1 -1
  5. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/main.py +28 -3
  6. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/postprocess.py +137 -19
  7. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/processor.py +19 -15
  8. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/utils.py +43 -7
  9. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/pyproject.toml +1 -3
  10. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/LICENSE +0 -0
  11. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/SOURCES.txt +0 -0
  12. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/dependency_links.txt +0 -0
  13. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/entry_points.txt +0 -0
  14. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/requires.txt +0 -0
  15. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/top_level.txt +0 -0
  16. {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-ultralytics-plugin
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: An MkDocs plugin that provides Ultralytics Docs customizations at https://docs.ultralytics.com.
5
5
  Author-email: Glenn Jocher <hello@ultralytics.com>
6
6
  Maintainer-email: Ultralytics <hello@ultralytics.com>
@@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: 3.11
24
24
  Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
26
  Classifier: Programming Language :: Python :: 3.14
27
+ Classifier: Programming Language :: Python :: 3.15
27
28
  Classifier: Topic :: Software Development
28
29
  Classifier: Topic :: Scientific/Engineering
29
30
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -41,11 +42,11 @@ Provides-Extra: dev
41
42
  Requires-Dist: pytest; extra == "dev"
42
43
  Dynamic: license-file
43
44
 
44
- <a href="https://www.ultralytics.com/"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
45
+ <a href="https://www.ultralytics.com"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
45
46
 
46
47
  # 🚀 MkDocs Ultralytics Plugin
47
48
 
48
- Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.com/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
49
+ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.org/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
49
50
 
50
51
  **Two modes available:**
51
52
 
@@ -55,8 +56,8 @@ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your
55
56
  [![Ultralytics Actions](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml/badge.svg)](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml)
56
57
 
57
58
  [![Ultralytics Discord](https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue)](https://discord.com/invite/ultralytics)
58
- [![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com/)
59
- [![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://reddit.com/r/ultralytics)
59
+ [![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com)
60
+ [![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://www.reddit.com/r/ultralytics/)
60
61
 
61
62
  ## ✨ Features
62
63
 
@@ -64,12 +65,13 @@ This tool seamlessly integrates valuable features into your documentation site:
64
65
 
65
66
  - **Meta Tag Generation**: Automatically creates meta description and image tags using the first paragraph and image found on each page, crucial for SEO and social previews.
66
67
  - **Keyword Customization**: Allows you to define specific meta keywords directly within your Markdown front matter for targeted SEO.
67
- - **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://developer.x.com/en/docs/x-for-websites/cards/overview/summary-card-with-large-image) meta tags to ensure your content looks great when shared on social platforms.
68
+ - **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://docs.x.com/overview) meta tags to ensure your content looks great when shared on social platforms.
68
69
  - **Simple Sharing**: Inserts convenient share buttons for Twitter and LinkedIn at the end of your content, encouraging readers to share.
69
70
  - **Git Insights**: Gathers and displays [Git](https://git-scm.com/) commit information, including update dates and authors, directly within the page footer for transparency.
70
71
  - **JSON-LD Support**: Adds structured data in JSON-LD format, helping search engines understand your content better and potentially enabling rich results.
71
72
  - **FAQ Parsing**: Automatically parses FAQ sections (if present) and includes them in the structured data for enhanced search visibility.
72
73
  - **Copy for LLM**: Adds a button to copy page content in Markdown format, optimized for sharing with AI assistants.
74
+ - **LLMs.txt Generation**: Generates an `llms.txt` index after builds for LLM-friendly site discovery.
73
75
  - **Customizable Styling**: Includes optional inline CSS to maintain consistent styling across your documentation, aligning with themes like [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
74
76
 
75
77
  ## 🛠️ Installation
@@ -161,7 +163,7 @@ jekyll build && python postprocess.py
161
163
 
162
164
  ## ⚙️ Configuration Options
163
165
 
164
- Both modes support the same configuration options:
166
+ Both modes support the feature options below. Differences: `enabled` applies to plugin mode only, while postprocess mode reads `site_dir`, `docs_dir`, and `site_url` as arguments (plugin mode gets these from `mkdocs.yml`) and additionally accepts `use_processes` (default `True`) and `workers` (default: CPU count) to control parallelism.
165
167
 
166
168
  | Option | Type | Default | Description |
167
169
  | ------------------- | ---- | ------- | -------------------------------- |
@@ -177,6 +179,7 @@ Both modes support the same configuration options:
177
179
  | `add_json_ld` | bool | `False` | Add JSON-LD structured data |
178
180
  | `add_css` | bool | `True` | Include inline CSS styles |
179
181
  | `add_copy_llm` | bool | `True` | Add "Copy for LLM" button |
182
+ | `add_llms_txt` | bool | `True` | Generate an `llms.txt` index |
180
183
 
181
184
  ## 🧩 How It Works
182
185
 
@@ -236,7 +239,7 @@ Both plugin mode and postprocess mode use the same `process_html()` function in
236
239
 
237
240
  Collaboration fuels innovation! 🤗 The success of Ultralytics' open-source projects thrives on community contributions. We welcome your involvement, whether it's fixing bugs, proposing new features, improving documentation, engaging in discussions, or sharing how you use Ultralytics tools.
238
241
 
239
- Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing/) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇‍♂️ every contribution!
242
+ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇‍♂️ every contribution!
240
243
 
241
244
  [![Ultralytics open-source contributors](https://raw.githubusercontent.com/ultralytics/assets/main/im/image-contributors.png)](https://github.com/ultralytics/ultralytics/graphs/contributors)
242
245
 
@@ -244,7 +247,7 @@ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributi
244
247
 
245
248
  Ultralytics provides two licensing options:
246
249
 
247
- - **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-v3) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
250
+ - **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-3.0) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
248
251
  - **Enterprise License**: Designed for commercial applications, this license allows seamless integration of Ultralytics software into commercial products, bypassing AGPL-3.0 requirements. Visit [Ultralytics Licensing](https://www.ultralytics.com/license) for details.
249
252
 
250
253
  ## ✉️ Connect with Us
@@ -259,7 +262,7 @@ Encountered a bug or have an idea? Visit [GitHub Issues](https://github.com/ultr
259
262
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
260
263
  <a href="https://twitter.com/ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-twitter.png" width="3%" alt="Ultralytics Twitter"></a>
261
264
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
262
- <a href="https://youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
265
+ <a href="https://www.youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
263
266
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
264
267
  <a href="https://www.tiktok.com/@ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-tiktok.png" width="3%" alt="Ultralytics TikTok"></a>
265
268
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
@@ -1,8 +1,8 @@
1
- <a href="https://www.ultralytics.com/"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
1
+ <a href="https://www.ultralytics.com"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
2
2
 
3
3
  # 🚀 MkDocs Ultralytics Plugin
4
4
 
5
- Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.com/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
5
+ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.org/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
6
6
 
7
7
  **Two modes available:**
8
8
 
@@ -12,8 +12,8 @@ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your
12
12
  [![Ultralytics Actions](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml/badge.svg)](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml)
13
13
 
14
14
  [![Ultralytics Discord](https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue)](https://discord.com/invite/ultralytics)
15
- [![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com/)
16
- [![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://reddit.com/r/ultralytics)
15
+ [![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com)
16
+ [![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://www.reddit.com/r/ultralytics/)
17
17
 
18
18
  ## ✨ Features
19
19
 
@@ -21,12 +21,13 @@ This tool seamlessly integrates valuable features into your documentation site:
21
21
 
22
22
  - **Meta Tag Generation**: Automatically creates meta description and image tags using the first paragraph and image found on each page, crucial for SEO and social previews.
23
23
  - **Keyword Customization**: Allows you to define specific meta keywords directly within your Markdown front matter for targeted SEO.
24
- - **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://developer.x.com/en/docs/x-for-websites/cards/overview/summary-card-with-large-image) meta tags to ensure your content looks great when shared on social platforms.
24
+ - **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://docs.x.com/overview) meta tags to ensure your content looks great when shared on social platforms.
25
25
  - **Simple Sharing**: Inserts convenient share buttons for Twitter and LinkedIn at the end of your content, encouraging readers to share.
26
26
  - **Git Insights**: Gathers and displays [Git](https://git-scm.com/) commit information, including update dates and authors, directly within the page footer for transparency.
27
27
  - **JSON-LD Support**: Adds structured data in JSON-LD format, helping search engines understand your content better and potentially enabling rich results.
28
28
  - **FAQ Parsing**: Automatically parses FAQ sections (if present) and includes them in the structured data for enhanced search visibility.
29
29
  - **Copy for LLM**: Adds a button to copy page content in Markdown format, optimized for sharing with AI assistants.
30
+ - **LLMs.txt Generation**: Generates an `llms.txt` index after builds for LLM-friendly site discovery.
30
31
  - **Customizable Styling**: Includes optional inline CSS to maintain consistent styling across your documentation, aligning with themes like [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
31
32
 
32
33
  ## 🛠️ Installation
@@ -118,7 +119,7 @@ jekyll build && python postprocess.py
118
119
 
119
120
  ## ⚙️ Configuration Options
120
121
 
121
- Both modes support the same configuration options:
122
+ Both modes support the feature options below. Differences: `enabled` applies to plugin mode only, while postprocess mode reads `site_dir`, `docs_dir`, and `site_url` as arguments (plugin mode gets these from `mkdocs.yml`) and additionally accepts `use_processes` (default `True`) and `workers` (default: CPU count) to control parallelism.
122
123
 
123
124
  | Option | Type | Default | Description |
124
125
  | ------------------- | ---- | ------- | -------------------------------- |
@@ -134,6 +135,7 @@ Both modes support the same configuration options:
134
135
  | `add_json_ld` | bool | `False` | Add JSON-LD structured data |
135
136
  | `add_css` | bool | `True` | Include inline CSS styles |
136
137
  | `add_copy_llm` | bool | `True` | Add "Copy for LLM" button |
138
+ | `add_llms_txt` | bool | `True` | Generate an `llms.txt` index |
137
139
 
138
140
  ## 🧩 How It Works
139
141
 
@@ -193,7 +195,7 @@ Both plugin mode and postprocess mode use the same `process_html()` function in
193
195
 
194
196
  Collaboration fuels innovation! 🤗 The success of Ultralytics' open-source projects thrives on community contributions. We welcome your involvement, whether it's fixing bugs, proposing new features, improving documentation, engaging in discussions, or sharing how you use Ultralytics tools.
195
197
 
196
- Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing/) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇‍♂️ every contribution!
198
+ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇‍♂️ every contribution!
197
199
 
198
200
  [![Ultralytics open-source contributors](https://raw.githubusercontent.com/ultralytics/assets/main/im/image-contributors.png)](https://github.com/ultralytics/ultralytics/graphs/contributors)
199
201
 
@@ -201,7 +203,7 @@ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributi
201
203
 
202
204
  Ultralytics provides two licensing options:
203
205
 
204
- - **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-v3) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
206
+ - **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-3.0) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
205
207
  - **Enterprise License**: Designed for commercial applications, this license allows seamless integration of Ultralytics software into commercial products, bypassing AGPL-3.0 requirements. Visit [Ultralytics Licensing](https://www.ultralytics.com/license) for details.
206
208
 
207
209
  ## ✉️ Connect with Us
@@ -216,7 +218,7 @@ Encountered a bug or have an idea? Visit [GitHub Issues](https://github.com/ultr
216
218
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
217
219
  <a href="https://twitter.com/ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-twitter.png" width="3%" alt="Ultralytics Twitter"></a>
218
220
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
219
- <a href="https://youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
221
+ <a href="https://www.youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
220
222
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
221
223
  <a href="https://www.tiktok.com/@ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-tiktok.png" width="3%" alt="Ultralytics TikTok"></a>
222
224
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-ultralytics-plugin
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: An MkDocs plugin that provides Ultralytics Docs customizations at https://docs.ultralytics.com.
5
5
  Author-email: Glenn Jocher <hello@ultralytics.com>
6
6
  Maintainer-email: Ultralytics <hello@ultralytics.com>
@@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: 3.11
24
24
  Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
26
  Classifier: Programming Language :: Python :: 3.14
27
+ Classifier: Programming Language :: Python :: 3.15
27
28
  Classifier: Topic :: Software Development
28
29
  Classifier: Topic :: Scientific/Engineering
29
30
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -41,11 +42,11 @@ Provides-Extra: dev
41
42
  Requires-Dist: pytest; extra == "dev"
42
43
  Dynamic: license-file
43
44
 
44
- <a href="https://www.ultralytics.com/"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
45
+ <a href="https://www.ultralytics.com"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
45
46
 
46
47
  # 🚀 MkDocs Ultralytics Plugin
47
48
 
48
- Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.com/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
49
+ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.org/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
49
50
 
50
51
  **Two modes available:**
51
52
 
@@ -55,8 +56,8 @@ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your
55
56
  [![Ultralytics Actions](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml/badge.svg)](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml)
56
57
 
57
58
  [![Ultralytics Discord](https://img.shields.io/discord/1089800235347353640?logo=discord&logoColor=white&label=Discord&color=blue)](https://discord.com/invite/ultralytics)
58
- [![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com/)
59
- [![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://reddit.com/r/ultralytics)
59
+ [![Ultralytics Forums](https://img.shields.io/discourse/users?server=https%3A%2F%2Fcommunity.ultralytics.com&logo=discourse&label=Forums&color=blue)](https://community.ultralytics.com)
60
+ [![Ultralytics Reddit](https://img.shields.io/reddit/subreddit-subscribers/ultralytics?style=flat&logo=reddit&logoColor=white&label=Reddit&color=blue)](https://www.reddit.com/r/ultralytics/)
60
61
 
61
62
  ## ✨ Features
62
63
 
@@ -64,12 +65,13 @@ This tool seamlessly integrates valuable features into your documentation site:
64
65
 
65
66
  - **Meta Tag Generation**: Automatically creates meta description and image tags using the first paragraph and image found on each page, crucial for SEO and social previews.
66
67
  - **Keyword Customization**: Allows you to define specific meta keywords directly within your Markdown front matter for targeted SEO.
67
- - **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://developer.x.com/en/docs/x-for-websites/cards/overview/summary-card-with-large-image) meta tags to ensure your content looks great when shared on social platforms.
68
+ - **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://docs.x.com/overview) meta tags to ensure your content looks great when shared on social platforms.
68
69
  - **Simple Sharing**: Inserts convenient share buttons for Twitter and LinkedIn at the end of your content, encouraging readers to share.
69
70
  - **Git Insights**: Gathers and displays [Git](https://git-scm.com/) commit information, including update dates and authors, directly within the page footer for transparency.
70
71
  - **JSON-LD Support**: Adds structured data in JSON-LD format, helping search engines understand your content better and potentially enabling rich results.
71
72
  - **FAQ Parsing**: Automatically parses FAQ sections (if present) and includes them in the structured data for enhanced search visibility.
72
73
  - **Copy for LLM**: Adds a button to copy page content in Markdown format, optimized for sharing with AI assistants.
74
+ - **LLMs.txt Generation**: Generates an `llms.txt` index after builds for LLM-friendly site discovery.
73
75
  - **Customizable Styling**: Includes optional inline CSS to maintain consistent styling across your documentation, aligning with themes like [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
74
76
 
75
77
  ## 🛠️ Installation
@@ -161,7 +163,7 @@ jekyll build && python postprocess.py
161
163
 
162
164
  ## ⚙️ Configuration Options
163
165
 
164
- Both modes support the same configuration options:
166
+ Both modes support the feature options below. Differences: `enabled` applies to plugin mode only, while postprocess mode reads `site_dir`, `docs_dir`, and `site_url` as arguments (plugin mode gets these from `mkdocs.yml`) and additionally accepts `use_processes` (default `True`) and `workers` (default: CPU count) to control parallelism.
165
167
 
166
168
  | Option | Type | Default | Description |
167
169
  | ------------------- | ---- | ------- | -------------------------------- |
@@ -177,6 +179,7 @@ Both modes support the same configuration options:
177
179
  | `add_json_ld` | bool | `False` | Add JSON-LD structured data |
178
180
  | `add_css` | bool | `True` | Include inline CSS styles |
179
181
  | `add_copy_llm` | bool | `True` | Add "Copy for LLM" button |
182
+ | `add_llms_txt` | bool | `True` | Generate an `llms.txt` index |
180
183
 
181
184
  ## 🧩 How It Works
182
185
 
@@ -236,7 +239,7 @@ Both plugin mode and postprocess mode use the same `process_html()` function in
236
239
 
237
240
  Collaboration fuels innovation! 🤗 The success of Ultralytics' open-source projects thrives on community contributions. We welcome your involvement, whether it's fixing bugs, proposing new features, improving documentation, engaging in discussions, or sharing how you use Ultralytics tools.
238
241
 
239
- Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing/) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇‍♂️ every contribution!
242
+ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇‍♂️ every contribution!
240
243
 
241
244
  [![Ultralytics open-source contributors](https://raw.githubusercontent.com/ultralytics/assets/main/im/image-contributors.png)](https://github.com/ultralytics/ultralytics/graphs/contributors)
242
245
 
@@ -244,7 +247,7 @@ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributi
244
247
 
245
248
  Ultralytics provides two licensing options:
246
249
 
247
- - **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-v3) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
250
+ - **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-3.0) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
248
251
  - **Enterprise License**: Designed for commercial applications, this license allows seamless integration of Ultralytics software into commercial products, bypassing AGPL-3.0 requirements. Visit [Ultralytics Licensing](https://www.ultralytics.com/license) for details.
249
252
 
250
253
  ## ✉️ Connect with Us
@@ -259,7 +262,7 @@ Encountered a bug or have an idea? Visit [GitHub Issues](https://github.com/ultr
259
262
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
260
263
  <a href="https://twitter.com/ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-twitter.png" width="3%" alt="Ultralytics Twitter"></a>
261
264
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
262
- <a href="https://youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
265
+ <a href="https://www.youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
263
266
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
264
267
  <a href="https://www.tiktok.com/@ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-tiktok.png" width="3%" alt="Ultralytics TikTok"></a>
265
268
  <img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
@@ -1,6 +1,6 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.2.6"
4
4
 
5
5
  from .main import MetaPlugin
6
6
  from .postprocess import postprocess_site
@@ -1,4 +1,5 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
+ """MkDocs plugin entrypoint for Ultralytics documentation metadata."""
2
3
 
3
4
  from __future__ import annotations
4
5
 
@@ -7,8 +8,9 @@ from pathlib import Path
7
8
  from mkdocs.config import config_options
8
9
  from mkdocs.plugins import BasePlugin
9
10
 
10
- import plugin.processor as processor
11
+ from plugin import processor
11
12
  from plugin.processor import process_html
13
+ from plugin.utils import resolve_all_authors
12
14
 
13
15
 
14
16
  class MetaPlugin(BasePlugin):
@@ -27,9 +29,11 @@ class MetaPlugin(BasePlugin):
27
29
  ("add_json_ld", config_options.Type(bool, default=False)),
28
30
  ("add_css", config_options.Type(bool, default=True)),
29
31
  ("add_copy_llm", config_options.Type(bool, default=True)),
32
+ ("add_llms_txt", config_options.Type(bool, default=True)),
30
33
  )
31
34
 
32
35
  def __init__(self):
36
+ """Initialize cached repository metadata for page processing."""
33
37
  super().__init__()
34
38
  self.git_repo_url = None
35
39
  self.git_data = None
@@ -43,6 +47,12 @@ class MetaPlugin(BasePlugin):
43
47
  docs_dir = Path(config["docs_dir"])
44
48
  md_files = [str(p) for p in docs_dir.rglob("*.md")] if docs_dir.exists() else []
45
49
  self.git_repo_url, self.git_data = processor.build_git_map(md_files)
50
+ self.git_data = resolve_all_authors(
51
+ self.git_data,
52
+ default_author=self.config.get("default_author"),
53
+ repo_url=self.git_repo_url,
54
+ verbose=self.config.get("verbose", True),
55
+ )
46
56
  return config
47
57
 
48
58
  def on_post_page(self, output: str, page, config) -> str:
@@ -57,7 +67,8 @@ class MetaPlugin(BasePlugin):
57
67
  )
58
68
 
59
69
  try:
60
- page_url = (config["site_url"] or "") + page.url.rstrip("/")
70
+ # MkDocs normalizes site_url to end in "/" and gives the home page url "./"
71
+ page_url = f"{(config['site_url'] or '').rstrip('/')}/{page.url}".rstrip("/.") or "/"
61
72
  title = page.title
62
73
  keywords = page.meta.get("keywords", None) if hasattr(page, "meta") else None
63
74
 
@@ -69,7 +80,6 @@ class MetaPlugin(BasePlugin):
69
80
  git_data=self.git_data,
70
81
  repo_url=self.git_repo_url,
71
82
  default_image=self.config["default_image"],
72
- default_author=self.config["default_author"],
73
83
  keywords=keywords,
74
84
  add_desc=self.config["add_desc"],
75
85
  add_image=self.config["add_image"],
@@ -84,3 +94,18 @@ class MetaPlugin(BasePlugin):
84
94
  if self.config["verbose"]:
85
95
  print(f"ERROR - mkdocs-ultralytics-plugin: Failed to process {page.file.src_path}: {e}")
86
96
  return output # Return original output on error
97
+
98
+ def on_post_build(self, config):
99
+ """Generate llms.txt after build completes. Added for mkdocs build compatibility. Not needed for zensical build."""
100
+ if not self.config.get("enabled", True) or not self.config.get("add_llms_txt", True):
101
+ return
102
+ from plugin.postprocess import generate_llms_txt
103
+
104
+ generate_llms_txt(
105
+ site_dir=Path(config["site_dir"]),
106
+ docs_dir=Path(config["docs_dir"]),
107
+ site_url=config.get("site_url", ""),
108
+ site_name=config.get("site_name"),
109
+ site_description=config.get("site_description"),
110
+ nav=config.get("nav"),
111
+ )
@@ -14,7 +14,7 @@ try:
14
14
  except ImportError:
15
15
  TQDM = None
16
16
 
17
- import plugin.processor as processor
17
+ from plugin import processor
18
18
  from plugin.processor import process_html
19
19
  from plugin.utils import resolve_all_authors
20
20
 
@@ -88,7 +88,7 @@ def process_html_file(
88
88
 
89
89
  # Get page URL - calculate relative path from site_dir
90
90
  rel_path = html_path.relative_to(site_dir).as_posix()
91
- page_url = f"{site_url.rstrip('/')}/{rel_path}".replace("/index.html", "/")
91
+ page_url = f"{site_url.rstrip('/')}/{rel_path}".replace("/index.html", "") or "/"
92
92
 
93
93
  # Get title
94
94
  title = soup.find("h1").text if soup.find("h1") else soup.title.string if soup.title else ""
@@ -134,6 +134,120 @@ def process_html_file(
134
134
  return False
135
135
 
136
136
 
137
+ def generate_llms_txt(
138
+ site_dir: Path,
139
+ docs_dir: Path,
140
+ site_url: str,
141
+ site_name: str | None = None,
142
+ site_description: str | None = None,
143
+ nav: list | None = None,
144
+ ) -> None:
145
+ """Generate llms.txt file for LLM consumption."""
146
+ import yaml
147
+
148
+ # Fallback to reading mkdocs.yml if config values not provided (standalone postprocess mode)
149
+ if site_name is None or nav is None:
150
+
151
+ class _Loader(yaml.SafeLoader):
152
+ pass
153
+
154
+ _Loader.add_multi_constructor("", lambda loader, suffix, node: None)
155
+
156
+ mkdocs_yml = site_dir.parent / "mkdocs.yml"
157
+ if mkdocs_yml.exists():
158
+ config = yaml.load(mkdocs_yml.read_text(), Loader=_Loader) or {}
159
+ site_name = site_name or config.get("site_name", "Documentation")
160
+ site_description = site_description or config.get("site_description", "")
161
+ nav = nav or config.get("nav")
162
+ site_name = site_name or "Documentation"
163
+ site_description = site_description or ""
164
+
165
+ lines = [f"# {site_name}", f"> {site_description}"]
166
+ seen_urls: set[str] = set()
167
+ site_url = site_url.rstrip("/")
168
+
169
+ def get_description(md_path: Path) -> str:
170
+ """Extract description from markdown frontmatter."""
171
+ try:
172
+ content = md_path.read_text()
173
+ if content.startswith("---"):
174
+ end = content.find("\n---\n", 3)
175
+ if end != -1:
176
+ fm = yaml.safe_load(content[4:end]) or {}
177
+ return fm.get("description", "")
178
+ except Exception:
179
+ pass
180
+ return ""
181
+
182
+ def md_to_url(md_path: str) -> str:
183
+ """Convert markdown path to HTML URL."""
184
+ url = md_path[:-3] if md_path.endswith(".md") else md_path
185
+ url = url[:-6] if url.endswith("/index") else url
186
+ return (site_url or "/") if url == "index" else f"{site_url}/{url}"
187
+
188
+ if nav:
189
+
190
+ def process_items(items, indent=0):
191
+ """Recursively process nav items with indentation (Vercel-style)."""
192
+ prefix = " " * indent + "- "
193
+ for item in items:
194
+ if isinstance(item, str):
195
+ md = docs_dir / item
196
+ if md.exists():
197
+ url = md_to_url(item)
198
+ if url in seen_urls:
199
+ continue
200
+ seen_urls.add(url)
201
+ desc = get_description(md)
202
+ # Use parent dir name for index.md, else filename
203
+ title = md.parent.name if md.stem == "index" else md.stem
204
+ title = title.replace("-", " ").replace("_", " ").title()
205
+ desc_part = f": {desc}" if desc else ""
206
+ lines.append(f"{prefix}[{title}]({url}){desc_part}")
207
+ elif isinstance(item, dict):
208
+ for k, v in item.items():
209
+ if isinstance(v, str):
210
+ md = docs_dir / v
211
+ if md.exists():
212
+ url = md_to_url(v)
213
+ if url in seen_urls:
214
+ continue
215
+ seen_urls.add(url)
216
+ desc = get_description(md)
217
+ desc_part = f": {desc}" if desc else ""
218
+ lines.append(f"{prefix}[{k}]({url}){desc_part}")
219
+ elif isinstance(v, list):
220
+ # Nested section - plain text header, then recurse
221
+ lines.append(f"{prefix}{k}")
222
+ process_items(v, indent + 1)
223
+
224
+ # Top-level nav items become ## sections
225
+ for item in nav:
226
+ if isinstance(item, str):
227
+ process_items([item], indent=0)
228
+ elif isinstance(item, dict):
229
+ for k, v in item.items():
230
+ if isinstance(v, list):
231
+ lines.extend(["", f"## {k}"])
232
+ process_items(v, indent=0)
233
+ else:
234
+ process_items([{k: v}], indent=0)
235
+ else:
236
+ for md in sorted(docs_dir.rglob("*.md")):
237
+ rel = md.relative_to(docs_dir).as_posix()
238
+ url = md_to_url(rel)
239
+ if url in seen_urls:
240
+ continue
241
+ seen_urls.add(url)
242
+ desc = get_description(md)
243
+ title = md.stem.replace("-", " ").replace("_", " ").title()
244
+ desc_part = f": {desc}" if desc else ""
245
+ lines.append(f"- [{title}]({url}){desc_part}")
246
+
247
+ (site_dir / "llms.txt").write_text("\n".join(lines))
248
+ print("Generated llms.txt")
249
+
250
+
137
251
  def postprocess_site(
138
252
  site_dir: str | Path = "site",
139
253
  docs_dir: str | Path = "docs",
@@ -148,6 +262,7 @@ def postprocess_site(
148
262
  add_json_ld: bool = False,
149
263
  add_css: bool = True,
150
264
  add_copy_llm: bool = True,
265
+ add_llms_txt: bool = True,
151
266
  verbose: bool = True,
152
267
  use_processes: bool = True,
153
268
  workers: int | None = None,
@@ -190,23 +305,23 @@ def postprocess_site(
190
305
  # Enable logging only for the synchronous path; pools run without per-task log_fn to remain pickle-safe.
191
306
  log_fn = (progress.write if verbose and progress else print if verbose else None) if worker_count == 1 else None
192
307
 
193
- task_kwargs = dict(
194
- site_dir=site_dir,
195
- md_index=md_index,
196
- git_data=git_data,
197
- repo_url=repo_url,
198
- site_url=site_url,
199
- default_image=default_image,
200
- add_desc=add_desc,
201
- add_image=add_image,
202
- add_keywords=add_keywords,
203
- add_share_buttons=add_share_buttons,
204
- add_authors=add_authors,
205
- add_json_ld=add_json_ld,
206
- add_css=add_css,
207
- add_copy_llm=add_copy_llm,
208
- verbose=verbose,
209
- )
308
+ task_kwargs = {
309
+ "site_dir": site_dir,
310
+ "md_index": md_index,
311
+ "git_data": git_data,
312
+ "repo_url": repo_url,
313
+ "site_url": site_url,
314
+ "default_image": default_image,
315
+ "add_desc": add_desc,
316
+ "add_image": add_image,
317
+ "add_keywords": add_keywords,
318
+ "add_share_buttons": add_share_buttons,
319
+ "add_authors": add_authors,
320
+ "add_json_ld": add_json_ld,
321
+ "add_css": add_css,
322
+ "add_copy_llm": add_copy_llm,
323
+ "verbose": verbose,
324
+ }
210
325
 
211
326
  if worker_count == 1:
212
327
  for html_file in html_files:
@@ -250,6 +365,9 @@ def postprocess_site(
250
365
 
251
366
  print(f"✅ Postprocessing complete: {processed}/{len(html_files)} files processed")
252
367
 
368
+ if add_llms_txt:
369
+ generate_llms_txt(site_dir, docs_dir, site_url)
370
+
253
371
 
254
372
  if __name__ == "__main__":
255
373
  postprocess_site()
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
  import json
7
7
  import re
8
8
  import subprocess
9
- from datetime import datetime, timedelta
9
+ from datetime import datetime, timedelta, timezone
10
10
  from pathlib import Path
11
11
  from typing import Any
12
12
  from urllib.parse import quote
@@ -15,7 +15,7 @@ from bs4 import BeautifulSoup
15
15
 
16
16
  from plugin.utils import calculate_time_difference, get_youtube_video_ids
17
17
 
18
- today = datetime.now()
18
+ today = datetime.now(timezone.utc)
19
19
  DEFAULT_CREATION_DATE = (today - timedelta(days=365)).strftime("%Y-%m-%d %H:%M:%S +0000")
20
20
  DEFAULT_MODIFIED_DATE = (today - timedelta(days=40)).strftime("%Y-%m-%d %H:%M:%S +0000")
21
21
 
@@ -136,7 +136,7 @@ def build_git_map(file_paths: list[str] | list[Path]) -> tuple[str | None, dict[
136
136
  str(repo_root),
137
137
  "log",
138
138
  "--name-only",
139
- "--pretty=format:%ad\t%ae",
139
+ "--pretty=format:%H\t%ad\t%ae",
140
140
  "--date=format:%Y-%m-%d %H:%M:%S %z",
141
141
  "--",
142
142
  *[str(p) for p in rel_paths],
@@ -147,17 +147,18 @@ def build_git_map(file_paths: list[str] | list[Path]) -> tuple[str | None, dict[
147
147
  except subprocess.CalledProcessError:
148
148
  return repo_url, git_data
149
149
 
150
+ current_commit = None
150
151
  current_date = None
151
152
  current_email = None
152
153
  for line in output:
153
154
  if not line.strip():
154
155
  continue
155
156
  parts = line.split("\t")
156
- if len(parts) == 2:
157
- current_date, current_email = parts
157
+ if len(parts) == 3:
158
+ current_commit, current_date, current_email = parts
158
159
  continue
159
160
 
160
- if current_date and current_email:
161
+ if current_commit and current_date and current_email:
161
162
  abs_path = (repo_root / line.strip()).resolve()
162
163
  key = str(abs_path)
163
164
  entry = git_data.setdefault(
@@ -166,11 +167,13 @@ def build_git_map(file_paths: list[str] | list[Path]) -> tuple[str | None, dict[
166
167
  "creation_date": current_date,
167
168
  "last_modified_date": current_date,
168
169
  "emails": {},
170
+ "commits": {},
169
171
  },
170
172
  )
171
173
  entry.setdefault("last_modified_date", current_date)
172
174
  entry["creation_date"] = current_date
173
175
  entry["emails"][current_email] = entry["emails"].get(current_email, 0) + 1
176
+ entry["commits"].setdefault(current_email, current_commit)
174
177
 
175
178
  return repo_url, git_data
176
179
 
@@ -436,21 +439,22 @@ def process_html(
436
439
  let rawUrl = editBtn.href.replace('github.com', 'raw.githubusercontent.com');
437
440
  rawUrl = rawUrl.replace('/blob/', '/').replace('/tree/', '/');
438
441
 
439
- try {{
442
+ async function getContent() {{
440
443
  const response = await fetch(rawUrl);
441
444
  let markdown = await response.text();
442
-
443
445
  if (markdown.startsWith('---')) {{
444
446
  const frontMatterEnd = markdown.indexOf('\\n---\\n', 3);
445
- if (frontMatterEnd !== -1) {{
446
- markdown = markdown.substring(frontMatterEnd + 5).trim();
447
- }}
447
+ if (frontMatterEnd !== -1) markdown = markdown.substring(frontMatterEnd + 5).trim();
448
448
  }}
449
-
450
449
  const title = document.querySelector('h1')?.textContent || document.title;
451
- const content = `# ${{title}}\\n\\nSource: ${{window.location.href}}\\n\\n---\\n\\n${{markdown}}`;
450
+ return `# ${{title}}\\n\\nSource: ${{window.location.href}}\\n\\n---\\n\\n${{markdown}}`;
451
+ }}
452
452
 
453
- await navigator.clipboard.writeText(content);
453
+ try {{
454
+ const clipboardItem = new ClipboardItem({{
455
+ 'text/plain': getContent().then(text => new Blob([text], {{ type: 'text/plain' }}))
456
+ }});
457
+ await navigator.clipboard.write([clipboardItem]);
454
458
  button.innerHTML = checkIcon + ' Copied!';
455
459
  setTimeout(() => {{ button.innerHTML = originalHTML; }}, 2000);
456
460
  }} catch (err) {{
@@ -525,7 +529,7 @@ def process_html(
525
529
  {
526
530
  "@type": "Organization",
527
531
  "name": "Ultralytics",
528
- "url": "https://ultralytics.com/",
532
+ "url": "https://ultralytics.com",
529
533
  }
530
534
  ],
531
535
  "abstract": meta.get("description", ""),
@@ -6,6 +6,7 @@ import re
6
6
  from datetime import datetime
7
7
  from pathlib import Path
8
8
  from typing import Any
9
+ from urllib.parse import urlparse
9
10
 
10
11
  import requests
11
12
  import yaml
@@ -97,14 +98,31 @@ def save_author_cache(cache: dict[str, dict[str, str | None]]) -> None:
97
98
  print(f"{WARNING} Failed to save author cache: {e}")
98
99
 
99
100
 
101
+ def _github_repo_path(repo_url: str | None) -> str | None:
102
+ """Return the owner/repo path for a GitHub repository URL."""
103
+ if not repo_url:
104
+ return None
105
+ parsed = urlparse(repo_url)
106
+ if parsed.hostname != "github.com":
107
+ return None
108
+ path = parsed.path.strip("/")
109
+ return path[:-4] if path.endswith(".git") else path or None
110
+
111
+
100
112
  def resolve_github_user(
101
- email: str, cache: dict[str, dict[str, str | None]], verbose: bool = True
113
+ email: str,
114
+ cache: dict[str, dict[str, str | None]],
115
+ repo_url: str | None = None,
116
+ commit_sha: str | None = None,
117
+ verbose: bool = True,
102
118
  ) -> dict[str, str | None]:
103
119
  """Resolve a single email to GitHub username and avatar, updating cache in-place.
104
120
 
105
121
  Args:
106
122
  email (str): The email address to resolve.
107
123
  cache (dict): The author cache dict (modified in-place if new entry added).
124
+ repo_url (str, optional): GitHub repository URL used for commit API fallback.
125
+ commit_sha (str, optional): Commit SHA authored by the email.
108
126
  verbose (bool): Whether to print API call info.
109
127
 
110
128
  Returns:
@@ -113,8 +131,8 @@ def resolve_github_user(
113
131
  if not email or not email.strip():
114
132
  return {"username": None, "avatar": None}
115
133
 
116
- # Return cached result if available
117
- if email in cache:
134
+ # Return complete cached results immediately. Incomplete cached entries may be refreshed from commit metadata.
135
+ if email in cache and cache[email].get("username") and cache[email].get("avatar"):
118
136
  return cache[email]
119
137
 
120
138
  # Parse username directly from GitHub noreply emails
@@ -127,6 +145,20 @@ def resolve_github_user(
127
145
  cache[email] = {"username": username, "avatar": avatar}
128
146
  return cache[email]
129
147
 
148
+ # Query the commit API when git history provides a commit for this email. This resolves authors whose commit email
149
+ # is linked to a GitHub account but hidden from user search.
150
+ if (repo_path := _github_repo_path(repo_url)) and commit_sha:
151
+ try:
152
+ response = requests.get(f"https://api.github.com/repos/{repo_path}/commits/{commit_sha}", timeout=TIMEOUT)
153
+ if response.status_code == 200:
154
+ data = response.json()
155
+ author = data.get("author") or {}
156
+ if author.get("login") and author.get("avatar_url"):
157
+ cache[email] = {"username": author["login"], "avatar": author["avatar_url"]}
158
+ return cache[email]
159
+ except Exception:
160
+ pass
161
+
130
162
  # Query GitHub REST API
131
163
  if verbose:
132
164
  print(f"Running GitHub REST API for author {email}")
@@ -173,10 +205,13 @@ def resolve_all_authors(
173
205
  if not git_data:
174
206
  return git_data
175
207
 
176
- # Collect all unique emails across all files
208
+ # Collect all unique emails across all files, with one representative commit SHA per email.
177
209
  all_emails: set[str] = set()
210
+ commits: dict[str, str] = {}
178
211
  for entry in git_data.values():
179
212
  all_emails.update(entry.get("emails", {}).keys())
213
+ for email, commit in entry.get("commits", {}).items():
214
+ commits.setdefault(email, commit)
180
215
  if default_author:
181
216
  all_emails.add(default_author)
182
217
  all_emails.discard("")
@@ -189,8 +224,9 @@ def resolve_all_authors(
189
224
  cache_modified = False
190
225
 
191
226
  for email in sorted(all_emails):
192
- if email not in cache:
193
- resolve_github_user(email, cache, verbose=verbose)
227
+ cached = cache.get(email, {})
228
+ if email not in cache or (commits.get(email) and not (cached.get("username") and cached.get("avatar"))):
229
+ resolve_github_user(email, cache, repo_url=repo_url, commit_sha=commits.get(email), verbose=verbose)
194
230
  cache_modified = True
195
231
 
196
232
  if cache_modified:
@@ -199,7 +235,7 @@ def resolve_all_authors(
199
235
  # Build authors list for each file entry
200
236
  github_repo_url = repo_url or "https://github.com/ultralytics/ultralytics"
201
237
 
202
- for file_path, entry in git_data.items():
238
+ for entry in git_data.values():
203
239
  emails = entry.get("emails", {})
204
240
  if not emails and default_author:
205
241
  emails = {default_author: 1}
@@ -50,6 +50,7 @@ classifiers = [
50
50
  "Programming Language :: Python :: 3.12",
51
51
  "Programming Language :: Python :: 3.13",
52
52
  "Programming Language :: Python :: 3.14",
53
+ "Programming Language :: Python :: 3.15",
53
54
  "Topic :: Software Development",
54
55
  "Topic :: Scientific/Engineering",
55
56
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
@@ -77,8 +78,5 @@ ultralytics = "plugin:MetaPlugin" # executes the function `MetaPlugin` from thi
77
78
  [project.optional-dependencies] # Optional
78
79
  dev = ["pytest"]
79
80
 
80
- [tool.setuptools] # This is configuration specific to the `setuptools` build backend.
81
- package-data = { "sample" = ["*.yaml"] }
82
-
83
81
  [tool.setuptools.dynamic]
84
82
  version = {attr = "plugin.__version__"}