diff --git a/.flox/env/manifest.lock b/.flox/env/manifest.lock index d5c204ec..5e202422 100644 --- a/.flox/env/manifest.lock +++ b/.flox/env/manifest.lock @@ -18,6 +18,9 @@ "pkg-path": "findutils", "outputs": "all" }, + "gnumake": { + "pkg-path": "gnumake" + }, "gnused": { "pkg-path": "gnused", "outputs": "all" @@ -836,6 +839,136 @@ "group": "toplevel", "priority": 5 }, + { + "attr_path": "gnumake", + "broken": false, + "derivation": "/nix/store/pf71f0ja823aanl6073z3slrpz2hpxr1-gnumake-4.4.1.drv", + "description": "Tool to control the generation of non-source files from sources", + "install_id": "gnumake", + "license": "GPL-3.0-or-later", + "locked_url": "https://github.com/flox/nixpkgs?rev=8eaee110344796db060382e15d3af0a9fc396e0e", + "name": "gnumake-4.4.1", + "pname": "gnumake", + "rev": "8eaee110344796db060382e15d3af0a9fc396e0e", + "rev_count": 864002, + "rev_date": "2025-09-19T10:20:10Z", + "scrape_date": "2025-09-21T05:38:43.319343Z", + "stabilities": [ + "unstable" + ], + "unfree": false, + "version": "4.4.1", + "outputs_to_install": [ + "man", + "out" + ], + "outputs": { + "info": "/nix/store/cwx5agxi3ig3gmbk4c4dn7df2krzlddy-gnumake-4.4.1-info", + "man": "/nix/store/a4aay80xgirjm8hk1rd142qcd1kkfps8-gnumake-4.4.1-man", + "out": "/nix/store/sjxx5p05vzq7xam62h21cyzkbyb1amvd-gnumake-4.4.1" + }, + "system": "aarch64-darwin", + "group": "toplevel", + "priority": 5 + }, + { + "attr_path": "gnumake", + "broken": false, + "derivation": "/nix/store/876aq0p8d0z7sfyjdawn9mrdfnv7n458-gnumake-4.4.1.drv", + "description": "Tool to control the generation of non-source files from sources", + "install_id": "gnumake", + "license": "GPL-3.0-or-later", + "locked_url": "https://github.com/flox/nixpkgs?rev=8eaee110344796db060382e15d3af0a9fc396e0e", + "name": "gnumake-4.4.1", + "pname": "gnumake", + "rev": "8eaee110344796db060382e15d3af0a9fc396e0e", + "rev_count": 864002, + "rev_date": "2025-09-19T10:20:10Z", + "scrape_date": "2025-09-21T06:10:24.182468Z", + "stabilities": [ + "unstable" + ], + "unfree": false, + "version": "4.4.1", + "outputs_to_install": [ + "man", + "out" + ], + "outputs": { + "debug": "/nix/store/j8lcp5zjdq0l0ipvji7s13vdc53nzcki-gnumake-4.4.1-debug", + "info": "/nix/store/8922q241lh4qbxd2g7jxsnjnkfmgap3z-gnumake-4.4.1-info", + "man": "/nix/store/0a4l47b9sqc28ssi5hsq21ivs2hmbzcp-gnumake-4.4.1-man", + "out": "/nix/store/9cns3585v908dwbf5nfqqjghv955ndrq-gnumake-4.4.1" + }, + "system": "aarch64-linux", + "group": "toplevel", + "priority": 5 + }, + { + "attr_path": "gnumake", + "broken": false, + "derivation": "/nix/store/xrm5hvv49gd5v31937jmr0vc6m8a1v64-gnumake-4.4.1.drv", + "description": "Tool to control the generation of non-source files from sources", + "install_id": "gnumake", + "license": "GPL-3.0-or-later", + "locked_url": "https://github.com/flox/nixpkgs?rev=8eaee110344796db060382e15d3af0a9fc396e0e", + "name": "gnumake-4.4.1", + "pname": "gnumake", + "rev": "8eaee110344796db060382e15d3af0a9fc396e0e", + "rev_count": 864002, + "rev_date": "2025-09-19T10:20:10Z", + "scrape_date": "2025-09-21T06:39:00.878032Z", + "stabilities": [ + "unstable" + ], + "unfree": false, + "version": "4.4.1", + "outputs_to_install": [ + "man", + "out" + ], + "outputs": { + "info": "/nix/store/451pi5y9s89na99pxv6jjvqa44r08dha-gnumake-4.4.1-info", + "man": "/nix/store/g7nffhgbmv3r01199lhp0qz741kvnlvf-gnumake-4.4.1-man", + "out": "/nix/store/fy063r4nqi1w79bklqhiv7ny0xwdqjp3-gnumake-4.4.1" + }, + "system": "x86_64-darwin", + "group": "toplevel", + "priority": 5 + }, + { + "attr_path": "gnumake", + "broken": false, + "derivation": "/nix/store/riz7jd6hvqpxzxgyhj76ianh96sxhvz4-gnumake-4.4.1.drv", + "description": "Tool to control the generation of non-source files from sources", + "install_id": "gnumake", + "license": "GPL-3.0-or-later", + "locked_url": "https://github.com/flox/nixpkgs?rev=8eaee110344796db060382e15d3af0a9fc396e0e", + "name": "gnumake-4.4.1", + "pname": "gnumake", + "rev": "8eaee110344796db060382e15d3af0a9fc396e0e", + "rev_count": 864002, + "rev_date": "2025-09-19T10:20:10Z", + "scrape_date": "2025-09-21T07:10:55.800436Z", + "stabilities": [ + "unstable" + ], + "unfree": false, + "version": "4.4.1", + "outputs_to_install": [ + "man", + "out" + ], + "outputs": { + "debug": "/nix/store/7vrxj6zy7y4a83d2q9585sxmcnkfs9ml-gnumake-4.4.1-debug", + "info": "/nix/store/m0ijkc5j3wdawh302pns9b45v9n6nq64-gnumake-4.4.1-info", + "man": "/nix/store/ha44mgbdcrzgah0dnjd28ax4hrdkc4mm-gnumake-4.4.1-man", + "out": "/nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1" + }, + "system": "x86_64-linux", + "group": "toplevel", + "priority": 5 + }, { "attr_path": "gnused", "broken": false, diff --git a/.flox/env/manifest.toml b/.flox/env/manifest.toml index 56b8828f..f98239d0 100644 --- a/.flox/env/manifest.toml +++ b/.flox/env/manifest.toml @@ -23,6 +23,7 @@ markdownlint-cli2.pkg-path = "markdownlint-cli2" markdownlint-cli2.pkg-group = "lint" curl.pkg-path = "curl" curl.outputs = "all" +just.pkg-path = "just" [hook] on-activate = ''' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8075be19..42a22a28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,7 @@ jobs: with: command: | mkdocs build + python3 tools/generate_llms_txt.py ./site mkdir -p ./public/docs cp -R ./site/* ./public/docs/ chmod -R +w ./public/docs diff --git a/README.md b/README.md index da0ff4f3..be795a3b 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,55 @@ Live at: [flox.dev/docs](https://flox.dev/docs). ## Usage +### Quick Start + +```bash +# Activate the Flox environment +$ flox activate +✅ You are now using the environment 'floxdocs'. + +# Start development server +λ (floxdocs) $ just dev +``` + +The documentation will be available at `http://127.0.0.1:8000` with live reload. + +### Available Commands + +```bash +just dev # Start development server with live reload +just build # Build static site and generate AI files +just clean # Clean build artifacts +just # Show all available commands ``` + +### Flox Native Usage + +You can still use the original Flox services approach: + +```bash $ flox activate ✔ You are now using the environment 'floxdocs'. λ (floxdocs) $ flox services start mkdocs ✔ Service 'mkdocs' started. + ``` + Once mkdocs service started you can preview the documentation at `https://127.0.0.1:8000`. +## AI-Friendly Documentation + +This documentation site automatically generates AI-friendly files for different use cases: + +- **`llms.txt`** - Optimized for AI agents with critical rules, workflows, and curated link list +- **`llms-full.txt`** - Full content for RAG systems and answer engines, generated from source Markdown + +These files are automatically generated during the build process and are available at: +- `https://flox.dev/docs/llms.txt` +- `https://flox.dev/docs/llms-full.txt` ## Guidelines diff --git a/docs/llms.txt b/docs/llms.txt index d6d88411..580857a1 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -2,7 +2,27 @@ Flox is a next-generation package manager and environment manager for software development and delivery. It allows for creation of reproducible environments for projects, either through imperative commands or declarative manifests. -If you see a `.flox` directory, that means the you are working with Flox to manage project dependencies and the environment lifecycle. +If you see a `.flox` directory, that means you are working with Flox to manage project dependencies and the environment lifecycle. + +## Installation + +**Do NOT use `curl | bash` or reference `install.flox.dev` — that does not exist.** + +Install Flox from `flox.dev/download` or via a package manager: + +```bash +# macOS +brew install flox + +# Debian/Ubuntu — download .deb from flox.dev/download, then: +sudo apt install /path/to/flox.deb + +# RPM (RedHat/CentOS/Amazon Linux) — download .rpm from flox.dev/download, then: +sudo rpm -ivh /path/to/flox.rpm + +# Verify installation +flox --version +``` ## Essential Commands ```bash @@ -12,11 +32,13 @@ flox show # Show package details and versions flox init -d # Initialize Flox environment in a directory flox list -d # List installed packages flox install -d # Install package -flox activate -d -- # Run command in environment +flox activate -d -c # Run command in environment ``` ## Critical Rules for Agentic Usage -1. **NEVER run `flox activate` interactively** - use `flox activate -- ` +1. **NEVER run `flox activate` interactively** — two non-interactive forms: + - `flox activate -c ` — runs in a subshell with full hooks and profile scripts (preferred for agents) + - `flox activate -- ` — exec mode, replaces the shell process, profile scripts do not run 2. **ALWAYS use `flox install`** instead of apt/yum/rpm/brew 3. Edit `.flox/env/manifest.toml` directly (not using `flox edit`) @@ -45,6 +67,6 @@ To define services, add them to the manifest. Examples: api.command = "go run main.go" # Backend API server frontend.command = "npm run dev" # Frontend dev server mydaemon.command = "mydaemon start" # Daemon that forks -mydaemon.is_daemon = true +mydaemon.is-daemon = true mydaemon.shutdown.command = "mydaemon stop" ``` diff --git a/justfile b/justfile new file mode 100644 index 00000000..47e95814 --- /dev/null +++ b/justfile @@ -0,0 +1,28 @@ +# Flox Documentation +# Run `just` to see available recipes + +# Show available recipes +default: + @just --list + +# Start development server with live reload +dev: + @echo "Starting development server..." + @echo "Site will be available at: http://127.0.0.1:8000" + @echo "Press Ctrl+C to stop" + mkdocs serve + +# Build static site and generate AI files +build: + @echo "Building static site..." + mkdocs build + @echo "Generating AI files..." + python3 tools/generate_llms_txt.py ./site + @echo "Build complete! Site available in ./site/" + +# Clean build artifacts +clean: + @echo "Cleaning build artifacts..." + rm -rf site/ + rm -rf public/ + @echo "Clean complete!" diff --git a/lychee.toml b/lychee.toml index 4ad5d078..2f1b69c4 100644 --- a/lychee.toml +++ b/lychee.toml @@ -10,6 +10,12 @@ host_request_interval = "500ms" exclude = [ '^javascript:', '^data:', + '^http://localhost', + '\.svc\.cluster\.local', + 'https://auth\.flox\.dev/oauth/token', + 'https://nix\.dev/manual/nix/2\.17/', + '^https://cache\.flox\.dev/?$', + 'downloads\.flox\.dev/by-env/stable/rpm$', 'bash/manual/html_node', 'https://www\.gnu\.org/software/make/', 'https://github\.com/flox/catalog-util', diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 00000000..70626a2f --- /dev/null +++ b/tools/README.md @@ -0,0 +1,33 @@ +# Tools + +This directory contains build tools and utilities for the Flox documentation site. + +## Scripts + +### `generate_llms_txt.py` + +Generates AI-friendly documentation files from the built MkDocs site: + +- **`llms.txt`** - Agent-focused file with critical rules, workflows, and curated link list +- **`llms-full.txt`** - Full content for RAG systems and answer engines, generated from source Markdown + +**Usage:** + +```bash +python3 tools/generate_llms_txt.py +``` + +### `generate_llms_txt.sh` + +Convenience script for local development. Generates both AI files after a MkDocs build. + +**Usage:** + +```bash +mkdocs build +./tools/generate_llms_txt.sh +``` + +## Integration + +These scripts are automatically run during CI builds in `.github/workflows/ci.yml` to ensure the AI files are always up-to-date with the documentation content. diff --git a/tools/generate_llms_txt.py b/tools/generate_llms_txt.py new file mode 100755 index 00000000..00224417 --- /dev/null +++ b/tools/generate_llms_txt.py @@ -0,0 +1,393 @@ +#!/usr/bin/env python3 +""" +Generate llms.txt from the built MkDocs site. +This script scans the built site directory and creates a comprehensive llms.txt file. +""" + +import os +import re +import sys +from pathlib import Path +from urllib.parse import unquote + + +def normalize_url(path: str, base_url: str = "https://flox.dev/docs") -> str: + """Convert file path to URL.""" + # Remove leading/trailing slashes + path = path.strip('/') + if not path or path == 'index.html': + return base_url + # Remove .html extension + path = path.replace('.html', '') + # Remove trailing /index so URLs are clean (e.g. /docs/foo not /docs/foo/index) + path = re.sub(r'/index$', '', path) + return f"{base_url}/{path}" + + +def extract_title_from_html(html_content: str) -> str: + """Extract page title from HTML content.""" + # Check if this is a redirect page + if 'Redirecting...' in html_content or 'redirect' in html_content.lower(): + return None # Signal this is a redirect page + + # Try to find the first h1 tag + h1_match = re.search(r']*>(.*?)', html_content, re.DOTALL) + if h1_match: + title = h1_match.group(1).strip() + # Remove markdown tags and clean up + title = re.sub(r'<[^>]+>', '', title) + return title + + # Fallback to title tag + title_match = re.search(r'(.*?)', html_content, re.DOTALL) + if title_match: + title = title_match.group(1).strip() + title = title.replace(' - Flox Docs', '').strip() + return title + + return "Untitled" + + +def extract_description_from_html(html_content: str) -> str: + """Extract meta description or first paragraph.""" + # Try to find meta description + desc_match = re.search(r']*>.*?]*>(.*?)

', html_content, re.DOTALL) + if para_match: + desc = para_match.group(1).strip() + # Remove HTML tags + desc = re.sub(r'<[^>]+>', '', desc) + # Limit length + if len(desc) > 200: + desc = desc[:200] + "..." + return desc + + return "" + + +def categorize_page(url: str, title: str) -> str: + """Categorize pages for better organization.""" + if '/concepts/' in url: + return 'concepts' + elif '/tutorials/' in url: + return 'tutorials' + elif '/man/' in url: + return 'manual' + elif '/languages/' in url: + return 'languages' + elif '/install-flox/' in url: + return 'installation' + elif '/customer/' in url: + return 'customer' + elif '/snippets/' in url: + return 'snippets' + elif url.endswith('/docs') or url.endswith('/docs/'): + return 'overview' + else: + return 'other' + + +def get_page_description(url: str, title: str) -> str: + """Get a helpful description for common pages.""" + descriptions = { + 'concepts/environments': 'Understanding Flox environments and how they work', + 'concepts/activation': 'How to activate and use Flox environments', + 'concepts/floxhub': 'Understanding FloxHub package registry and sharing', + 'concepts/generations': 'Environment snapshots and version management', + 'concepts/packages-and-catalog': 'Package management and the Flox catalog', + 'concepts/services': 'Running services within Flox environments', + 'concepts/composition': 'Combining and layering multiple environments', + 'concepts/builds': 'Building packages and environments', + 'concepts/publishing': 'Publishing packages to FloxHub', + 'tutorials/creating-environments': 'Step-by-step guide to create your first environment', + 'tutorials/sharing-environments': 'How to share environments with team members', + 'tutorials/customizing-environments': 'Customizing shell environment and behavior', + 'tutorials/build-and-publish': 'Building and publishing custom packages', + 'tutorials/ci-cd': 'Using Flox in continuous integration pipelines', + 'tutorials/composition': 'Reusing and combining developer environments', + 'tutorials/multi-arch-environments': 'Cross-platform environment design', + 'tutorials/cuda': 'Using CUDA with Flox environments', + 'tutorials/migrations/homebrew': 'Migrating from Homebrew to Flox', + 'tutorials/migrations/nvm': 'Migrating from Node Version Manager to Flox', + 'languages/python': 'Python development with Flox', + 'languages/nodejs': 'Node.js and JavaScript development', + 'languages/go': 'Go development with Flox', + 'languages/rust': 'Rust development with Flox', + 'languages/c': 'C/C++ development with Flox', + 'languages/jvm': 'Java and JVM development', + 'languages/ruby': 'Ruby development with Flox', + 'install-flox/install': 'Installation instructions for Flox', + 'install-flox/uninstall': 'How to uninstall Flox', + 'flox-5-minutes': 'Quick start guide to get up and running', + } + + # Extract the key part of the URL + key = url.replace('https://flox.dev/docs/', '').replace('/index', '') + return descriptions.get(key, '') + + +def get_site_structure(site_dir: Path) -> list: + """Get all pages from the built site.""" + pages = [] + html_files = list(site_dir.rglob('*.html')) + + for html_file in sorted(html_files): + # Skip generated JS/search files + if html_file.name in ['search.html', '404.html', 'sitemap.xml']: + continue + + rel_path = html_file.relative_to(site_dir) + url = normalize_url(str(rel_path)) + + try: + with open(html_file, 'r', encoding='utf-8') as f: + content = f.read() + + title = extract_title_from_html(content) + + # Skip redirect pages + if title is None: + continue + + description = extract_description_from_html(content) + + description = get_page_description(url, title) + category = categorize_page(url, title) + + pages.append({ + 'url': url, + 'title': title, + 'path': str(rel_path), + 'description': description, + 'category': category + }) + except Exception as e: + print(f"Error processing {html_file}: {e}", file=sys.stderr) + + return pages + + +def extract_page_content(html_content: str) -> str: + """Extract main content from HTML for answer engine.""" + # Remove script and style elements + content = re.sub(r']*>.*?', '', html_content, flags=re.DOTALL) + content = re.sub(r']*>.*?', '', content, flags=re.DOTALL) + + # Extract main content area + main_match = re.search(r']*>(.*?)', content, re.DOTALL) + if main_match: + content = main_match.group(1) + + # Remove unwanted elements + content = re.sub(r']*>.*?', '', content, flags=re.DOTALL) + content = re.sub(r']*>.*?', '', content, flags=re.DOTALL) + content = re.sub(r']*>.*?', '', content, flags=re.DOTALL) + + # Remove license/copyright text + content = re.sub(r'Permission is hereby granted.*?DEALINGS IN THE SOFTWARE\.', '', content, flags=re.DOTALL) + content = re.sub(r'Copyright.*?All rights reserved\.', '', content, flags=re.DOTALL) + content = re.sub(r'THE SOFTWARE IS PROVIDED.*?DEALINGS IN THE SOFTWARE\.', '', content, flags=re.DOTALL) + content = re.sub(r'-->.*?Have questions\?', '', content, flags=re.DOTALL) + + # Remove HTML tags but preserve structure + content = re.sub(r']*>(.*?)', r'\n\n#\1 \2\n', content) + content = re.sub(r']*>(.*?)

', r'\1\n\n', content) + content = re.sub(r']*>(.*?)', r'- \1\n', content) + content = re.sub(r']*>(.*?)', r'`\1`', content) + content = re.sub(r']*>]*>(.*?)', r'```\n\1\n```', content, flags=re.DOTALL) + content = re.sub(r'<[^>]+>', '', content) + + # Clean up whitespace and remove empty lines + content = re.sub(r'\n\s*\n\s*\n', '\n\n', content) + content = re.sub(r'^\s*\n', '', content, flags=re.MULTILINE) + content = content.strip() + + # Only return substantial content (more than 100 chars) + if len(content) < 100: + return "" + + return content + + +# Curated list of key documentation pages for AI agents. +# Maintained manually to surface the most useful pages rather than +# auto-generating an exhaustive 88-page sitemap. +CURATED_LINKS = [ + ("Getting Started", [ + ("Flox in 5 Minutes", "https://flox.dev/docs/flox-5-minutes/", "Quick start guide"), + ("Install Flox", "https://flox.dev/docs/install-flox/install/", "Install via flox.dev/download"), + ("What is Flox?", "https://flox.dev/docs/", "Overview"), + ]), + ("Core Concepts", [ + ("Environments", "https://flox.dev/docs/concepts/environments/", "What environments are and how they work"), + ("Activation", "https://flox.dev/docs/concepts/activation/", "How to activate — never run interactively"), + ("Catalog & Packages", "https://flox.dev/docs/concepts/packages-and-catalog/", "Package discovery and installation"), + ("Services", "https://flox.dev/docs/concepts/services/", "Running processes in environments"), + ("Generations", "https://flox.dev/docs/concepts/generations/", "Snapshots and rollbacks"), + ("FloxHub", "https://flox.dev/docs/concepts/floxhub/", "Package registry and environment sharing"), + ]), + ("Tutorials", [ + ("Creating Environments", "https://flox.dev/docs/tutorials/creating-environments/", ""), + ("Sharing Environments", "https://flox.dev/docs/tutorials/sharing-environments/", ""), + ("Customizing the Shell", "https://flox.dev/docs/tutorials/customizing-environments/", ""), + ("CI/CD", "https://flox.dev/docs/tutorials/ci-cd/", ""), + ("Layering Environments", "https://flox.dev/docs/tutorials/layering-multiple-environments/", ""), + ("Reusing Environments", "https://flox.dev/docs/tutorials/composition/", ""), + ("Build and Publish", "https://flox.dev/docs/tutorials/build-and-publish/", ""), + ("Multi-arch Environments", "https://flox.dev/docs/tutorials/multi-arch-environments/", ""), + ("CUDA", "https://flox.dev/docs/tutorials/cuda/", ""), + ("Migrate from Homebrew", "https://flox.dev/docs/tutorials/migrations/homebrew/", ""), + ("Migrate from nvm", "https://flox.dev/docs/tutorials/migrations/nvm/", ""), + ("Default Environment", "https://flox.dev/docs/tutorials/default-environment/", ""), + ]), + ("Language Guides", [ + ("Python", "https://flox.dev/docs/languages/python/", ""), + ("Node.js", "https://flox.dev/docs/languages/nodejs/", ""), + ("Go", "https://flox.dev/docs/languages/go/", ""), + ("Rust", "https://flox.dev/docs/languages/rust/", ""), + ("C/C++", "https://flox.dev/docs/languages/c/", ""), + ("JVM", "https://flox.dev/docs/languages/jvm/", ""), + ("Ruby", "https://flox.dev/docs/languages/ruby/", ""), + ]), + ("Reference", [ + ("manifest.toml", "https://flox.dev/docs/reference/command-reference/", "Manifest syntax reference"), + ]), + ("Optional", [ + ("Kubernetes Integration", "https://flox.dev/docs/k8s/intro/", ""), + ("Known Issues", "https://flox.dev/docs/customer/known-issues/", ""), + ]), +] + + +def generate_llms_txt(site_dir: Path, output_path: Path): + """Generate llms.txt file for agents.""" + # Read existing llms.txt to get the header + existing_llms = Path('docs/llms.txt') + header = "" + if existing_llms.exists(): + with open(existing_llms, 'r') as f: + lines = f.readlines() + # Extract header (everything before the Sitemap section) + for i, line in enumerate(lines): + if line.startswith('## Sitemap'): + header = ''.join(lines[:i]) + break + header = ''.join(lines) + + # Add key terminology and quick reference section + terminology = """ +## Key Terms +- **Environment**: A reproducible development environment with specific packages and configurations +- **Manifest**: A declarative configuration file (manifest.toml) defining an environment's packages and settings +- **Generation**: A snapshot of an environment at a specific point in time, allowing rollbacks +- **FloxHub**: The package registry and sharing platform for Flox environments +- **Activation**: Running commands within a Flox environment's context +- **Catalog**: The collection of available packages that can be installed +- **Services**: Long-running processes defined in the manifest that can be managed by Flox + +## Quick Reference +### Common Workflows +- **Install Flox**: Visit `flox.dev/download` (not install.flox.dev — that does not exist) +- **New Project**: `flox init -d .` → `flox install -d . ` → `flox activate -d . -- ` +- **Multi-environment Project**: `flox init -d backend` + `flox init -d frontend` +- **Sharing Environment**: `flox push` → `flox pull` on another machine +- **Package Management**: `flox search ` → `flox show ` → `flox install -d . ` +- **Service Management**: Define in manifest → `flox services start ` → `flox services status` + +""" + + # Build sitemap from the curated link list + sitemap = ["## Documentation\n\n"] + for section_title, links in CURATED_LINKS: + sitemap.append(f"### {section_title}\n\n") + for title, url, desc in links: + if desc: + sitemap.append(f"- [{title}]({url}): {desc}\n") + else: + sitemap.append(f"- [{title}]({url})\n") + sitemap.append("\n") + + # Write the complete llms.txt + content = header + terminology + ''.join(sitemap) + + with open(output_path, 'w') as f: + f.write(content) + + print(f"Generated {output_path} with {sum(len(links) for _, links in CURATED_LINKS)} curated links") + + +def generate_llms_full(docs_dir: Path, output_path: Path): + """Generate llms-full.txt from source Markdown files. + + Substitutes MkDocs template variables so that URLs and version + references are valid in the output (e.g. download links). + """ + md_files = sorted(docs_dir.rglob("*.md")) + + # Read template variables from mkdocs.yml and environment + flox_version = os.environ.get('FLOX_VERSION', '') + flox_public_key = 'flox-cache-public-1:7F4OyH7ZCnFhcze3fJdfyXYLQw/aV7GEed86nQ7IsOs=' + substitutions = { + '{{ FLOX_VERSION }}': flox_version, + '{{ FLOX_PUBLIC_KEY }}': flox_public_key, + } + + with open(output_path, 'w') as f: + f.write("# Flox Documentation — Full Content\n\n") + f.write("Complete documentation for RAG systems and Cursor @Docs.\n\n") + + for md_file in md_files: + rel = md_file.relative_to(docs_dir) + f.write(f"\n\n---\n\n## {rel}\n\n") + content = md_file.read_text(encoding='utf-8') + for placeholder, value in substitutions.items(): + content = content.replace(placeholder, value) + f.write(content) + + print(f"Generated {output_path} from {len(md_files)} source files") + + +def main(): + if len(sys.argv) < 2: + print("Usage: generate_llms_txt.py ") + sys.exit(1) + + site_dir = Path(sys.argv[1]) + + if not site_dir.exists(): + print(f"Error: Directory {site_dir} does not exist") + sys.exit(1) + + # Source markdown lives one level up from the built site + docs_dir = site_dir.parent / "docs" + + # Generate files in temp location first, then copy to site root + import tempfile + import shutil + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + llms_temp = temp_path / 'llms.txt' + llms_full_temp = temp_path / 'llms-full.txt' + + print("Generating files for AI systems...") + generate_llms_txt(site_dir, llms_temp) + generate_llms_full(docs_dir, llms_full_temp) + + # Copy to site root + llms_final = site_dir / 'llms.txt' + llms_full_final = site_dir / 'llms-full.txt' + + shutil.copy2(llms_temp, llms_final) + shutil.copy2(llms_full_temp, llms_full_final) + + print("Generated both llms.txt (for agents) and llms-full.txt (for RAG/answer engines)") + + +if __name__ == '__main__': + main() diff --git a/tools/generate_llms_txt.sh b/tools/generate_llms_txt.sh new file mode 100755 index 00000000..663db93e --- /dev/null +++ b/tools/generate_llms_txt.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Generate AI files for local development +# This script should be run after mkdocs builds the site + +set -euo pipefail + +# Ensure site directory exists +if [ ! -d "site" ]; then + echo "Error: site directory not found. Please run 'mkdocs build' first." + exit 1 +fi + +# Generate both AI files +python3 tools/generate_llms_txt.py ./site + +echo "Generated both llms.txt (for agents) and llms-full.txt (for RAG/answer engines)" + +