Skip to content

Commit 7a68a72

Browse files
committed
feat: add /llms.txt endpoints for LLM-friendly documentation
Add two new routes that serve LLM-friendly plain text documentation following the llms.txt specification (https://llmstxt.org/): - GET /llms.txt - Returns a top-level index of all available documentation with links to each doc section. - GET /:doc/llms.txt - Returns a per-documentation index with all entries grouped by type, linking to individual pages. The format follows the llms.txt standard: - Title as a Markdown heading - Description as a blockquote - Sections with links in Markdown format This enables LLM tools and AI-powered development environments to efficiently discover and reference DevDocs documentation. Closes #2520
1 parent e894da2 commit 7a68a72

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed

lib/app.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,70 @@ def modern_browser?(browser)
301301
200
302302
end
303303

304+
# llms.txt - LLM-friendly documentation index
305+
# See https://llmstxt.org/ for the specification
306+
get '/llms.txt' do
307+
content_type 'text/plain'
308+
309+
lines = []
310+
lines << '# DevDocs'
311+
lines << ''
312+
lines << '> DevDocs combines multiple API documentations in a fast, organized, and searchable interface.'
313+
lines << ''
314+
lines << '## Documentation'
315+
lines << ''
316+
317+
settings.docs.each do |slug, doc|
318+
lines << "- [#{doc['full_name']}](https://devdocs.io/#{slug}/)"
319+
end
320+
321+
lines.join("\n")
322+
end
323+
324+
get %r{/([\w~\.%]+)/llms\.txt} do |doc|
325+
doc.sub! '%7E', '~'
326+
return 404 unless @doc = find_doc(doc)
327+
328+
content_type 'text/plain'
329+
330+
index_path = File.join(settings.docs_path, @doc['slug'], 'index.json')
331+
return 404 unless File.exist?(index_path)
332+
333+
index = JSON.parse(File.read(index_path))
334+
335+
lines = []
336+
lines << "# #{@doc['full_name']}"
337+
lines << ''
338+
lines << "> #{@doc['full_name']} documentation on DevDocs."
339+
lines << ''
340+
341+
if index['types'] && !index['types'].empty?
342+
grouped = {}
343+
(index['entries'] || []).each do |entry|
344+
type_name = entry['type'] || 'General'
345+
grouped[type_name] ||= []
346+
grouped[type_name] << entry
347+
end
348+
349+
grouped.sort_by { |type, _| type }.each do |type, entries|
350+
lines << "## #{type}"
351+
lines << ''
352+
entries.each do |entry|
353+
path = entry['path'].to_s.split('#').first
354+
lines << "- [#{entry['name']}](https://devdocs.io/#{@doc['slug']}/#{path})"
355+
end
356+
lines << ''
357+
end
358+
else
359+
(index['entries'] || []).each do |entry|
360+
path = entry['path'].to_s.split('#').first
361+
lines << "- [#{entry['name']}](https://devdocs.io/#{@doc['slug']}/#{path})"
362+
end
363+
end
364+
365+
lines.join("\n")
366+
end
367+
304368
%w(docs.json application.js application.css).each do |asset|
305369
class_eval <<-CODE, __FILE__, __LINE__ + 1
306370
get '/#{asset}' do

0 commit comments

Comments
 (0)