Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Add doctor and clean commands for environment health

## Summary
This PR adds two new commands to help maintain a healthy claudette environment:
- `claudette doctor` - Comprehensive health check and diagnostics
- `claudette clean` - Cleanup tool for orphaned project metadata

## Problem
Users were experiencing issues where `claudette remove` would sometimes leave behind metadata files, causing:
- Phantom projects appearing in `claudette list`
- Failures when running `claudette activate` on these orphaned projects
- No easy way to diagnose environment issues

## Solution

### 🩺 `claudette doctor` command
Comprehensive health check that detects:
- Missing initialization or configuration issues
- Required tool availability (Docker, Git, Node.js, Python, uv)
- Orphaned projects (metadata without corresponding worktrees)
- Port conflicts between projects
- Provides actionable fix suggestions

Example output:
```
🩺 Claudette Doctor - Environment Health Check

Checking claudette initialization... ✓
Checking Superset base repository... ✓
Checking worktree directory... ✓
Checking Docker installation... ✓
Checking Docker daemon... ✓
Checking Git installation... ✓
Checking Node.js installation... ✓ (v20.18.1)
Checking Python version... ✓ (3.10.14)
Checking uv installation... ✓

Scanning for projects... 1 project(s) found
• orphaned-project: directory missing

──────────────────────────────────────────────────

❌ Found 1 issue(s):
• Orphaned project 'orphaned-project': worktree directory missing

⚠️ Orphaned projects detected!
Run claudette clean to remove orphaned project metadata
```

### 🧹 `claudette clean` command
Cleanup tool for orphaned project metadata:
- Finds projects with missing worktrees or corrupted metadata
- Removes stale metadata files that `claudette remove` may have missed
- Supports `--dry-run` to preview what would be cleaned
- Supports `--force` to skip confirmation prompt
- Only removes metadata files, never actual project files

Example usage:
```bash
# Preview what would be cleaned
claudette clean --dry-run

# Clean with confirmation prompt
claudette clean

# Clean without confirmation
claudette clean --force
```

## Changes
- Added `doctor` command in `src/claudette/cli.py`
- Added `clean` command in `src/claudette/cli.py`
- Added `from_file` classmethod to `ProjectMetadata` in `src/claudette/config.py`
- Updated README.md with documentation for both commands
- Added comprehensive tests in `tests/test_doctor_clean.py`

## Testing
- Added unit tests for both commands covering various scenarios
- Manually tested with orphaned projects
- Verified dry-run mode works correctly
- Tested force flag skips confirmation

## Documentation
Updated README.md with detailed descriptions of both commands including:
- Purpose and use cases
- Available options
- When to use each command

## Backward Compatibility
- No breaking changes
- Commands are additive only
- Existing functionality remains unchanged

## Future Enhancements
- Could add auto-fix capability to `doctor` command
- Could integrate `clean` functionality into `doctor --fix`
- Could add more health checks (disk space, network connectivity, etc.)
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ clo --help
- 🎯 **Auto Port Assignment** - Automatically finds available ports when not specified
- 🧊 **Freeze/Thaw Projects** - Save ~3GB per project by removing dependencies when not in use
- 🔗 **GitHub PR Integration** - Track and quickly access pull requests associated with projects
- 🚀 **iTerm Launch** - One command to create project and open complete dev environment (macOS)

## Installation

Expand Down Expand Up @@ -230,6 +231,16 @@ Manage GitHub PR associations:
- `clo pr clear` - Remove PR association from current/specified project
- `clo pr open` - Open associated PR in browser

### `claudette launch <project> [port]` (macOS/iTerm2 only)
Launch complete dev environment in iTerm with multiple tabs:
- Creates project if it doesn't exist (auto-assigns port if needed)
- Opens new iTerm window with 3 pre-configured tabs:
- Tab 1: Shell with project activated
- Tab 2: Docker containers running
- Tab 3: Claude Code AI assistant
- Requires iTerm2 with Python API enabled
- Install support: `pip install superset-claudette[iterm]`

### `claudette nuke` (DANGEROUS!)
Completely removes claudette and all projects:
- Stops ALL Docker containers
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ dev = [
"build>=1.0.0",
"twine>=4.0.0",
]
iterm = [
"iterm2>=2.7",
]

[project.scripts]
claudette = "claudette.cli:app"
Expand Down
154 changes: 154 additions & 0 deletions src/claudette/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3444,6 +3444,160 @@ def sync(
console.print(f"[dim]Expected at: {project_path / 'PROJECT.md'}[/dim]")


@app.command()
def launch(
project: str = typer.Argument(..., help="Project name to launch"),
port: Optional[int] = typer.Argument(
None, min=9000, max=9999, help="Port for frontend (auto-assigned if not provided)"
),
) -> None:
"""🚀 Launch complete dev environment in iTerm with multiple tabs."""
console.print(f"\n[bold blue]🚀 Launching development environment for {project}[/bold blue]\n")

# Check if iTerm2 is available
try:
import iterm2
except ImportError:
console.print("[red]❌ iTerm2 Python API not installed![/red]")
console.print("\nTo install it, run:")
console.print("[cyan]pip install iterm2[/cyan]")
console.print("\nAlso make sure iTerm2 is installed and Python API is enabled:")
console.print("1. Open iTerm2 Preferences")
console.print("2. Go to General → Magic")
console.print("3. Enable 'Enable Python API'")
raise typer.Exit(1)

# Check if project exists
metadata_dir = settings.claudette_home / "projects"
project_exists = False
existing_port = None

if metadata_dir.exists():
# Check for existing project
metadata_file = metadata_dir / f"{project}.claudette"
project_folder = metadata_dir / project

if metadata_file.exists() or (project_folder.exists() and (project_folder / ".claudette").exists()):
try:
metadata = ProjectMetadata.load(project, settings.claudette_home)
project_exists = True
existing_port = metadata.port
console.print(f"[green]✓[/green] Project '{project}' already exists (port: {existing_port})")
except Exception:
pass

# If project doesn't exist, create it
if not project_exists:
console.print(f"[yellow]Project '{project}' not found. Creating it now...[/yellow]")

# Determine port
if port is None:
port = ProjectMetadata.suggest_port(settings.claudette_home)
console.print(f"[cyan]Auto-assigned port: {port}[/cyan]")

# Create the project using the add command logic
# We'll call the add command programmatically
from typer.testing import CliRunner
runner = CliRunner()
result = runner.invoke(app, ["add", project, str(port)])

if result.exit_code != 0:
console.print(f"[red]Failed to create project: {result.stdout}[/red]")
raise typer.Exit(1)

console.print(f"[green]✓[/green] Project '{project}' created successfully")
else:
port = existing_port

# Now launch iTerm with the project
import sys
import tempfile
import textwrap

# Create a Python script to run the iTerm automation
iterm_script = textwrap.dedent(f'''
#!/usr/bin/env python3
import iterm2
import asyncio

async def main(connection):
app = await iterm2.async_get_app(connection)

# Create a new window
window = await iterm2.Window.async_create(connection)

# Get the first tab that's created by default
tab1 = window.current_tab
session1 = tab1.current_session

# Tab 1: Basic shell with project activated
await session1.async_send_text("cd {settings.worktree_base / project}\\n")
await session1.async_send_text("claudette activate {project}\\n")
await tab1.async_set_title("Shell")

# Tab 2: Docker
tab2 = await window.async_create_tab()
session2 = tab2.current_session
await session2.async_send_text("cd {settings.worktree_base / project}\\n")
await session2.async_send_text("claudette activate {project}\\n")
await session2.async_send_text("claudette docker up\\n")
await tab2.async_set_title("Docker")

# Tab 3: Claude Code
tab3 = await window.async_create_tab()
session3 = tab3.current_session
await session3.async_send_text("cd {settings.worktree_base / project}\\n")
await session3.async_send_text("claudette activate {project}\\n")
await session3.async_send_text("claudette claude code\\n")
await tab3.async_set_title("Claude Code")

# Set window title
await window.async_set_title("Claudette: {project}")

iterm2.run_until_complete(main)
''')

# Write script to temp file and execute it
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(iterm_script)
script_path = f.name

try:
# Run the script using the current Python interpreter
result = subprocess.run(
[sys.executable, script_path],
capture_output=True,
text=True,
timeout=10
)

if result.returncode == 0:
console.print(f"\n[green]✅ iTerm environment launched successfully![/green]")
console.print(f"\n[bold]Tabs created:[/bold]")
console.print(f" 1. [cyan]Shell[/cyan] - Project shell with venv activated")
console.print(f" 2. [cyan]Docker[/cyan] - Running docker-compose up")
console.print(f" 3. [cyan]Claude Code[/cyan] - Claude Code AI assistant")
console.print(f"\n[dim]Frontend will be available at: http://localhost:{port}[/dim]")
else:
console.print(f"[red]Error launching iTerm: {result.stderr}[/red]")
console.print("\n[yellow]Troubleshooting:[/yellow]")
console.print("1. Make sure iTerm2 is running")
console.print("2. Check that Python API is enabled in iTerm2 preferences")
console.print(" (Preferences → General → Magic → Enable Python API)")
console.print("3. Try running: pip install --upgrade iterm2")

except subprocess.TimeoutExpired:
console.print("[yellow]iTerm launch script timed out - windows may still be opening[/yellow]")
except Exception as e:
console.print(f"[red]Failed to launch iTerm: {e}[/red]")
finally:
# Clean up temp file
try:
os.unlink(script_path)
except:
pass


@app.command()
def nuke() -> None:
"""🚨 COMPLETELY REMOVE claudette and all projects (DANGEROUS!)"""
Expand Down