Prefer xiaohe ensure-agent hints and support non-interactive yes installs.

Install scripts and product CLI can install the default backend without a picker,
and skip messages point at xiaohe when it is on PATH.
This commit is contained in:
Zhengshou Lai
2026-08-10 07:46:55 +08:00
parent 5efc8380fb
commit 18154020a3
2 changed files with 33 additions and 13 deletions
+14 -3
View File
@@ -25,12 +25,14 @@ def installable_backends() -> list[str]:
def run_ensure_agents( def run_ensure_agents(
*, *,
prefer: list[str] | None = None, prefer: list[str] | None = None,
yes: bool = False,
) -> list[str]: ) -> list[str]:
"""Show status, let user pick missing CLIs, install them. """Show status, let user pick missing CLIs, install them.
``prefer``: when interactive, Enter defaults to these names (intersected ``prefer``: when interactive, Enter defaults to these names (intersected
with missing) instead of all missing — used by xiaohe for its default with missing) instead of all missing — used by xiaohe for its default
agent. Returns backends that remain missing afterwards. agent. ``yes``: skip the picker and install prefer (or all missing).
Returns backends that remain missing afterwards.
""" """
targets = installable_backends() targets = installable_backends()
if not targets: if not targets:
@@ -66,7 +68,8 @@ def run_ensure_agents(
console.print("[green]All installable agent CLIs ready.[/green]") console.print("[green]All installable agent CLIs ready.[/green]")
return [] return []
if not _is_interactive(): interactive = _is_interactive() and not yes
if not interactive:
to_install = ( to_install = (
[n for n in (prefer or []) if n in missing] or list(missing) [n for n in (prefer or []) if n in missing] or list(missing)
) )
@@ -140,12 +143,20 @@ def run_ensure_agents(
still = [n for n in missing if n not in to_install or n in failed] still = [n for n in missing if n not in to_install or n in failed]
if failed: if failed:
console.print() console.print()
hint = (
"xiaohe ensure-agent"
if __import__("shutil").which("xiaohe")
else "myagents ensure-agent"
)
console.print( console.print(
"[yellow]Some installs failed.[/yellow] Install Node.js if needed, " "[yellow]Some installs failed.[/yellow] Install Node.js if needed, "
"then re-run [cyan]myagents ensure-agent[/cyan]." f"then: [cyan]{hint}[/cyan]."
) )
elif not still: elif not still:
console.print("[green]Selected agent CLIs ready.[/green]") console.print("[green]Selected agent CLIs ready.[/green]")
if __import__("shutil").which("xiaohe"):
console.print(" Next: [cyan]xiaohe[/cyan]")
else:
console.print( console.print(
" Launch: [cyan]myclaude[/cyan] / [cyan]mycodex[/cyan] / …" " Launch: [cyan]myclaude[/cyan] / [cyan]mycodex[/cyan] / …"
) )
+16 -7
View File
@@ -393,6 +393,15 @@ def _exec_tmux(backend: str, chat_cwd: Path, cmd: list[str]) -> None:
raise SystemExit(proc.returncode) raise SystemExit(proc.returncode)
def _ensure_agent_hint() -> str:
"""Prefer product-facing xiaohe when installed; else myagents."""
return (
"xiaohe ensure-agent"
if shutil.which("xiaohe")
else "myagents ensure-agent"
)
def _run_install(config: dict) -> str | None: def _run_install(config: dict) -> str | None:
"""Run ``install_cmd`` and return the resolved binary path, or None.""" """Run ``install_cmd`` and return the resolved binary path, or None."""
install_cmd = config.get("install_cmd") install_cmd = config.get("install_cmd")
@@ -400,12 +409,12 @@ def _run_install(config: dict) -> str | None:
return None return None
installer = shutil.which(install_cmd[0]) installer = shutil.which(install_cmd[0])
if not installer: if not installer:
hint = _ensure_agent_hint()
stderr_console.print( stderr_console.print(
f"[red]{install_cmd[0]} not found[/red] — cannot auto-install " f"[red]{install_cmd[0]} not found[/red] — cannot auto-install "
f"{config['binary']}.\n" f"{config['binary']}.\n"
" Install Node.js (e.g. [cyan]brew install node[/cyan]), then run " " Run: [cyan]brew install node[/cyan] (or install Node.js), then "
"[cyan]myagents ensure-agent[/cyan] or launch " f"[cyan]{hint}[/cyan]."
f"[cyan]my{config.get('binary', 'claude')}[/cyan]."
) )
return None return None
stderr_console.print( stderr_console.print(
@@ -417,10 +426,10 @@ def _run_install(config: dict) -> str | None:
) )
proc = subprocess.run([installer, *install_cmd[1:]], check=False) proc = subprocess.run([installer, *install_cmd[1:]], check=False)
if proc.returncode != 0: if proc.returncode != 0:
hint = _ensure_agent_hint()
stderr_console.print( stderr_console.print(
f"[red]Install failed (exit {proc.returncode}).[/red]\n" f"[red]Install failed (exit {proc.returncode}).[/red]\n"
" Fix the error above, then run " f" Run: [cyan]{hint}[/cyan]."
"[cyan]myagents ensure-agent[/cyan]."
) )
return None return None
# Refresh PATH for this process; offer to persist npm bin into shell rc. # Refresh PATH for this process; offer to persist npm bin into shell rc.
@@ -460,9 +469,9 @@ def _offer_install(config: dict, *, yes: bool | None = None) -> str | None:
except (click.Abort, EOFError): except (click.Abort, EOFError):
return None return None
if answer.strip().lower() not in ("y", "yes", ""): if answer.strip().lower() not in ("y", "yes", ""):
hint = _ensure_agent_hint()
stderr_console.print( stderr_console.print(
"[dim]Skipped.[/dim] Later: [cyan]myagents ensure-agent[/cyan] " f"[dim]Skipped. Later:[/dim] [cyan]{hint}[/cyan]"
"or launch again (e.g. [cyan]myclaude[/cyan])."
) )
return None return None
return _run_install(config) return _run_install(config)