diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..4c382f7 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,79 @@ +# Copyright (C) 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +name: deploy-docs + +on: + push: + branches: + - main + paths: + - 'README.md' + - 'website/**' + - '.github/workflows/deploy-docs.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Concurrency control - only one deployment at a time +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + name: Build Documentation + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Python + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: website/requirements.txt + + - name: Install dependencies + working-directory: website + run: | + pip install --upgrade pip + pip install -r requirements.txt + + - name: Build MkDocs site + working-directory: website + run: mkdocs build --strict --verbose + + - name: Upload artifact + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + with: + path: ./website/site + + deploy: + name: Deploy to GitHub Pages + needs: build + runs-on: ubuntu-24.04 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.gitignore b/.gitignore index 82ddfc6..10916d6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ go.work # Build artifacts /kortex-cli dist/ + +# MkDocs build output +website/site/ +website/docs/*.md diff --git a/website/WEBSITE.md b/website/WEBSITE.md new file mode 100644 index 0000000..4330a40 --- /dev/null +++ b/website/WEBSITE.md @@ -0,0 +1,41 @@ +# Website Documentation + +This directory contains the MkDocs configuration for automatically generating the kortex-cli documentation website from the repository's README.md. + +## How It Works + +1. **Single Source**: The project `README.md` (in the repository root) is the only file you edit +2. **Automatic Splitting**: The `hooks.py` script splits README.md by `##` headings into separate pages +3. **Build & Deploy**: GitHub Actions automatically builds and deploys the site on merge to main + +## Local Preview + +```bash +# From the website/ directory: +cd website + +# Create and activate virtual environment (first time only) +python3 -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate + +# Install dependencies (first time only) +pip install -r requirements.txt + +# Start preview server +mkdocs serve + +# Open http://127.0.0.1:8000 in browser +``` + +## Files + +- **mkdocs.yml** - MkDocs configuration (theme, plugins, SEO settings) +- **hooks.py** - Custom hook that splits README.md into multiple pages +- **requirements.txt** - Python dependencies (MkDocs, Material theme, plugins) +- **docs/** - Temporary directory for generated markdown files (ignored by git) + +## Deployment + +The website is automatically deployed to GitHub Pages when changes are pushed to the `main` branch. + +Website URL: https://kortex-hub.github.io/kortex-cli/ diff --git a/website/docs/.gitkeep b/website/docs/.gitkeep new file mode 100644 index 0000000..a2c3fed --- /dev/null +++ b/website/docs/.gitkeep @@ -0,0 +1,2 @@ +# This directory is used by MkDocs during the build process. +# Markdown files are automatically generated from README.md via hooks.py diff --git a/website/hooks.py b/website/hooks.py new file mode 100644 index 0000000..72d47e5 --- /dev/null +++ b/website/hooks.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +""" +MkDocs hooks for automatically splitting README.md into multiple pages. + +This hook reads README.md and creates separate pages for each ## (level 2) heading. +The pages are created as virtual files during the build process, so README.md remains +the single source of truth. +""" + +import re +import logging +from pathlib import Path + +logger = logging.getLogger("mkdocs.plugins") + + +def slugify(text): + """Convert heading text to a URL-friendly slug.""" + # Remove special characters and convert to lowercase + slug = re.sub(r'[^\w\s-]', '', text.lower()) + # Replace spaces with hyphens + slug = re.sub(r'[\s_]+', '-', slug) + # Remove leading/trailing hyphens + slug = slug.strip('-') + return slug + + +def split_readme_content(content): + """ + Split README.md content into sections based on ## headings. + + Returns: + list of dict: Each dict contains 'title', 'slug', and 'content' + """ + sections = [] + lines = content.split('\n') + + # First section: everything before first ## heading (becomes index.md) + current_section = { + 'title': 'Home', + 'slug': 'index', + 'content': [] + } + + i = 0 + # Capture content before first ## heading + while i < len(lines): + line = lines[i] + if line.startswith('## '): + break + current_section['content'].append(line) + i += 1 + + sections.append(current_section) + + # Process each ## section + while i < len(lines): + line = lines[i] + + if line.startswith('## '): + # Start new section + title = line[3:].strip() # Remove "## " + slug = slugify(title) + + current_section = { + 'title': title, + 'slug': slug, + 'content': [] # Don't include the heading (MkDocs shows it as page title) + } + sections.append(current_section) + else: + # Add to current section + current_section['content'].append(line) + + i += 1 + + # Join content lines back into strings + for section in sections: + section['content'] = '\n'.join(section['content']) + + return sections + + +def on_files(files, config): + """ + Called after files are collected from docs_dir. + Split README.md into multiple virtual files. + """ + from mkdocs.structure.files import File + + # Read README.md from project root + # docs_dir is website/docs -> parent is website -> parent is repo root + docs_dir_abs = Path(config['docs_dir']).resolve() + readme_path = docs_dir_abs.parent.parent / 'README.md' + + if not readme_path.exists(): + logger.warning(f"README.md not found at {readme_path}") + return files + + logger.info(f"Splitting README.md from {readme_path}") + + with open(readme_path, 'r', encoding='utf-8') as f: + readme_content = f.read() + + # Split README into sections + sections = split_readme_content(readme_content) + + logger.info(f"Found {len(sections)} sections in README.md") + + # Remove existing index.md symlink if it exists + files_to_remove = [f for f in files if f.src_path == 'index.md'] + for f in files_to_remove: + files.remove(f) + + # Create virtual files for each section + for section in sections: + filename = f"{section['slug']}.md" + + # Create a temporary file + temp_path = Path(config['docs_dir']) / filename + temp_path.write_text(section['content'], encoding='utf-8') + + # Create MkDocs File object + file_obj = File( + path=filename, + src_dir=config['docs_dir'], + dest_dir=config['site_dir'], + use_directory_urls=config['use_directory_urls'] + ) + + files.append(file_obj) + logger.debug(f"Created virtual file: {filename} ({section['title']})") + + return files + + +def on_nav(nav, config, files): + """ + Called after navigation is created. + Build navigation structure from split sections. + """ + from mkdocs.structure.nav import Navigation + + # Read README.md again to get section titles in order + # Use same path calculation as on_files for consistency + docs_dir_abs = Path(config['docs_dir']).resolve() + readme_path = docs_dir_abs.parent.parent / 'README.md' + + if not readme_path.exists(): + return nav + + with open(readme_path, 'r', encoding='utf-8') as f: + readme_content = f.read() + + sections = split_readme_content(readme_content) + + # Build navigation items + nav_items = [] + for section in sections: + filename = f"{section['slug']}.md" + + # Find corresponding file + file_obj = None + for f in files: + if f.src_path == filename: + file_obj = f + break + + if file_obj: + from mkdocs.structure.pages import Page + page = Page(section['title'], file_obj, config) + nav_items.append(page) + + # Create new navigation + nav.items = nav_items + nav.pages = nav_items + + return nav + + +def on_post_build(config): + """ + Called after site is built. + Clean up temporary markdown files created during build. + """ + docs_dir = Path(config['docs_dir']) + + # Remove all .md files in docs_dir (they were created temporarily) + for md_file in docs_dir.glob('*.md'): + md_file.unlink() + logger.debug(f"Cleaned up temporary file: {md_file}") diff --git a/website/mkdocs.yml b/website/mkdocs.yml new file mode 100644 index 0000000..b7bcaf9 --- /dev/null +++ b/website/mkdocs.yml @@ -0,0 +1,141 @@ +# Copyright (C) 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +# Site metadata +site_name: kortex-cli Documentation +site_description: Command-line interface for launching and managing AI agents (Claude Code, Goose, Cursor) with custom configurations +site_author: Red Hat +site_url: https://kortex-hub.github.io/kortex-cli/ + +# Repository configuration +repo_name: kortex-hub/kortex-cli +repo_url: https://github.com/kortex-hub/kortex-cli +edit_uri: blob/main/README.md + +# Copyright +copyright: Copyright © 2026 Red Hat, Inc. Licensed under Apache License 2.0 + +# Documentation source +docs_dir: docs +site_dir: site + +# Hooks for automatic README.md splitting +hooks: + - hooks.py + +# Theme configuration +theme: + name: material + language: en + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: red + accent: red + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: red + accent: red + toggle: + icon: material/brightness-4 + name: Switch to light mode + font: + text: Red Hat Text + code: Red Hat Mono + features: + - navigation.instant + - navigation.tracking + - navigation.top + - navigation.footer + - search.suggest + - search.highlight + - search.share + - content.code.copy + - content.code.annotate + - content.action.edit + - toc.follow + icon: + repo: fontawesome/brands/github + +# Plugins for SEO and functionality +plugins: + - search: + separator: '[\s\-,:!=\[\]()"/]+|(?!\b)(?=[A-Z][a-z])|\.(?!\d)|&[lg]t;' + - minify: + minify_html: true + minify_js: true + minify_css: true + htmlmin_opts: + remove_comments: true + cache_safe: true + +# Markdown extensions for better rendering +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - tables + - toc: + permalink: true + permalink_title: Anchor link to this section + toc_depth: 3 + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + +# SEO and social media optimization +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/kortex-hub/kortex-cli + name: kortex-cli on GitHub + generator: false # Don't show "Made with Material for MkDocs" + +# Additional CSS and JavaScript (if needed in future) +# extra_css: +# - assets/extra.css +# extra_javascript: +# - assets/extra.js diff --git a/website/requirements.txt b/website/requirements.txt new file mode 100644 index 0000000..efc7583 --- /dev/null +++ b/website/requirements.txt @@ -0,0 +1,28 @@ +# Copyright (C) 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +# MkDocs core - static site generator +mkdocs==1.6.1 + +# Material theme - modern, responsive design with built-in SEO features +mkdocs-material==9.5.44 + +# Minify plugin - compress HTML/CSS/JS for better performance +mkdocs-minify-plugin==0.8.0 + +# Dependencies for social cards and icons (used by Material theme) +pillow==12.1.1 +cairosvg==2.9.0