feat(cli): offer the previous run's selections as defaults (#1236, #920)

- analysts, depth, provider, models and language prefill; prompts still shown
- values no longer offered by the current catalog are dropped
- environment variables keep skipping their step
This commit is contained in:
Yijia-Xiao
2026-09-16 21:45:08 +00:00
parent d8eceb6571
commit 375af054b7
3 changed files with 288 additions and 31 deletions

View File

@@ -132,8 +132,16 @@ def get_analysis_date() -> str:
return date.strip()
def select_analysts(asset_type: AssetType = AssetType.STOCK) -> list[AnalystType]:
"""Select analysts using an interactive checkbox."""
def _matching_choice(options, default):
"""The option value equal to ``default``, or None to leave the menu as is."""
return next((value for _, value in options if value == default), None)
def select_analysts(asset_type: AssetType = AssetType.STOCK, default=None) -> list[AnalystType]:
"""Select analysts using an interactive checkbox.
``default`` pre-checks the previous run's analysts; the prompt still shows.
"""
available_analysts = filter_analysts_for_asset_type(
[value for _, value in ANALYST_ORDER],
asset_type,
@@ -141,7 +149,7 @@ def select_analysts(asset_type: AssetType = AssetType.STOCK) -> list[AnalystType
choices = questionary.checkbox(
"Select Your [Analysts Team]:",
choices=[
questionary.Choice(display, value=value)
questionary.Choice(display, value=value, checked=value.value in (default or []))
for display, value in ANALYST_ORDER
if value in available_analysts
],
@@ -164,7 +172,7 @@ def select_analysts(asset_type: AssetType = AssetType.STOCK) -> list[AnalystType
return choices
def select_research_depth() -> int:
def select_research_depth(default=None) -> int:
"""Select research depth using an interactive selection."""
# Define research depth options with their corresponding values
@@ -179,6 +187,7 @@ def select_research_depth() -> int:
choices=[
questionary.Choice(display, value=value) for display, value in DEPTH_OPTIONS
],
default=_matching_choice(DEPTH_OPTIONS, default),
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
style=questionary.Style(
[
@@ -289,7 +298,7 @@ def _prompt_custom_model_id() -> str:
return _require_text("Enter model ID:", "Please enter a model ID.")
def _select_model(provider: str, mode: str) -> str:
def _select_model(provider: str, mode: str, default=None) -> str:
"""Select a model for the given provider and mode (quick/deep)."""
if provider.lower() == "openrouter":
return select_openrouter_model(mode)
@@ -306,6 +315,7 @@ def _select_model(provider: str, mode: str) -> str:
questionary.Choice(display, value=value)
for display, value in get_model_options(provider, mode)
],
default=_matching_choice(get_model_options(provider, mode), default),
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
style=questionary.Style(
[
@@ -326,14 +336,14 @@ def _select_model(provider: str, mode: str) -> str:
return choice
def select_shallow_thinking_agent(provider) -> str:
def select_shallow_thinking_agent(provider, default=None) -> str:
"""Select shallow thinking llm engine using an interactive selection."""
return _select_model(provider, "quick")
return _select_model(provider, "quick", default)
def select_deep_thinking_agent(provider) -> str:
def select_deep_thinking_agent(provider, default=None) -> str:
"""Select deep thinking llm engine using an interactive selection."""
return _select_model(provider, "deep")
return _select_model(provider, "deep", default)
def _llm_provider_table() -> list[tuple[str, str, str | None]]:
"""(display_name, provider_key, base_url) for every supported provider.
@@ -402,9 +412,15 @@ def prompt_openai_compatible_url() -> str:
return url.strip()
def select_llm_provider() -> tuple[str, str | None]:
def select_llm_provider(default=None) -> tuple[str, str | None]:
"""Select the LLM provider and its API endpoint."""
PROVIDERS = _llm_provider_table()
# A region-specific key (qwen-cn) is chosen in a later prompt; the menu
# lists the base provider.
base = (default or "").split("-cn")[0]
preselected = next(
((key, url) for _, key, url in PROVIDERS if key == base), None
)
choice = questionary.select(
"Select your LLM Provider:",
@@ -412,6 +428,7 @@ def select_llm_provider() -> tuple[str, str | None]:
questionary.Choice(display, value=(provider_key, url))
for display, provider_key, url in PROVIDERS
],
default=preselected,
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
style=questionary.Style(
[
@@ -458,9 +475,9 @@ def ask_anthropic_effort() -> str | None:
return questionary.select(
"Select Effort Level:",
choices=[
questionary.Choice("High (recommended)", "high"),
questionary.Choice("Medium (balanced)", "medium"),
questionary.Choice("Low (faster, cheaper)", "low"),
questionary.Choice("High (recommended)", "high"),
questionary.Choice("Medium (balanced)", "medium"),
questionary.Choice("Low (faster, cheaper)", "low"),
],
style=questionary.Style([
("selected", "fg:cyan noinherit"),
@@ -479,8 +496,8 @@ def ask_gemini_thinking_config() -> str | None:
return questionary.select(
"Select Thinking Mode:",
choices=[
questionary.Choice("Enable Thinking (recommended)", "high"),
questionary.Choice("Minimal/Disable Thinking", "minimal"),
questionary.Choice("Enable Thinking (recommended)", "high"),
questionary.Choice("Minimal/Disable Thinking", "minimal"),
],
style=questionary.Style([
("selected", "fg:green noinherit"),
@@ -654,24 +671,30 @@ def ensure_api_key(provider: str) -> str | None:
return key
def ask_output_language() -> str:
"""Ask for report output language."""
def ask_output_language(default=None) -> str:
"""Ask for report output language.
``default`` is offered only when it is one of the listed languages: a custom
one entered last time is free text, which the menu cannot preselect.
"""
choices = [
questionary.Choice("English (default)", "English"),
questionary.Choice("Chinese (中文)", "Chinese"),
questionary.Choice("Japanese (日本語)", "Japanese"),
questionary.Choice("Korean (한국어)", "Korean"),
questionary.Choice("Hindi (हिन्दी)", "Hindi"),
questionary.Choice("Spanish (Español)", "Spanish"),
questionary.Choice("Portuguese (Português)", "Portuguese"),
questionary.Choice("French (Français)", "French"),
questionary.Choice("German (Deutsch)", "German"),
questionary.Choice("Arabic (العربية)", "Arabic"),
questionary.Choice("Russian (Русский)", "Russian"),
questionary.Choice("Custom language", "custom"),
]
choice = questionary.select(
"Select Output Language:",
choices=[
questionary.Choice("English (default)", "English"),
questionary.Choice("Chinese (中文)", "Chinese"),
questionary.Choice("Japanese (日本語)", "Japanese"),
questionary.Choice("Korean (한국어)", "Korean"),
questionary.Choice("Hindi (हिन्दी)", "Hindi"),
questionary.Choice("Spanish (Español)", "Spanish"),
questionary.Choice("Portuguese (Português)", "Portuguese"),
questionary.Choice("French (Français)", "French"),
questionary.Choice("German (Deutsch)", "German"),
questionary.Choice("Arabic (العربية)", "Arabic"),
questionary.Choice("Russian (Русский)", "Russian"),
questionary.Choice("Custom language", "custom"),
],
choices=choices,
default=_matching_choice([(c.title, c.value) for c in choices], default),
style=questionary.Style([
("selected", "fg:yellow noinherit"),
("highlighted", "fg:yellow noinherit"),