From fa5f7f5b88ed7c08a82bcd5d8ac27ff956de595a Mon Sep 17 00:00:00 2001 From: chengyongru <2755839590@qq.com> Date: Sat, 20 Jun 2026 23:38:12 +0800 Subject: [PATCH] fix: make onboarding api-key first Maintainer edit: collapse the first-run wizard to an API-key-only Quick Start and move lower-frequency provider, model, channel, gateway, and tool settings behind Advanced Settings. --- docs/start-without-technical-background.md | 26 +- nanobot/cli/onboard.py | 282 +++++---------------- tests/agent/test_onboard_logic.py | 176 +------------ 3 files changed, 83 insertions(+), 401 deletions(-) diff --git a/docs/start-without-technical-background.md b/docs/start-without-technical-background.md index d5a383e4..786b28d5 100644 --- a/docs/start-without-technical-background.md +++ b/docs/start-without-technical-background.md @@ -162,15 +162,8 @@ You will see a menu like this: ```text > What would you like to configure? - [Q] Quick Start (recommended) - [P] LLM Provider - [M] Model Presets - [C] Chat Channel - [H] Channel Common - [A] Agent Settings - [I] API Server - [G] Gateway - [T] Tools + [Q] Quick Start (API key only) + [A] Advanced Settings [V] View Configuration Summary [S] Save and Exit [X] Exit Without Saving @@ -185,21 +178,20 @@ Move through the wizard like this: | A field you do not need | Keep the shown default or leave it blank, then press `Enter`. | | A back option | Choose it to return to the previous menu. | -For the first setup, choose `[Q] Quick Start (recommended)`. It has a recommended path for a local browser UI and an advanced path where you can choose every detail yourself. The other main menu items are advanced settings. +For the first setup, choose `[Q] Quick Start (API key only)`. It configures the recommended local browser UI and model choice for you. Use `Advanced Settings` later only if you need a different provider, model, channel, gateway, or tool setup. If you are following the OpenRouter example: -1. Choose `[Q] Quick Start (recommended)`. -2. Choose `Recommended: local WebUI`. -3. Paste your OpenRouter API key, or press `Enter` and add it to the config file later. -4. Review the Quick Start summary. -5. Choose `[S] Save and Exit`. +1. Choose `[Q] Quick Start (API key only)`. +2. Paste your OpenRouter API key, or press `Enter` and add it to the config file later. +3. Review the Quick Start summary. +4. Choose `[S] Save and Exit`. The recommended path enables the local WebUI with default WebSocket settings and uses a built-in model choice. You do not need to choose a model ID for the first run. -If OpenRouter later says your account cannot use the built-in model choice, return to the wizard, choose `[Q] Quick Start (recommended)`, then choose `Choose provider and entry point` and enter another OpenRouter model ID that your account can access. +If OpenRouter later says your account cannot use the built-in model choice, return to the wizard, choose `Advanced Settings`, then edit `Model Presets` and enter another OpenRouter model ID that your account can access. -If you are using another provider, use the same wizard choices but substitute that provider's values: +If you already know that you need another provider, choose `Advanced Settings` instead of Quick Start and enter that provider's values: | Wizard field | What to enter | |---|---| diff --git a/nanobot/cli/onboard.py b/nanobot/cli/onboard.py index 7239725d..e438cbc5 100644 --- a/nanobot/cli/onboard.py +++ b/nanobot/cli/onboard.py @@ -54,35 +54,10 @@ _BACK_PRESSED = object() # Sentinel value for back navigation # offer existing presets as choices (e.g. AgentDefaults.model_preset). _MODEL_PRESET_CACHE: set[str] = set() -_QUICK_START_TARGETS = { - "WebUI / local browser (recommended)": "websocket", - "No chat channel yet": None, - "Telegram": "telegram", - "WeChat": "weixin", - "WhatsApp": "whatsapp", - "Feishu / Lark": "feishu", - "Slack": "slack", - "Discord": "discord", -} -_QUICK_START_RECOMMENDED_CHOICE = "Recommended: local WebUI" -_QUICK_START_CUSTOM_CHOICE = "Choose provider and entry point" _QUICK_START_RECOMMENDED_PROVIDER = "openrouter" _QUICK_START_RECOMMENDED_MODEL = "anthropic/claude-sonnet-4.5" -_QUICK_START_CHANNEL_FIELDS = { - "telegram": (("token", "Telegram bot token from BotFather"),), - "feishu": ( - ("app_id", "Feishu/Lark App ID"), - ("app_secret", "Feishu/Lark App Secret"), - ), - "slack": ( - ("bot_token", "Slack bot token (xoxb-...)"), - ("app_token", "Slack app token (xapp-...)"), - ), - "discord": (("token", "Discord bot token"),), -} - -_QUICK_START_STEPS = ("Setup", "AI provider", "Entry point", "Review") +_QUICK_START_STEPS = ("API key", "WebUI", "Review") # Low-contrast terminal palette inspired by JetBrains Darcula/Islands. _UI_ACCENT = "#6B9BFA" @@ -413,9 +388,9 @@ def _show_main_menu_header() -> None: body = Table.grid(expand=True) body.add_column(ratio=1) body.add_row(f"{__logo__} [bold {_UI_TEXT}]nanobot[/] [{_UI_MUTED}]v{__version__}[/]") - body.add_row(f"[{_UI_ACCENT}]Quick Start configures one AI model and one entry point.[/]") + body.add_row(f"[{_UI_ACCENT}]Quick Start only needs an API key.[/]") body.add_row( - f"[{_UI_MUTED}]WebUI, chat channels, and advanced settings stay available when you need them.[/]" + f"[{_UI_MUTED}]Provider, channel, model, and gateway settings stay in Advanced.[/]" ) console.print( Panel( @@ -1052,17 +1027,6 @@ def _get_provider_names() -> dict[str, str]: return {name: data[0] for name, data in info.items() if name} -def _get_quick_start_provider_choices() -> dict[str, str]: - """Return display-name -> provider key choices for chat-capable providers.""" - from nanobot.providers.registry import PROVIDERS - - return { - spec.display_name or spec.name: spec.name - for spec in PROVIDERS - if not spec.is_oauth and not spec.is_transcription_only - } - - def _configure_provider(config: Config, provider_name: str) -> None: """Configure a single LLM provider.""" provider_config = getattr(config.providers, provider_name, None) @@ -1421,14 +1385,6 @@ def _pause() -> None: # --- Quick Start --- -def _quick_start_model_default(config: Config, provider_name: str) -> str: - """Return a low-risk default only when the existing model likely still fits.""" - current = config.resolve_preset() - if provider_name in {"openrouter", "anthropic"}: - return current.model - return "" - - def _set_primary_quick_start_preset(config: Config, provider_name: str, model: str) -> None: """Store the primary preset used by Quick Start.""" config.model_presets["primary"] = ModelPresetConfig( @@ -1454,68 +1410,9 @@ def _show_quick_start_progress(active_step: int) -> None: console.print() -def _configure_quick_start_provider(config: Config) -> bool: - """Configure the minimum needed provider + primary model preset.""" - _show_quick_start_progress(2) - provider_choices = _get_quick_start_provider_choices() - answer = _select_with_back( - "Choose your AI provider:", - list(provider_choices), - default="OpenRouter", - ) - if answer is _BACK_PRESSED or answer is None: - return False - - assert isinstance(answer, str) - provider_name = provider_choices[answer] - provider_config = getattr(config.providers, provider_name, None) - if provider_config is None: - console.print(f"[red]Unknown provider: {provider_name}[/red]") - return False - - _display, _is_gateway, is_local, default_api_base = _get_provider_info().get( - provider_name, (provider_name, False, False, "") - ) - if default_api_base and not provider_config.api_base: - provider_config.api_base = default_api_base - - if not is_local: - api_key = _input_with_existing( - "API key (leave blank only if this provider does not use one)", - provider_config.api_key, - "str", - ) - if api_key is not None: - provider_config.api_key = api_key or None - - if provider_name == "custom" or is_local: - api_base = _input_with_existing( - "API base URL", - provider_config.api_base, - "str", - ) - if api_base is not None: - provider_config.api_base = api_base or None - - model = _input_model_with_autocomplete( - "Model ID", - _quick_start_model_default(config, provider_name), - provider_name, - ) - if model is None: - return False - model = model.strip() - if not model: - console.print("[yellow]! Model ID is required for Quick Start[/yellow]") - return False - - _set_primary_quick_start_preset(config, provider_name, model) - return True - - def _configure_recommended_provider(config: Config) -> bool: """Configure the beginner path provider with one API-key prompt.""" - _show_quick_start_progress(2) + _show_quick_start_progress(1) provider_name = _QUICK_START_RECOMMENDED_PROVIDER provider_config = getattr(config.providers, provider_name, None) if provider_config is None: @@ -1546,7 +1443,7 @@ def _configure_recommended_provider(config: Config) -> bool: def _enable_quick_start_websocket_defaults(config: Config) -> bool: """Enable local WebUI with the default WebSocket settings.""" - _show_quick_start_progress(3) + _show_quick_start_progress(2) config_cls = _get_channel_config_class("websocket") if config_cls is None: console.print("[red]No configuration class found for websocket[/red]") @@ -1560,61 +1457,9 @@ def _enable_quick_start_websocket_defaults(config: Config) -> bool: return True -def _configure_quick_start_channel(config: Config, channel_name: str | None) -> bool: - """Enable one common channel with only the fields needed to connect.""" - _show_quick_start_progress(3) - if channel_name is None: - return True - - config_cls = _get_channel_config_class(channel_name) - if config_cls is None: - console.print(f"[red]No configuration class found for {channel_name}[/red]") - return False - - current = getattr(config.channels, channel_name, None) or {} - model = config_cls.model_validate(current) - if channel_name == "websocket": - enable = _input_bool("Enable WebSocket channel for local WebUI", True) - if not enable: - console.print("[yellow]! WebSocket channel was not enabled[/yellow]") - return False - if hasattr(model, "enabled"): - setattr(model, "enabled", True) - updated = _configure_pydantic_model(model, "WebSocket") - if updated is None: - return False - setattr(config.channels, channel_name, updated.model_dump(by_alias=True, exclude_none=True)) - return True - - channel_cls = _get_channel_class(channel_name) - display_name = _get_channel_names().get(channel_name, channel_name) - if _channel_supports_login(channel_cls): - return _run_channel_login(config, channel_name, model, display_name) - - required_fields = _QUICK_START_CHANNEL_FIELDS.get(channel_name, ()) - for field_name, prompt in required_fields: - value = _input_with_existing(prompt, getattr(model, field_name, ""), "str") - if value is not None: - setattr(model, field_name, value) - - missing = [ - prompt - for field_name, prompt in required_fields - if not str(getattr(model, field_name, "") or "").strip() - ] - if missing: - console.print(f"[yellow]! {missing[0]} is required; channel was not enabled[/yellow]") - return False - - if hasattr(model, "enabled"): - setattr(model, "enabled", True) - setattr(config.channels, channel_name, model.model_dump(by_alias=True, exclude_none=True)) - return True - - def _show_quick_start_summary(config: Config, channel_name: str | None) -> None: """Show the small summary users need before returning to the menu.""" - _show_quick_start_progress(4) + _show_quick_start_progress(3) preset = config.model_presets.get("primary") api_key_status = None if preset: @@ -1655,54 +1500,19 @@ def _show_quick_start_summary(config: Config, channel_name: str | None) -> None: def _configure_quick_start(config: Config) -> None: - """First-run path: model + optional chat channel, with advanced settings hidden.""" + """First-run path: API key + local WebUI, with advanced settings hidden.""" console.clear() _show_section_header( "Quick Start", - "Start with the local browser UI, or choose each detail yourself.", + "Paste one API key. nanobot will use recommended local WebUI defaults.", ) - _show_quick_start_progress(1) - answer = _select_with_back( - "Choose setup:", - [_QUICK_START_RECOMMENDED_CHOICE, _QUICK_START_CUSTOM_CHOICE, "<- Back"], - default=_QUICK_START_RECOMMENDED_CHOICE, - ) - if answer is _BACK_PRESSED or answer is None or answer == "<- Back": - return - - assert isinstance(answer, str) - - if answer == _QUICK_START_RECOMMENDED_CHOICE: - if not _configure_recommended_provider(config): - _pause() - return - if not _enable_quick_start_websocket_defaults(config): - _pause() - return - _show_quick_start_summary(config, "websocket") + if not _configure_recommended_provider(config): _pause() return - - if answer != _QUICK_START_CUSTOM_CHOICE: - return - - answer = _select_with_back( - "How do you want to use nanobot first?", - list(_QUICK_START_TARGETS) + ["<- Back"], - default="WebUI / local browser (recommended)", - ) - if answer is _BACK_PRESSED or answer is None or answer == "<- Back": - return - - assert isinstance(answer, str) - channel_name = _QUICK_START_TARGETS[answer] - if not _configure_quick_start_provider(config): + if not _enable_quick_start_websocket_defaults(config): _pause() return - if not _configure_quick_start_channel(config, channel_name): - _pause() - return - _show_quick_start_summary(config, channel_name) + _show_quick_start_summary(config, "websocket") _pause() @@ -1737,6 +1547,54 @@ def _prompt_main_menu_exit(has_unsaved_changes: bool) -> str: return "resume" +def _configure_advanced_settings(config: Config) -> None: + """Show lower-frequency setup options behind one advanced menu.""" + last_choice: str | None = None + while True: + try: + console.clear() + _show_section_header( + "Advanced Settings", + "Use these when the default API-key setup is not enough.", + ) + answer = _get_questionary().select( + "What would you like to configure?", + choices=[ + "[P] LLM Provider", + "[M] Model Presets", + "[C] Chat Channel", + "[H] Channel Common", + "[A] Agent Settings", + "[I] API Server", + "[G] Gateway", + "[T] Tools", + "<- Back", + ], + default=last_choice, + qmark=">", + ).ask() + except KeyboardInterrupt: + break + + if answer is None or answer == "<- Back": + break + + _advanced_dispatch = { + "[P] LLM Provider": lambda: _configure_providers(config), + "[M] Model Presets": lambda: _configure_model_presets(config), + "[C] Chat Channel": lambda: _configure_channels(config), + "[H] Channel Common": lambda: _configure_general_settings(config, "Channel Common"), + "[A] Agent Settings": lambda: _configure_general_settings(config, "Agent Settings"), + "[I] API Server": lambda: _configure_general_settings(config, "API Server"), + "[G] Gateway": lambda: _configure_general_settings(config, "Gateway"), + "[T] Tools": lambda: _configure_general_settings(config, "Tools"), + } + action_fn = _advanced_dispatch.get(answer) + if action_fn: + last_choice = answer + action_fn() + + def run_onboard(initial_config: Config | None = None) -> OnboardResult: """Run the interactive onboarding questionnaire. @@ -1768,15 +1626,8 @@ def run_onboard(initial_config: Config | None = None) -> OnboardResult: answer = _get_questionary().select( "What would you like to configure?", choices=[ - "[Q] Quick Start (recommended)", - "[P] LLM Provider", - "[M] Model Presets", - "[C] Chat Channel", - "[H] Channel Common", - "[A] Agent Settings", - "[I] API Server", - "[G] Gateway", - "[T] Tools", + "[Q] Quick Start (API key only)", + "[A] Advanced Settings", "[V] View Configuration Summary", "[S] Save and Exit", "[X] Exit Without Saving", @@ -1796,15 +1647,8 @@ def run_onboard(initial_config: Config | None = None) -> OnboardResult: continue _menu_dispatch = { - "[Q] Quick Start (recommended)": lambda: _configure_quick_start(config), - "[P] LLM Provider": lambda: _configure_providers(config), - "[M] Model Presets": lambda: _configure_model_presets(config), - "[C] Chat Channel": lambda: _configure_channels(config), - "[H] Channel Common": lambda: _configure_general_settings(config, "Channel Common"), - "[A] Agent Settings": lambda: _configure_general_settings(config, "Agent Settings"), - "[I] API Server": lambda: _configure_general_settings(config, "API Server"), - "[G] Gateway": lambda: _configure_general_settings(config, "Gateway"), - "[T] Tools": lambda: _configure_general_settings(config, "Tools"), + "[Q] Quick Start (API key only)": lambda: _configure_quick_start(config), + "[A] Advanced Settings": lambda: _configure_advanced_settings(config), "[V] View Configuration Summary": lambda: _show_summary(config), } diff --git a/tests/agent/test_onboard_logic.py b/tests/agent/test_onboard_logic.py index 3fa919b0..d9e3b8ab 100644 --- a/tests/agent/test_onboard_logic.py +++ b/tests/agent/test_onboard_logic.py @@ -417,18 +417,6 @@ class TestProviderChannelInfo: assert isinstance(value, tuple) assert len(value) == 4 # (display_name, needs_api_key, needs_api_base, env_var) - def test_quick_start_provider_choices_cover_chat_registry(self): - from nanobot.cli.onboard import _get_quick_start_provider_choices - - choices = _get_quick_start_provider_choices() - - assert choices["OpenRouter"] == "openrouter" - assert choices["Moonshot"] == "moonshot" - assert choices["NVIDIA NIM"] == "nvidia" - assert "OpenAI Codex" not in choices - assert "Github Copilot" not in choices - assert "AssemblyAI" not in choices - class _SimpleDraftModel(BaseModel): api_key: str = "" @@ -523,6 +511,7 @@ class TestRunOnboardExitBehavior: responses = iter( [ + "[A] Advanced Settings", "[A] Agent Settings", KeyboardInterrupt(), "[X] Exit Without Saving", @@ -874,7 +863,7 @@ class TestMainMenuUpdate: initial_config = Config() responses = iter([ - "[Q] Quick Start (recommended)", + "[Q] Quick Start (API key only)", "[S] Save and Exit", ]) @@ -900,86 +889,6 @@ class TestMainMenuUpdate: assert result.should_save is True assert result.config.agents.defaults.bot_name == "quickbot" - def test_quick_start_configures_primary_preset_and_telegram(self, monkeypatch): - """Quick Start should set only the minimum provider, model and channel fields.""" - config = Config() - selections = iter([ - onboard_wizard._QUICK_START_CUSTOM_CHOICE, - "Telegram", - "OpenRouter", - ]) - - def fake_select_with_back(*_args, **_kwargs): - return next(selections) - - def fake_input(prompt, *_args, **_kwargs): - if "API key" in prompt: - return "sk-or-test" - if "Telegram bot token" in prompt: - return "123:abc" - raise AssertionError(prompt) - - monkeypatch.setattr(onboard_wizard.console, "clear", lambda: None) - monkeypatch.setattr(onboard_wizard, "_show_section_header", lambda *a, **kw: None) - monkeypatch.setattr(onboard_wizard, "_select_with_back", fake_select_with_back) - monkeypatch.setattr(onboard_wizard, "_input_with_existing", fake_input) - monkeypatch.setattr( - onboard_wizard, - "_input_model_with_autocomplete", - lambda *_args, **_kwargs: "anthropic/claude-sonnet-4.5", - ) - monkeypatch.setattr(onboard_wizard, "_print_summary_panel", lambda *a, **kw: None) - monkeypatch.setattr(onboard_wizard, "_pause", lambda: None) - - onboard_wizard._configure_quick_start(config) - - assert config.providers.openrouter.api_key == "sk-or-test" - assert config.providers.openrouter.api_base == "https://openrouter.ai/api/v1" - assert config.agents.defaults.model_preset == "primary" - assert config.model_presets["primary"].provider == "openrouter" - assert config.model_presets["primary"].model == "anthropic/claude-sonnet-4.5" - telegram = getattr(config.channels, "telegram") - assert telegram["enabled"] is True - assert telegram["token"] == "123:abc" - - def test_quick_start_custom_webui_opens_websocket_config(self, monkeypatch): - """The custom WebUI path should still expose WebSocket settings.""" - config = Config() - selections = iter([ - onboard_wizard._QUICK_START_CUSTOM_CHOICE, - "WebUI / local browser (recommended)", - "OpenRouter", - ]) - - def fake_select_with_back(*_args, **_kwargs): - return next(selections) - - def fake_configure(model, display_name): - assert display_name == "WebSocket" - assert model.enabled is True - return model - - monkeypatch.setattr(onboard_wizard.console, "clear", lambda: None) - monkeypatch.setattr(onboard_wizard, "_show_section_header", lambda *a, **kw: None) - monkeypatch.setattr(onboard_wizard, "_select_with_back", fake_select_with_back) - monkeypatch.setattr(onboard_wizard, "_input_with_existing", lambda *a, **kw: "sk-or-test") - monkeypatch.setattr(onboard_wizard, "_input_bool", lambda *a, **kw: True) - monkeypatch.setattr(onboard_wizard, "_configure_pydantic_model", fake_configure) - monkeypatch.setattr( - onboard_wizard, - "_input_model_with_autocomplete", - lambda *_args, **_kwargs: "anthropic/claude-sonnet-4.5", - ) - monkeypatch.setattr(onboard_wizard, "_print_summary_panel", lambda *a, **kw: None) - monkeypatch.setattr(onboard_wizard, "_pause", lambda: None) - - onboard_wizard._configure_quick_start(config) - - websocket = getattr(config.channels, "websocket") - assert websocket["enabled"] is True - assert websocket["websocketRequiresToken"] is True - assert config.agents.defaults.model_preset == "primary" - def test_quick_start_recommended_webui_skips_advanced_prompts(self, monkeypatch): """The beginner path should only ask for the API key and use safe defaults.""" config = Config() @@ -992,11 +901,6 @@ class TestMainMenuUpdate: monkeypatch.setattr(onboard_wizard.console, "clear", lambda: None) monkeypatch.setattr(onboard_wizard, "_show_section_header", lambda *a, **kw: None) - monkeypatch.setattr( - onboard_wizard, - "_select_with_back", - lambda *_args, **_kwargs: onboard_wizard._QUICK_START_RECOMMENDED_CHOICE, - ) monkeypatch.setattr(onboard_wizard, "_input_with_existing", lambda *a, **kw: "sk-or-test") monkeypatch.setattr(onboard_wizard, "_input_model_with_autocomplete", fail_model_input) monkeypatch.setattr(onboard_wizard, "_configure_pydantic_model", fail_websocket_config) @@ -1037,68 +941,6 @@ class TestMainMenuUpdate: assert rows["API key"] == "add later" assert "add your API key" in rows["Next"] - def test_quick_start_channel_requires_token_before_enable(self, monkeypatch): - """Quick Start should not enable token-based channels with blank credentials.""" - config = Config() - - monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None) - monkeypatch.setattr(onboard_wizard, "_input_with_existing", lambda *a, **kw: "") - - assert onboard_wizard._configure_quick_start_channel(config, "telegram") is False - assert getattr(config.channels, "telegram", None) is None - - def test_quick_start_login_channel_runs_login_without_fields(self, monkeypatch): - """Quick Start should use interactive login for channels that support it.""" - from nanobot.channels.base import BaseChannel - - config = Config() - calls: dict[str, Any] = {} - - class LoginConfig(BaseModel): - enabled: bool = False - token: str = "" - - class LoginChannel(BaseChannel): - name = "loginchat" - display_name = "Login Chat" - - async def login(self, force: bool = False) -> bool: - calls["force"] = force - calls["enabled_during_login"] = self.config.enabled - return True - - async def start(self) -> None: - pass - - async def stop(self) -> None: - pass - - async def send(self, msg) -> None: - pass - - def fail_input(*_args, **_kwargs): - raise AssertionError("Quick Start should not ask for manual channel fields") - - monkeypatch.setattr(onboard_wizard, "_show_quick_start_progress", lambda *_args: None) - monkeypatch.setattr( - onboard_wizard, - "_get_channel_config_class", - lambda channel: LoginConfig if channel == "loginchat" else None, - ) - monkeypatch.setattr( - onboard_wizard, - "_get_channel_class", - lambda channel: LoginChannel if channel == "loginchat" else None, - ) - monkeypatch.setattr(onboard_wizard, "_get_channel_names", lambda: {"loginchat": "Login Chat"}) - monkeypatch.setattr(onboard_wizard, "_input_with_existing", fail_input) - - assert onboard_wizard._configure_quick_start_channel(config, "loginchat") is True - - loginchat = getattr(config.channels, "loginchat") - assert loginchat["enabled"] is True - assert calls == {"force": False, "enabled_during_login": True} - def test_configure_login_channel_defaults_to_login(self, monkeypatch): """The channel wizard should start login before exposing advanced fields.""" from nanobot.channels.base import BaseChannel @@ -1154,7 +996,7 @@ class TestMainMenuUpdate: assert calls == {"force": False} def test_main_menu_dispatch_includes_channel_common(self): - """Main menu dispatch should route [H] to Channel Common.""" + """Advanced menu dispatch should route [H] to Channel Common.""" # We verify by checking the dispatch table is set up correctly # The menu items are defined inline in run_onboard, so we test @@ -1166,7 +1008,7 @@ class TestMainMenuUpdate: assert "Channel Common" in _SETTINGS_SETTER def test_main_menu_dispatch_includes_api_server(self): - """Main menu dispatch should route [I] to API Server.""" + """Advanced menu dispatch should route [I] to API Server.""" from nanobot.cli.onboard import _SETTINGS_GETTER, _SETTINGS_SECTIONS, _SETTINGS_SETTER assert "API Server" in _SETTINGS_SECTIONS @@ -1174,10 +1016,11 @@ class TestMainMenuUpdate: assert "API Server" in _SETTINGS_SETTER def test_run_onboard_channel_common_edit(self, monkeypatch): - """run_onboard should handle [H] Channel Common correctly.""" + """run_onboard should handle [H] Channel Common through Advanced Settings.""" initial_config = Config() responses = iter([ + "[A] Advanced Settings", "[H] Channel Common", KeyboardInterrupt(), "[S] Save and Exit", @@ -1209,10 +1052,11 @@ class TestMainMenuUpdate: assert result.config.channels.send_tool_hints is True def test_run_onboard_api_server_edit(self, monkeypatch): - """run_onboard should handle [I] API Server correctly.""" + """run_onboard should handle [I] API Server through Advanced Settings.""" initial_config = Config() responses = iter([ + "[A] Advanced Settings", "[I] API Server", KeyboardInterrupt(), "[S] Save and Exit", @@ -1552,13 +1396,15 @@ class TestModelPresetWizard: assert callable(_configure_model_presets) def test_run_onboard_model_presets_edit(self, monkeypatch): - """run_onboard should handle [M] Model Presets correctly.""" + """run_onboard should handle [M] Model Presets through Advanced Settings.""" from nanobot.config.schema import ModelPresetConfig initial_config = Config() responses = iter([ + "[A] Advanced Settings", "[M] Model Presets", + KeyboardInterrupt(), "[S] Save and Exit", ])