Metadata-Version: 2.4
Name: coding-academy-lecture-manager
Version: 1.2.0
Summary: Coding-Academy Lecture Manager - A course content processing system
Project-URL: Homepage, https://github.com/hoelzl/clm/
Project-URL: Documentation, https://github.com/hoelzl/clm/blob/main/README.md
Project-URL: Repository, https://github.com/hoelzl/clm/
Project-URL: Bug Tracker, https://github.com/hoelzl/clm/issues
Author-email: "Dr. Matthias Hölzl" <tc@xantira.com>
License: MIT
License-File: LICENSE
Keywords: content,course,education,jupyter,notebooks
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Education
Classifier: Topic :: Software Development :: Documentation
Requires-Python: >=3.11
Requires-Dist: attrs>=25.4.0
Requires-Dist: click>=8.1.0
Requires-Dist: docker>=6.0.0
Requires-Dist: fastapi>=0.104.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: loguru>=0.7.0
Requires-Dist: platformdirs>=3.0.0
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: pydantic>=2.8.2
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: rich>=13.7.0
Requires-Dist: tabulate>=0.9.0
Requires-Dist: uvicorn>=0.24.0
Requires-Dist: watchdog>=6.0.0
Provides-Extra: all
Requires-Dist: accelerate>=1.11.0; extra == 'all'
Requires-Dist: aiofiles~=24.1.0; extra == 'all'
Requires-Dist: beautifulsoup4>=4.12.2; extra == 'all'
Requires-Dist: bm25s[core]>=0.3.2.post1; extra == 'all'
Requires-Dist: clean-text>=0.6.0; extra == 'all'
Requires-Dist: cookiecutter; extra == 'all'
Requires-Dist: diffusers>=0.35.1; extra == 'all'
Requires-Dist: fastai>=2.7; extra == 'all'
Requires-Dist: fastembed>=0.6.0; extra == 'all'
Requires-Dist: faster-whisper>=1.0.0; extra == 'all'
Requires-Dist: ftfy>=6.1.1; extra == 'all'
Requires-Dist: gradio>=6.5.1; extra == 'all'
Requires-Dist: hf-xet>=1.1.10; extra == 'all'
Requires-Dist: httptools>=0.6.2; extra == 'all'
Requires-Dist: hypothesis>=6.148.2; extra == 'all'
Requires-Dist: icecream>=2.1.10; extra == 'all'
Requires-Dist: inscriptis>=1.0.2; extra == 'all'
Requires-Dist: ipykernel~=6.29.5; extra == 'all'
Requires-Dist: ipytest; extra == 'all'
Requires-Dist: ipython~=8.26.0; extra == 'all'
Requires-Dist: ipywidgets>=8.1.0; extra == 'all'
Requires-Dist: jinja2>=3.1; extra == 'all'
Requires-Dist: jinja2~=3.1.4; extra == 'all'
Requires-Dist: jupytext>=1.16.4; extra == 'all'
Requires-Dist: langchain-anthropic; extra == 'all'
Requires-Dist: langchain-community>=0.3.0; extra == 'all'
Requires-Dist: langchain-core; extra == 'all'
Requires-Dist: langchain-openai; extra == 'all'
Requires-Dist: langchain-qdrant; extra == 'all'
Requires-Dist: langchain-text-splitters; extra == 'all'
Requires-Dist: langchain>=1.2.7; extra == 'all'
Requires-Dist: langfuse; extra == 'all'
Requires-Dist: langgraph>=1.1.2; extra == 'all'
Requires-Dist: langsmith; extra == 'all'
Requires-Dist: matplotlib>=3.9.2; extra == 'all'
Requires-Dist: mcp>=1.0.0; extra == 'all'
Requires-Dist: mypy>=1.0; extra == 'all'
Requires-Dist: nbconvert~=7.16.4; extra == 'all'
Requires-Dist: nbformat~=5.10.4; extra == 'all'
Requires-Dist: newspaper4k>=0.2.8; extra == 'all'
Requires-Dist: numba>=0.60.0; extra == 'all'
Requires-Dist: numpy>=1.24.0; extra == 'all'
Requires-Dist: numpy>=2.0.1; extra == 'all'
Requires-Dist: obsws-python>=1.7.0; extra == 'all'
Requires-Dist: onnxruntime>=1.17.0; extra == 'all'
Requires-Dist: openai>=1.0.0; extra == 'all'
Requires-Dist: openai>=2.8.1; extra == 'all'
Requires-Dist: opencv-python>=4.8.0; extra == 'all'
Requires-Dist: packaging>=25.0; extra == 'all'
Requires-Dist: pandas>=2.2.2; extra == 'all'
Requires-Dist: pillow>=10.0.0; extra == 'all'
Requires-Dist: pillow>=11.2.1; extra == 'all'
Requires-Dist: plotly>=6.4.0; extra == 'all'
Requires-Dist: protobuf>=6.32.1; extra == 'all'
Requires-Dist: pymediawiki>=0.7.5; extra == 'all'
Requires-Dist: pypdf>=6.3.2; extra == 'all'
Requires-Dist: pytesseract>=0.3.10; extra == 'all'
Requires-Dist: pytest-asyncio>=0.21; extra == 'all'
Requires-Dist: pytest-cov>=4.0; extra == 'all'
Requires-Dist: pytest-mock>=3.12.0; extra == 'all'
Requires-Dist: pytest-timeout>=2.2.0; extra == 'all'
Requires-Dist: pytest-xdist>=3.5.0; extra == 'all'
Requires-Dist: pytest>=7.0; extra == 'all'
Requires-Dist: python-dateutil>=2.8.0; extra == 'all'
Requires-Dist: python-multipart>=0.0.6; extra == 'all'
Requires-Dist: qdrant-client>=1.13.0; extra == 'all'
Requires-Dist: ragas; extra == 'all'
Requires-Dist: rapidfuzz>=3.0.0; extra == 'all'
Requires-Dist: respx>=0.20.0; extra == 'all'
Requires-Dist: rich>=13.7.0; extra == 'all'
Requires-Dist: ruff>=0.1.0; extra == 'all'
Requires-Dist: scikit-learn>=1.5.1; extra == 'all'
Requires-Dist: scipy>=1.14.0; extra == 'all'
Requires-Dist: seaborn>=0.13.2; extra == 'all'
Requires-Dist: sentencepiece>=0.2.1; extra == 'all'
Requires-Dist: skorch>=1.0.2; extra == 'all'
Requires-Dist: soundfile>=0.12.0; extra == 'all'
Requires-Dist: sqlalchemy>=2.0.32; extra == 'all'
Requires-Dist: tenacity~=9.0.0; extra == 'all'
Requires-Dist: textual>=0.50.0; extra == 'all'
Requires-Dist: tiktoken>=0.9.0; extra == 'all'
Requires-Dist: timm>=1.0.15; extra == 'all'
Requires-Dist: toolz>=0.12.1; extra == 'all'
Requires-Dist: torch>=2.8.0; extra == 'all'
Requires-Dist: torchaudio>=2.8.0; extra == 'all'
Requires-Dist: torchvision>=0.20.0; extra == 'all'
Requires-Dist: tqdm>=4.66.5; extra == 'all'
Requires-Dist: trafilatura>=1.6.0; extra == 'all'
Requires-Dist: transformers; extra == 'all'
Requires-Dist: watchfiles>=0.18.0; extra == 'all'
Requires-Dist: wsproto>=1.2.0; extra == 'all'
Provides-Extra: all-workers
Requires-Dist: aiofiles~=24.1.0; extra == 'all-workers'
Requires-Dist: ipykernel~=6.29.5; extra == 'all-workers'
Requires-Dist: ipython~=8.26.0; extra == 'all-workers'
Requires-Dist: ipywidgets>=8.1.0; extra == 'all-workers'
Requires-Dist: jinja2~=3.1.4; extra == 'all-workers'
Requires-Dist: jupytext>=1.16.4; extra == 'all-workers'
Requires-Dist: nbconvert~=7.16.4; extra == 'all-workers'
Requires-Dist: nbformat~=5.10.4; extra == 'all-workers'
Requires-Dist: tenacity~=9.0.0; extra == 'all-workers'
Provides-Extra: dev
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
Requires-Dist: pytest-timeout>=2.2.0; extra == 'dev'
Requires-Dist: pytest-xdist>=3.5.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.20.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Provides-Extra: drawio
Requires-Dist: aiofiles~=24.1.0; extra == 'drawio'
Requires-Dist: tenacity~=9.0.0; extra == 'drawio'
Provides-Extra: mcp
Requires-Dist: mcp>=1.0.0; extra == 'mcp'
Requires-Dist: rapidfuzz>=3.0.0; extra == 'mcp'
Provides-Extra: ml
Requires-Dist: accelerate>=1.11.0; extra == 'ml'
Requires-Dist: beautifulsoup4>=4.12.2; extra == 'ml'
Requires-Dist: bm25s[core]>=0.3.2.post1; extra == 'ml'
Requires-Dist: clean-text>=0.6.0; extra == 'ml'
Requires-Dist: cookiecutter; extra == 'ml'
Requires-Dist: diffusers>=0.35.1; extra == 'ml'
Requires-Dist: fastai>=2.7; extra == 'ml'
Requires-Dist: fastembed>=0.6.0; extra == 'ml'
Requires-Dist: ftfy>=6.1.1; extra == 'ml'
Requires-Dist: gradio>=6.5.1; extra == 'ml'
Requires-Dist: hf-xet>=1.1.10; extra == 'ml'
Requires-Dist: hypothesis>=6.148.2; extra == 'ml'
Requires-Dist: icecream>=2.1.10; extra == 'ml'
Requires-Dist: inscriptis>=1.0.2; extra == 'ml'
Requires-Dist: ipytest; extra == 'ml'
Requires-Dist: langchain-anthropic; extra == 'ml'
Requires-Dist: langchain-community>=0.3.0; extra == 'ml'
Requires-Dist: langchain-core; extra == 'ml'
Requires-Dist: langchain-openai; extra == 'ml'
Requires-Dist: langchain-qdrant; extra == 'ml'
Requires-Dist: langchain-text-splitters; extra == 'ml'
Requires-Dist: langchain>=1.2.7; extra == 'ml'
Requires-Dist: langfuse; extra == 'ml'
Requires-Dist: langgraph>=1.1.2; extra == 'ml'
Requires-Dist: langsmith; extra == 'ml'
Requires-Dist: matplotlib>=3.9.2; extra == 'ml'
Requires-Dist: newspaper4k>=0.2.8; extra == 'ml'
Requires-Dist: numba>=0.60.0; extra == 'ml'
Requires-Dist: numpy>=2.0.1; extra == 'ml'
Requires-Dist: openai>=2.8.1; extra == 'ml'
Requires-Dist: packaging>=25.0; extra == 'ml'
Requires-Dist: pandas>=2.2.2; extra == 'ml'
Requires-Dist: pillow>=11.2.1; extra == 'ml'
Requires-Dist: plotly>=6.4.0; extra == 'ml'
Requires-Dist: protobuf>=6.32.1; extra == 'ml'
Requires-Dist: pymediawiki>=0.7.5; extra == 'ml'
Requires-Dist: pypdf>=6.3.2; extra == 'ml'
Requires-Dist: qdrant-client>=1.13.0; extra == 'ml'
Requires-Dist: ragas; extra == 'ml'
Requires-Dist: scikit-learn>=1.5.1; extra == 'ml'
Requires-Dist: scipy>=1.14.0; extra == 'ml'
Requires-Dist: seaborn>=0.13.2; extra == 'ml'
Requires-Dist: sentencepiece>=0.2.1; extra == 'ml'
Requires-Dist: skorch>=1.0.2; extra == 'ml'
Requires-Dist: sqlalchemy>=2.0.32; extra == 'ml'
Requires-Dist: tiktoken>=0.9.0; extra == 'ml'
Requires-Dist: timm>=1.0.15; extra == 'ml'
Requires-Dist: toolz>=0.12.1; extra == 'ml'
Requires-Dist: torch>=2.8.0; extra == 'ml'
Requires-Dist: torchaudio>=2.8.0; extra == 'ml'
Requires-Dist: torchvision>=0.20.0; extra == 'ml'
Requires-Dist: tqdm>=4.66.5; extra == 'ml'
Requires-Dist: trafilatura>=1.6.0; extra == 'ml'
Requires-Dist: transformers; extra == 'ml'
Provides-Extra: notebook
Requires-Dist: ipykernel~=6.29.5; extra == 'notebook'
Requires-Dist: ipython~=8.26.0; extra == 'notebook'
Requires-Dist: ipywidgets>=8.1.0; extra == 'notebook'
Requires-Dist: jinja2~=3.1.4; extra == 'notebook'
Requires-Dist: jupytext>=1.16.4; extra == 'notebook'
Requires-Dist: nbconvert~=7.16.4; extra == 'notebook'
Requires-Dist: nbformat~=5.10.4; extra == 'notebook'
Provides-Extra: plantuml
Requires-Dist: aiofiles~=24.1.0; extra == 'plantuml'
Requires-Dist: tenacity~=9.0.0; extra == 'plantuml'
Provides-Extra: recordings
Requires-Dist: jinja2>=3.1; extra == 'recordings'
Requires-Dist: numpy>=1.24.0; extra == 'recordings'
Requires-Dist: obsws-python>=1.7.0; extra == 'recordings'
Requires-Dist: onnxruntime>=1.17.0; extra == 'recordings'
Requires-Dist: python-multipart>=0.0.6; extra == 'recordings'
Requires-Dist: soundfile>=0.12.0; extra == 'recordings'
Provides-Extra: slides
Requires-Dist: rapidfuzz>=3.0.0; extra == 'slides'
Provides-Extra: summarize
Requires-Dist: openai>=1.0.0; extra == 'summarize'
Provides-Extra: tui
Requires-Dist: rich>=13.7.0; extra == 'tui'
Requires-Dist: textual>=0.50.0; extra == 'tui'
Provides-Extra: voiceover
Requires-Dist: faster-whisper>=1.0.0; extra == 'voiceover'
Requires-Dist: opencv-python>=4.8.0; extra == 'voiceover'
Requires-Dist: pillow>=10.0.0; extra == 'voiceover'
Requires-Dist: pytesseract>=0.3.10; extra == 'voiceover'
Requires-Dist: python-dateutil>=2.8.0; extra == 'voiceover'
Requires-Dist: rapidfuzz>=3.0.0; extra == 'voiceover'
Provides-Extra: voiceover-cohere
Requires-Dist: sentencepiece>=0.1.99; extra == 'voiceover-cohere'
Requires-Dist: soundfile>=0.12.0; extra == 'voiceover-cohere'
Requires-Dist: torch>=2.0.0; extra == 'voiceover-cohere'
Requires-Dist: transformers>=4.52.0; extra == 'voiceover-cohere'
Provides-Extra: voiceover-granite
Requires-Dist: soundfile>=0.12.0; extra == 'voiceover-granite'
Requires-Dist: torch>=2.0.0; extra == 'voiceover-granite'
Requires-Dist: transformers>=4.52.0; extra == 'voiceover-granite'
Provides-Extra: web
Requires-Dist: httptools>=0.6.2; extra == 'web'
Requires-Dist: watchfiles>=0.18.0; extra == 'web'
Requires-Dist: wsproto>=1.2.0; extra == 'web'
Description-Content-Type: text/markdown

# CLM - Coding-Academy Lecture Manager

[![CI](https://github.com/hoelzl/clm/actions/workflows/ci.yml/badge.svg)](https://github.com/hoelzl/clm/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/hoelzl/clm/branch/master/graph/badge.svg)](https://codecov.io/gh/hoelzl/clm)

**Version**: 1.2.0 | **License**: MIT | **Python**: 3.11, 3.12, 3.13, 3.14

CLM is a course content processing system that converts educational materials (Jupyter notebooks, PlantUML diagrams, Draw.io diagrams) into multiple output formats.

## Quick Start

### Installation

```bash
# Install from PyPI
pip install coding-academy-lecture-manager

# Or with all optional dependencies (workers, TUI, web dashboard)
pip install "coding-academy-lecture-manager[all]"
```

For development, clone the repository and install in editable mode:

```bash
git clone https://github.com/hoelzl/clm.git
cd clm
pip install -e ".[all]"
```

### Basic Usage

```bash
# Convert a course
clm build /path/to/course.xml

# Watch for changes and auto-rebuild
clm build /path/to/course.xml --watch

# Show help
clm --help
```

## Features

- **Multiple Output Formats**: HTML slides, Jupyter notebooks, extracted code
- **Multi-Language Notebooks**: Python, C++, C#, Java, TypeScript, Markdown
- **Diagram Support**: PlantUML and Draw.io conversion
- **Multiple Output Targets**: Separate student/solution/instructor outputs
- **Watch Mode**: Auto-rebuild on file changes
- **Incremental Builds**: Content-based caching
- **LLM Summaries**: Generate course summaries with `clm summarize` using any OpenAI-compatible LLM API
- **Recording Management**: Manage video recording workflows with pluggable backends — local ONNX pipeline, iZotope RX 11 external tool, or Auphonic cloud processing — plus assembly, job tracking, and per-course status (`clm recordings`)
- **MCP Server**: Model Context Protocol server for AI-assisted slide authoring (`clm mcp`) with 12 tools for course navigation, validation, normalization, and bilingual editing
- **Slide Authoring Tools**: Topic resolution (`clm resolve-topic`), fuzzy search (`clm search-slides`), spec/slide validation (`clm validate-spec`, `clm validate-slides`), normalization (`clm normalize-slides`), bilingual language view (`clm language-view`), sync suggestions (`clm suggest-sync`), voiceover extraction (`clm extract-voiceover`), and structured JSON outlines (`clm outline --format json`)
- **Voiceover Sync**: Synchronize video recordings with slides to auto-generate speaker notes (`clm voiceover sync`)
- **LLM Polish**: Clean up speaker notes with LLM-powered text polishing (`clm polish`)
- **Git Integration**: Manage output repos with `clm git init/sync/status`, including `--amend` and `--force-with-lease` for iterative workflows
- **Flexible Remote URLs**: Configurable git remote URL templates for SSH, custom hosts, etc.

## Documentation

**For Users**:
- [User Guide](docs/user-guide/README.md) - Complete usage guide
- [Quick Start](docs/user-guide/quick-start.md) - Build your first course
- [Spec File Reference](docs/user-guide/spec-file-reference.md) - Course XML format
- [Configuration](docs/user-guide/configuration.md) - Configuration options
- [Changelog](CHANGELOG.md) - Version history

**For Developers**:
- [Contributing Guide](CONTRIBUTING.md) - How to contribute
- [Developer Guide](docs/developer-guide/README.md) - Development documentation
- [Architecture](docs/developer-guide/architecture.md) - System design
- [CLAUDE.md](CLAUDE.md) - AI assistant reference

## Development Setup

```bash
# Install pre-commit hooks (recommended)
uv run pre-commit install

# This enables automatic linting (ruff) and type checking (mypy) on every commit
```

## Testing

```bash
# Run unit tests
pytest

# Run all tests (unit, integration, e2e)
pytest -m ""

# Run with coverage
pytest --cov=src/clm
```

## License

MIT License - see [LICENSE](LICENSE) for details.

## Links

- **Repository**: https://github.com/hoelzl/clm/
- **Issues**: https://github.com/hoelzl/clm/issues
