conduct-cli 0.4.45__tar.gz → 0.4.46__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.4.45
3
+ Version: 0.4.46
4
4
  Summary: CLI for Conduct AI — install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "conduct-cli"
7
- version = "0.4.45"
7
+ version = "0.4.46"
8
8
  description = "CLI for Conduct AI — install agents, manage projects, run tests"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1756,9 +1756,61 @@ def _render_table(rows: list[dict]) -> str:
1756
1756
  return "\n".join(lines)
1757
1757
 
1758
1758
 
1759
+ def _fetch_runs(cfg: dict) -> list[dict]:
1760
+ """Fetch recent runs from GET /runs."""
1761
+ server = cfg.get("server", "").rstrip("/")
1762
+ api_key = cfg.get("api_key", "")
1763
+ ws_id = cfg.get("workspace", "")
1764
+ if not all([server, api_key, ws_id]):
1765
+ return []
1766
+ try:
1767
+ hdrs = {"Content-Type": "application/json", "X-Api-Key": api_key}
1768
+ data = api.req("GET", f"{server}/runs?limit=15&workspace_id={ws_id}", hdrs)
1769
+ runs = data if isinstance(data, list) else data.get("runs", data.get("items", []))
1770
+ return runs[:15]
1771
+ except Exception:
1772
+ return []
1773
+
1774
+
1775
+ def _fetch_guard_activity(cfg: dict, guard_cfg: dict) -> list[dict]:
1776
+ """Fetch recent Guard events grouped by developer."""
1777
+ server = cfg.get("server", "").rstrip("/")
1778
+ api_key = cfg.get("api_key", "")
1779
+ ws_id = cfg.get("workspace", "")
1780
+ if not all([server, api_key, ws_id]):
1781
+ return []
1782
+ try:
1783
+ hdrs = {"Content-Type": "application/json", "X-Api-Key": api_key}
1784
+ data = api.req("GET", f"{server}/guard/events?workspace_id={ws_id}&limit=200", hdrs)
1785
+ events = data if isinstance(data, list) else data.get("events", [])
1786
+ # Group by user_email
1787
+ devs: dict[str, dict] = {}
1788
+ for e in events:
1789
+ email = e.get("user_email") or e.get("email") or "unknown"
1790
+ blocked = e.get("decision") == "block"
1791
+ tokens = e.get("tokens_used") or e.get("tokens", 0) or 0
1792
+ if email not in devs:
1793
+ devs[email] = {"email": email, "calls": 0, "blocked": 0, "tokens": 0}
1794
+ devs[email]["calls"] += 1
1795
+ devs[email]["blocked"] += int(blocked)
1796
+ devs[email]["tokens"] += tokens
1797
+ return sorted(devs.values(), key=lambda d: d["calls"], reverse=True)
1798
+ except Exception:
1799
+ return []
1800
+
1801
+
1759
1802
  def _render_tui(rows: list[dict]) -> None:
1760
1803
  """Full-screen TUI with live refresh. Uses rich.live if available, else ANSI loop."""
1761
1804
 
1805
+ cfg = _load_config()
1806
+ guard_cfg = {}
1807
+ _gp = Path.home() / ".conductguard" / "config.json"
1808
+ if _gp.exists():
1809
+ try:
1810
+ guard_cfg = json.loads(_gp.read_text())
1811
+ except Exception:
1812
+ pass
1813
+
1762
1814
  def _build_rich_display(rows):
1763
1815
  from rich.table import Table
1764
1816
  from rich.panel import Panel
@@ -1812,7 +1864,7 @@ def _render_tui(rows: list[dict]) -> None:
1812
1864
  # Summary header
1813
1865
  summary = f"[bold]{active}[/] active · [bold]{guard_on}[/] with Guard · [dim]refreshing every 5s — Ctrl+C to quit[/]"
1814
1866
 
1815
- panels = [Panel(tbl, title="[bold blue]conduct sessions[/]", subtitle=summary, expand=True)]
1867
+ panels = [Panel(tbl, title="[bold blue] My Sessions[/]", subtitle=summary, expand=True)]
1816
1868
 
1817
1869
  # Context pressure panel
1818
1870
  if high_ctx:
@@ -1823,6 +1875,74 @@ def _render_tui(rows: list[dict]) -> None:
1823
1875
  )
1824
1876
  panels.append(Panel(warnings, title="[yellow bold]⚠ Context pressure[/]", expand=True))
1825
1877
 
1878
+ # Agent Runs panel
1879
+ run_data = _fetch_runs(cfg)
1880
+ runs_tbl = Table(box=box.SIMPLE, expand=True, show_header=True, header_style="bold white")
1881
+ runs_tbl.add_column("Agent", min_width=22)
1882
+ runs_tbl.add_column("Status", width=12)
1883
+ runs_tbl.add_column("Duration", width=10, justify="right")
1884
+ runs_tbl.add_column("Project", min_width=14)
1885
+ runs_tbl.add_column("Triggered", width=12)
1886
+
1887
+ if run_data:
1888
+ for r in run_data:
1889
+ status = r.get("status", "")
1890
+ scolor = {"running": "green", "succeeded": "dim green", "failed": "red",
1891
+ "paused": "yellow", "pending": "cyan", "cancelled": "dim"}.get(status, "white")
1892
+ sicon = {"running": "●", "succeeded": "✓", "failed": "✗",
1893
+ "paused": "⏸", "pending": "○", "cancelled": "—"}.get(status, "?")
1894
+ # Duration
1895
+ created = r.get("created_at") or r.get("started_at") or 0
1896
+ ended = r.get("completed_at") or r.get("finished_at") or 0
1897
+ if created and ended:
1898
+ secs = int(ended - created)
1899
+ dur = f"{secs//60}:{secs%60:02d}"
1900
+ elif created and status == "running":
1901
+ import datetime as _dt
1902
+ secs = int(time.time() - (created if created > 1e10 else created))
1903
+ dur = f"{secs//60}:{secs%60:02d}"
1904
+ else:
1905
+ dur = "—"
1906
+ agent_name = r.get("workflow_name") or r.get("name") or "—"
1907
+ project_name = r.get("project_name") or r.get("project") or "—"
1908
+ trigger = r.get("trigger_type") or r.get("triggered_by") or "manual"
1909
+ runs_tbl.add_row(
1910
+ agent_name[:28],
1911
+ Text(f"{sicon} {status}", style=scolor),
1912
+ dur,
1913
+ project_name[:18],
1914
+ trigger[:12],
1915
+ )
1916
+ else:
1917
+ runs_tbl.add_row("[dim]No runs yet or not connected[/]", "", "", "", "")
1918
+
1919
+ panels.append(Panel(runs_tbl, title="[bold green]▶ Agent Runs[/]", expand=True))
1920
+
1921
+ # Team Activity panel (Guard)
1922
+ team_data = _fetch_guard_activity(cfg, guard_cfg)
1923
+ team_tbl = Table(box=box.SIMPLE, expand=True, show_header=True, header_style="bold white")
1924
+ team_tbl.add_column("Developer", min_width=28)
1925
+ team_tbl.add_column("Calls", width=8, justify="right")
1926
+ team_tbl.add_column("Blocked", width=8, justify="right")
1927
+ team_tbl.add_column("Tokens", width=10, justify="right")
1928
+ team_tbl.add_column("Guard", width=7, justify="center")
1929
+
1930
+ if team_data:
1931
+ for d in team_data:
1932
+ blocked_text = Text(str(d["blocked"]), style="red bold" if d["blocked"] else "dim")
1933
+ guard_text = Text("✓", style="green")
1934
+ team_tbl.add_row(
1935
+ d["email"][:32],
1936
+ str(d["calls"]),
1937
+ blocked_text,
1938
+ _fmt_tokens(d["tokens"]),
1939
+ guard_text,
1940
+ )
1941
+ else:
1942
+ team_tbl.add_row("[dim]No team activity or not connected[/]", "", "", "", "")
1943
+
1944
+ panels.append(Panel(team_tbl, title="[bold magenta]👥 Team Activity (Guard)[/]", expand=True))
1945
+
1826
1946
  from rich.console import Group
1827
1947
  return Group(*panels)
1828
1948
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.4.45
3
+ Version: 0.4.46
4
4
  Summary: CLI for Conduct AI — install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
File without changes
File without changes
File without changes