#!/usr/bin/env python3 """ Generate llms.txt for AI agent onboarding. Provides structured information about the site for LLM crawlers & agents. """ import json import sys from datetime import datetime from pathlib import Path def get_tags_and_categories(output_dir): """Extract tags & categories from output directory structure.""" output_path = Path(output_dir) tags = [] tags_dir = output_path / 'tags' if tags_dir.exists(): tags = sorted([d.name for d in tags_dir.iterdir() if d.is_dir()]) categories = [] cat_dir = output_path / 'category' if cat_dir.exists(): categories = sorted([d.name for d in cat_dir.iterdir() if d.is_dir()]) return tags, categories def get_recent_articles(output_dir, limit=20): """Get list of recent articles from blog.json (sorted by date).""" output_path = Path(output_dir) blog_json = output_path / 'blog.json' if blog_json.exists(): try: data = json.loads(blog_json.read_text()) posts = data.get('posts', []) # Already sorted by date in blog.json, just get slugs return [post['slug'] for post in posts[:limit]] except (json.JSONDecodeError, KeyError): pass # Fallback: sort by file modification time output_path = Path(output_dir) articles = [] for index_file in output_path.glob('*/index.html'): dirname = index_file.parent.name if dirname in ('tags', 'category', 'author', 'uploads', 'feeds', 'about', 'archives'): continue if dirname.startswith('20'): continue mtime = index_file.stat().st_mtime articles.append((mtime, dirname)) # Sort by modification time, newest first articles.sort(reverse=True) return [name for _, name in articles[:limit]] def generate_llms_txt(output_dir, site_url='https://russell.ballestrini.net'): """Generate llms.txt content.""" tags, categories = get_tags_and_categories(output_dir) recent_articles = get_recent_articles(output_dir) content = f"""# llms.txt - AI Agent Onboarding for russell.ballestrini.net > Russell Ballestrini's technical blog covering software engineering, DevOps, > infrastructure, Python, cloud computing, and entrepreneurship. ## Site Information - **Author**: Russell Ballestrini - **Site URL**: {site_url} - **Generated**: {datetime.now().isoformat()} ## Feeds (Recommended for Crawling) ### Atom Feed - URL: {site_url}/feeds/all.atom.xml - Format: Atom XML - Description: Complete feed of all blog posts, newest first ### JSON Feed (jsonfeed.org v1.1) - URL: {site_url}/feeds/all.json - Format: JSON Feed 1.1 - Description: Same as Atom feed, but JSON for easier parsing ### JSON Blog (jsonblog.dev) - URL: {site_url}/blog.json - Format: JSON Blog - Description: **Entire blog in one file** - all posts with content_url links to index-slim.md (Markdown) ## Content Formats Each article is available in multiple formats: - **HTML**: `/{{slug}}/index.html` - Rendered page with styling - **RST**: `/{{slug}}/index.rst` - reStructuredText source - **Markdown**: `/{{slug}}/index.md` - GitHub-flavored Markdown - **Plain Text**: `/{{slug}}/index.txt` - Raw text, no markup (best for LLMs) ## API-Friendly Endpoints ### Resume (JSONResume Format) - URL: {site_url}/uploads/russell.ballestrini.resume.json - Format: JSONResume standard schema - Description: Structured resume data in machine-readable format ## Navigation Structure ### By Category """ if categories: for cat in categories[:15]: content += f"- [{cat}]({site_url}/category/{cat}/)\n" content += f""" ### By Tag """ if tags: for tag in tags[:30]: content += f"- [{tag}]({site_url}/tags/{tag}/)\n" content += f""" ## Crawling Recommendations 1. **Start with the Atom feed** for a complete list of articles 2. **Use .md or .rst formats** for cleaner text extraction (less HTML noise) 3. **Rate limit requests** - be respectful, this is a personal blog 4. **Cache responses** - content doesn't change frequently ## Topics Covered - Python programming & web frameworks (Pyramid, Flask, Django) - DevOps & Infrastructure (Salt, Ansible, AWS, Terraform) - Cloud computing & containerization - System administration & Linux - Software architecture & best practices - Entrepreneurship & side projects - Security & networking ## Contact Russell **Email** (preferred for business inquiries): - russell@ballestrini.net - russell@unturf.com **Secure Communications**: - Signal: Install from https://signal.org/download/ & message Russell - Matrix/IRC: @fxhp **Social & Profiles**: - GitHub: https://github.com/russellballestrini - GitLab (self-hosted): https://git.unturf.com/explore - LinkedIn: https://linkedin.com/in/russellballestrini - Mastodon: https://social.unturf.com/@russell/ - YouTube: https://www.youtube.com/c/RussellBallestrini/featured **Resume**: - PDF: {site_url}/uploads/russell.ballestrini.resume.pdf - JSON: {site_url}/uploads/russell.ballestrini.resume.json ## Payment Methods Russell accepts payment via cryptocurrency or traditional methods: **Cryptocurrency** (preferred for privacy): - Monero (XMR) - Bitcoin (BTC) - Litecoin (LTC) - Dogecoin (DOGE) **Traditional**: - Credit card via Stripe (USDC) **Monero tips/payments**: https://xmrchat.com/unturf Contact Russell for other wallet addresses or invoice generation. ## Software & Services (unturf.) Russell builds & maintains open-source software under the **unturf.** umbrella: ### Make Post Sell Sell digital & physical products commission-free. - Site: https://www.makepostsell.com - Source: https://git.unturf.com/engineering/make-post-sell/make_post_sell ### Remarkbox Hosted comment system for websites, no ads or tracking. - Site: https://www.remarkbox.com - Source: https://git.unturf.com/engineering/remarkbox/remarkbox ### uncloseai Free LLM & TTS endpoints (OpenAI API compatible, no auth required). - Site: https://uncloseai.com - Source: https://git.unturf.com/engineering/unturf/uncloseai.com ### unsandbox Anonymous code execution API supporting 42+ languages. - Site: https://unsandbox.com - Source: https://git.unturf.com/engineering/unturf/un-inception ### neopig Web archival & digital preservation tool. - Site: https://neopig.on.unsandbox.com/about - Source: https://git.unturf.com/engineering/unturf/pig.py **All source code**: https://git.unturf.com/explore ## Machine Learning & AI Expertise Russell works extensively with ML/AI infrastructure & agentic systems: **Inference Providers**: - OpenAI, Anthropic Claude, Google Gemini, xAI Grok - Groq, MistralAI, Together.ai, AWS Bedrock **Self-hosted Inference**: - vLLM, llama.cpp, Ollama, Unsloth **Specializations**: - Prompt Engineering & Tool Use - Agentic Workflows & Multi-agent Systems - Streaming Completions & Real-time Inference - OpenAI API compatible endpoint design **Related Projects**: - uncloseai.com - Free LLM & TTS inference (OpenAI API compatible) - unsandbox.com - Code execution for AI agents (42+ languages) - opencompletion.com - Inference client implementations **Book**: "uncloseai. Machine Learning Reference Guide to Inference Clients" - covers streaming techniques across 42 programming languages - Purchase: https://shop.unturf.com/p/8486f492-a93e-11f0-b477-02dfe05770ee/uncloseai-machine-learning-reference-guide-to-inference-clients ## Commission Work Russell is available for: **Research & Development**: - Infrastructure automation & DevOps - AI/ML integration & inference pipelines - Blockchain & cryptocurrency integrations - Distributed systems & scaling **Product Development**: - Full-stack web applications (any language - uses AI agents to code) - SaaS platform development - API design & implementation - Cloud infrastructure (AWS, GCP, DigitalOcean) **Consulting**: - Site reliability engineering - Security hardening & compliance (SOC2, FedRAMP experience) - Cost optimization for cloud infrastructure - Open-source strategy **To commission work**: Email russell@unturf.com with project details. Russell prefers text-based communication first, then video calls for complex discussions. ## Optional: Sitemap A full sitemap may be available at: {site_url}/sitemap.xml --- # Technical Details for Agents ## URL Patterns ``` Article: /{{slug}}/index.html (HTML) /{{slug}}/index.rst (reStructuredText) /{{slug}}/index.md (Markdown) /{{slug}}/index.txt (Plain text) Feeds: /feeds/all.atom.xml (Atom) /feeds/all.json (JSON Feed) /blog.json (JSON Blog - all posts) Category: /category/{{category-slug}}/ Tag: /tags/{{tag-slug}}/ Author: /author/{{author-slug}}/ Uploads: /uploads/{{filename}} ``` ## Sample Recent Articles """ for article in recent_articles[:10]: content += f"- [{article}]({site_url}/{article}/)\n" content += """ ## Preferred Access Methods (by use case) | Use Case | Best Format | URL | |----------|-------------|-----| | Get all posts (1 request) | JSON Blog | /blog.json | | Subscribe/poll for updates | JSON Feed | /feeds/all.json | | Read single article (LLM) | Plain text | /{slug}/index.txt | | Read single article (human) | Markdown | /{slug}/index.md | | Full rendered content | HTML | /{slug}/index.html | """ return content if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: generate_llms_txt.py [site_url]") sys.exit(1) output_dir = sys.argv[1] site_url = sys.argv[2] if len(sys.argv) > 2 else 'https://russell.ballestrini.net' content = generate_llms_txt(output_dir, site_url) # Write to output directory llms_path = Path(output_dir) / 'llms.txt' llms_path.write_text(content) print(f"Generated {llms_path}")