# Plan: `cde-onboard` — Interactive ONA Onboarding CLI

## Overview

Build a Python CLI package (`cde-onboard`) installable via `pipx install cde-onboard` from PyPI. Provides interactive, hands-on TUI tutorials to onboard engineers onto ONA Environments (formerly Gitpod). Engineers use GitLab and self-hosted AWS runners (but don't manage runners themselves).

Inspired by [ACFS](https://github.com/Dicklesworthstone/agentic_coding_flywheel_setup) — adopting its progressive disclosure, progress tracking, and hands-on scaffolding patterns, adapted to a Python/Textual TUI.

---

## Key Decisions

| Decision | Choice |
|----------|--------|
| Package name | `cde-onboard` |
| CLI framework | Rich + Textual (full TUI) |
| CLI entry point | Typer |
| Tutorial mode | Hands-on (scaffolds real config files) |
| Output directory | `./cde-onboard-practice/` (current directory) |
| Progress tracking | `~/.cde-onboard/progress.json` |
| Python version | 3.11+ |
| Build system | Hatchling |
| Data models | Pydantic v2 |

---

## Project Structure

```
/data/projects/ona-user-onboarding/
├── pyproject.toml
├── CLAUDE.md
├── src/
│   └── cde_onboard/
│       ├── __init__.py
│       ├── __main__.py
│       ├── cli.py                      # Typer entry point
│       ├── app.py                      # Textual App (OnboardApp)
│       ├── constants.py                # Paths, app metadata
│       ├── models.py                   # Pydantic: LessonStep, CodeBlock, ProgressData, etc.
│       ├── progress.py                 # ProgressManager (load/save/reset)
│       ├── scaffolder.py               # File scaffolding engine
│       ├── screens/
│       │   ├── __init__.py
│       │   ├── welcome.py              # Landing page with Start/Choose/Quit
│       │   ├── lesson_menu.py          # Lesson picker with checkmarks
│       │   ├── lesson.py               # Main teaching screen (steps + code + scaffold)
│       │   └── completion.py           # All-done celebration
│       ├── widgets/
│       │   ├── __init__.py
│       │   ├── lesson_sidebar.py       # Left sidebar: lesson list with progress
│       │   ├── step_view.py            # Renders one lesson step
│       │   ├── code_panel.py           # Syntax-highlighted code block (Rich Syntax)
│       │   └── info_panel.py           # Callout/explanation box
│       ├── lessons/
│       │   ├── __init__.py             # LESSONS registry + get_lesson()
│       │   ├── base.py                 # BaseLesson ABC
│       │   ├── lesson_01_devcontainer.py
│       │   ├── lesson_02_first_environment.py
│       │   ├── lesson_03_tasks.py
│       │   ├── lesson_04_services.py
│       │   ├── lesson_05_dotfiles.py
│       │   └── lesson_06_multi_repo.py
│       ├── content/
│       │   ├── __init__.py
│       │   └── templates/              # Scaffolded file templates
│       │       ├── devcontainer_basic.json
│       │       ├── devcontainer_multi_repo.json
│       │       ├── automations_task.yaml
│       │       ├── automations_service.yaml
│       │       ├── docker_compose_service.yaml
│       │       ├── dotfiles_install.sh
│       │       ├── dotfiles_bashrc
│       │       └── dotfiles_gitconfig
│       └── styles/
│           └── app.tcss                # Textual CSS
└── tests/
    ├── conftest.py
    ├── test_progress.py
    ├── test_scaffolder.py
    ├── test_models.py
    └── test_app.py
```

---

## Architecture

### CLI Entry (`cli.py`)

Typer app with:
- Default (no args): launches Textual TUI
- `--lesson N` / `-l N`: jump to lesson N
- `--list`: print Rich table of lessons with progress (non-TUI)
- `--reset`: clear progress

### Textual App (`app.py`)

Screen stack navigation:
```
OnboardApp
  ├── WelcomeScreen (landing)
  ├── LessonMenuScreen (picker with checkmarks)
  ├── LessonScreen(N) (teaching content + scaffold button)
  └── CompletionScreen (celebration)
```

`ProgressManager` instantiated once at App level, passed to screens.

### Lesson System

Each lesson is a Python class extending `BaseLesson`:
```python
class BaseLesson(ABC):
    @classmethod
    @abstractmethod
    def meta(cls) -> LessonMeta: ...       # id, number, title, description, overview

    @abstractmethod
    def steps(self) -> list[LessonStep]: ...  # ordered teaching steps

    @abstractmethod
    def scaffold_files(self) -> list[ScaffoldFile]: ...  # files to create
```

`LessonStep` contains: `title`, `explanation` (markdown), optional `code` (CodeBlock), optional `why` (callout), optional `deep_dive` (collapsible).

### Scaffolding

Templates are plain files in `content/templates/`, read via `importlib.resources`. Written to `./cde-onboard-practice/lesson-NN-<slug>/` in the user's CWD.

### Progress

Pydantic model persisted as JSON to `~/.cde-onboard/progress.json`:
```json
{
  "completed_lessons": ["devcontainer-basics", "first-environment"],
  "started_at": "2026-01-28T14:30:00+00:00",
  "last_activity": "2026-01-28T15:45:00+00:00"
}
```

---

## Lesson Content (6 Lessons)

### Lesson 1: Dev Container Basics
**ID**: `devcontainer-basics`
**Steps**: What is a devcontainer? → Choosing a base image → Adding features → Editor extensions → Lifecycle commands → Full example
**Scaffolds**: `lesson-01-devcontainer/.devcontainer/devcontainer.json`
**Key content**: `.devcontainer/devcontainer.json` structure, Microsoft base images, features from `ghcr.io/devcontainers/features/`, `customizations.vscode.extensions`, lifecycle commands (`onCreateCommand`, `postCreateCommand`, `postStartCommand`)

### Lesson 2: Setting Up Your First Environment
**ID**: `first-environment`
**Steps**: Authenticating with GitLab (OAuth) → Creating environment from a GitLab repo → Connecting via VS Code Browser → Workspace structure (`/workspaces/<repo>`) → Associating projects for prebuilds → Environment lifecycle (start/stop/resume/delete)
**Scaffolds**: `lesson-02-first-environment/README.md` (summary reference)
**Key content**:
- **GitLab OAuth**: First-time users authenticate with GitLab via OAuth in the ONA dashboard. After this one-time setup, ONA has full access to GitLab — no Personal Access Tokens (PATs) needed for cloning, pushing, or raising MRs from within ONA environments.
- **VS Code Browser**: The primary connection method. Users open their environment directly from the ONA dashboard in VS Code running in the browser — no local installs required.
- Prebuild setup, why prebuilds matter for fast starts, environment lifecycle

### Lesson 3: Tasks
**ID**: `tasks`
**Steps**: What are tasks? → `.ona/automations.yaml` structure → Trigger types → `dependsOn` → Complete example
**Scaffolds**: `lesson-03-tasks/.ona/automations.yaml`
**Key ONA details**: Tasks use flat `command` field. Triggers: `postDevcontainerStart`, `postEnvironmentStart`, `prebuild`, `manual`. CLI: `gitpod automations task start <name>`

**Template example**:
```yaml
tasks:
  install-deps:
    name: Install Dependencies
    description: Install all project dependencies
    command: npm install
    triggeredBy:
      - postDevcontainerStart
  run-migrations:
    name: Run Database Migrations
    command: python manage.py migrate
    triggeredBy:
      - postDevcontainerStart
    dependsOn:
      - postgres
```

### Lesson 4: Services
**ID**: `services`
**Steps**: What are services? → Defining a service → Health checks (`commands.ready`) → Docker Compose + `network_mode: host` → Graceful shutdown → Full services + tasks example
**Scaffolds**: `lesson-04-services/.ona/automations.yaml` + `lesson-04-services/docker-compose.yaml`
**Key ONA details**: Services use nested `commands.start` and `commands.ready` (NOT flat). `network_mode: host` is **required** on all Docker Compose services. CLI: `gitpod automations service start <name>`

**Template example**:
```yaml
services:
  postgres:
    name: PostgreSQL Database
    description: PostgreSQL 16 via Docker Compose
    triggeredBy:
      - postDevcontainerStart
    commands:
      start: docker compose up postgres
      ready: pg_isready -h localhost -p 5432
```

```yaml
# docker-compose.yaml — ALL services MUST use network_mode: host
services:
  postgres:
    image: postgres:16
    network_mode: host
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: myapp_dev
```

### Lesson 5: Dotfiles
**ID**: `dotfiles`
**Steps**: What are dotfiles? → Creating a dotfiles repo (GitLab) → Install script (priority: install.sh > bootstrap.sh > setup.sh) → Linking to ONA (`gitpod user dotfiles set --repository <url>`) → Best practices
**Scaffolds**: `lesson-05-dotfiles/dotfiles/install.sh`, `.bashrc`, `.gitconfig`
**Key ONA details**: Cloned to `~/dotfiles`. Install script must be non-interactive. Rebuild with `gitpod environment devcontainer rebuild`. One dotfiles repo per user.

### Lesson 6: Managing Multiple Repos
**ID**: `multi-repo`
**Steps**: Why multi-repo? → Approach 1: `onCreateCommand` in devcontainer.json (parallel cloning via object form) → Approach 2: Tasks in automations.yaml → `/workspaces/<repo>` convention → VS Code multi-root workspaces
**Scaffolds**: `lesson-06-multi-repo/.devcontainer/devcontainer.json`
**Key content**: Clone to `/workspaces/<repo-name>`, object form of `onCreateCommand` for parallel execution, conditional cloning in tasks

---

## Dependencies

```toml
dependencies = [
    "textual>=1.0.0,<2.0.0",
    "rich>=13.0.0,<14.0.0",
    "typer>=0.12.0,<1.0.0",
    "pydantic>=2.0.0,<3.0.0",
    "pyyaml>=6.0,<7.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "pytest-asyncio>=0.23",
    "textual-dev>=1.0",
    "ruff>=0.5",
    "mypy>=1.10",
]
```

Entry point: `cde-onboard = "cde_onboard.cli:app"`

---

## Implementation Sequence

| Phase | What | Key Files |
|-------|------|-----------|
| 0 | Place plan in project | `PLAN.md` |
| 1 | Multi-agent collaboration setup | `AGENTS.md` — defines how Claude, Codex, and Gemini collaborate on implementation and tests |
| 2 | Project skeleton | `pyproject.toml`, `__init__.py`, `__main__.py`, `constants.py`, `models.py`, `CLAUDE.md` |
| 3 | Progress + scaffolder + tests | `progress.py`, `scaffolder.py`, `tests/test_progress.py`, `tests/test_scaffolder.py` |
| 4 | CLI entry point | `cli.py` |
| 5 | Textual app + WelcomeScreen + CSS | `app.py`, `screens/welcome.py`, `styles/app.tcss` |
| 6 | LessonMenuScreen + sidebar widget | `screens/lesson_menu.py`, `widgets/lesson_sidebar.py` |
| 7 | Core widgets | `widgets/step_view.py`, `widgets/code_panel.py`, `widgets/info_panel.py` |
| 8 | LessonScreen | `screens/lesson.py` |
| 9 | BaseLesson + registry | `lessons/base.py`, `lessons/__init__.py` |
| 10 | Lessons 1-6 + all templates | `lessons/lesson_01_*.py` through `lesson_06_*.py`, all `content/templates/*` |
| 11 | CompletionScreen | `screens/completion.py` |
| 12 | Polish + tests | CSS refinement, `test_app.py`, `test_models.py` |
| 13 | Build + publish | `python -m build`, `twine upload` |

---

## Verification

### Local Testing
```bash
cd /data/projects/ona-user-onboarding
python3.11 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

# Launch TUI
cde-onboard

# CLI options
cde-onboard --list
cde-onboard --lesson 3
cde-onboard --reset

# Hot reload during development
textual run --dev src/cde_onboard/app.py
```

### Automated Tests
```bash
pytest tests/ -v
ruff check src/ tests/
mypy src/cde_onboard/
```

### Manual Checklist
- [ ] TUI launches with welcome screen
- [ ] All 6 lessons listed in menu with correct titles
- [ ] Each lesson renders steps, code panels, collapsible deep dives
- [ ] "Scaffold & Complete" writes files to `./cde-onboard-practice/lesson-NN-*/`
- [ ] Completed lessons show checkmarks in sidebar and menu
- [ ] Progress persists across restarts
- [ ] `--lesson N` jumps directly to lesson N
- [ ] `--list` prints table, `--reset` clears progress
- [ ] Completing all 6 shows celebration screen
- [ ] Scaffolded YAML/JSON files are valid
- [ ] Docker Compose template has `network_mode: host`
- [ ] Services use `commands.start`/`commands.ready` (nested)
- [ ] Tasks use `command` (flat)
- [ ] All example URLs use GitLab (not GitHub)
- [ ] Lesson 2 uses VS Code Browser only (no Desktop or SSH)
- [ ] Lesson 2 explains GitLab OAuth (no PAT needed after first auth)
- [ ] `pipx install .` works from local source

### PyPI Publish
```bash
python -m build
twine upload --repository testpypi dist/*
pipx install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ cde-onboard
# Verify, then:
twine upload dist/*
```

---

## ONA-Specific Rules (must be enforced throughout)

1. Config file: `.ona/automations.yaml`
2. Services use nested `commands:` → `start:`, `ready:`, `stop:`
3. Tasks use flat `command:`
4. `network_mode: host` required on ALL Docker Compose services
5. CLI commands use `gitpod` (e.g., `gitpod automations task start`, `gitpod user dotfiles set`)
6. Dotfiles cloned to `~/dotfiles`
7. Additional repos cloned to `/workspaces/<repo-name>`
8. All example repo URLs use GitLab (`gitlab.example.com`), never GitHub
9. Triggers: `postDevcontainerStart`, `postEnvironmentStart`, `prebuild`, `manual`
10. GitLab OAuth: ONA authenticates with GitLab via OAuth (one-time setup in ONA dashboard). No PATs needed — users can clone, push, and raise MRs natively within ONA environments after authenticating.
11. Connection method: VS Code Browser (from ONA dashboard) is the primary/only method to teach. Do not reference VS Code Desktop or SSH connections.

## Sources

- [ONA Dev Container Config](https://ona.com/docs/ona/configuration/devcontainer/overview)
- [ONA Getting Started](https://ona.com/docs/ona/configuration/devcontainer/getting-started)
- [ONA Tasks & Services Overview](https://ona.com/docs/ona/configuration/tasks-and-services/overview)
- [ONA Tasks & Services Config](https://ona.com/docs/ona/configuration/tasks-and-services/basic-configuration)
- [ONA Tasks & Services Examples](https://ona.com/docs/ona/configuration/tasks-and-services/examples)
- [ONA Dotfiles](https://ona.com/docs/ona/configuration/dotfiles/overview)
- [ONA Multi-Repository](https://ona.com/docs/ona/configuration/multi-repository)
- [ONA Optimizing Startup](https://ona.com/docs/ona/configuration/devcontainer/optimizing-startup-times)
- [ACFS Inspiration Repo](https://github.com/Dicklesworthstone/agentic_coding_flywheel_setup)
