From 4371ddb620db0447e6a5b790e893b962dff8f53a Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:25:12 -0700 Subject: [PATCH] feat(cli): keep lite autoroute up and down as hidden deprecated aliases --- litellm/proxy/client/cli/README.md | 2 + .../client/cli/commands/autoroute/commands.py | 34 ++++++++++++++++- .../client/cli/autoroute/test_commands.py | 38 +++++++++++++++---- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/client/cli/README.md b/litellm/proxy/client/cli/README.md index d4af140fcbb..a02d7cce0d8 100644 --- a/litellm/proxy/client/cli/README.md +++ b/litellm/proxy/client/cli/README.md @@ -668,6 +668,8 @@ lite autoroute start lite autoroute stop # only needed if `start` was killed uncleanly instead of Ctrl-C'd ``` +The previous names, `lite autoroute up` and `lite autoroute down`, still work as hidden aliases of `start` and `stop`: each prints a deprecation notice on stderr and will be removed in a future release + #### Caveats Adaptive mode's learned state does not persist across `lite autoroute start` sessions -- there is no local database, so every session starts adaptive selection cold. A Claude Code session already running before `start` ran, or still running when it stops, keeps whatever settings it loaded at its own startup; like `lite up`, this is a one-time file patch and restore, not a live traffic interceptor. Only Claude Code is supported, for the same reason as `lite up`: no other supported agent (for example Cursor) has an equivalent hot-patchable config file. diff --git a/litellm/proxy/client/cli/commands/autoroute/commands.py b/litellm/proxy/client/cli/commands/autoroute/commands.py index c9cf78886fb..05c21875f84 100644 --- a/litellm/proxy/client/cli/commands/autoroute/commands.py +++ b/litellm/proxy/client/cli/commands/autoroute/commands.py @@ -88,14 +88,17 @@ def configure(ctx: click.Context) -> None: run_configure_wizard(ctx) -@autoroute_group.command("start") -@click.option( +_PORT_OPTION: Final = click.option( "--port", type=click.IntRange(1, 65535), default=DEFAULT_AUTOROUTE_PORT, show_default=True, help="Loopback port for the ephemeral proxy; stable across runs so configured clients keep working.", ) + + +@autoroute_group.command("start") +@_PORT_OPTION def start(port: int) -> None: """Launch the ephemeral auto-router proxy and route Claude Code through it""" if not CONFIG_PATH.exists(): @@ -241,4 +244,31 @@ def stop() -> None: click.echo(f"Removed {CLAUDE_SETTINGS_PATH} (it did not exist before `lite autoroute start`).") +AUTOROUTE_ALIAS_DEPRECATION_NOTICE: Final = ( + "`lite autoroute {retired}` is deprecated and will be removed in a future release; " + "run `lite autoroute {current}` instead, it takes the same options." +) + + +def _warn_deprecated_alias(retired: str, current: str) -> None: + click.secho(AUTOROUTE_ALIAS_DEPRECATION_NOTICE.format(retired=retired, current=current), err=True, fg="yellow") + + +@autoroute_group.command("up", hidden=True) +@_PORT_OPTION +@click.pass_context +def up(ctx: click.Context, port: int) -> None: + """Deprecated alias of `lite autoroute start`""" + _warn_deprecated_alias("up", "start") + ctx.invoke(start, port=port) + + +@autoroute_group.command("down", hidden=True) +@click.pass_context +def down(ctx: click.Context) -> None: + """Deprecated alias of `lite autoroute stop`""" + _warn_deprecated_alias("down", "stop") + ctx.invoke(stop) + + __all__ = ["autoroute_group"] diff --git a/tests/test_litellm/proxy/client/cli/autoroute/test_commands.py b/tests/test_litellm/proxy/client/cli/autoroute/test_commands.py index 8886be622d5..890c249ca5e 100644 --- a/tests/test_litellm/proxy/client/cli/autoroute/test_commands.py +++ b/tests/test_litellm/proxy/client/cli/autoroute/test_commands.py @@ -547,17 +547,41 @@ class TestStopCommand: class TestSubcommandNames: - def test_start_and_stop_replace_up_and_down(self): + def test_start_and_stop_are_the_listed_commands(self): """`lite up` already routes an existing proxy into Claude Code, so the ephemeral proxy's - launcher and its recovery path are `start` and `stop`, with no `up`/`down` alias left.""" + launcher and its recovery path are listed as `start` and `stop`; the old names stay callable + but are hidden from the listing.""" runner = CliRunner() - for retired in ("up", "down"): - result = runner.invoke(autoroute_group, [retired, "--help"]) - assert result.exit_code == 2, result.output - assert f"No such command '{retired}'" in result.output + listing = runner.invoke(autoroute_group, ["--help"]) + assert listing.exit_code == 0, listing.output + listed = {line.split()[0] for line in listing.output.splitlines() if line.startswith(" ")} + assert {"configure", "start", "stop"} <= listed + assert listed.isdisjoint({"up", "down"}) - for name in ("start", "stop"): + for name in ("start", "stop", "up", "down"): result = runner.invoke(autoroute_group, [name, "--help"]) assert result.exit_code == 0, result.output assert "Show this message and exit" in result.output + + def test_up_warns_then_behaves_like_start(self, monkeypatch, tmp_path): + _patch_paths(monkeypatch, tmp_path) + runner = CliRunner() + + result = runner.invoke(autoroute_group, ["up", "--port", "5555"]) + + assert result.exit_code == 1, result.output + assert "`lite autoroute up` is deprecated" in result.stderr + assert "run `lite autoroute start` instead" in result.stderr + assert "No config found. Run `lite autoroute configure` first." in result.output + + def test_down_warns_then_behaves_like_stop(self, monkeypatch, tmp_path): + _patch_paths(monkeypatch, tmp_path) + runner = CliRunner() + + result = runner.invoke(autoroute_group, ["down"]) + + assert result.exit_code == 0, result.output + assert "`lite autoroute down` is deprecated" in result.stderr + assert "run `lite autoroute stop` instead" in result.stderr + assert "Nothing to restore." in result.output