agnes-the-ai-analyst/cli/commands/catalog.py
ZdenekSrotyr 103efb69f0 chore(cli-rename): replace stale da verbs in active code paths
Bring admin UI, audit-log messages, code comments, and analyst-facing
skill docs in line with the post-bootstrap CLI surface (`agnes pull`,
`agnes push`, `agnes init`, `agnes snapshot create`). The legacy
`_LEGACY_STRINGS` detection tuple in `app/api/claude_md.py` and the hook
upgrade markers in `cli/lib/hooks.py` are intentionally left as-is —
they exist precisely to flag pre-rewrite content for re-authoring.

Strip "(folded from `da metrics list`)" / "(lifted from `da metrics
show`)" / "Replaces the old `da analyst status`" docstring noise — the
rename history is in CHANGELOG.md, not in module docstrings.
2026-05-04 21:10:43 +02:00

133 lines
4.3 KiB
Python

"""`agnes catalog` — list registered tables and metric definitions (spec §4.1)."""
import json as json_lib
from typing import Optional
import typer
from cli.client import api_get
from cli.v2_client import api_get_json, V2ClientError
catalog_app = typer.Typer(help="List tables (and metrics, with --metrics) visible to you")
@catalog_app.callback(invoke_without_command=True)
def catalog(
ctx: typer.Context,
json: bool = typer.Option(False, "--json", help="Emit raw JSON"),
refresh: bool = typer.Option(False, "--refresh", help="Bypass client-side cache"),
metrics: bool = typer.Option(
False,
"--metrics",
help="List metric definitions instead of tables. Combine with --show <id> for details.",
),
show: Optional[str] = typer.Option(
None,
"--show",
help="With --metrics: show details for one metric id (e.g. revenue/mrr).",
),
):
"""List tables visible to you (RBAC-filtered).
With ``--metrics`` lists registered metric definitions; pair with
``--show <id>`` to dump one definition.
"""
if ctx.invoked_subcommand is not None:
return
if metrics:
if show:
_show_one_metric(show, as_json=json)
else:
_list_metrics(as_json=json)
return
try:
data = api_get_json("/api/v2/catalog", refresh=int(refresh))
except V2ClientError as e:
typer.echo(f"Error: catalog fetch failed: {e}", err=True)
raise typer.Exit(5)
if json:
typer.echo(json_lib.dumps(data, indent=2))
return
# Human-readable table
typer.echo(f"{'ID':30s} {'SOURCE':10s} {'MODE':8s} {'FLAVOR':10s} NAME")
for t in data.get("tables", []):
typer.echo(
f"{t['id']:30s} {t['source_type']:10s} {t['query_mode']:8s} "
f"{t['sql_flavor']:10s} {t.get('name', '')}"
)
def _list_metrics(as_json: bool, category: Optional[str] = None) -> None:
"""List metric definitions from the server."""
params = {}
if category:
params["category"] = category
resp = api_get("/api/metrics", params=params)
if resp.status_code != 200:
typer.echo(f"Failed: {resp.json().get('detail', resp.text)}", err=True)
raise typer.Exit(1)
data = resp.json()
metrics = data if isinstance(data, list) else data.get("metrics", [])
if as_json:
typer.echo(json_lib.dumps(metrics, indent=2, default=str))
return
if not metrics:
typer.echo("No metrics found.")
return
# Group by category for display
by_category: dict = {}
for m in metrics:
cat = m.get("category", "uncategorized")
by_category.setdefault(cat, []).append(m)
for cat, items in sorted(by_category.items()):
typer.echo(f"\n[{cat}]")
for m in items:
name = m.get("name", m.get("id", "?"))
display = m.get("display_name", name)
unit = m.get("unit", "")
unit_str = f" ({unit})" if unit else ""
typer.echo(f" {name:30s} {display}{unit_str}")
def _show_one_metric(metric_id: str, as_json: bool) -> None:
"""Show details for a single metric."""
resp = api_get(f"/api/metrics/{metric_id}")
if resp.status_code == 404:
typer.echo(f"Metric not found: {metric_id}", err=True)
raise typer.Exit(1)
if resp.status_code != 200:
typer.echo(f"Failed: {resp.json().get('detail', resp.text)}", err=True)
raise typer.Exit(1)
m = resp.json()
if as_json:
typer.echo(json_lib.dumps(m, indent=2, default=str))
return
typer.echo(f"ID: {m.get('id', metric_id)}")
typer.echo(f"Name: {m.get('name', '')}")
typer.echo(f"Display Name: {m.get('display_name', '')}")
typer.echo(f"Category: {m.get('category', '')}")
typer.echo(f"Type: {m.get('type', '')}")
if m.get("unit"):
typer.echo(f"Unit: {m['unit']}")
if m.get("grain"):
typer.echo(f"Grain: {m['grain']}")
if m.get("table_name"):
typer.echo(f"Table: {m['table_name']}")
if m.get("description"):
typer.echo(f"Description: {m['description']}")
if m.get("sql"):
typer.echo(f"SQL:\n {m['sql']}")
if m.get("synonyms"):
typer.echo(f"Synonyms: {', '.join(m['synonyms'])}")