conduct-cli 0.4.44__tar.gz → 0.4.45__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.
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/PKG-INFO +2 -1
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/pyproject.toml +2 -2
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli/main.py +128 -70
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli.egg-info/PKG-INFO +2 -1
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli.egg-info/requires.txt +1 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/README.md +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/setup.cfg +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/setup.py +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli/guard.py +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.4.44 → conduct_cli-0.4.45}/tests/test_switch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conduct-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.45
|
|
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
|
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
|
21
21
|
Requires-Python: >=3.9
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
24
25
|
|
|
25
26
|
# conduct-cli
|
|
26
27
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "conduct-cli"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.45"
|
|
8
8
|
description = "CLI for Conduct AI — install agents, manage projects, run tests"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python :: 3.12",
|
|
24
24
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
25
25
|
]
|
|
26
|
-
dependencies = ["pyyaml>=6.0"]
|
|
26
|
+
dependencies = ["pyyaml>=6.0", "rich>=13.0"]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
29
29
|
Homepage = "https://conductai.ai"
|
|
@@ -1757,79 +1757,137 @@ def _render_table(rows: list[dict]) -> str:
|
|
|
1757
1757
|
|
|
1758
1758
|
|
|
1759
1759
|
def _render_tui(rows: list[dict]) -> None:
|
|
1760
|
-
"""Full-screen TUI with live refresh
|
|
1760
|
+
"""Full-screen TUI with live refresh. Uses rich.live if available, else ANSI loop."""
|
|
1761
|
+
|
|
1762
|
+
def _build_rich_display(rows):
|
|
1763
|
+
from rich.table import Table
|
|
1764
|
+
from rich.panel import Panel
|
|
1765
|
+
from rich.columns import Columns
|
|
1766
|
+
from rich import box
|
|
1767
|
+
from rich.text import Text
|
|
1768
|
+
|
|
1769
|
+
active = sum(1 for r in rows if r["alive"])
|
|
1770
|
+
guard_on = sum(1 for r in rows if r["guard"])
|
|
1771
|
+
high_ctx = [r for r in rows if r["ctx_pct"] >= 60 and r["alive"]]
|
|
1772
|
+
|
|
1773
|
+
# Main sessions table
|
|
1774
|
+
tbl = Table(box=box.ROUNDED, expand=True, show_header=True, header_style="bold white")
|
|
1775
|
+
tbl.add_column("AI", width=4, style="bold")
|
|
1776
|
+
tbl.add_column("Project", min_width=14)
|
|
1777
|
+
tbl.add_column("Session", width=10, style="dim")
|
|
1778
|
+
tbl.add_column("Model", min_width=14)
|
|
1779
|
+
tbl.add_column("CTX", width=16)
|
|
1780
|
+
tbl.add_column("Tokens", width=10, justify="right")
|
|
1781
|
+
tbl.add_column("Turns", width=6, justify="right")
|
|
1782
|
+
tbl.add_column("Guard", width=6, justify="center")
|
|
1783
|
+
tbl.add_column("Status", width=8)
|
|
1784
|
+
|
|
1785
|
+
for r in rows:
|
|
1786
|
+
ai_text = Text(r.get("ai", "CC"), style="bold cyan" if r.get("ai") == "CD" else "bold blue")
|
|
1787
|
+
status_text = Text("active", style="green") if r["alive"] else Text("idle", style="dim")
|
|
1788
|
+
guard_text = Text("✓", style="green") if r["guard"] else Text("—", style="dim")
|
|
1789
|
+
model_short = r["model"].replace("claude-", "").replace("-20", " 20") if r["model"] not in ("—", "") else "—"
|
|
1790
|
+
|
|
1791
|
+
pct = r["ctx_pct"]
|
|
1792
|
+
if pct:
|
|
1793
|
+
filled = round(pct / 100 * 10)
|
|
1794
|
+
bar = "█" * filled + "░" * (10 - filled)
|
|
1795
|
+
color = "red" if pct >= 80 else "yellow" if pct >= 60 else "green"
|
|
1796
|
+
ctx_text = Text(f"{bar} {pct}%", style=color)
|
|
1797
|
+
else:
|
|
1798
|
+
ctx_text = Text("—", style="dim")
|
|
1799
|
+
|
|
1800
|
+
tbl.add_row(
|
|
1801
|
+
ai_text,
|
|
1802
|
+
r["project"],
|
|
1803
|
+
r["session_id"],
|
|
1804
|
+
model_short,
|
|
1805
|
+
ctx_text,
|
|
1806
|
+
_fmt_tokens(r["ctx_tokens"]) if r["ctx_tokens"] else "—",
|
|
1807
|
+
str(r["turns"]),
|
|
1808
|
+
guard_text,
|
|
1809
|
+
status_text,
|
|
1810
|
+
)
|
|
1811
|
+
|
|
1812
|
+
# Summary header
|
|
1813
|
+
summary = f"[bold]{active}[/] active · [bold]{guard_on}[/] with Guard · [dim]refreshing every 5s — Ctrl+C to quit[/]"
|
|
1814
|
+
|
|
1815
|
+
panels = [Panel(tbl, title="[bold blue]conduct sessions[/]", subtitle=summary, expand=True)]
|
|
1816
|
+
|
|
1817
|
+
# Context pressure panel
|
|
1818
|
+
if high_ctx:
|
|
1819
|
+
warnings = "\n".join(
|
|
1820
|
+
f"[{'red' if r['ctx_pct'] >= 80 else 'yellow'}]{r['project']}[/] "
|
|
1821
|
+
f"[dim]{r['session_id']}[/] {r['ctx_pct']}% — consider /compact"
|
|
1822
|
+
for r in high_ctx
|
|
1823
|
+
)
|
|
1824
|
+
panels.append(Panel(warnings, title="[yellow bold]⚠ Context pressure[/]", expand=True))
|
|
1825
|
+
|
|
1826
|
+
from rich.console import Group
|
|
1827
|
+
return Group(*panels)
|
|
1828
|
+
|
|
1829
|
+
# Try rich.live first (works without raw TTY)
|
|
1761
1830
|
try:
|
|
1762
|
-
import
|
|
1763
|
-
import
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
def _render_frame(rows):
|
|
1776
|
-
cols, _ = shutil.get_terminal_size((120, 40))
|
|
1777
|
-
out = []
|
|
1778
|
-
|
|
1779
|
-
# Header bar
|
|
1780
|
-
title = " conduct sessions (TUI — q or Ctrl+C to quit) "
|
|
1781
|
-
pad = max(0, cols - len(title))
|
|
1782
|
-
out.append(f"{BOLD}\033[44m{title}{' ' * pad}\033[0m")
|
|
1783
|
-
out.append("")
|
|
1784
|
-
|
|
1785
|
-
# Summary line
|
|
1786
|
-
active = sum(1 for r in rows if r["alive"])
|
|
1787
|
-
guard_on = sum(1 for r in rows if r["guard"])
|
|
1788
|
-
out.append(f" {BOLD}{active}{RESET} active session(s) · {BOLD}{guard_on}{RESET} with Guard · refreshing every 5s")
|
|
1789
|
-
out.append("")
|
|
1790
|
-
|
|
1791
|
-
# Table
|
|
1792
|
-
out.append(_render_table(rows))
|
|
1793
|
-
|
|
1794
|
-
# Context pressure panel — sessions above 60%
|
|
1795
|
-
high = [r for r in rows if r["ctx_pct"] >= 60 and r["alive"]]
|
|
1796
|
-
if high:
|
|
1797
|
-
out.append(f" {YELLOW}{BOLD}⚠ Context pressure{RESET}")
|
|
1798
|
-
for r in high:
|
|
1799
|
-
color = RED if r["ctx_pct"] >= 80 else YELLOW
|
|
1800
|
-
out.append(f" {color}{r['project']}{RESET} ({r['session_id']}) {r['ctx_pct']}% — consider /compact")
|
|
1801
|
-
out.append("")
|
|
1802
|
-
|
|
1803
|
-
sys.stdout.write("\n".join(out))
|
|
1804
|
-
sys.stdout.flush()
|
|
1805
|
-
|
|
1806
|
-
# Use raw terminal input for 'q' detection (non-blocking)
|
|
1807
|
-
import select, tty, termios
|
|
1808
|
-
old = termios.tcgetattr(sys.stdin)
|
|
1809
|
-
try:
|
|
1810
|
-
tty.setcbreak(sys.stdin.fileno())
|
|
1811
|
-
while not stop:
|
|
1812
|
-
_clear()
|
|
1813
|
-
rows = _load_sessions()
|
|
1814
|
-
_render_frame(rows)
|
|
1815
|
-
# Wait up to 5s, exit on 'q'
|
|
1816
|
-
for _ in range(50):
|
|
1817
|
-
if select.select([sys.stdin], [], [], 0.1)[0]:
|
|
1818
|
-
ch = sys.stdin.read(1)
|
|
1819
|
-
if ch in ("q", "Q"):
|
|
1820
|
-
stop = True
|
|
1821
|
-
break
|
|
1822
|
-
if stop:
|
|
1823
|
-
break
|
|
1824
|
-
finally:
|
|
1825
|
-
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old)
|
|
1826
|
-
_clear()
|
|
1827
|
-
print("conduct sessions exited.")
|
|
1831
|
+
from rich.live import Live
|
|
1832
|
+
from rich.console import Console
|
|
1833
|
+
console = Console()
|
|
1834
|
+
with Live(console=console, refresh_per_second=0.2, screen=True) as live:
|
|
1835
|
+
while True:
|
|
1836
|
+
live.update(_build_rich_display(_load_sessions()))
|
|
1837
|
+
time.sleep(5)
|
|
1838
|
+
return
|
|
1839
|
+
except KeyboardInterrupt:
|
|
1840
|
+
return
|
|
1841
|
+
except ImportError:
|
|
1842
|
+
pass # fall through to ANSI loop
|
|
1828
1843
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
print(f"{YELLOW}TUI
|
|
1844
|
+
# ANSI fallback — requires real TTY
|
|
1845
|
+
if not sys.stdin.isatty():
|
|
1846
|
+
print(f"{YELLOW}TUI requires a real terminal. Run directly in your shell, not via a subprocess.{RESET}")
|
|
1832
1847
|
print(_render_table(rows))
|
|
1848
|
+
return
|
|
1849
|
+
|
|
1850
|
+
import shutil, signal, select, tty, termios
|
|
1851
|
+
stop = False
|
|
1852
|
+
def _sig(s, f): nonlocal stop; stop = True
|
|
1853
|
+
signal.signal(signal.SIGINT, _sig)
|
|
1854
|
+
|
|
1855
|
+
def _frame(rows):
|
|
1856
|
+
cols, _ = shutil.get_terminal_size((120, 40))
|
|
1857
|
+
title = " conduct sessions (Ctrl+C or q to quit) "
|
|
1858
|
+
pad = max(0, cols - len(title))
|
|
1859
|
+
active = sum(1 for r in rows if r["alive"])
|
|
1860
|
+
guard_on = sum(1 for r in rows if r["guard"])
|
|
1861
|
+
out = [
|
|
1862
|
+
f"{BOLD}\033[44m{title}{' ' * pad}\033[0m",
|
|
1863
|
+
f"\n {BOLD}{active}{RESET} active · {BOLD}{guard_on}{RESET} with Guard · refreshing every 5s\n",
|
|
1864
|
+
_render_table(rows),
|
|
1865
|
+
]
|
|
1866
|
+
high = [r for r in rows if r["ctx_pct"] >= 60 and r["alive"]]
|
|
1867
|
+
if high:
|
|
1868
|
+
out.append(f" {YELLOW}{BOLD}⚠ Context pressure{RESET}")
|
|
1869
|
+
for r in high:
|
|
1870
|
+
c = RED if r["ctx_pct"] >= 80 else YELLOW
|
|
1871
|
+
out.append(f" {c}{r['project']}{RESET} ({r['session_id']}) {r['ctx_pct']}% — consider /compact")
|
|
1872
|
+
sys.stdout.write("\033[2J\033[H" + "\n".join(out))
|
|
1873
|
+
sys.stdout.flush()
|
|
1874
|
+
|
|
1875
|
+
old = termios.tcgetattr(sys.stdin)
|
|
1876
|
+
try:
|
|
1877
|
+
tty.setcbreak(sys.stdin.fileno())
|
|
1878
|
+
while not stop:
|
|
1879
|
+
_frame(_load_sessions())
|
|
1880
|
+
for _ in range(50):
|
|
1881
|
+
if select.select([sys.stdin], [], [], 0.1)[0]:
|
|
1882
|
+
if sys.stdin.read(1) in ("q", "Q"):
|
|
1883
|
+
stop = True
|
|
1884
|
+
break
|
|
1885
|
+
if stop:
|
|
1886
|
+
break
|
|
1887
|
+
finally:
|
|
1888
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old)
|
|
1889
|
+
sys.stdout.write("\033[2J\033[H")
|
|
1890
|
+
print("conduct sessions exited.")
|
|
1833
1891
|
|
|
1834
1892
|
|
|
1835
1893
|
def cmd_sessions(args):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: conduct-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.45
|
|
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
|
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
|
21
21
|
Requires-Python: >=3.9
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
24
25
|
|
|
25
26
|
# conduct-cli
|
|
26
27
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|