refactor(backend): backend 改名 provider,拆出 info 子命令
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""``xiaohe info`` — show current version, agent, and provider."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
def _package_version() -> str:
|
||||
try:
|
||||
return version("myagents")
|
||||
except PackageNotFoundError:
|
||||
return "0.0.0"
|
||||
|
||||
|
||||
def _describe_runtime() -> str:
|
||||
from myagents.commands.upgrade import _current_version
|
||||
|
||||
cur = _current_version()
|
||||
return cur if cur else "development"
|
||||
|
||||
|
||||
@click.command("info")
|
||||
def info_cmd() -> None:
|
||||
"""Show xiaohe status: version, agent, and provider."""
|
||||
from myagents.commands.switch import _resolve_agent
|
||||
from myagents.claude_settings import describe_active_backend
|
||||
from myagents.commands.provider import provider_list_table
|
||||
|
||||
table = Table(show_header=False, box=None, padding=(0, 1))
|
||||
table.add_column("key", style="dim")
|
||||
table.add_column("value")
|
||||
|
||||
table.add_row("Version", f"myagents {_package_version()} (runtime: {_describe_runtime()})")
|
||||
|
||||
default_agent = _resolve_agent()
|
||||
table.add_row("Agent", f"{default_agent} (xiaohe → my{default_agent})")
|
||||
|
||||
table.add_row("Provider", describe_active_backend())
|
||||
|
||||
console.print(table)
|
||||
console.print()
|
||||
provider_list_table()
|
||||
console.print()
|
||||
console.print("[dim]Switch with:[/dim]")
|
||||
console.print(" xiaohe switch version xiaohe switch agent xiaohe switch provider")
|
||||
@@ -1,4 +1,4 @@
|
||||
"""``xiaohe backend`` — switch Claude Code's model backend."""
|
||||
"""``xiaohe switch provider`` — switch Claude Code's LLM provider."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -30,7 +30,6 @@ def _secure_prompt_key(provider: Provider) -> str:
|
||||
try:
|
||||
return click.prompt(prompt_text, hide_input=True, err=True)
|
||||
except click.UsageError:
|
||||
# click.prompt raises when stdin is not a TTY. Fall back to /dev/tty.
|
||||
try:
|
||||
with open("/dev/tty", encoding="utf-8") as tty: # noqa: PTH123
|
||||
return tty.readline().rstrip("\n")
|
||||
@@ -40,16 +39,10 @@ def _secure_prompt_key(provider: Provider) -> str:
|
||||
) from exc
|
||||
|
||||
|
||||
@click.group("backend")
|
||||
def backend_cmd() -> None:
|
||||
"""Switch Claude Code's model backend (DeepSeek, Kimi, Kimi Code, Claude)."""
|
||||
|
||||
|
||||
@backend_cmd.command("list")
|
||||
def backend_list() -> None:
|
||||
"""List built-in backends and the active one."""
|
||||
def provider_list_table() -> None:
|
||||
"""Print the provider list table (reusable from info command)."""
|
||||
active = get_active_provider()
|
||||
console.print("[bold]Built-in backends:[/bold]")
|
||||
console.print("[bold]Available providers:[/bold]")
|
||||
for provider in list_providers():
|
||||
marker = ""
|
||||
if active and active.id == provider.id:
|
||||
@@ -65,32 +58,43 @@ def backend_list() -> None:
|
||||
)
|
||||
|
||||
|
||||
@backend_cmd.command("current")
|
||||
def backend_current() -> None:
|
||||
"""Show the active backend for this workspace."""
|
||||
console.print(f"[bold]Current backend:[/bold] {describe_active_backend()}")
|
||||
@click.group("provider")
|
||||
def provider_cmd() -> None:
|
||||
"""Switch Claude Code's LLM provider (DeepSeek, Kimi, Kimi Code, Claude)."""
|
||||
|
||||
|
||||
@backend_cmd.command("use")
|
||||
@provider_cmd.command("list")
|
||||
def provider_list() -> None:
|
||||
"""List available providers and the active one."""
|
||||
provider_list_table()
|
||||
|
||||
|
||||
@provider_cmd.command("current")
|
||||
def provider_current() -> None:
|
||||
"""Show the active provider."""
|
||||
console.print(f"[bold]Active provider:[/bold] {describe_active_backend()}")
|
||||
|
||||
|
||||
@provider_cmd.command("use")
|
||||
@click.argument("provider_id")
|
||||
@click.option("--key", help="API key (scripting only; appears in shell history)")
|
||||
@click.option("--model", help="Override the default model for this provider")
|
||||
@click.option("--model", help="Override the default model")
|
||||
@click.option("--base-url", help="Override the provider base URL")
|
||||
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
||||
def backend_use(
|
||||
def provider_use(
|
||||
provider_id: str,
|
||||
key: str | None,
|
||||
model: str | None,
|
||||
base_url: str | None,
|
||||
yes: bool,
|
||||
) -> None:
|
||||
"""Switch Claude Code to the given backend."""
|
||||
"""Switch to the given provider."""
|
||||
try:
|
||||
provider = get_provider(provider_id)
|
||||
except KeyError:
|
||||
known = ", ".join(p.id for p in list_providers())
|
||||
raise click.ClickException(
|
||||
f"Unknown backend '{provider_id}'. Choose from: {known}"
|
||||
f"Unknown provider '{provider_id}'. Choose from: {known}"
|
||||
)
|
||||
|
||||
if is_third_party(provider):
|
||||
@@ -98,7 +102,7 @@ def backend_use(
|
||||
if not resolved_key:
|
||||
resolved_key = _secure_prompt_key(provider)
|
||||
if not resolved_key:
|
||||
raise click.ClickException("API key is required for this backend.")
|
||||
raise click.ClickException("API key is required for this provider.")
|
||||
else:
|
||||
resolved_key = None
|
||||
|
||||
@@ -109,7 +113,7 @@ def backend_use(
|
||||
summary += f" (base-url: {base_url})"
|
||||
|
||||
if not yes and not click.confirm(
|
||||
f"Set backend to {summary} for this workspace?",
|
||||
f"Switch LLM provider to {summary}?",
|
||||
default=True,
|
||||
err=True,
|
||||
):
|
||||
@@ -125,37 +129,37 @@ def backend_use(
|
||||
save_settings(settings)
|
||||
set_setting("backend_provider", provider.id)
|
||||
|
||||
console.print(f"[bold green]Backend switched to {summary}.[/bold green]")
|
||||
console.print(f"[bold green]Switched to {summary}.[/bold green]")
|
||||
if provider.id == "claude":
|
||||
console.print(
|
||||
"[dim]Cleared third-party backend env vars from ~/.claude/settings.json.[/dim]"
|
||||
"[dim]Cleared third-party provider env vars from ~/.claude/settings.json.[/dim]"
|
||||
)
|
||||
|
||||
|
||||
@backend_cmd.command("reset")
|
||||
@provider_cmd.command("reset")
|
||||
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
||||
def backend_reset(yes: bool) -> None:
|
||||
"""Reset to the official Claude backend."""
|
||||
def provider_reset(yes: bool) -> None:
|
||||
"""Reset to the official Claude provider."""
|
||||
ctx = click.get_current_context()
|
||||
ctx.invoke(backend_use, provider_id="claude", yes=yes)
|
||||
ctx.invoke(provider_use, provider_id="claude", yes=yes)
|
||||
|
||||
|
||||
@backend_cmd.group("key")
|
||||
def backend_key() -> None:
|
||||
@provider_cmd.group("key")
|
||||
def provider_key() -> None:
|
||||
"""Manage stored API keys."""
|
||||
|
||||
|
||||
@backend_key.command("set")
|
||||
@provider_key.command("set")
|
||||
@click.argument("provider_id")
|
||||
@click.option("--key", help="API key (scripting only; appears in shell history)")
|
||||
def backend_key_set(provider_id: str, key: str | None) -> None:
|
||||
"""Store an API key for a backend without switching to it."""
|
||||
def provider_key_set(provider_id: str, key: str | None) -> None:
|
||||
"""Store an API key without switching."""
|
||||
try:
|
||||
provider = get_provider(provider_id)
|
||||
except KeyError:
|
||||
known = ", ".join(p.id for p in list_providers())
|
||||
raise click.ClickException(
|
||||
f"Unknown backend '{provider_id}'. Choose from: {known}"
|
||||
f"Unknown provider '{provider_id}'. Choose from: {known}"
|
||||
)
|
||||
|
||||
if provider.id == "claude":
|
||||
@@ -175,22 +179,17 @@ def backend_key_set(provider_id: str, key: str | None) -> None:
|
||||
)
|
||||
|
||||
|
||||
@backend_key.command("rm")
|
||||
@provider_key.command("rm")
|
||||
@click.argument("provider_id")
|
||||
@click.option(
|
||||
"--yes",
|
||||
"-y",
|
||||
is_flag=True,
|
||||
help="Skip the confirmation prompt",
|
||||
)
|
||||
def backend_key_rm(provider_id: str, yes: bool) -> None:
|
||||
"""Remove the stored API key for a backend."""
|
||||
@click.option("--yes", "-y", is_flag=True, help="Skip the confirmation prompt")
|
||||
def provider_key_rm(provider_id: str, yes: bool) -> None:
|
||||
"""Remove the stored API key for a provider."""
|
||||
try:
|
||||
provider = get_provider(provider_id)
|
||||
except KeyError:
|
||||
known = ", ".join(p.id for p in list_providers())
|
||||
raise click.ClickException(
|
||||
f"Unknown backend '{provider_id}'. Choose from: {known}"
|
||||
f"Unknown provider '{provider_id}'. Choose from: {known}"
|
||||
)
|
||||
|
||||
if not yes and not click.confirm(
|
||||
@@ -208,4 +207,4 @@ def backend_key_rm(provider_id: str, yes: bool) -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
backend_cmd()
|
||||
provider_cmd()
|
||||
@@ -1,23 +1,29 @@
|
||||
"""``xiaohe switch`` — move ``current`` to another installed runtime version.
|
||||
"""``xiaohe switch`` — switch version, agent, or provider.
|
||||
|
||||
Rollback/roll-forward counterpart to ``xiaohe upgrade``: repoints the
|
||||
``current`` symlink, reinstalls the CLI tools from that runtime, and re-syncs
|
||||
the workspace. Run without an argument to list installed versions and pick
|
||||
one interactively.
|
||||
Subcommands:
|
||||
version — switch the active runtime version (rollback/roll-forward)
|
||||
agent — switch the default agent CLI (claude / kimi / codex / hermes)
|
||||
provider — switch Claude Code's LLM provider (DeepSeek / Kimi / Kimi Code / Claude)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
|
||||
from myagents.commands.sync_workspace import _print_report, sync_workspace
|
||||
from myagents.commands.upgrade import _current_version, _install_tools, _runtime_root
|
||||
from myagents.entrypoints import _KNOWN_BACKENDS
|
||||
from myagents.project_root import get_workspace_root
|
||||
from myagents.settings import get_setting, set_setting
|
||||
|
||||
stderr_console = Console(stderr=True)
|
||||
console = Console()
|
||||
|
||||
|
||||
# ── version ────────────────────────────────────────────────────────────────
|
||||
|
||||
def _installed_versions() -> list[str]:
|
||||
from myagents.commands.upgrade import _runtime_root
|
||||
|
||||
root = _runtime_root()
|
||||
if not root.is_dir():
|
||||
return []
|
||||
@@ -26,11 +32,14 @@ def _installed_versions() -> list[str]:
|
||||
)
|
||||
|
||||
|
||||
@click.command("switch")
|
||||
@click.command("version")
|
||||
@click.argument("version", required=False)
|
||||
@click.option("--yes", is_flag=True, help="Skip the confirmation prompt")
|
||||
def switch_cmd(version: str | None, yes: bool) -> None:
|
||||
def switch_version(version: str | None, yes: bool) -> None:
|
||||
"""Switch the active runtime to another installed version (rollback)."""
|
||||
from myagents.commands.sync_workspace import _print_report, sync_workspace
|
||||
from myagents.commands.upgrade import _current_version, _install_tools, _runtime_root
|
||||
|
||||
versions = _installed_versions()
|
||||
current = _current_version()
|
||||
if not versions:
|
||||
@@ -82,3 +91,76 @@ def switch_cmd(version: str | None, yes: bool) -> None:
|
||||
f"\n[bold green]Switched: {current} -> {version}[/bold green] "
|
||||
"(open a new terminal to pick up the change)"
|
||||
)
|
||||
|
||||
|
||||
# ── agent ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def agent_list_table() -> None:
|
||||
"""Print the agent list (reusable from info command)."""
|
||||
default = _resolve_agent()
|
||||
console.print("[bold]Available agents:[/bold]")
|
||||
for name in _KNOWN_BACKENDS:
|
||||
marker = " [green](current)[/green]" if name == default else ""
|
||||
console.print(f" {name}{marker}")
|
||||
|
||||
|
||||
def _resolve_agent() -> str:
|
||||
import re
|
||||
|
||||
agent = str(get_setting("default_agent", "") or "")
|
||||
agent = re.sub(r"^my", "", agent.strip().lower())
|
||||
return agent if agent in _KNOWN_BACKENDS else "claude"
|
||||
|
||||
|
||||
@click.command("agent")
|
||||
@click.argument("agent", required=False)
|
||||
def switch_agent(agent: str | None) -> None:
|
||||
"""Switch the default agent CLI."""
|
||||
known = list(_KNOWN_BACKENDS)
|
||||
default = _resolve_agent()
|
||||
|
||||
if not agent:
|
||||
console.print("[bold]Available agents:[/bold]")
|
||||
for name in known:
|
||||
marker = " [green](current)[/green]" if name == default else ""
|
||||
console.print(f" {name}{marker}")
|
||||
agent = str(click.prompt("Switch to", err=True))
|
||||
|
||||
agent = agent.strip().lower()
|
||||
if agent.startswith("my"):
|
||||
agent = agent[2:]
|
||||
if agent not in _KNOWN_BACKENDS:
|
||||
raise click.ClickException(
|
||||
f"Unknown agent '{agent}'. Choose from: {', '.join(known)}"
|
||||
)
|
||||
if agent == default:
|
||||
console.print(f"Already on {agent}.")
|
||||
return
|
||||
|
||||
set_setting("default_agent", agent)
|
||||
console.print(f"[bold green]Switched default agent to {agent}.[/bold green]")
|
||||
console.print(f"[dim]Use 'xiaohe' (or 'my{agent}') to launch.[/dim]")
|
||||
|
||||
|
||||
# ── switch group ───────────────────────────────────────────────────────────
|
||||
|
||||
@click.group("switch", invoke_without_command=True)
|
||||
@click.pass_context
|
||||
def switch_cmd(ctx: click.Context) -> None:
|
||||
"""Switch version, agent, or provider.
|
||||
|
||||
Run without arguments to see all options.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
console.print("[bold]Use one of:[/bold]")
|
||||
console.print(" xiaohe switch version — switch runtime version")
|
||||
console.print(" xiaohe switch agent — switch default agent CLI")
|
||||
console.print(" xiaohe switch provider — switch LLM provider")
|
||||
console.print()
|
||||
console.print("[dim]Run 'xiaohe switch <subcommand> --help' for details.[/dim]")
|
||||
|
||||
|
||||
switch_cmd.add_command(switch_version)
|
||||
switch_cmd.add_command(switch_agent)
|
||||
|
||||
# provider_cmd is added in entrypoints.py (lazy import to avoid cycles)
|
||||
|
||||
@@ -26,16 +26,19 @@ def default_agent() -> str:
|
||||
|
||||
|
||||
def build_xiaohe_cli():
|
||||
"""The ``xiaohe`` command group: agent forwarding + init/sync/upgrade/switch/uninstall/version."""
|
||||
from myagents.commands.backend import backend_cmd
|
||||
"""The ``xiaohe`` command group: agent forwarding + info/init/sync/upgrade/switch/uninstall/version."""
|
||||
from myagents.commands.info import info_cmd
|
||||
from myagents.commands.provider import provider_cmd
|
||||
from myagents.commands.switch import switch_cmd
|
||||
from myagents.commands.sync_workspace import init_cmd, sync_cmd
|
||||
from myagents.commands.uninstall import uninstall_cmd
|
||||
from myagents.commands.upgrade import upgrade_cmd
|
||||
from myagents.commands.version import version_cmd
|
||||
|
||||
switch_cmd.add_command(provider_cmd)
|
||||
|
||||
xiaohe_cli = build_cli(default_agent(), prog_name="xiaohe", offer_install=True)
|
||||
xiaohe_cli.add_command(backend_cmd)
|
||||
xiaohe_cli.add_command(info_cmd)
|
||||
xiaohe_cli.add_command(init_cmd)
|
||||
xiaohe_cli.add_command(sync_cmd)
|
||||
xiaohe_cli.add_command(upgrade_cmd)
|
||||
|
||||
Reference in New Issue
Block a user