Add new system commands: check, version, clean, report

- check: Comprehensive environment validation (60+ tests)
- version: Definitive version catalog for all dependencies
- clean: Workspace cleanup (caches, temp files, build artifacts)
- report: Generate diagnostic JSON for bug reports and support
This commit is contained in:
Vijay Janapa Reddi
2025-11-26 18:10:47 +01:00
parent 62511ae91d
commit f913b54356
4 changed files with 903 additions and 0 deletions

181
tito/commands/check.py Normal file
View File

@@ -0,0 +1,181 @@
"""
Check command for TinyTorch CLI: comprehensive environment validation.
Runs 60+ automated tests to validate the entire TinyTorch environment.
Perfect for students to share with TAs when something isn't working.
"""
import sys
import subprocess
from argparse import ArgumentParser, Namespace
from pathlib import Path
from rich.panel import Panel
from rich.table import Table
from .base import BaseCommand
class CheckCommand(BaseCommand):
@property
def name(self) -> str:
return "check"
@property
def description(self) -> str:
return "Run comprehensive environment validation (60+ tests)"
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument(
'--verbose',
'-v',
action='store_true',
help='Show detailed test output'
)
def run(self, args: Namespace) -> int:
"""Run comprehensive validation tests with rich output."""
console = self.console
console.print()
console.print(Panel(
"🧪 Running Comprehensive Environment Validation\n\n"
"This will test 60+ aspects of your TinyTorch environment.\n"
"Perfect for sharing with TAs if something isn't working!",
title="TinyTorch Environment Check",
border_style="bright_cyan"
))
console.print()
# Check if tests directory exists
tests_dir = Path("tests/environment")
if not tests_dir.exists():
console.print(Panel(
"[red]❌ Validation tests not found![/red]\n\n"
f"Expected location: {tests_dir.absolute()}\n\n"
"Please ensure you're running this from the TinyTorch root directory.",
title="Error",
border_style="red"
))
return 1
# Run the validation tests with pytest
test_files = [
"tests/environment/test_setup_validation.py",
"tests/environment/test_all_requirements.py"
]
console.print("[bold cyan]Running validation tests...[/bold cyan]")
console.print()
# Build pytest command
pytest_args = [
sys.executable, "-m", "pytest"
] + test_files + [
"-v" if args.verbose else "-q",
"--tb=short",
"--color=yes",
"-p", "no:warnings" # Suppress warnings for cleaner output
]
# Run pytest and capture output
result = subprocess.run(
pytest_args,
capture_output=True,
text=True
)
# Parse test results from output
output_lines = result.stdout.split('\n')
# Count results
passed = failed = skipped = 0
for line in output_lines:
if 'passed' in line.lower():
# Extract numbers from pytest summary
import re
match = re.search(r'(\d+) passed', line)
if match:
passed = int(match.group(1))
match = re.search(r'(\d+) failed', line)
if match:
failed = int(match.group(1))
match = re.search(r'(\d+) skipped', line)
if match:
skipped = int(match.group(1))
# Display results with rich formatting
console.print()
# Summary table
results_table = Table(title="Test Results Summary", show_header=True, header_style="bold magenta")
results_table.add_column("Category", style="cyan", width=30)
results_table.add_column("Count", justify="right", width=10)
results_table.add_column("Status", width=20)
if passed > 0:
results_table.add_row("Tests Passed", str(passed), "[green]✅ OK[/green]")
if failed > 0:
results_table.add_row("Tests Failed", str(failed), "[red]❌ Issues Found[/red]")
if skipped > 0:
results_table.add_row("Tests Skipped", str(skipped), "[yellow]⏭️ Optional[/yellow]")
console.print(results_table)
console.print()
# Overall health status
if failed == 0:
status_panel = Panel(
"[bold green]✅ Environment is HEALTHY![/bold green]\n\n"
f"All {passed} required checks passed.\n"
f"{skipped} optional checks skipped.\n\n"
"Your TinyTorch environment is ready to use! 🎉\n\n"
"[dim]Next: [/dim][cyan]tito module 01[/cyan]",
title="Environment Status",
border_style="green"
)
else:
status_panel = Panel(
f"[bold red]❌ Found {failed} issue(s)[/bold red]\n\n"
f"{passed} checks passed, but some components need attention.\n\n"
"[bold]What to share with your TA:[/bold]\n"
"1. Copy the output above\n"
"2. Include the error messages below\n"
"3. Mention what you were trying to do\n\n"
"[dim]Or try:[/dim] [cyan]tito setup[/cyan] [dim]to reinstall[/dim]",
title="Environment Status",
border_style="red"
)
console.print(status_panel)
# Show detailed output if verbose or if there are failures
if args.verbose or failed > 0:
console.print()
console.print(Panel("📋 Detailed Test Output", border_style="blue"))
console.print()
console.print(result.stdout)
if result.stderr:
console.print()
console.print(Panel("⚠️ Error Messages", border_style="yellow"))
console.print()
console.print(result.stderr)
# Add helpful hints for common failures
if failed > 0:
console.print()
console.print(Panel(
"[bold]Common Solutions:[/bold]\n\n"
"• Missing packages: [cyan]pip install -r requirements.txt[/cyan]\n"
"• Jupyter issues: [cyan]pip install --upgrade jupyterlab[/cyan]\n"
"• Import errors: [cyan]pip install -e .[/cyan] [dim](reinstall TinyTorch)[/dim]\n"
"• Still stuck: Run [cyan]tito system check --verbose[/cyan]\n\n"
"[dim]Then share the full output with your TA[/dim]",
title="💡 Quick Fixes",
border_style="yellow"
))
console.print()
# Return appropriate exit code
return 0 if failed == 0 else 1

View File

@@ -0,0 +1,232 @@
"""
Clean command for TinyTorch CLI: clean up generated files and caches.
"""
from argparse import ArgumentParser, Namespace
from pathlib import Path
import shutil
from rich.panel import Panel
from rich.table import Table
from rich.prompt import Confirm
from .base import BaseCommand
class CleanWorkspaceCommand(BaseCommand):
@property
def name(self) -> str:
return "clean"
@property
def description(self) -> str:
return "Clean up generated files, caches, and temporary files"
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument(
'--all',
action='store_true',
help='Clean everything including build artifacts'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be deleted without actually deleting'
)
parser.add_argument(
'-y', '--yes',
action='store_true',
help='Skip confirmation prompt'
)
def run(self, args: Namespace) -> int:
console = self.console
console.print()
console.print(Panel(
"🧹 Cleaning TinyTorch Workspace",
title="Workspace Cleanup",
border_style="bright_yellow"
))
console.print()
# Define patterns to clean
patterns = {
'__pycache__': ('__pycache__/', 'Python bytecode cache'),
'.pytest_cache': ('.pytest_cache/', 'Pytest cache'),
'.ipynb_checkpoints': ('.ipynb_checkpoints/', 'Jupyter checkpoints'),
'*.pyc': ('*.pyc', 'Compiled Python files'),
'*.pyo': ('*.pyo', 'Optimized Python files'),
'*.pyd': ('*.pyd', 'Python extension modules'),
}
if args.all:
# Additional patterns for --all
patterns.update({
'.coverage': ('.coverage', 'Coverage data'),
'htmlcov': ('htmlcov/', 'Coverage HTML report'),
'.tox': ('.tox/', 'Tox environments'),
'dist': ('dist/', 'Distribution files'),
'build': ('build/', 'Build files'),
'*.egg-info': ('*.egg-info/', 'Egg info directories'),
})
# Scan for files to delete
console.print("[bold cyan]🔍 Scanning for files to clean...[/bold cyan]")
console.print()
files_to_delete = []
total_size = 0
# Find __pycache__ directories
for pycache_dir in Path.cwd().rglob('__pycache__'):
for file in pycache_dir.iterdir():
if file.is_file():
files_to_delete.append(file)
total_size += file.stat().st_size
files_to_delete.append(pycache_dir)
# Find .pytest_cache directories
for cache_dir in Path.cwd().rglob('.pytest_cache'):
for file in cache_dir.rglob('*'):
if file.is_file():
files_to_delete.append(file)
total_size += file.stat().st_size
files_to_delete.append(cache_dir)
# Find .ipynb_checkpoints directories
for checkpoint_dir in Path.cwd().rglob('.ipynb_checkpoints'):
for file in checkpoint_dir.rglob('*'):
if file.is_file():
files_to_delete.append(file)
total_size += file.stat().st_size
files_to_delete.append(checkpoint_dir)
# Find .pyc, .pyo, .pyd files
for ext in ['*.pyc', '*.pyo', '*.pyd']:
for file in Path.cwd().rglob(ext):
if file.is_file():
files_to_delete.append(file)
total_size += file.stat().st_size
if args.all:
# Additional cleanups for --all flag
for pattern in ['.coverage', 'htmlcov', '.tox', 'dist', 'build']:
target = Path.cwd() / pattern
if target.exists():
if target.is_file():
files_to_delete.append(target)
total_size += target.stat().st_size
elif target.is_dir():
for file in target.rglob('*'):
if file.is_file():
files_to_delete.append(file)
total_size += file.stat().st_size
files_to_delete.append(target)
# Find .egg-info directories
for egg_info in Path.cwd().rglob('*.egg-info'):
if egg_info.is_dir():
for file in egg_info.rglob('*'):
if file.is_file():
files_to_delete.append(file)
total_size += file.stat().st_size
files_to_delete.append(egg_info)
if not files_to_delete:
console.print(Panel(
"[green]✅ Workspace is already clean![/green]\n\n"
"No temporary files or caches found.",
title="Clean Workspace",
border_style="green"
))
return 0
# Count file types
file_count = len([f for f in files_to_delete if f.is_file()])
dir_count = len([f for f in files_to_delete if f.is_dir()])
# Summary table
summary_table = Table(title="Files Found", show_header=True, header_style="bold yellow")
summary_table.add_column("Type", style="cyan", width=30)
summary_table.add_column("Count", justify="right", width=15)
summary_table.add_column("Size", width=20)
# Count by type
pycache_count = len([f for f in files_to_delete if '__pycache__' in str(f)])
pytest_count = len([f for f in files_to_delete if '.pytest_cache' in str(f)])
checkpoint_count = len([f for f in files_to_delete if '.ipynb_checkpoints' in str(f)])
pyc_count = len([f for f in files_to_delete if str(f).endswith(('.pyc', '.pyo', '.pyd'))])
if pycache_count > 0:
summary_table.add_row("__pycache__/", str(pycache_count), "")
if pytest_count > 0:
summary_table.add_row(".pytest_cache/", str(pytest_count), "")
if checkpoint_count > 0:
summary_table.add_row(".ipynb_checkpoints/", str(checkpoint_count), "")
if pyc_count > 0:
summary_table.add_row("*.pyc/*.pyo/*.pyd", str(pyc_count), f"{total_size / 1024 / 1024:.2f} MB")
if args.all:
other_count = file_count - pycache_count - pytest_count - checkpoint_count - pyc_count
if other_count > 0:
summary_table.add_row("Build artifacts", str(other_count), "")
summary_table.add_row("[bold]Total[/bold]", f"[bold]{file_count} files, {dir_count} dirs[/bold]", f"[bold]{total_size / 1024 / 1024:.2f} MB[/bold]")
console.print(summary_table)
console.print()
# Dry run mode
if args.dry_run:
console.print(Panel(
"[yellow]🔍 DRY RUN MODE[/yellow]\n\n"
f"Would delete {file_count} files and {dir_count} directories ({total_size / 1024 / 1024:.2f} MB)\n\n"
"[dim]Remove --dry-run flag to actually delete these files[/dim]",
title="Dry Run",
border_style="yellow"
))
return 0
# Confirm deletion
if not args.yes:
confirmed = Confirm.ask(
f"\n[yellow]⚠️ Delete {file_count} files and {dir_count} directories ({total_size / 1024 / 1024:.2f} MB)?[/yellow]",
default=False
)
if not confirmed:
console.print("\n[dim]Cleanup cancelled.[/dim]")
return 0
# Perform cleanup
console.print()
console.print("[bold cyan]🗑️ Cleaning workspace...[/bold cyan]")
deleted_files = 0
deleted_dirs = 0
freed_space = 0
# Delete files first, then directories
for item in files_to_delete:
try:
if item.is_file():
size = item.stat().st_size
item.unlink()
deleted_files += 1
freed_space += size
elif item.is_dir() and not any(item.iterdir()): # Only delete if empty
shutil.rmtree(item, ignore_errors=True)
deleted_dirs += 1
except Exception as e:
console.print(f"[dim red] ✗ Failed to delete {item}: {e}[/dim red]")
# Success message
console.print()
console.print(Panel(
f"[bold green]✅ Workspace Cleaned![/bold green]\n\n"
f"🗑️ Deleted: {deleted_files} files, {deleted_dirs} directories\n"
f"💾 Freed: {freed_space / 1024 / 1024:.2f} MB\n"
f"⏱️ Time: < 1 second",
title="Cleanup Complete",
border_style="green"
))
return 0

363
tito/commands/report.py Normal file
View File

@@ -0,0 +1,363 @@
"""
Report command for TinyTorch CLI: generate comprehensive diagnostic report.
This command generates a JSON report containing all environment information,
perfect for sharing with TAs, instructors, or when filing bug reports.
"""
import sys
import os
import platform
import shutil
import json
from datetime import datetime
from pathlib import Path
from argparse import ArgumentParser, Namespace
from rich.panel import Panel
from rich.table import Table
from .base import BaseCommand
class ReportCommand(BaseCommand):
@property
def name(self) -> str:
return "report"
@property
def description(self) -> str:
return "Generate comprehensive diagnostic report (JSON)"
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument(
'-o', '--output',
type=str,
default=None,
help='Output file path (default: tinytorch-report-TIMESTAMP.json)'
)
parser.add_argument(
'--stdout',
action='store_true',
help='Print JSON to stdout instead of file'
)
def run(self, args: Namespace) -> int:
console = self.console
console.print()
console.print(Panel(
"📋 Generating TinyTorch Diagnostic Report\n\n"
"[dim]This report contains all environment information\n"
"needed for debugging and support.[/dim]",
title="System Report",
border_style="bright_yellow"
))
console.print()
# Collect all diagnostic information
report = self._collect_report_data()
# Determine output path
if args.stdout:
# Print to stdout
print(json.dumps(report, indent=2))
return 0
else:
# Write to file
if args.output:
output_path = Path(args.output)
else:
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
output_path = Path.cwd() / f"tinytorch-report-{timestamp}.json"
try:
with open(output_path, 'w') as f:
json.dump(report, f, indent=2)
console.print(Panel(
f"[bold green]✅ Report Generated Successfully![/bold green]\n\n"
f"📄 File: [cyan]{output_path}[/cyan]\n"
f"📦 Size: {output_path.stat().st_size} bytes\n\n"
f"[dim]Share this file with your TA or instructor for support.[/dim]",
title="Report Complete",
border_style="green"
))
return 0
except Exception as e:
console.print(Panel(
f"[red]❌ Failed to write report:[/red]\n\n{str(e)}",
title="Error",
border_style="red"
))
return 1
def _collect_report_data(self) -> dict:
"""Collect comprehensive diagnostic information."""
report = {
"report_metadata": {
"generated_at": datetime.now().isoformat(),
"report_version": "1.0",
"tinytorch_cli_version": self._get_tinytorch_version()
},
"system": self._collect_system_info(),
"python": self._collect_python_info(),
"environment": self._collect_environment_info(),
"dependencies": self._collect_dependencies_info(),
"tinytorch": self._collect_tinytorch_info(),
"modules": self._collect_modules_info(),
"git": self._collect_git_info(),
"disk_memory": self._collect_disk_memory_info()
}
return report
def _get_tinytorch_version(self) -> str:
"""Get TinyTorch version."""
try:
import tinytorch
return getattr(tinytorch, '__version__', 'unknown')
except ImportError:
return "not_installed"
def _collect_system_info(self) -> dict:
"""Collect system information."""
return {
"os": platform.system(),
"os_release": platform.release(),
"os_version": platform.version(),
"machine": platform.machine(),
"processor": platform.processor(),
"platform": platform.platform(),
"node": platform.node()
}
def _collect_python_info(self) -> dict:
"""Collect Python interpreter information."""
return {
"version": sys.version,
"version_info": {
"major": sys.version_info.major,
"minor": sys.version_info.minor,
"micro": sys.version_info.micro,
"releaselevel": sys.version_info.releaselevel,
"serial": sys.version_info.serial
},
"implementation": platform.python_implementation(),
"compiler": platform.python_compiler(),
"executable": sys.executable,
"prefix": sys.prefix,
"base_prefix": getattr(sys, 'base_prefix', sys.prefix),
"path": sys.path
}
def _collect_environment_info(self) -> dict:
"""Collect environment variables and paths."""
venv_exists = self.venv_path.exists()
in_venv = (
os.environ.get('VIRTUAL_ENV') is not None or
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix) or
hasattr(sys, 'real_prefix')
)
return {
"working_directory": str(Path.cwd()),
"virtual_environment": {
"exists": venv_exists,
"active": in_venv,
"path": str(self.venv_path) if venv_exists else None,
"VIRTUAL_ENV": os.environ.get('VIRTUAL_ENV'),
"CONDA_DEFAULT_ENV": os.environ.get('CONDA_DEFAULT_ENV'),
"CONDA_PREFIX": os.environ.get('CONDA_PREFIX')
},
"PATH": os.environ.get('PATH', '').split(os.pathsep),
"PYTHONPATH": os.environ.get('PYTHONPATH', '').split(os.pathsep) if os.environ.get('PYTHONPATH') else []
}
def _collect_dependencies_info(self) -> dict:
"""Collect installed package information."""
dependencies = {}
# Core dependencies
packages = [
('numpy', 'numpy'),
('pytest', 'pytest'),
('PyYAML', 'yaml'),
('rich', 'rich'),
('jupyterlab', 'jupyterlab'),
('jupytext', 'jupytext'),
('nbformat', 'nbformat'),
('nbgrader', 'nbgrader'),
('nbconvert', 'nbconvert'),
('jupyter', 'jupyter'),
('matplotlib', 'matplotlib'),
('psutil', 'psutil'),
('black', 'black'),
('isort', 'isort'),
('flake8', 'flake8')
]
for display_name, import_name in packages:
try:
module = __import__(import_name)
version = getattr(module, '__version__', 'unknown')
location = getattr(module, '__file__', 'unknown')
dependencies[display_name] = {
"installed": True,
"version": version,
"location": location
}
except ImportError:
dependencies[display_name] = {
"installed": False,
"version": None,
"location": None
}
return dependencies
def _collect_tinytorch_info(self) -> dict:
"""Collect TinyTorch package information."""
try:
import tinytorch
version = getattr(tinytorch, '__version__', 'unknown')
location = Path(tinytorch.__file__).parent
# Check if in development mode
is_dev = (location / '../setup.py').exists() or (location / '../pyproject.toml').exists()
return {
"installed": True,
"version": version,
"location": str(location),
"development_mode": is_dev,
"package_structure": {
"has_init": (location / '__init__.py').exists(),
"has_core": (location / 'core').exists(),
"has_ops": (location / 'ops').exists()
}
}
except ImportError:
return {
"installed": False,
"version": None,
"location": None,
"development_mode": False,
"package_structure": {}
}
def _collect_modules_info(self) -> dict:
"""Collect TinyTorch modules information."""
modules_dir = Path.cwd() / "modules"
if not modules_dir.exists():
return {"exists": False, "modules": []}
modules = []
for module_path in sorted(modules_dir.iterdir()):
if module_path.is_dir() and module_path.name.startswith(('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')):
module_info = {
"name": module_path.name,
"path": str(module_path),
"has_notebook": any(module_path.glob("*.ipynb")),
"has_dev_py": any(module_path.glob("*_dev.py")),
"has_tests": (module_path / "tests").exists()
}
modules.append(module_info)
return {
"exists": True,
"count": len(modules),
"modules": modules
}
def _collect_git_info(self) -> dict:
"""Collect git repository information."""
git_dir = Path.cwd() / ".git"
if not git_dir.exists():
return {"is_repo": False}
try:
import subprocess
# Get current branch
branch = subprocess.check_output(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
stderr=subprocess.DEVNULL,
text=True
).strip()
# Get current commit
commit = subprocess.check_output(
['git', 'rev-parse', '--short', 'HEAD'],
stderr=subprocess.DEVNULL,
text=True
).strip()
# Get remote URL
try:
remote = subprocess.check_output(
['git', 'remote', 'get-url', 'origin'],
stderr=subprocess.DEVNULL,
text=True
).strip()
except:
remote = None
# Check for uncommitted changes
status = subprocess.check_output(
['git', 'status', '--porcelain'],
stderr=subprocess.DEVNULL,
text=True
).strip()
has_changes = len(status) > 0
return {
"is_repo": True,
"branch": branch,
"commit": commit,
"remote": remote,
"has_uncommitted_changes": has_changes,
"status": status if has_changes else None
}
except:
return {"is_repo": True, "error": "Failed to get git info"}
def _collect_disk_memory_info(self) -> dict:
"""Collect disk space and memory information."""
info = {}
# Disk space
try:
disk_usage = shutil.disk_usage(Path.cwd())
info["disk"] = {
"total_bytes": disk_usage.total,
"used_bytes": disk_usage.used,
"free_bytes": disk_usage.free,
"total_gb": round(disk_usage.total / (1024**3), 2),
"used_gb": round(disk_usage.used / (1024**3), 2),
"free_gb": round(disk_usage.free / (1024**3), 2),
"percent_used": round((disk_usage.used / disk_usage.total) * 100, 1)
}
except Exception as e:
info["disk"] = {"error": str(e)}
# Memory
try:
import psutil
mem = psutil.virtual_memory()
info["memory"] = {
"total_bytes": mem.total,
"available_bytes": mem.available,
"used_bytes": mem.used,
"total_gb": round(mem.total / (1024**3), 2),
"available_gb": round(mem.available / (1024**3), 2),
"used_gb": round(mem.used / (1024**3), 2),
"percent_used": mem.percent
}
except ImportError:
info["memory"] = {"error": "psutil not installed"}
except Exception as e:
info["memory"] = {"error": str(e)}
return info

127
tito/commands/version.py Normal file
View File

@@ -0,0 +1,127 @@
"""
Version command for TinyTorch CLI: show version information for TinyTorch and dependencies.
"""
from argparse import ArgumentParser, Namespace
import sys
import os
from pathlib import Path
from datetime import datetime
from rich.panel import Panel
from rich.table import Table
from .base import BaseCommand
class VersionCommand(BaseCommand):
@property
def name(self) -> str:
return "version"
@property
def description(self) -> str:
return "Show version information for TinyTorch and dependencies"
def add_arguments(self, parser: ArgumentParser) -> None:
# No arguments needed
pass
def run(self, args: Namespace) -> int:
console = self.console
console.print()
console.print(Panel(
"📦 TinyTorch Version Information",
title="Version Info",
border_style="bright_magenta"
))
console.print()
# Main Version Table
version_table = Table(title="TinyTorch", show_header=True, header_style="bold cyan")
version_table.add_column("Component", style="yellow", width=25)
version_table.add_column("Version", style="white", width=50)
# TinyTorch Version
try:
import tinytorch
tinytorch_version = getattr(tinytorch, '__version__', '0.1.0-dev')
tinytorch_path = Path(tinytorch.__file__).parent
version_table.add_row("TinyTorch", f"v{tinytorch_version}")
version_table.add_row(" └─ Installation", "Development Mode")
version_table.add_row(" └─ Location", str(tinytorch_path))
# Check if it's a git repo
git_dir = Path.cwd() / ".git"
if git_dir.exists():
try:
import subprocess
result = subprocess.run(
["git", "rev-parse", "--short", "HEAD"],
capture_output=True,
text=True,
check=True
)
commit_hash = result.stdout.strip()
version_table.add_row(" └─ Git Commit", commit_hash)
except Exception:
pass
except ImportError:
version_table.add_row("TinyTorch", "[red]Not Installed[/red]")
# Python Version
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
version_table.add_row("Python", python_version)
console.print(version_table)
console.print()
# Dependencies Version Table
deps_table = Table(title="Core Dependencies", show_header=True, header_style="bold magenta")
deps_table.add_column("Package", style="cyan", width=20)
deps_table.add_column("Version", style="white", width=20)
deps_table.add_column("Status", width=30)
dependencies = [
('numpy', 'NumPy'),
('pytest', 'pytest'),
('yaml', 'PyYAML'),
('rich', 'Rich'),
('jupyterlab', 'JupyterLab'),
('jupytext', 'Jupytext'),
('nbformat', 'nbformat'),
]
for import_name, display_name in dependencies:
try:
module = __import__(import_name)
version = getattr(module, '__version__', 'unknown')
deps_table.add_row(display_name, version, "[green]✅ Installed[/green]")
except ImportError:
deps_table.add_row(display_name, "", "[red]❌ Not Installed[/red]")
console.print(deps_table)
console.print()
# System Info (brief)
system_table = Table(title="System", show_header=True, header_style="bold blue")
system_table.add_column("Component", style="yellow", width=20)
system_table.add_column("Value", style="white", width=50)
import platform
system_table.add_row("OS", f"{platform.system()} {platform.release()}")
system_table.add_row("Architecture", platform.machine())
system_table.add_row("Python Implementation", platform.python_implementation())
console.print(system_table)
console.print()
# Helpful info panel
console.print(Panel(
"[dim]💡 For complete system information, run:[/dim] [cyan]tito system info[/cyan]\n"
"[dim]💡 To check environment health, run:[/dim] [cyan]tito system health[/cyan]",
border_style="blue"
))
return 0