"""Project management commands — local YAML or remote API.""" import click from solar_eval.cli.config import config from solar_eval.cli.formatters import format_projects_table def _resolve_project_id(client, name_or_id: str) -> str: """Resolve a project name to its API ID (remote mode only).""" if len(name_or_id) == 24: try: int(name_or_id, 16) return name_or_id except ValueError: pass projects = client.get("/api/projects") for p in projects: if p["name"] == name_or_id: return p["id"] raise click.ClickException(f"Project not found: {name_or_id}") @click.group("projects") def projects_group() -> None: """Manage evaluation projects.""" @projects_group.command("list") @click.pass_context def list_projects(ctx: click.Context) -> None: """List projects (from YAML locally, or from API remotely).""" cfg = ctx.obj["config"] if cfg.is_remote: from solar_eval.cli.client import EvalClient client = EvalClient(cfg.remote_url, cfg.timeout) projects = client.get("/api/projects") if not projects: click.secho("No projects found.", fg="yellow") return click.secho(format_projects_table(projects), fg="blue") else: from solar_eval.core.project_loader import load_all_project_configs configs = load_all_project_configs(cfg.projects_dir, cfg.config_dirs) if not configs: click.secho(f"No project YAML files in {cfg.projects_dir}", fg="yellow") return rows = [{ "name": c["name"], "display_name": c.get("display_name", c["name"]), "description": c.get("description", ""), "task_count": len(c.get("tasks", [])), } for c in configs] click.secho(format_projects_table(rows), fg="blue") @projects_group.command("show") @click.argument("name") @click.pass_context def show_project(ctx: click.Context, name: str) -> None: """Show project details by name.""" cfg = ctx.obj["config"] if cfg.is_remote: from solar_eval.cli.client import EvalClient client = EvalClient(cfg.remote_url, cfg.timeout) project_id = _resolve_project_id(client, name) project = client.get(f"/api/projects/{project_id}") else: from solar_eval.core.project_loader import load_all_project_configs configs = load_all_project_configs(cfg.projects_dir, cfg.config_dirs) project = next((c for c in configs if c["name"] == name), None) if not project: raise click.ClickException(f"Project not found: {name}") project["display_name"] = project.get("display_name", name) project["config"] = project click.secho(f"Project: {project.get('display_name', name)}", fg="blue", bold=True) click.echo(f" Name: {project.get('name', name)}") click.echo(f" Description: {project.get('description', '-')}") tasks = project.get("config", {}).get("tasks", project.get("tasks", [])) if tasks: click.echo(f" Tasks ({len(tasks)}):") for t in tasks: ev = t.get("evaluator", {}) click.echo(f" - {t['name']} (pipeline: {t.get('pipeline', '?')}, evaluator: {ev.get('type', '?')})")