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
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -30,7 +30,6 @@ def _secure_prompt_key(provider: Provider) -> str:
|
|||||||
try:
|
try:
|
||||||
return click.prompt(prompt_text, hide_input=True, err=True)
|
return click.prompt(prompt_text, hide_input=True, err=True)
|
||||||
except click.UsageError:
|
except click.UsageError:
|
||||||
# click.prompt raises when stdin is not a TTY. Fall back to /dev/tty.
|
|
||||||
try:
|
try:
|
||||||
with open("/dev/tty", encoding="utf-8") as tty: # noqa: PTH123
|
with open("/dev/tty", encoding="utf-8") as tty: # noqa: PTH123
|
||||||
return tty.readline().rstrip("\n")
|
return tty.readline().rstrip("\n")
|
||||||
@@ -40,16 +39,10 @@ def _secure_prompt_key(provider: Provider) -> str:
|
|||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
@click.group("backend")
|
def provider_list_table() -> None:
|
||||||
def backend_cmd() -> None:
|
"""Print the provider list table (reusable from info command)."""
|
||||||
"""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."""
|
|
||||||
active = get_active_provider()
|
active = get_active_provider()
|
||||||
console.print("[bold]Built-in backends:[/bold]")
|
console.print("[bold]Available providers:[/bold]")
|
||||||
for provider in list_providers():
|
for provider in list_providers():
|
||||||
marker = ""
|
marker = ""
|
||||||
if active and active.id == provider.id:
|
if active and active.id == provider.id:
|
||||||
@@ -65,32 +58,43 @@ def backend_list() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@backend_cmd.command("current")
|
@click.group("provider")
|
||||||
def backend_current() -> None:
|
def provider_cmd() -> None:
|
||||||
"""Show the active backend for this workspace."""
|
"""Switch Claude Code's LLM provider (DeepSeek, Kimi, Kimi Code, Claude)."""
|
||||||
console.print(f"[bold]Current backend:[/bold] {describe_active_backend()}")
|
|
||||||
|
|
||||||
|
|
||||||
@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.argument("provider_id")
|
||||||
@click.option("--key", help="API key (scripting only; appears in shell history)")
|
@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("--base-url", help="Override the provider base URL")
|
||||||
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
||||||
def backend_use(
|
def provider_use(
|
||||||
provider_id: str,
|
provider_id: str,
|
||||||
key: str | None,
|
key: str | None,
|
||||||
model: str | None,
|
model: str | None,
|
||||||
base_url: str | None,
|
base_url: str | None,
|
||||||
yes: bool,
|
yes: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Switch Claude Code to the given backend."""
|
"""Switch to the given provider."""
|
||||||
try:
|
try:
|
||||||
provider = get_provider(provider_id)
|
provider = get_provider(provider_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
known = ", ".join(p.id for p in list_providers())
|
known = ", ".join(p.id for p in list_providers())
|
||||||
raise click.ClickException(
|
raise click.ClickException(
|
||||||
f"Unknown backend '{provider_id}'. Choose from: {known}"
|
f"Unknown provider '{provider_id}'. Choose from: {known}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_third_party(provider):
|
if is_third_party(provider):
|
||||||
@@ -98,7 +102,7 @@ def backend_use(
|
|||||||
if not resolved_key:
|
if not resolved_key:
|
||||||
resolved_key = _secure_prompt_key(provider)
|
resolved_key = _secure_prompt_key(provider)
|
||||||
if not resolved_key:
|
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:
|
else:
|
||||||
resolved_key = None
|
resolved_key = None
|
||||||
|
|
||||||
@@ -109,7 +113,7 @@ def backend_use(
|
|||||||
summary += f" (base-url: {base_url})"
|
summary += f" (base-url: {base_url})"
|
||||||
|
|
||||||
if not yes and not click.confirm(
|
if not yes and not click.confirm(
|
||||||
f"Set backend to {summary} for this workspace?",
|
f"Switch LLM provider to {summary}?",
|
||||||
default=True,
|
default=True,
|
||||||
err=True,
|
err=True,
|
||||||
):
|
):
|
||||||
@@ -125,37 +129,37 @@ def backend_use(
|
|||||||
save_settings(settings)
|
save_settings(settings)
|
||||||
set_setting("backend_provider", provider.id)
|
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":
|
if provider.id == "claude":
|
||||||
console.print(
|
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")
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
||||||
def backend_reset(yes: bool) -> None:
|
def provider_reset(yes: bool) -> None:
|
||||||
"""Reset to the official Claude backend."""
|
"""Reset to the official Claude provider."""
|
||||||
ctx = click.get_current_context()
|
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")
|
@provider_cmd.group("key")
|
||||||
def backend_key() -> None:
|
def provider_key() -> None:
|
||||||
"""Manage stored API keys."""
|
"""Manage stored API keys."""
|
||||||
|
|
||||||
|
|
||||||
@backend_key.command("set")
|
@provider_key.command("set")
|
||||||
@click.argument("provider_id")
|
@click.argument("provider_id")
|
||||||
@click.option("--key", help="API key (scripting only; appears in shell history)")
|
@click.option("--key", help="API key (scripting only; appears in shell history)")
|
||||||
def backend_key_set(provider_id: str, key: str | None) -> None:
|
def provider_key_set(provider_id: str, key: str | None) -> None:
|
||||||
"""Store an API key for a backend without switching to it."""
|
"""Store an API key without switching."""
|
||||||
try:
|
try:
|
||||||
provider = get_provider(provider_id)
|
provider = get_provider(provider_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
known = ", ".join(p.id for p in list_providers())
|
known = ", ".join(p.id for p in list_providers())
|
||||||
raise click.ClickException(
|
raise click.ClickException(
|
||||||
f"Unknown backend '{provider_id}'. Choose from: {known}"
|
f"Unknown provider '{provider_id}'. Choose from: {known}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if provider.id == "claude":
|
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.argument("provider_id")
|
||||||
@click.option(
|
@click.option("--yes", "-y", is_flag=True, help="Skip the confirmation prompt")
|
||||||
"--yes",
|
def provider_key_rm(provider_id: str, yes: bool) -> None:
|
||||||
"-y",
|
"""Remove the stored API key for a provider."""
|
||||||
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."""
|
|
||||||
try:
|
try:
|
||||||
provider = get_provider(provider_id)
|
provider = get_provider(provider_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
known = ", ".join(p.id for p in list_providers())
|
known = ", ".join(p.id for p in list_providers())
|
||||||
raise click.ClickException(
|
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(
|
if not yes and not click.confirm(
|
||||||
@@ -208,4 +207,4 @@ def backend_key_rm(provider_id: str, yes: bool) -> None:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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
|
Subcommands:
|
||||||
``current`` symlink, reinstalls the CLI tools from that runtime, and re-syncs
|
version — switch the active runtime version (rollback/roll-forward)
|
||||||
the workspace. Run without an argument to list installed versions and pick
|
agent — switch the default agent CLI (claude / kimi / codex / hermes)
|
||||||
one interactively.
|
provider — switch Claude Code's LLM provider (DeepSeek / Kimi / Kimi Code / Claude)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from myagents.commands.sync_workspace import _print_report, sync_workspace
|
from myagents.entrypoints import _KNOWN_BACKENDS
|
||||||
from myagents.commands.upgrade import _current_version, _install_tools, _runtime_root
|
|
||||||
from myagents.project_root import get_workspace_root
|
from myagents.project_root import get_workspace_root
|
||||||
|
from myagents.settings import get_setting, set_setting
|
||||||
|
|
||||||
stderr_console = Console(stderr=True)
|
stderr_console = Console(stderr=True)
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
# ── version ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _installed_versions() -> list[str]:
|
def _installed_versions() -> list[str]:
|
||||||
|
from myagents.commands.upgrade import _runtime_root
|
||||||
|
|
||||||
root = _runtime_root()
|
root = _runtime_root()
|
||||||
if not root.is_dir():
|
if not root.is_dir():
|
||||||
return []
|
return []
|
||||||
@@ -26,11 +32,14 @@ def _installed_versions() -> list[str]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@click.command("switch")
|
@click.command("version")
|
||||||
@click.argument("version", required=False)
|
@click.argument("version", required=False)
|
||||||
@click.option("--yes", is_flag=True, help="Skip the confirmation prompt")
|
@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)."""
|
"""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()
|
versions = _installed_versions()
|
||||||
current = _current_version()
|
current = _current_version()
|
||||||
if not versions:
|
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] "
|
f"\n[bold green]Switched: {current} -> {version}[/bold green] "
|
||||||
"(open a new terminal to pick up the change)"
|
"(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():
|
def build_xiaohe_cli():
|
||||||
"""The ``xiaohe`` command group: agent forwarding + init/sync/upgrade/switch/uninstall/version."""
|
"""The ``xiaohe`` command group: agent forwarding + info/init/sync/upgrade/switch/uninstall/version."""
|
||||||
from myagents.commands.backend import backend_cmd
|
from myagents.commands.info import info_cmd
|
||||||
|
from myagents.commands.provider import provider_cmd
|
||||||
from myagents.commands.switch import switch_cmd
|
from myagents.commands.switch import switch_cmd
|
||||||
from myagents.commands.sync_workspace import init_cmd, sync_cmd
|
from myagents.commands.sync_workspace import init_cmd, sync_cmd
|
||||||
from myagents.commands.uninstall import uninstall_cmd
|
from myagents.commands.uninstall import uninstall_cmd
|
||||||
from myagents.commands.upgrade import upgrade_cmd
|
from myagents.commands.upgrade import upgrade_cmd
|
||||||
from myagents.commands.version import version_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 = 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(init_cmd)
|
||||||
xiaohe_cli.add_command(sync_cmd)
|
xiaohe_cli.add_command(sync_cmd)
|
||||||
xiaohe_cli.add_command(upgrade_cmd)
|
xiaohe_cli.add_command(upgrade_cmd)
|
||||||
|
|||||||
Reference in New Issue
Block a user