diff --git a/parallel_web_tools/cli/commands.py b/parallel_web_tools/cli/commands.py index 0e4eabe..1accedc 100644 --- a/parallel_web_tools/cli/commands.py +++ b/parallel_web_tools/cli/commands.py @@ -1088,19 +1088,13 @@ def memory_clear(memory_scope_key: str | None, confirm_clear: bool, output_json: # Search Command # ============================================================================= -# Search mode translation. V1 accepts turbo/fast/basic/advanced natively; the -# Beta-only values stay accepted CLI inputs and are translated so existing -# scripts keep working. Beta's `fast` used to downgrade to basic, but V1 now has -# a native fast mode of its own, so the name passes through. -_SEARCH_MODE_MAP = { - "turbo": "turbo", - "fast": "fast", - "basic": "basic", - "advanced": "advanced", +# V1 modes are listed first in CLI help; fast is now native and passes through. +# Beta-only names remain deprecated aliases so existing scripts keep working. +_SEARCH_MODES = ("turbo", "fast", "basic", "advanced") +_DEPRECATED_SEARCH_MODE_ALIASES = { "one-shot": "basic", "agentic": "advanced", } -_DEPRECATED_SEARCH_MODES = {"one-shot", "agentic"} def _emit_deprecation(message: str) -> None: @@ -1135,7 +1129,7 @@ def build_search_v1_kwargs( if objective: kwargs["objective"] = objective if mode: - kwargs["mode"] = _SEARCH_MODE_MAP.get(mode, mode) + kwargs["mode"] = _DEPRECATED_SEARCH_MODE_ALIASES.get(mode, mode) if excerpt_max_chars_total is not None: kwargs["max_chars_total"] = excerpt_max_chars_total if session_id: @@ -1165,10 +1159,12 @@ def build_search_v1_kwargs( @click.option("-q", "--query", multiple=True, help="Keyword search query (can be repeated)") @click.option( "--mode", - type=click.Choice(list(_SEARCH_MODE_MAP.keys())), + type=click.Choice([*_SEARCH_MODES, *_DEPRECATED_SEARCH_MODE_ALIASES]), default="basic", - help="Search mode: turbo (fastest), fast (high quality within ~1s), basic, " - "or advanced (highest quality; one-shot → basic, agentic → advanced)", + help=( + "Search mode: turbo (fastest), fast (high quality within ~1s), basic, " + "or advanced (highest quality). Deprecated aliases: one-shot → basic, agentic → advanced" + ), show_default=True, ) @click.option("--max-results", type=int, help="Maximum results (defaults to server-side default of 10)") @@ -1222,12 +1218,9 @@ def search( if not objective and not query: raise click.UsageError("Provide an OBJECTIVE argument or at least one --query option.") - if mode in _DEPRECATED_SEARCH_MODES: - new_mode = _SEARCH_MODE_MAP[mode] - _emit_deprecation( - f"--mode {mode} is a Beta value and will stop working after the Beta API sunset (June 2026). " - f"Use --mode {new_mode} instead." - ) + if mode in _DEPRECATED_SEARCH_MODE_ALIASES: + new_mode = _DEPRECATED_SEARCH_MODE_ALIASES[mode] + _emit_deprecation(f"--mode {mode} is a deprecated alias. Use --mode {new_mode} instead.") source_policy: dict[str, Any] = {} if include_domains: diff --git a/tests/test_cli.py b/tests/test_cli.py index b4c37f2..5c47572 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -358,6 +358,13 @@ def test_search_help_shows_comma_separated(self, runner): assert "--exclude-domains" in result.output assert "comma-separated" in result.output + def test_search_help_prioritizes_native_modes(self, runner): + """Should list V1-native modes before deprecated aliases.""" + result = runner.invoke(main, ["search", "--help"]) + assert result.exit_code == 0 + assert "--mode [turbo|fast|basic|advanced|one-shot|agentic]" in result.output + assert "Deprecated aliases" in result.output + def test_search_no_args(self, runner): """Should error without objective or query.""" result = runner.invoke(main, ["search"]) @@ -1620,9 +1627,9 @@ def test_deprecated_modes_emit_warning_to_stderr(self, runner, mock_cli_client, ) assert result.exit_code == 0 - assert "[deprecated]" in result.stderr - assert deprecated_mode in result.stderr - assert expected_new in result.stderr + assert result.stderr.strip() == ( + f"[deprecated] --mode {deprecated_mode} is a deprecated alias. Use --mode {expected_new} instead." + ) # JSON stdout must remain clean json.loads(result.stdout) # SDK call uses translated mode @@ -1640,6 +1647,7 @@ def test_new_modes_do_not_emit_warning(self, runner, mock_cli_client, new_mode): assert result.exit_code == 0 assert "[deprecated]" not in result.stderr + assert mock_cli_client.search.call_args.kwargs["mode"] == new_mode class TestExtractDeprecationWarnings: